Use CasesTreasury & Self-Custody

Treasury and self-custody

Onboard your own entity, receive into your own accounts, and hold the result in wallets only your Recovery Key controls.

You are your own customer, so the funds the custody model protects are yours.

The shape

Build order

StepCallNotes
1. Onboard your entityPOST /api/customers with kind: "business"Your operating company, verified through KYB like any other business customer
2. Register your keysDashboard, WalletsPublic halves only, once per environment, and never a private key
3. ActivateDashboard, WalletsSigned by the cold Recovery Key. Happens once, ever
4. Create walletsPOST /api/customers/{customer_id}/walletsOne per chain, or several per chain if you want separated balances
5. ReceivePOST /api/customers/{customer_id}/virtual_accountsDestination is a wallet you hold, or an address you already control
6. MovePOST /api/wallets/{wallet_id}/operations, then authorizeVerified and signed in your KMS

Read the custody model before you generate a key

Wallet addresses derive from your Recovery Key. It is your permanent identity, it cannot be rotated, and losing it means no new Primary Signing Key can ever be installed. For what each of your two keys does and where it belongs, see Wallets.

Generate the Recovery Key offline, in the device it will live in, and back it up like a root of trust. Read Custody Architecture first, and treat that generation as a change-controlled event with more than one person present.

What the model guarantees

If our API, our database, our infrastructure keys, or any third party we rely on were fully compromised, an attacker still could not move a token out of one of these wallets. Moving funds requires a fresh signature from your Primary Signing Key, over bytes your own verifier has already rebuilt and checked.

The threat model table states what each compromise scenario can and cannot do, and you can check every claim in it against a public chain or with code you run.

Separating balances

Each POST /api/customers/{customer_id}/wallets call mints a new wallet with a new address, including on a chain you already have one on. Use that to separate concerns rather than tracking sub-balances yourself:

  • an operating wallet that funds day-to-day payouts,
  • a reserve wallet nothing automated can reach,
  • a per-counterparty wallet, so an address identifies who paid.

partner_reference_id is your handle on each one, and list endpoints filter by it. name is a display label and is deliberately not unique.

Operational discipline

  • Alert on every verifier refusal. A refusal means something did not match, which makes it the highest-signal alert you have.
  • Watch wallet_transaction.created (with kind: "deposit"). You want to know within seconds when funds arrive at a treasury address from an unexpected source.
  • Use read_only API keys for reporting jobs and anything that reads balances. A read-only key cannot create an operation.
  • Reconcile balances from the chain. GET /api/wallets/{wallet_id} and GET /api/wallets/total_balances read balances from the chain, so they match what an explorer shows. GET /api/wallets/{wallet_id}/history is the activity log beneath them.