> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinbankroll.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js helpers

> @joinbankroll/sdk/next — the origin, the session on a request, the manifest route, and the treasury.

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`](/build/session#verify-it-on-your-server) and the
[manifest](/build/manifest) format, both of which any stack can use directly.

## The origin

```ts theme={null}
import { getOrigin } from "@joinbankroll/sdk/next";

const origin = await getOrigin(); // 'https://acme.example'
```

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.

<Warning>
  `getOrigin()` throws outside a request, so **every route that reaches it must
  opt out of prerendering**:

  ```ts theme={null}
  export const dynamic = "force-dynamic";
  ```

  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.
</Warning>

## The session on a request

```ts theme={null}
import { requireSession, requireIdentity, Unauthorized } from "@joinbankroll/sdk/next";

export const dynamic = "force-dynamic";

export async function POST(request: Request) {
  const session = await requireSession(request); // throws Unauthorized
  requireIdentity(session);                      // throws unless verified

  // session.user.wallet — the user's stable id, and payout target
}
```

| Helper                     | Behavior                                                                                                                                                                                                                                 |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getSession(request)`      | Reads `x-bankroll-token`, verifies it against Bankroll's public keys with `audience: await getOrigin()`, and returns the [`BankrollSession`](/build/session#the-session) — or `null` for a missing, forged, expired, or wrong-app token. |
| `requireSession(request)`  | The same, throwing `Unauthorized` instead of returning `null`.                                                                                                                                                                           |
| `requireIdentity(session)` | Throws unless `session.user.identity` is truthy. Real money moves only for a verified person — gate every paid action on it.                                                                                                             |

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:

```ts theme={null}
try {
  const session = await requireSession(request);
} catch (error) {
  if (error instanceof Unauthorized) {
    return Response.json({ error: "unauthorized" }, { status: 401 });
  }
  throw error;
}
```

## The manifest route

```ts theme={null}
// app/.well-known/bankroll.jwt/route.ts
import { manifestRoute, treasuryAddress } from "@joinbankroll/sdk/next";

export const dynamic = "force-dynamic";

export const GET = manifestRoute({
  name: () => "Acme Games",
  launch: "/app",
  payments: () => treasuryAddress(),
});
```

`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.

| Field        | Required | Type                                            | Description                                                                                                                                                                                                                                                                                                                                                                              |
| ------------ | -------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`       | Yes      | `() => string`                                  | Your app's display name, as Bankroll shows it when someone connects.                                                                                                                                                                                                                                                                                                                     |
| `launch`     | Yes      | `string`                                        | Where the host boots your app — a path resolved against your origin. The origin usually serves a landing page, so without this the host opens the lander.                                                                                                                                                                                                                                |
| `payments`   | Yes      | `() => string \| null`                          | The address charges settle to. Returning `null` omits the capability entirely, so an app that hasn't finished setup advertises only what it can honor.                                                                                                                                                                                                                                   |
| `supportUrl` | No       | `() => string \| null`                          | Where your users get help. Omitted from the payload when it resolves to nothing.                                                                                                                                                                                                                                                                                                         |
| `iconDigest` | No       | `() => string \| null`                          | A [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hash of your icon's exact bytes (`sha256-<base64>`). A changed digest is a manifest change, which is what carries a [replaced icon](/build/manifest#updating-your-icon--icondigest) to connected users. Omitted when it resolves to nothing — omit it whenever you serve no icon. |
| `appTokens`  | No       | `() => Record<string, { name?, description? }>` | The [tokens your app issues](/build/app-tokens), keyed by mint. Omitted when empty — an absent claim means only HSUSD may settle your charges, which is not the same as an empty one.                                                                                                                                                                                                    |

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](/build/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:

```ts theme={null}
import { treasuryAddress, requireTreasury } from "@joinbankroll/sdk/next";

treasuryAddress();   // 'J6L3…' — the public address, or null when unset
requireTreasury();   // the PaymentSigner, or throws with the setup instruction
treasurySigner();    // the PaymentSigner, or null when unset
```

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.

<Note>
  `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](/build/app-tokens). See
  [Paying a user](/build/payouts).
</Note>
