Deposits & withdrawals
The collateral lifecycle — on-chain in, engine-approved out, with a nonce that makes replay impossible.
Collateral movement is the one flow that is fully on-chain on both ends. Trading is off-chain; custody never is.
Deposit
User exercises a deposit on the Vault contract, locking collateral and producing a
DepositReceipt.
The indexer observes the ledger transaction and writes it into deposit_receipts.
The engine sees inbound.deposit on its own ledger feed and credits the trading balance.
Deposits need no engine approval — the collateral is already locked. The only latency is ledger finality plus indexer lag.
curl 'http://localhost:4000/v1/users/Alice::1220…/deposits?limit=20'Withdrawal
Withdrawal is a request/approve flow, because the engine must confirm the collateral is not backing an open position.
Request (wallet-signed, on-chain)
The user submits a WithdrawalRequest carrying the current withdrawal counter as a nonce.
Engine evaluates
inbound.withdrawal_request reaches the engine. It checks free collateral against margin
requirements at the last committed batch.
Approve or reject (on-chain)
engine.approve_submitted or engine.reject_submitted. On approval the vault releases and the
counter increments.
The counter is the replay defence. GET /v1/users/:party/withdrawal-counter returns the
current nonce. Signing a request with a stale counter produces a request the vault will reject —
which is exactly what stops a captured request from being resubmitted later.
Why withdrawals wait for a commitment
The engine can only safely release collateral it can prove is unencumbered, and the proof is the committed batch. A withdrawal evaluated against uncommitted fills could release margin backing a position that the ledger does not yet know closed.
So user-visible withdrawal latency ≈ time to next batch commitment + ledger finality. Shrinking it means shortening the cadence — see Batches & commitments.
Reads
| Route | Returns |
|---|---|
GET /v1/deposits?user&before&limit | Global deposit receipts, paginated |
GET /v1/withdrawals?user&status&limit | Global withdrawal requests |
GET /v1/users/:party/deposits | Scoped, user-only |
GET /v1/users/:party/withdrawals?status | Scoped, filterable by status |
GET /v1/users/:party/withdrawal-counter | Current nonce |
Testnet faucet
On devnet the BFF exposes a self-service top-up, gated by FAUCET_ENABLED, a per-party cooldown,
and a balance ceiling:
curl -X POST 'http://localhost:4000/v1/users/Alice::1220…/faucet'A party can only faucet itself — the route is guarded by requireOwnParty.