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

PhaseCallWhat it does
LockopenSessionReserves lockAmount of the player’s balance and returns a sessionToken. The session may only spend up to the lock.
Playbet / win / rollbackMove value inside the session, off-chain, as fast as the game needs. Nothing touches the chain.
ReadbalanceFetch the live session balance whenever you need it.
SettlesettleSessionReleases the lock once and moves the net result on-chain. Returns the final payout.

An end-to-end round

TS
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 duplicate bet or win returns the stored result and never double-moves. Use your own bet id as the transactionUuid — it is also your reconciliation key. Safe to retry on any network blip.
  • win must reference its bet. Pass referenceTransactionUuid = the bet’s transactionUuid. 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 sets roundClosed: true.
  • Integer money only. Base units as bigint (lamports, micro-USDC). Never floats.
A loss is just a bet with no win
There is no explicit “lose” call. A bet that never gets a matching 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