Diagram
States
| State | Meaning | Balance | Next action |
|---|---|---|---|
PENDING | Just created; balance locked | locked | automatic: AML runs |
AML_REVIEW | Reserved (MVP: not used — AML done pre-request in core-api) | locked | — |
MLRO_REVIEW | Awaiting human compliance decision | locked | staff via /internal/.../aml/decision |
CO_SIGNED | Backend signed; waiting for submission | locked | on-chain broadcast |
SUBMITTED | On-chain tx broadcast; txHash stored | locked | on-chain confirm/revert |
CONFIRMED | Terminal — funds left platform | locked -= amount | — |
REJECTED | Terminal — denied / failed / reverted | locked → available | — |
CANCELLED | Terminal — user-cancelled pre-submit | locked → available | — |
Cancellation rules
Users can cancel while status isPENDING, MLRO_REVIEW, or
CO_SIGNED:
SUBMITTED the tx is already in the mempool — cancel is not
possible. Response:
Audit trail
Every transition writes an immutable row towithdrawal_events:
CREATED, AML_PASSED, AML_FLAGGED, MLRO_APPROVED,
MLRO_REJECTED, BACKEND_COSIGNED, SUBMITTED, CONFIRMED,
REVERTED, USER_CANCELLED, REJECTED.
The table has NO_UPDATE / NO_DELETE PG rules enforcing append-only
semantics. Events are exposed via
GET /api/me/withdrawals/{id}/events.