@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/walletQuick start
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
| Method | Endpoint | Returns |
|---|---|---|
getBalance(userId) | GET /balance | Promise<number> |
deduct(params) | POST /deduct | Promise<DeductResult> |
createTopupSession(params) | POST /topup-session | Promise<{ url: string | null }> |
listTransactions(params) | GET /transactions | Promise<TransactionsResult> |
getRates() | GET /rates | Promise<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 workDeductResult:
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 errorlistTransactions(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:
getBalance→0on errordeduct→{ allowed: false }on errorcreateTopupSession→{ url: null }on errorlistTransactions→{ items: [], nextCursor: null }on error
This means a transient failure never silently grants usage or credits. See Errors & fail-closed for the full contract.