Skip to main content
Every Built-for-Bankroll app needs the same few things on the server, and none of them are app-specific: knowing which origin it is served from, verifying the session token on a request, serving the manifest, and holding a treasury. @joinbankroll/sdk/next is those, so they are an upgrade rather than a file you maintain. The entry is server-only — it imports next/headers, which throws in a client bundle, so a mistake fails at build rather than shipping the wrong thing to a browser. Nothing here is required: every helper is a convenience over verifyToken and the manifest format, both of which any stack can use directly.

The origin

Your app’s origin, read from the request’s own host header — so preview deployments, custom domains, and local tunnels each identify themselves correctly with nothing configured. It is the audience a session token is minted for and the sub a manifest claims, which is why it is never guessed: an origin inferred from a deployment environment variable would verify tokens for somewhere the app is not actually served from.
getOrigin() throws outside a request, so every route that reaches it must opt out of prerendering:
The SDK cannot declare that for you — Next reads it only from the route file itself. That covers the manifest route and any route resolving a session.

The session on a request

Because the audience comes from getOrigin(), a token minted for another app can never authenticate a user on yours, and there is no origin constant to keep in step across environments. Unauthorized is a plain Error subclass — map it to a 401 wherever you handle route errors:

The manifest route

manifestRoute(app) returns the GET handler for /.well-known/bankroll.jwt. You supply what is genuinely your app’s; it fills in sub, aud, manifestVersion, and the capability shape. Each is a function rather than a value because it is evaluated per request: payments can start returning an address the moment a treasury key is set, without a redeploy. Entries whose name or description is an empty string are dropped rather than served, since one invalid entry would take the whole manifest down with it. See The manifest for what each claim means to the host, and for building one on another stack.

The treasury

Your app’s one secret is BANKROLL_TREASURY_KEY, a base58 Solana secret key. These read it, and are re-exported from @joinbankroll/sdk/server too:
The address is derived from the secret key, so the payment address your manifest advertises can never drift from the wallet that actually signs — pass treasuryAddress straight to manifestRoute’s payments. An app runs fine with no treasury: it simply can’t take or send money, and says so in its manifest. That is why the accessors answer “not configured” rather than throwing, and only requireTreasury() — what a money path calls — fails.
pay() already defaults to this signer, so a payout needs nothing passed. Reach for requireTreasury() when you need the signer itself — a custom instruction, or minting an app token. See Paying a user.