Transaction State Machines for Multi-Chain Custody: What Actually Breaks

Maciej Lewandowski · July 2026 · Field notes from custody & wallet infrastructure

Every wallet backend has a transaction status column. Very few have a transaction state machine. The difference shows up the first time a chain does something the happy path did not anticipate, which on real chains is roughly every week.

A status column answers "what is it now". A state machine answers three harder questions: which transitions are legal, what triggers them, and what happens when the world moves backwards. In custody, where every transition can move client money, those questions are the system.

The states you actually need

The shape that has survived contact with BTC, EVM chains and Solana looks roughly like this:

CREATED → POLICY_APPROVED → SIGNED → BROADCAST → PENDING → CONFIRMED(n) → FINALIZED

with exits to FAILED, REPLACED and EXPIRED, and one deliberately uncomfortable arrow: CONFIRMED → PENDING, for reorgs. A few of these deserve defending:

Backwards transitions, or: reorgs are not exceptional

The transition everyone forgets is the one that goes backwards. A block containing your confirmed transaction can stop being part of the canonical chain. On fast chains with probabilistic finality this is routine; on Bitcoin it is rare but priced in; on Ethereum it is bounded by the finalized checkpoint. When it happens, the transaction must walk back to PENDING, any ledger effect must be reversed with a compensating entry (append-only ledgers make this provable, mutable balance columns make it archaeology), and someone should be paged.

A state machine with no backwards transitions is a statement of faith that the chain will never disagree with your database. The chain will disagree with your database.

Idempotency is the other half of the design

Most real incidents are not exotic chain behaviour. They are retries. A timeout on broadcast, an operator clicking again, a queue redelivering a message. Every one of those must be safe, which means:

Where each chain bends the model

The audit trail is not a log file

In a regulated environment, every transition needs: previous state, new state, trigger (who or what), timestamp, and the evidence (block hash, node response, policy decision id). Stored as data, queryable, immutable. When a client asks why a withdrawal took forty minutes, or an auditor asks who approved it, the answer is a query, not a grep through application logs that rotated last month.

What actually breaks: a short list from production

  1. Treating broadcast as done. The transaction vanishes from the mempool and from your books at the same time.
  2. No REPLACED state. Fee-bumps look like failures; monitoring cries wolf until people stop listening.
  3. Re-signing on retry instead of re-broadcasting persisted bytes.
  4. Per-transaction thinking on Ethereum, ignoring the nonce queue.
  5. No EXPIRED state on Solana; transactions rot in PENDING forever.
  6. Crediting on CONFIRMED with one global threshold for every chain.
  7. No backwards transition for reorgs, so reconciliation finds the hole weeks later.

None of these are hard to fix on a whiteboard. All of them are expensive to fix after the money moved.