Atomic wallet and economy ledger for Convex: per-subject multi-currency balances with idempotent grants, lazy time-regen, and spend that never goes negative.
npm install @vllnt/convex-wallet@vllnt/convex-wallet stores consumable balances and a ledger of value changes. Currency names and subject references come from your application, so a balance can represent credits, energy, points, or another unit.
Configure currencies and ceilings, then grant, earn, spend, or transfer amounts. Spending checks available balance atomically. Idempotency keys protect supported grant and spend paths, and history records deltas and reasons. Optional regeneration calculates replenishment from server time without a periodic refill job.
The host authenticates callers, verifies purchases, and controls every credit and debit. This is not a payment processor, bank account, or subscription system. UI, pricing, reward eligibility, and external payment reconciliation stay in your app.
Maintained by [bntvllnt](https://bntvllnt.com) at [VLLNT](https://vllnt.com). Find the author on [GitHub](https://github.com/bntvllnt) and [X](https://x.com/bntvllnt).
The @vllnt/convex-wallet component provides a `grant` method that accepts an idempotencyKey, typically a receipt ID, scoped per subject and currency. Calling `grant` multiple times with the same key credits the user exactly once, making it safe to retry IAP verification flows. The host is responsible for verifying the purchase before calling `grant`.
The `spend` method in @vllnt/convex-wallet runs inside the Convex mutation transaction, so a balance can never go negative and concurrent spend attempts cannot double-debit. It returns `{ ok, balance, code }` so callers can handle `INSUFFICIENT` without try/catch around the entire mutation.
Currencies configured with a `regen` object in the Wallet constructor regenerate automatically on read using the server clock, requiring no scheduled functions or cron jobs. The `balance` and `balances` queries return the regen-aware current value, and regen is capped at the configured `cap` value.
The @vllnt/convex-wallet component supports arbitrary named currencies like coins, gems, and energy in a single Wallet instance. Each currency can have independent configuration for a max ceiling, regen rate, and regen cap. The `history` query returns a signed ledger of deltas with reasons for any currency per subject.
Yes. The `spend` method in @vllnt/convex-wallet runs atomically inside the Convex mutation transaction. If the subject's balance is insufficient, the call returns `{ ok: false, code: 'INSUFFICIENT' }` without committing any change. Concurrent spends are also safe because Convex mutations are serialized.
The `grant` method accepts an `idempotencyKey` string that is scoped per subject reference and currency. If the same key has already been applied, the call returns the current balance without crediting again. This is intended for IAP or Stripe flows where a webhook or client retry might call grant more than once for the same receipt.
When a currency is configured with a `regen` object, @vllnt/convex-wallet stores the timestamp of the last known balance update. On each `balance` or `balances` query, the component computes how many regen intervals have elapsed using the server clock and adds the accrued amount up to the configured cap. No scheduled function or cron is required because the calculation happens at read time.
Every value-moving method in @vllnt/convex-wallet validates the amount and rejects non-finite or non-positive values with the error code `INVALID_AMOUNT`. This prevents silent bugs from floating-point edge cases or client-supplied inputs.
The package exports optional tree-shakeable React hooks from `@vllnt/convex-wallet/react`, including `useBalance` and `useBalances`. These hooks take the host application's own re-exported query references rather than importing the component's internal API directly, so the component remains sandboxed. React is an optional peer dependency and is not bundled if unused.