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

# App tokens

> Issue your own token — promo credit, or funds for testing — and charge and pay it out with the same calls you use for real money.

An app token is a token **you** create and hand out for free: promo credit, a
welcome bonus, or funds for exercising your money loop without spending real
money. It is worth nothing outside your app, which is the point — you can give
away as much as you like and it can never be cashed out.

Bankroll shows it as your app's funds, and `charge()` and `pay()` settle in it
with the same calls they use for HSUSD. The only difference is one field.

<Warning>
  An app token costs you nothing to create, so **never release HSUSD-priced value
  for one**. Check which asset actually paid, and pay back in the asset that paid.
  [Below](#check-which-asset-paid).
</Warning>

## Mint it

Your token must be **9 decimals**, with one token worth one dollar inside your
app. That is the only shape charges settle in — Bankroll refuses any other
scale before it signs, so this is not a preference.

Create the mint with your treasury as the mint authority, and mint the supply
to your treasury: it needs to hold the supply to pay anyone back.

```bash theme={null}
spl-token create-token --decimals 9              # prints <MINT>
spl-token create-account <MINT> --owner <TREASURY>
spl-token mint <MINT> 1000000 <TREASURY_TOKEN_ACCOUNT>
```

Creating a mint costs about **0.0035 SOL** in one-time, refundable account rent
— supply is a number, so a million tokens cost the same as one. Skip the freeze
authority: a token nobody can freeze is easier to reason about, and freezing
your own users' balances is a footgun.

<Note>
  The [starter](https://github.com/inplayinnovation/bankroll-starter) ships the
  CLI for this: `npx bankroll token create --name "Acme Credits"` does all of the
  above with the treasury key it already has, and writes the mint into
  `app-tokens.json` — the `appTokens` claim your manifest serves, which is what
  names the token inside Bankroll. The Metaplex metadata below is still yours to
  add: it is what names the token everywhere else.
</Note>

## Name it

A mint holds no name, symbol, or logo. Those live in a separate **Metaplex
metadata account**, and `spl-token create-token` does not create one — you add
it with a `CreateMetadataAccountV3` instruction signed by your mint authority,
pointing at JSON you host:

```json theme={null}
{
  "name": "Acme Credit",
  "symbol": "ACME",
  "description": "Promo credit for Acme.",
  "image": "https://acme.example/token.png"
}
```

Bankroll does not read any of it — the wallet names your token from the
`appTokens` entry in your manifest, so it displays correctly with or without
this. Everywhere else does read it: with no metadata account your token is
**"Unknown Token"** in explorers, in other wallets, and in your users' own
transaction history. Host the JSON somewhere you control long term; the URI is
capped at 200 characters and changing it later costs an on-chain update, while
editing the JSON behind a stable URI costs nothing.

<Warning>
  Do not reach for Token-2022's `NonTransferable` extension to stop people moving
  your token around. A charge settles as a **transfer to your payment address**,
  so a non-transferable token cannot be spent in your app at all — and you would
  lose refunds, support credits, and every treasury operation with it. Bankroll
  already refuses to buy, sell, or send app tokens in the wallet.
</Warning>

## Declare it

Add the mint to your [manifest](/build/manifest) under `appTokens`. This is what
lets a charge settle in it, and what makes Bankroll show it as your app's funds
rather than an unattributed holding.

```json theme={null}
{
  "manifestVersion": 1,
  "sub": "https://acme.example",
  "aud": "bankroll-app-host",
  "capabilities": {
    "session": true,
    "payments": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
  },
  "appTokens": {
    "Fh2EUwnL52CbeHttBGdW8yHKshvCbVR7pTEa5JYLKxJm": {
      "name": "Acme Credits",
      "description": "Promo funds for Acme Games"
    }
  }
}
```

The keys are the mints. Declaring them is also a **limit**: Bankroll lets your
app charge HSUSD or the mints you declared, and nothing else — so a page that
gets hijacked can reach your own token, never a user's unrelated holdings.

## Charge in it

Name the mint on `charge()`. Omit `token` and the charge settles in HSUSD, as
before.

```ts theme={null}
const signature = await bankroll.charge({
  amountCents: 500,
  token: "Fh2EUwnL52CbeHttBGdW8yHKshvCbVR7pTEa5JYLKxJm",
});
```

The user sees which asset they are spending on the payment sheet, so a charge in
your token is never mistaken for one in real money.

## Check which asset paid

`confirmCharge` reports the `mint` that paid. **Checking it is not optional.**
The signature is supplied by the client, so anything settled on-chain reaches
your server — including a worthless token the sender minted themselves, which
passes every other check.

```ts Node.js theme={null}
import { confirmCharge, HSUSD_MINT } from "@joinbankroll/sdk/server";

const APP_TOKEN = "Fh2EUwnL52CbeHttBGdW8yHKshvCbVR7pTEa5JYLKxJm";

const payment = await confirmCharge(signature);

if (payment.payee !== PAYMENT_ADDRESS) throw new Error("not paid to your address");
if (payment.mint !== HSUSD_MINT && payment.mint !== APP_TOKEN) {
  throw new Error("paid in an asset we don't accept");
}
if (payment.amountCents !== order.amountCents) throw new Error("wrong amount");
if (payment.payer !== session.user.wallet) throw new Error("paid by another wallet");

// Record which asset paid, so what you give back can match it.
```

## Pay it out

`pay()` takes the same field. Pay back in **the asset that paid** — if you sell
something for tokens and pay out HSUSD, you have built a way to turn free credit
into real money.

```ts Node.js theme={null}
await pay({
  to: session.user.wallet,
  amountCents: order.amountCents,
  token: order.mint, // whatever bought it
});
```

Your treasury needs a balance of the token to pay out, the same way it needs
HSUSD — mint yourself more whenever you like; you hold the mint authority.

## What users see

A declared token appears in the user's Bankroll wallet under **App Tokens**,
with your app's name and icon and the name you declared, valued at a dollar a
token. It is grouped apart from tradeable holdings because that is what it is:
money that spends in your app and nowhere else.

Users only see it while your app is connected. Disconnect, and it becomes an
ordinary token in their wallet.
