Skip to main content
The reference pages track the latest @joinbankroll/sdk. This page is the per-version history — check it to see when a feature landed or what changed between the version you have and the latest. Install the latest with:
Versions follow semantic versioning. While the SDK is pre-1.0 a minor release can carry a breaking change; those are called out below.
Test environments. An app’s test deployment carries one setting, BANKROLL_ENVIRONMENT=test, which Bankroll sets for it. The manifest route then declares environment: "test", and the host charges that deployment in test cash, Bankroll’s unlimited test token, instead of BCASH. On the server, HSUSD_MINT resolves to the test-cash mint on a test deployment and to BCASH everywhere else, so charges, payouts, and checkCharge need no code change; appEnvironment() says which one you are on. BCASH_MINT and TEST_CASH_MINT name the two mints. A deployment with no setting is live, as every deployment was before. See Test environments.
Reviews. bankroll.promptReview() asks the Bankroll host to show its thumbs up or down card at the moment your app picks. The host draws the card, records the answer, and decides how often it may show; the call resolves with nothing and never rejects. Verified apps only. See Reviews.
Restrictions. @joinbankroll/sdk/restrictions reads and evaluates a restriction policy: one JSON document that says where, and from what age, an app takes real money. restrictionPolicyFromEnv() reads the BANKROLL_RESTRICTIONS setting and restrictionFor(session, policy) returns one verdict with a reason and the minimumAge. No policy means no restriction; a policy with no age part means 18. Builder apps receive their policy from Bankroll. See Restrictions.
Timers, for verified apps. createTimer({ meta, firesInMinutes }, { origin }) on @joinbankroll/sdk/server sets a timer on Bankroll; when it fires Bankroll delivers timer.fired (id, meta, at) to the webhook route, once, to the minute. The route helper is now bankrollWebhook({ onConfirmed, onExpired, onFired? }); referenceWebhook stays as an alias. Under BANKROLL_MOCK=1 the timer is set locally and delivers the event itself. See Timers.
Under BANKROLL_MOCK=1 a mock payout’s signature is now deterministic (a digest of the bytes), a delivery the route refused is logged loudly, and the mock notes a confirmation only after the handler returned, so a handler that threw still gets the expiry.
Managed references, for verified apps. createManagedReference({ meta, expiresInSeconds? }, { origin }) on @joinbankroll/sdk/server, beside createReference, asks Bankroll for a reference and starts it watching the chain; referenceWebhook({ onConfirmed, onExpired }) on the new @joinbankroll/sdk/webhooks entry is the /api/bankroll/webhook route, verifying Bankroll’s signature with BANKROLL_WEBHOOK_SECRET and handing over reference.confirmed (the first successful transaction carrying the reference: signature and slot) or reference.expired; checkCharge(signature, expected), beside confirmCharge, reads a confirmed charge and checks it against what the entry was sold for, throwing ChargeMismatchError with the field that differed. Bankroll reports the signature only; the app still reads and judges the transaction. Under BANKROLL_MOCK=1 the reference is minted locally and the mock host’s charge(), a mock payout, and the window’s end deliver the events to the route themselves. See Webhooks and Let Bankroll watch it.Removed, breaking: pendingPayout, settlePayout, claimCharge and the receipts from 0.27.0. They put settlement bookkeeping in the SDK that belongs on Bankroll’s side; nothing shipped on them, and managed references replace them. checkCharge keeps the comparison claimCharge made.
Durable payouts and charge receipts on @joinbankroll/sdk/server, lifted from the starter’s p2p reference. pendingPayout(payee, recipients, memo?) records an obligation on the document that owes it, every party listed, zero lines included; settlePayout(path, { store, signer }) drives it through build, store-before-send, send, and confirmation, recovers after a crash or a lost reply by reference, resends the same bytes only inside the replay window the signer declares (PaymentSigner.replayWindowMs, 24 hours on the Privy signers), and builds new bytes only once nonpayment is proven. The last unresolved outcome stays on attempt.error. claimCharge({ store, entry, reference, signature?, expected }) confirms a reported charge or recovers one by reference, checks it against what the entry was sold for, names the field that differed, and binds it to exactly one entry by an immutable receipt under receipts/. buildPayout now accepts zero-cent lines; only negative or fractional cents are refused.
Matchmaking under the mock. With BANKROLL_MOCK=1 outside production, createMatchmaking pairs in-process by the service’s rules, with no app key and no network, and a ticket nobody has joined after three seconds meets a stand-in opponent, MOCK_OPPONENT from @joinbankroll/sdk/mock. A coding agent’s dev server and npm run check see a whole two-player round with one tester.
Privy peer range. @privy-io/node is accepted at >=0.10.1 instead of ^0.10.1; the privy entry calls the same two methods on every version since. Apps on a newer Privy package install without a peer conflict.
manifestClaims from @joinbankroll/sdk/manifest. The manifest’s claims as one pure function with no framework dependency — exactly what manifestRoute serves, for tooling that builds an app’s manifest before the app is deployed. manifestRoute now uses it; what it serves is unchanged.
Payouts under the mock. With BANKROLL_MOCK=1 outside production, mockPayoutSigner(address) from @joinbankroll/sdk/mock answers each send with a made-up signature and confirmPayout accepts it outright, so an app’s whole payout path runs end to end without a key and without money — what a coding agent’s dev server and npm run check need. A production build never reads the flag.
Several recipients in one transaction. pay, buildPayout and buildAndSignPayout accept { recipients: [{ to, amountCents, token? }], memo?, reference? } beside the single-recipient shape, which stays as the shorthand. One transfer per recipient, each in its own mint, in one transaction: a winner’s payout and the creator’s cut land together or not at all. Existing callers are unchanged.
Bankroll server wallets. delegatedPrivySigner() on the server entry pays out of a server wallet Bankroll created for your app: the app signs each payout request with its own key, Bankroll relays it to the wallet under its payout policy, and the wallet sponsors and broadcasts. Configured by the variables provisioning prints (BANKROLL_PAYEE, BANKROLL_DELEGATED_KEY, BANKROLL_DELEGATED_WALLET_ID, BANKROLL_PRIVY_APP_ID); explicit, never picked up from the environment on its own. Refusals are PayError('send_failed') with a DelegatedPrivySignerError cause.
buildPayout rejects a reference that names an account the payout already carries (the treasury, the recipient, either token account, the mint). The message compiler would fold such a key into the existing entry, and a lookup by it would answer with that account’s whole history rather than this payout. createReference() never produces one; the guard is for hand-supplied keys.
Find a payout by reference. buildPayout accepts a reference from createReference(); it rides on the transfer as an inert account key, and findPayoutByReference() finds the landed transaction by it. That is the id to store before the send for a signer that signs at send time (privySigner), where no signature can be known up front and the sponsor rewrites the bytes. The payout twin of findChargeByReference.
Matchmaking. @joinbankroll/sdk/matchmaking adds createTicket, listTickets, and cancelTicket for verified apps. Tickets use caller-chosen IDs, remain discoverable after matching or cancellation, and cancel atomically against pairing. Queue policy fixes headcount and an optional rating band; game conditions are shared, while results stay in your app.Shared app authentication. BANKROLL_APP_KEY authenticates matchmaking and notifications. Declare its public key as manifest appKey; use push: true for notification consent. appAddress() is exported from /server, and manifestRoute includes it by default when configured. Existing BANKROLL_PUSH_KEY apps keep their legacy notification format. Add appKey and have the manifest re-signed before using matchmaking; changing an app key or any other manifest claim requires re-signing and user re-consent.
September 2, 2026
@joinbankroll/sdk/mock — a stand-in host. mockHostScript({ payee }) defines window.bankroll in a plain browser the way the Bankroll app does, so the client SDK reports ready and every call succeeds without a phone. With BANKROLL_MOCK=1 outside production, getSession accepts its token, confirmCharge confirms its made-up signatures from their own contents, and findChargeByReference finds nothing. Production builds never read the flag.Charge-only mode in the starter. BANKROLL_PAYEE names a wallet address that receives payments when there is no treasury key; payouts answer 501. The starter also gains a catalog of priced items and npm run check, a headless phone-sized browser run against the stand-in host.
August 19, 2026
haptics() — the physical half of your UI. bankroll.haptics({ type }) plays a typed vibration through the phone’s engine: impacts (light / medium / heavy), outcomes (success / warning / error), or selection. An omitted or unknown type plays the host default, a heavy impact.Decoration only, so unlike every other capability it never rejects — in a plain browser, under an old host, or on any bridge failure it resolves having done nothing. Needs a Bankroll host at client version 4+, feature-detected; no new manifest claim, and no re-consent for already-connected apps.Offers on your tile. Serve /.well-known/bankroll-status and Bankroll rings your tile with your headline when you have something on offer for the person asking — per user when they’ve connected you, your generic new-user offer when they haven’t. Opening from the offer hands its key back to your app as ?offer=. Decorative only; nothing to install.Bankroll Verified. The green ✓ after an app’s name means Bankroll reviewed what the origin serves and signed its manifest. What it means, what it unlocks, how to get it — and it is now what makes play in your app count toward a friend’s referral.Referral links, in numbers. Share links now state the program your playLink({ referrer }) feeds: new user attributed at signup, 30ofplayacrossverifiedappswithin30days,30 of play across verified apps within 30 days, 10 each to both sides — and $10 to a verified app’s treasury per qualifying referral from its links.
August 18, 2026
Know a payout’s signature before you broadcast it. buildAndSignPayout() builds and signs in one call, returning the transaction, its deterministic signature, and its expiry with nothing sent — so your payout row stores the signature in the same write that locks it, and recovery is always a question the chain can answer. signPayout() does the signing half for bytes you built separately, and a custom PaymentSigner can opt in with signTransaction.Purely additive — no existing call changes behavior. The payout lifecycle docs now teach the full order: build → sign → store → send → confirm.
August 18, 2026
iconDigest on manifestRoute. manifestRoute() takes an optional iconDigest — a Subresource Integrity hash of your icon’s bytes. A changed digest is a manifest change, which is what carries a replaced icon to users who already connected. Omitted, like supportUrl, whenever it resolves to nothing.
August 18, 2026
Find a charge your page never reported. createReference() mints an id your server stores with the order and passes to charge(). The payment carries it on-chain, so when the page dies between the charge settling and the request that would have reported it, findChargeByReference() still finds the charge — by an id that existed before it did. ConfirmedCharge now carries the payment’s signature, so a recovered charge settles through the same code as a reported one.expiresInSeconds bounds a stale price. A charge left sitting on the pay sheet rejects charge_expired rather than settling against a price that has moved — nothing signed, nothing moved. It defaults to 90 seconds, and the host counts it down on the sheet.Passing a reference needs a current Bankroll app — older ones reject with update_required.
August 13, 2026
sendPush() — notify your users through Bankroll. From your server, sign a short-lived request with your push key and Bankroll delivers a notification to one of your users, titled with your app’s name; tapping it opens your app at the path you choose. pushAddress() derives the public key your manifest declares — manifestRoute’s new push entry.Push is permissioned: it works once Bankroll has signed your manifest, which attests the key, and only toward users who have opened your app. The push key is its own secret (BANKROLL_PUSH_KEY) — pushing and paying are different powers, so it is deliberately not the treasury key.
August 13, 2026
Share links carry a referrer. playLink(url, { referrer }) appends the wallet of the Bankroll user sharing the link, so an app can piggyback on Bankroll’s referral program instead of building its own — pass session.user.wallet and a new Bankroll user who opens the link is attributed to them.The referrer must be a real Bankroll user’s wallet; the program is person to person, so an app’s own treasury is attributed to nobody. An unrecognized referrer costs the attribution, never the link.
August 7, 2026
balances() and deposit() — prerelease. bankroll.balances() returns your app’s view of the user’s value — their cash, your app’s credits, and the balances of every mint your manifest declares. bankroll.deposit() presents the host’s deposit UI, optionally preselecting a funding source.Both are feature-detected and need a current Bankroll host; an older one rejects with update_required. Prerelease — the contract may still change.
July 31, 2026
supportUrl on manifestRoute. manifestRoute() takes an optional supportUrl and omits the claim when it resolves to nothing — an empty claim is still a claim, and would re-ask every connected user for consent once it gained a value.
July 29, 2026
Breaking: publicOrigin() is gone. It read BANKROLL_DEV_TUNNEL_ORIGIN so a development landing page could render a link a phone could reach; in production it was getOrigin() with extra steps. Call getOrigin().Removing it is what lets the development CLI start your server first and read launch from your live manifest, so the QR code it prints opens your app rather than your landing page.
July 28, 2026
Breaking: manifestRoute’s appTokens is a map. It took a single mint; it now takes Record<mint, { name?, description? }>, matching the manifest claim and letting an app issue several tokens that each carry their own display strings.
July 28, 2026
Three new entry points, all optional — the pieces every Built-for-Bankroll app was writing itself.
  • @joinbankroll/sdk/next — getOrigin(), getSession() / requireSession(), requireIdentity(), and manifestRoute(), which serves /.well-known/bankroll.jwt so you never hand-assemble the format. Plus the treasury: treasuryAddress() derives your payment address from BANKROLL_TREASURY_KEY, so what your manifest advertises can’t drift from the wallet that signs.
  • @joinbankroll/sdk/store — durable JSON with an atomic create and compare-and-swap, so a payment signature can’t be redeemed twice without provisioning a database. Filesystem and Vercel Blob backends behind one interface; @vercel/blob (>= 2.3.0) is an optional peer.
  • @joinbankroll/sdk/react — useBankrollStatus() / useBankrollChecked() that agree across hydration, bankrollFetch, verifyIdentity(), and a development overlay. react (>= 18) is an optional peer.
SOLANA_RPC_URL is now optional. Unset, the server half falls back to Solana’s public endpoint and warns once per process instead of failing, so an app can take its first payment before configuring an RPC. usingPublicRpc() reports whether the fallback is in play.
Set your own endpoint before you pay anyone. The public one rate-limits under concurrency, and a 429 while broadcasting a payout surfaces as PayError('rpc_error') with an unknown outcome — the one failure you cannot safely retry.
July 25, 2026
App tokens. charge() and pay() take an optional token — a mint you issue yourself, declared in your manifest’s appTokens — so you can sell and pay out in your own promo credit with the same calls you use for real money. Declaring your mints is also a limit: your app can charge HSUSD or those mints, and nothing else.
Breaking: confirmCharge() no longer guarantees HSUSD. It now reports the mint that paid and leaves the judgement to you, alongside the payee, amount and payer checks you already make. A server that upgrades without adding a mint check will accept a token the sender minted for nothing — the signature comes from the client, so any settled transfer reaches confirmCharge, and a worthless token passes every other check. Add:
Every asset a charge settles in carries HSUSD’s scale — 9 decimals, one token to the dollar — so amounts stay in whole US cents throughout. Bankroll refuses a mint of any other shape before it signs.
July 24, 2026
iconDigest manifest claim. Added the OPTIONAL iconDigest claim — a Subresource Integrity hash (sha256-<base64>) of your icon’s bytes. Declare it so a replaced icon reaches users who have already connected your app, instead of staying cached.
July 20, 2026
ConfirmedCharge.slot. confirmCharge() now returns the slot the transfer landed in — a chain-assigned, monotonic number. Use it as a stable ordering key for a purchase listing without giving up signature-keyed idempotency.
July 20, 2026
More reliable payout reconciliation. confirmPayout() now searches the transaction ledger instead of only the recent status cache, so confirming a long-since-landed payout resolves correctly.
When you reconcile old payouts, confirm against an endpoint with full transaction history — a pruned endpoint can report a landed transaction as expired, and paying again on that signal would double-pay.
July 19, 2026
Dependency hardening. Inlined the token-transfer instructions the SDK builds, removing a transitive dependency that carried an unfixable security advisory. No API changes.
July 18, 2026
Payout lifecycle. The single-call payout became a three-step lifecycle — buildPayout → sendPayout → confirmPayout — with a caller-owned payout-row state machine (created → submitted → confirmed | failed | expired) and confirmPayout() as the reconciliation primitive. pay() remains as their composition.
July 18, 2026
Server-side charge confirmation, and paying a user.
  • Added confirmCharge(signature) — read a settled charge’s facts from chain ({ payer, payee, amountCents, memo }) and verify them before releasing value.
  • Added a server payout: pay({ to, amountCents, memo? }, { signer }), signed with a PaymentSigner — keypairSigner(secretKey) or privySigner from @joinbankroll/sdk/privy.
Breaking — the client charge method was renamed bankroll.pay() → bankroll.charge(). The freed pay() name is now the server payout function. Update your charge calls when you upgrade. (The host bridge’s wire method stays pay — it’s a versioned protocol, so nothing on the host side changes.)
July 16, 2026
Charge error codes. Added idempotency_conflict (an idempotencyKey reused with different parameters) and payment_denied (the host declined at the pay sheet), and documented idempotencyKey on the charge input.
July 14, 2026
session(). The identity capability is now session(); identity() remains as a deprecated alias for older hosts. Added the BankrollSession type on the server entry.
July 9, 2026
Initial release. The Build-on-Bankroll client and server-side session-token verification (verifyToken).