Misar IO Docs

@misar/wallet SDK

Typed TypeScript client for the Universal Wallet: getBalance, deduct, createTopupSession, listTransactions, getRates.

@misar/wallet is the typed client for the Universal Wallet REST API. It wraps every endpoint, applies the service-key header, and enforces the fail-closed contract so a ledger outage can never grant free usage.

Installation

npm install @misar/wallet
# or
pnpm add @misar/wallet

Quick start

lib/wallet.ts
import { createWalletClient } from "@misar/wallet";

export const wallet = createWalletClient({
  baseUrl: process.env.WALLET_API_URL,          // default: https://api.misar.io/io/wallet
  serviceKey: process.env.WALLET_SERVICE_KEY!,  // server-only secret
});

Server-side only

The client holds WALLET_SERVICE_KEY. Instantiate it in server code (route handlers, server actions, workers) only — never in a client component or browser bundle.

createWalletClient(options)

Prop

Type

Methods

MethodEndpointReturns
getBalance(userId)GET /balancePromise<number>
deduct(params)POST /deductPromise<DeductResult>
createTopupSession(params)POST /topup-sessionPromise<{ url: string | null }>
listTransactions(params)GET /transactionsPromise<TransactionsResult>
getRates()GET /ratesPromise<CreditRate[]>

getBalance(userId)

const credits = await wallet.getBalance("usr_123");
// → 42  (1 credit = $1). Returns 0 on any error so the UI degrades gracefully.

deduct(params)

const result = await wallet.deduct({
  userId: "usr_123",
  feature: "blog.article.generate",
  count: 1,
  idempotencyKey: "article:my-post-slug", // stable key → safe retries
});

if (!result.allowed) {
  // Insufficient credits or the ledger was unreachable (fail-closed).
  // result.required tells you the shortfall.
  throw new Error("Out of credits");
}
// proceed with the billable work

DeductResult:

Prop

Type

createTopupSession(params)

const { url } = await wallet.createTopupSession({
  userId: "usr_123",
  amountDollars: 25,              // integer 10–100000
  product: "blog",
  returnUrl: "https://www.misar.blog/dashboard/billing",
});

if (url) redirect(url); // url is null on error

listTransactions(params)

let cursor: string | undefined;
do {
  const { items, nextCursor } = await wallet.listTransactions({
    userId: "usr_123",
    limit: 50,
    cursor,
  });
  // process items…
  cursor = nextCursor ?? undefined;
} while (cursor);

TransactionsResult is { items: WalletTransaction[]; nextCursor: string | null }.

getRates()

const rates = await wallet.getRates();
// → [{ feature: "blog.article.generate", label: "Generate an article", credits: 1, unit: "article" }, …]

Error handling

The SDK mirrors the API's fail-closed contract rather than throwing on network errors:

  • getBalance0 on error
  • deduct{ allowed: false } on error
  • createTopupSession{ url: null } on error
  • listTransactions{ items: [], nextCursor: null } on error

This means a transient failure never silently grants usage or credits. See Errors & fail-closed for the full contract.