Starter kit

Add Cashu to a small app without guessing the moving parts.

Start with one test mint, one local wallet state model, the five cashu-ts flows most apps need, and a 60-second no-signup paid-download demo using a fresh test token.

cashu-tstest mint: nofee.testnut.cashu.spaceexample app: 60-second paid downloads

Hosted example

Try the Cashu-paid download

Open Cashu zine demo

No signup, no account setup: mint a fresh 21 sat test token, paste it into the hosted demo, and see the server record a short-lived grant before returning the sample file link.

1 / install

Install the library

npm install @cashu/cashu-ts

2 / wallet

Create a wallet context

Make the mint and unit explicit. Production apps should treat mint choice as a product trust decision.

import { Wallet } from '@cashu/cashu-ts'

const mintUrl = 'https://nofee.testnut.cashu.space'
const wallet = new Wallet(mintUrl, { unit: 'sat' })
await wallet.loadMint()

const mintInfo = wallet.getMintInfo()
const activeKeyset = wallet.keyChain.cache.keysets.find(
  (keyset) => keyset.unit === 'sat' && keyset.active,
)

3 / state

Persist proof state deliberately

Proofs are bearer money. Track spendable, pending, and spent states so refreshes and retries do not lose value.

type StoredProof = {
  amount: number
  id: string
  secret: string
  C: string
  state: 'spendable' | 'pending' | 'spent'
}

type WalletRecord = {
  mintUrl: string
  unit: 'sat'
  keysetId?: string
  proofs: StoredProof[]
}

const sumBalance = (proofs: StoredProof[]) =>
  proofs.filter((proof) => proof.state === 'spendable').reduce((sum, proof) => sum + proof.amount, 0)

4 / app pattern

Paid unlock / digital download

A small useful pattern: buyer pays sats, the app records the payment, then returns a short-lived download URL.

type DigitalAsset = {
  id: string
  title: string
  priceSats: number
  fileKey: string
}

type UnlockGrant = {
  id: string
  assetId: string
  amountSats: number
  mintUrl: string
  paymentRef: string
  expiresAt: string
}

async function createUnlockGrant(asset: DigitalAsset, paymentRef: string): Promise<UnlockGrant> {
  return {
    id: crypto.randomUUID(),
    assetId: asset.id,
    amountSats: asset.priceSats,
    mintUrl: 'https://nofee.testnut.cashu.space',
    paymentRef,
    expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
  }
}

Build order

Copy these recipes in order

Back to playground