> ## Documentation Index
> Fetch the complete documentation index at: https://docs.testnet.dev.adipredictstreet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# State machine

> All withdrawal states, transitions, and balance effects.

## Diagram

```mermaid theme={null}
stateDiagram-v2
    [*] --> PENDING: POST /withdrawals/request
    PENDING --> CO_SIGNED: AML clean
    PENDING --> MLRO_REVIEW: AML flagged
    PENDING --> CANCELLED: user DELETE
    PENDING --> REJECTED: validation fail
    MLRO_REVIEW --> CO_SIGNED: MLRO approves
    MLRO_REVIEW --> REJECTED: MLRO denies
    MLRO_REVIEW --> CANCELLED: user DELETE
    CO_SIGNED --> SUBMITTED: on-chain tx broadcast
    CO_SIGNED --> REJECTED: submission fails
    CO_SIGNED --> CANCELLED: user DELETE
    SUBMITTED --> CONFIRMED: on-chain confirmation
    SUBMITTED --> REJECTED: chain revert
    CONFIRMED --> [*]
    REJECTED --> [*]
    CANCELLED --> [*]
```

## 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 is `PENDING`, `MLRO_REVIEW`, or
`CO_SIGNED`:

```http theme={null}
POST /api/me/withdrawals/{id}/cancel
# Withdrawal endpoints aren't exposed on the partner API today.
```

After `SUBMITTED` the tx is already in the mempool — cancel is not
possible. Response:

```json theme={null}
{ "code": "invalid_state", "message": "cannot cancel withdrawal in status=SUBMITTED (tx already broadcast or terminal)" }
```

## Audit trail

Every transition writes an immutable row to `withdrawal_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. **`GET /api/me/withdrawals/{id}/events`** returns the full
lifecycle audit stream:

```json theme={null}
{
  "id": "wd-...",
  "events": [
    {
      "id": "evt-...",
      "kind": "created",
      "fromStatus": null,
      "toStatus": "CREATED",
      "actor": "0xab12...",
      "txHash": null,
      "createdAt": "2026-04-30T12:00:00Z"
    },
    {
      "id": "evt-...",
      "kind": "broadcast",
      "fromStatus": "BACKEND_COSIGNED",
      "toStatus": "SUBMITTED",
      "actor": "system",
      "txHash": "0x...",
      "createdAt": "2026-04-30T12:00:05Z"
    }
  ]
}
```

`kind` is one of `created`, `auto_approved`, `mlro_approved`,
`mlro_rejected`, `cosigned`, `broadcast`, `confirmed`, `reverted`,
`cancelled`, `rejected`. `actor` is the EOA for user-initiated
transitions, `system` for back-office workflows, or `chain` for the
chain-watcher that flips `SUBMITTED → CONFIRMED`. Use this endpoint
for any compliance audit trail — `GET /api/me/withdrawals/{id}` only
returns the head state.
