CC Deposit Flow
CC (Canton Coin) is the native token of the Canton Network Global Synchronizer, issued by the Splice DSO as Amulet contracts. It is not a Rocky-issued token. This page explains how a user deposits CC from their own wallet into a Rocky trading account. The same flow applies to every Token Standard (CIP-56) asset Rocky supports; only the on-chain transfer mechanism differs (see §4).
Concepts used: Canton Party, Holding, Token Standard, canton-bridge. Read Canton Network first if needed.
The assets that can currently be deposited are those with deposit_enabled in GET /v1/assets. In production today: CC, CUSD, CBTC, cETH, USDC.B, USDCx, USX, aCC.
1. Overview
Rocky runs on Canton MainNet. A deposit is a real on-chain transfer from the user's own wallet party to the Rocky exchange party. Rocky creates no contracts on the user's behalf and never holds the user's key.
User wallet (Rocky Wallet / Console / Loop / Send)
│ 1. POST /v1/deposits/reference → target_party_id + deposit_ref
│ 2. Transfer to target_party_id in the wallet, reason/memo = deposit_ref
▼
Canton Global Synchronizer
│ user Holding −N, exchange Holding +N
▼
canton-bridge consumer (Ledger API update stream)
│ 3. Validate receiver / asset / deposit_ref / sender / amount
│ 4. Exercise PlatformAccount.Credit as the exchange party (KMS-signed)
│ 5. Publish NATS chain.events.deposit
▼
internal-ledger
│ 6. Idempotently credit ledger.spot_balances.free += N
│ 7. deposit_intents status → credited
▼
Frontend polls GET /v1/deposits and balances, shows the deposit as credited
Key point: a transfer without a deposit_ref is never credited automatically (see §6).
2. User's view
- Sign in with a wallet (
/v1/wallet/challenge→/v1/wallet/verify) to obtain a session token - On the deposit page pick an asset and amount; Rocky issues a deposit reference (
deposit_ref) valid for 1 hour - Following the on-screen instructions, transfer from the wallet to the Rocky exchange party with
deposit_refin the transfer's reason / memo (Rocky Wallet and Send Wallet fill it in automatically) - Once the Synchronizer confirms the transfer, canton-bridge normally observes and credits it within seconds
- The deposit record moves to
creditedand the spot balance increases
Rocky charges no deposit fee. Any traffic / gas cost shown by the wallet or validator is node traffic cost paid on the user side.
3. API
Every endpoint uses Authorization: Bearer <session_token> (from POST /v1/wallet/verify).
3.1 Create a deposit reference
POST /v1/deposits/reference
| Request field | Required | Description |
|---|---|---|
asset | yes | Asset symbol or alias, e.g. CC, CUSD |
amount_expected | no | Expected amount (positive decimal string). When set, the on-chain amount must match exactly |
provider | no | Wallet provider; defaults to the session's provider |
binding_id | no | Wallet binding id; defaults to the session's binding |
Response:
{
"deposit_id": "1a2b3c4d-…",
"asset": "CC",
"wallet_symbol": "CC",
"target_party_id": "<Rocky exchange party>",
"deposit_ref": "rocky:deposit:1a2b3c4d-…",
"reason_metadata_key": "splice.lfdecentralizedtrust.org/reason",
"expires_at": "2026-09-07T07:30:00Z",
"chain_action_policy": {
"customer_chain_tx_window_hours": 12,
"deposit_ref_required": true
}
}
target_party_idisEXCHANGE_PARTY_ID, shared by all assetsdeposit_refmust be placed verbatim in the transfer metadata under the key given byreason_metadata_keywallet_symbol/wallet_display_name/aliasesare the names the asset carries inside wallets, to help the UI guide the user to the right asset- The reference is bound to the session party that created it: the on-chain sender must be that party
3.2 Deposit history
GET /v1/deposits
Returns the current user's most recent 100 deposit intents:
| Field | Description |
|---|---|
deposit_id, asset, amount_expected, deposit_ref | As created |
status | pending (waiting for the transfer) → observed (bridge saw the transfer) → credited; also expired, rejected |
chain_tx_id, canton_update_id | The on-chain update once credited; verifiable in Canton Scan or on any participant hosting your party |
created_at, expires_at, credited_at | UTC timestamps |
3.3 Send Wallet only
POST /v1/deposits/send/transfer-factory
Send Wallet sessions only. The frontend hands the Token Standard TransferFactory choice arguments to Rocky, which assembles them via canton-bridge for Send Wallet to sign and submit. Other wallets perform the transfer inside the wallet and do not use this endpoint.
4. On-chain transfer mechanisms
| Asset | Mechanism | Notes |
|---|---|---|
CC | Splice Amulet TransferPreapproval | The Rocky exchange party registers a CC TransferPreapproval; wallets send CC straight to the exchange party through it. The bridge reads receiver, amount and deposit_ref from TransferPreapproval_Send* events |
CUSD, CBTC, cETH, USDC.B, USDCx, USX, aCC | Token Standard (CIP-56) TransferFactory | The transfer is either Direct (lands immediately) or an Offer (creates a TransferInstruction the exchange must accept). Offers addressed to the exchange party whose reason starts with rocky:deposit: are accepted automatically by the bridge, also KMS-signed |
The bridge only recognizes instruments registered in the canton-bridge configuration (admin party and instrument id must both match). Same-named tokens with a different instrument (for example the legacy USDCx) are rejected and logged.
5. Funds flow
Depositing N units of an asset:
| Layer | Change |
|---|---|
| On-chain Holdings | user party −N, exchange party +N |
Daml PlatformAccount(asset) | +N after Credit; chain_tx_id records the source update |
| Rocky sub-ledger | ledger.spot_balances.free +N; deposit_intents.status = credited |
Deposits land in the spot wallet. To use them as perpetual margin, move them with Spot Transfer.
6. Bridge validation and failure handling
canton-bridge credits a transfer only when all of the following hold:
- The receiver is the current Rocky exchange party
- The asset instrument is registered
- The reason metadata starts with
rocky:deposit:and matches a deposit intent in statuspending/observed - The intent has not expired (1 hour after creation)
- The sender party equals the session party that created the intent
- If the intent carries
amount_expected, the on-chain amount matches exactly - Each
chain_tx_id/deposit_refis credited at most once (idempotent)
| Failure | Handling |
|---|---|
deposit_ref missing, expired, unknown or already used | Not credited; warn logged |
| Instrument mismatch | Not credited; error logged |
| Sender party differs from the intent | Not credited |
Amount differs from amount_expected | Not credited; manual reconciliation |
| Wallet transfer succeeded but the bridge did not observe it | Keep the update id; ops inspect the ledger update stream |
Funds reached the exchange party but the Daml Credit failed | Ops retry the idempotent command or reconcile manually |
If a user sends assets to the exchange party manually without a valid rocky:deposit: reference, the assets reach the custody party but the balance is not credited automatically. Contact support for manual reconciliation.
7. Development environments
Local / dev environments can mint balance without a real transfer:
POST /v1/deposits/seed(requiresDEV_TOOLS_ENABLED=true; off in production): canton-bridge creates aDepositIntentDaml contract signed bySHADOW_USER_PARTY_IDand credits it. TheDepositIntenttemplate is used only on this dev path; MainNet deposits never create it. See Seed Deposit- The spot session Dev Faucet
8. Related
- Canton Network
- Request Withdrawal, Query Withdrawal
- Spot Transfer (move funds between spot and perp accounts)
- Spot On-chain Settlement
- Backend repo
doc/user-deposit-withdrawal-architecture.md(internal reference for deposit / withdrawal architecture and funds flow)