Use CasesDollar Accounts

Dollar accounts in your app

Give customers a balance they fund by bank transfer, hold as stablecoins, and spend from.

Your customers hold a balance and spend from it. Deposits arrive as fiat and settle into a wallet you show them.

The shape

Two things fund the balance: a bank deposit that converts, and a plain on-chain send from anyone at all. Your product has to handle both, because the second one needs no cooperation from you.

Build order

StepCall
1. OnboardPOST /api/customers with endorsements: ["endo_usd"]
2. SubmitPOST /api/customers/{id}/verifications/submit
3. Create the walletPOST /api/customers/{id}/wallets
4. Create the funding accountPOST /api/customers/{id}/virtual_accounts with {"kind": "wallet", "asset": "usdc", "wallet_id": "wal_..."}
5. Show the balanceGET /api/wallets/{id}
6. Show activityGET /api/wallets/{id}/history
7. SpendPOST /api/wallets/{id}/operations, then authorize

The balance is a chain read

GET /api/wallets/{id} returns balances read from the chain at request time, not from a ledger we keep. Three consequences for your product:

  • Direct deposits are immediately spendable. Someone pays your customer by pasting the address into any wallet app, and the funds count the moment they land.
  • Your records cannot diverge from reality, because the chain is the record.
  • insufficient_balance is judged against the chain when an operation is created, not against a number you cached.

Balances ride single-GET responses only. GET /api/wallets returns the same objects without a balances key, so list pages stay cheap. Use GET /api/wallets/total_balances for a portfolio roll-up.

GET /api/wallets/{id}/history is an activity log, not the source of truth for balances. Render the balance from the wallet object, and use history for the transaction list beneath it.

Deposit addresses are permanent

Each wallet is one address on one chain, and the address never changes. Show it as a QR code and let customers reuse it, which is what they expect from every other wallet they own.

If you need the same address on several EVM chains later, address_wallet_id extends an existing wallet's derivation onto another EVM chain. Solana keys are not EVM keys, so this is EVM only on both sides.

What to watch

  • wallet.deposit_received is your most important event. It fires for money that arrived without any API call of yours, which is the only notification you get that a customer was paid.
  • Sends need your signing service to be up. If it is down, sends stop. Deposits, balances, and history are unaffected. Decide what your UI shows in that window before it happens.
  • Every send is metered, and destination-chain gas passes through at cost plus your plan's markup. Deposits are free. See Fees & Quotes.

The design decision to make early

A dollar-account product is the case where the 30-second authorization window bites hardest, because a human is usually pressing the button.

Put the human decision before you create the operation. Your UI confirms the send, and only then does your backend create, verify, sign, and authorize in one synchronous block. Creating the operation while a confirmation dialog is open will expire it. See An operation expired before signing.