High-frequency ledger
Signing an on-chain transaction for every game action is too slow and too expensive for real-time play. The ledger is a seamless-wallet money layer built for it: lock a stake once, run bet / win / rollback off-chain at memory speed, and settle the net on-chain at the end. The wallet is the money source of truth; your game owns the game meaning.
The contract: lock → play → settle
| Phase | Call | What it does |
|---|---|---|
| Lock | openSession | Reserves lockAmount of the player’s balance and returns a sessionToken. The session may only spend up to the lock. |
| Play | bet / win / rollback | Move value inside the session, off-chain, as fast as the game needs. Nothing touches the chain. |
| Read | balance | Fetch the live session balance whenever you need it. |
| Settle | settleSession | Releases the lock once and moves the net result on-chain. Returns the final payout. |
An end-to-end round
import { IAMGameWalletServer } from "@iamgame/wallet-sdk-server";
const wallet = new IAMGameWalletServer({
secretKey: process.env.IAMGAME_WALLET_SECRET_KEY!, // sk_ — BACKEND ONLY
baseUrl: "https://api-wallet.iamgame.com/v1",
});
// 1) OPEN — one lock. Funds are held; the session may bet up to lockAmount.
// userId comes from verifySession, never from the client.
const s = await wallet.ledger.openSession({
userId,
gameCode: "momentum",
currency: "USDC",
lockAmount: 50_000_000n, // integer base units (micro-USDC), always
});
// s = { sessionToken, sessionId, currency, lockedBalance, balance }
// 2) PLAY — memory-speed, unbounded frequency. transactionUuid is YOUR bet id
// and the idempotency + reconciliation key.
await wallet.ledger.bet({
sessionToken: s.sessionToken,
transactionUuid: "bet-r1",
amount: 1_000_000n,
round: "1",
});
// A win MUST reference the bet it pays out.
await wallet.ledger.win({
sessionToken: s.sessionToken,
transactionUuid: "win-r1",
referenceTransactionUuid: "bet-r1",
amount: 1_800_000n,
round: "1",
roundClosed: true,
});
// Cancel a round if it voids — references the bet, order-independent.
// await wallet.ledger.rollback({ sessionToken: s.sessionToken, referenceTransactionUuid: "bet-r1" });
// Read the live session balance any time.
// const { balance } = await wallet.ledger.balance(s.sessionToken);
// 3) SETTLE — once. The NET result releases the lock and moves on-chain.
const { payout } = await wallet.ledger.settleSession(s.sessionToken);Semantics — get these exact
- Idempotent on
transactionUuid. A duplicatebetorwinreturns the stored result and never double-moves. Use your own bet id as thetransactionUuid— it is also your reconciliation key. Safe to retry on any network blip. winmust reference its bet. PassreferenceTransactionUuid= the bet’stransactionUuid. A win for a bet the ledger never saw is rejected.- Rollback is order-independent. It references a bet by
referenceTransactionUuid; a rollback that arrives before its bet is remembered and the late bet is voided. Use it to cancel a round cleanly. - Rounds drive reconciliation. Every transaction carries a
round; the last transaction of a round setsroundClosed: true. - Integer money only. Base units as
bigint(lamports, micro-USDC). Never floats.
win is simply money the player spent — settlement moves the net, so an unwon bet is the loss.Server-only by design
Every ledger call uses your sk_ key and must run on your backend. The browser must never be able to call win on itself — that would let a client mint its own payouts. The client SDK has no ledger surface at all; the ledger lives only in @iamgame/wallet-sdk-server. Resolve userId from verifySession, never from a request body.
Locked funds are protected
Funds held in an open session are untouchable by withdraw or export — a player can’t pull money out from under an active bet. settleSession is what releases the lock and moves the net result. Settle every session you open; don’t leave locks dangling.
Pari-mutuel & pools
The ledger holds per-player money, not the pool. Each player has their own session, and each stake is a bet. The pool, the odds, the split, and who won are your game’s domain: compute each winner’s payout, then call win against their bet. Your records and the ledger reconcile on the shared transactionUuid. For pooled escrow with an atomic conservation-guarded settle instead, that’s the pool-escrow model.
Environments & settlement
Build and test the whole flow in test (devnet) — it runs entirely off-chain. Real-money (live) sessions settle through an on-chain rail; until that rail is configured for an app, live sessions are refused.
Where to go next
- Choose a money model — when to reach for the ledger vs treasury vs pool escrow.
- Custodial treasury — deposits and withdrawals at the real-money cash boundary.