@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:
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, 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(), andmanifestRoute(), which serves/.well-known/bankroll.jwtso you never hand-assemble the format. Plus the treasury:treasuryAddress()derives your payment address fromBANKROLL_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.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.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 aPaymentSigner—keypairSigner(secretKey)orprivySignerfrom@joinbankroll/sdk/privy.
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).