Quickstart
This is the fast path: from zero to a settled USDC payment in under 30 minutes, entirely in sandbox (no real funds). Every step shows what you should get back.
You will:
- Get a sandbox API key and log in with the CLI.
- Create a wallet.
- Fund it from the faucet.
- Claim a payable handle and send a payment.
Node.js 20+. For the SDK: pnpm add @safebank/sdk. For the CLI, build it once
with pnpm --filter @safebank/cli build (see the CLI guide).
1. Get a key
API keys are minted per tenant. Grab a sandbox
key (sb_test_…) from the SafeBank developer dashboard, then save it to a
local CLI profile:
sb login sb_test_...
# validates the key by resolving your tenant, then writes ~/.safebank/config.json
Expected output confirms the tenant and masks the key:
Logged in · sandbox
tenant Acme Robotics (acme-robotics)
key sb_test_Ab12Cd34Ef56_…
profile sandbox → ~/.safebank/config.json
For the SDK and curl, keep the key in an env var instead:
export SAFEBANK_API_KEY=sb_test_...
The base URL is inferred from the key prefix — sb_test_ →
https://api.sandbox.safebank.ai. See Environments.
2. Create a wallet
A wallet is a Safe multisig. Create one owned by a signer address you control. In sandbox the CLI generates and stores a demo signer for you on first use.
- curl
- TypeScript
- CLI
curl -X POST https://api.sandbox.safebank.ai/v1/wallets \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Treasury",
"owners": [{ "address": "0xYourSignerAddress", "role": "ADMIN_OWNER" }],
"threshold": 1,
"chainId": 84532
}'
import { SafeBank } from '@safebank/sdk'
const sb = new SafeBank({ apiKey: process.env.SAFEBANK_API_KEY! })
const wallet = await sb.wallets.create({
name: 'Acme Treasury',
owners: [{ address: '0xYourSignerAddress', role: 'ADMIN_OWNER' }],
threshold: 1,
})
sb wallets create --name "Acme Treasury" --wait
# --wait polls until the Safe is DEPLOYED on-chain
You get back a Wallet. The address is a predicted Safe address at
first; status moves PENDING_DEPLOYMENT → DEPLOYING → DEPLOYED.
{
"id": "w_1a2b3c",
"address": "0x7Fb2…c41A",
"status": "PENDING_DEPLOYMENT",
"chainId": 84532,
"threshold": 1,
"predictedAddress": true,
"name": "Acme Treasury",
"owners": [{ "address": "0xYourSignerAddress", "role": "ADMIN_OWNER", "label": null }],
"createdAt": "2026-08-09T12:00:00.000Z"
}
Keep the id — you need it for the next steps.
3. Fund it from the faucet
Sandbox only. The faucet drips test USDC (default
250, capped at 1000 per drip).
- curl
- TypeScript
- CLI
curl -X POST https://api.sandbox.safebank.ai/v1/wallets/w_1a2b3c/fund \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "asset": "usdc", "amount": "250" }'
const drip = await sb.wallets.fund('w_1a2b3c', { asset: 'usdc', amount: '250' })
sb wallets fund w_1a2b3c --usdc 250
You get back a Drip. status is SUBMITTED and becomes CONFIRMED once the
transfer lands on-chain:
{
"id": "d_9z8y",
"walletId": "w_1a2b3c",
"asset": "usdc",
"amount": "250",
"chainId": 84532,
"status": "SUBMITTED",
"txHash": null,
"failureReason": null,
"createdAt": "2026-08-09T12:00:05.000Z"
}
4. Claim a handle
Payments can target a @handle. Give your recipient wallet a payable handle
via the directory (do this for a second wallet that will receive funds):
- curl
- TypeScript
- CLI
curl -X PUT https://api.sandbox.safebank.ai/v1/directory/handle \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "handle": "volt-components", "walletId": "w_recipient" }'
await sb.directory.claimHandle({ handle: 'volt-components', walletId: 'w_recipient' })
# The `sb demo` flow claims @volt-components for you end-to-end.
sb demo
Returns a HandleInfo:
{ "handle": "volt-components", "displayName": null, "address": "0x…", "chainId": 84532, "verified": true }
5. Send a payment
A payment is proposed → signed → executed. The API proposes it (screening
- calldata), you sign the returned
safeTxHashlocally with a raw secp256k1 signature, and submitting the signature auto-executes once the wallet's threshold is met.
- curl
- TypeScript
- CLI
# 1. Propose — returns a payment with a safeTxHash to sign.
curl -X POST https://api.sandbox.safebank.ai/v1/payments \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceWalletId": "w_1a2b3c",
"destination": { "type": "handle", "handle": "volt-components" },
"amount": { "value": "25.00" }
}'
# 2. Sign the safeTxHash locally (raw secp256k1 — NOT personal_sign), then:
curl -X POST https://api.sandbox.safebank.ai/v1/payments/p_abc/sign \
-H "X-SafeBank-Api-Key: $SAFEBANK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "signature": "0x<signature-over-safeTxHash>" }'
import { SafeBank, signSafeTxHash } from '@safebank/sdk'
const payment = await sb.payments.create({
sourceWalletId: 'w_1a2b3c',
destination: { type: 'handle', handle: 'volt-components' },
amount: { value: '25.00' },
})
// Raw secp256k1 over the 32-byte safeTxHash — never personal_sign.
const signature = await signSafeTxHash(payment.safeTxHash!, privateKey)
const settled = await sb.payments.sign(payment.id, signature) // auto-executes at threshold
sb pay send --from w_1a2b3c --to @volt-components --amount 25.00
# proposes, signs with your demo signer, and executes — all in one command
After signing, the payment settles. status goes to COMPLETED and
executedTxHash is populated:
{
"id": "p_abc",
"sourceWalletId": "w_1a2b3c",
"destinationType": "handle",
"destinationHandle": "volt-components",
"destinationAddress": "0x…",
"destinationChainId": 84532,
"asset": "usdc",
"amount": "25.00",
"status": "COMPLETED",
"safeTxHash": "0x…",
"executedTxHash": "0x…",
"memo": null,
"failureReason": null,
"createdAt": "2026-08-09T12:01:00.000Z"
}
That's the whole loop. 🎉
Next steps
- Payments — the propose → sign → execute lifecycle and the signing gotcha, in detail.
- TypeScript SDK — namespaces, error handling, retries, idempotency.
- Errors and Idempotency — write resilient integrations.
- REST reference — the full endpoint surface.