Skip to main content

Quickstart: your first USDC send

This page goes from a sandbox secret key to a USDC send settled on chain, as the calls you make, in order. Everything here is sandbox only.

You need curl, jq, a sandbox account at auth.sandbox.cashxchain.com with two-factor authentication set up (the web app asks for it before any payment), and a secret key from the sandbox console, dev.sandbox.cashxchain.com → API keys → Create key.

Bash
export CXC_BASE=https://api.sandbox.cashxchain.com
export CXC_KEY=cxc_sk_sandbox_... # your secret key
export RUN=$(date +%s) # makes this run's Idempotency-Keys unique

Every write below carries an Idempotency-Key. Sending the same request again under the same key is safe — it answers with what the first one did — so keep $RUN while you retry, and change it for a new operation. See Idempotency.


Step 1 — See what is offered​

Bash
curl -s "$CXC_BASE/v1/banking/capabilities" \
-H "Authorization: Bearer $CXC_KEY" | jq '{environment, flows: [.flows[] | {flow, status}]}'

environment is sandbox, and the digital-asset flows read sandbox_only, or requires_onboarding until Step 2 is done. Every field is explained in Capabilities.


Step 2 — Get approved​

A digital-asset account needs your business verified with our crypto-asset service provider. In the sandbox one call does it — no hosted form:

Bash
curl -s -X POST "$CXC_BASE/v1/sandbox/banking/kyb/digital-assets/approve" \
-H "Authorization: Bearer $CXC_KEY" | jq '{state, can_open_account, next_steps}'
JSON
{
"state": "active",
"can_open_account": true,
"next_steps": ["Open a USDC account at POST /v1/banking/accounts with currency USDC."]
}

No body and no Idempotency-Key; calling it again is harmless. In the web app, Open USDC account on the Digital Assets page approves you and opens the account (Steps 2 and 3) in one click. Details: Onboarding.


Step 3 — Open a USDC account​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/accounts" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: da-open-usdc-$RUN" \
-d '{"currency": "USDC"}' | jq '{currency_account, error, message}'

USDC=$(curl -s "$CXC_BASE/v1/banking/accounts" \
-H "Authorization: Bearer $CXC_KEY" \
| jq -r '.accounts[] | select(.currency == "USDC") | .id')
echo "$USDC"

The account opens with a wallet on Solana, and lists two identifiers: an EUR IBAN and its Solana address. 409 digital_assets_onboarding_required means Step 2 has not finished; 409 conflict means you already hold one — carry on with its id.


Step 4 — Add test funds​

Bash
curl -s -X POST "$CXC_BASE/v1/sandbox/banking/accounts/$USDC/simulate-deposit" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: da-test-funds-$RUN" \
-d '{"amount": "100", "network": "solana"}' | jq
JSON
{
"account_id": "…",
"currency": "USDC",
"network": "solana",
"amount": "100.000000",
"status": "simulated"
}

The 202 means the provider's sandbox was asked to deposit. The deposit is read and booked within seconds — a balance, an onchain_transfer row on GET /v1/transactions, and a payin.received webhook:

Bash
curl -s "$CXC_BASE/v1/banking/accounts/$USDC/balance" \
-H "Authorization: Bearer $CXC_KEY" | jq

Test funds are always credited, whatever you sent before. They are how you fund a digital-asset account from outside: no real or testnet asset sent to a sandbox address is credited. The faucet (POST /v1/sandbox/banking/faucet) is for bank accounts only: for USDC it answers 422 asset_unsupported.


Step 5 — Approve a recipient​

An API key sends to a third party only to a trusted beneficiary — a destination a person of your account approved, with their second factor. No key can approve one. Approve one in the web app:

  1. Sign in at app.sandbox.cashxchain.com.
  2. On the Digital Assets page, on the Approved recipients card, choose Manage, then Approve a recipient.
  3. Give it a name, and enter a Solana wallet address — not one of your own — and who holds it: for a third party, the name, street, city and country (the travel rule). Confirm with your second factor.

The sandbox lets API keys use an approval at once: its cooldown is 0 — trusted_beneficiary_cooldown_seconds in GET /v1/banking/accounts says what the deployment runs with. Read the approval's id with your key:

Bash
TRUSTED=$(curl -s "$CXC_BASE/v1/banking/digital-assets/trusted-beneficiaries" \
-H "Authorization: Bearer $CXC_KEY" \
| jq -r '[.trusted_beneficiaries[] | select(.status == "active" and .kind == "onchain")][0].id')
echo "$TRUSTED"

The approval itself — the request the web app sends, and every kind of destination — is in API keys and trusted beneficiaries.


Step 6 — Quote the send and lock its route​

A quote moves nothing. It checks the send as the payout will, answers the provider's minimum, and locks the route under a quote_id:

Bash
QUOTE=$(curl -s -X POST "$CXC_BASE/v1/banking/payouts/quote" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {"type": "onchain", "trusted_beneficiary_id": "'"$TRUSTED"'"}
}')
echo "$QUOTE" | jq '{amount, currency, network, destination_network, minimum, valid, notes, quote_id, expires_at}'
QUOTE_ID=$(echo "$QUOTE" | jq -r '.quote_id')

valid: true means our checks passed; minimum is the provider's minimum in the asset (1 USDC on chain). See Quotes and the route lock.


Step 7 — Send it​

Bash
PAYOUT=$(curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: da-first-send-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {"type": "onchain", "trusted_beneficiary_id": "'"$TRUSTED"'"},
"quote_id": "'"$QUOTE_ID"'"
}')
echo "$PAYOUT" | jq '{id, status, kind, network, destination_network, amount, currency}'
PAYOUT_ID=$(echo "$PAYOUT" | jq -r '.id')

202 Accepted with status: "submitted" and kind: "onchain". An API key needs no step-up for a send to a trusted beneficiary. The send is fixed at the source: amount is what leaves the wallet, at most two decimal places.


Step 8 — Follow it to settled​

Bash
curl -s "$CXC_BASE/v1/banking/payouts/$PAYOUT_ID" \
-H "Authorization: Bearer $CXC_KEY" | jq '{status, tx_hash, settled_at}'

Poll until the status is final. There is no webhook for a payout's status. The sandbox reads a send's status from the provider every 15 seconds for its first ten minutes, so poll at about that pace. An on-chain send is completed by the provider's sandbox — in our runs from about 25 seconds to a little over a minute after it accepted the send, and in one run between about 45 seconds and 3½ minutes — and reads settled, with its tx_hash, at the next of those reads. Statuses: Payouts. Sends the provider's sandbox leaves in flight — to an IBAN or a bank account, send-backs, swaps — the sandbox settles itself, by default from a minute after the provider accepts it (five minutes for a us_bank send), once the money has been seen leaving the wallet, and marks them simulated: true — see when a send reads settled.


Next​

  • API transfer guide — the same in depth, and bank to wallet, with the timings and the errors a client meets.
  • Sending — every destination, with the fields and a request for each: on chain and across chains, IBAN, US/UK/MX/BR bank accounts, @handle, swaps, send-back and convert.
  • Accounts and wallets — EURC and the other assets, wallets on more chains, digital_actions.
  • Errors — every digital-asset error code.
  • Sandbox behaviour — which sends finish in the sandbox, and which the sandbox settles itself.