Escrow is a state machine, not a feature
When we took on MOXAT — custodial USDT payments and escrow for transactions where the two sides don't trust each other — the first engineering decision had nothing to do with interfaces. It was a list of five words: created, funded, in escrow, released, settled. Everything the product does is a transition between those words, and nothing else is allowed to move money.
Why custody code rots
Most payment bugs aren't arithmetic bugs. They're path bugs: a refund handler that skips the ledger, an admin override added during an incident, a webhook that fires twice and credits twice. Each shortcut works the day it ships. Six months later nobody can say with confidence where money can move from, which is another way of saying nobody controls the money.
- Every movement of value is a named transition with preconditions — no anonymous updates to a balance column.
- A double-entry ledger records every transition. If it isn't in the ledger, it didn't happen.
- There is no admin override that bypasses the ledger. Disputes get their own explicit path: disputed → funds held → arbitrated release.
- Idempotency at every boundary — a webhook retry replays a transition that already happened and does nothing.
Deterministic tests, ugly cases first
A state machine earns its keep in testing. Every transition gets deterministic tests, and the rehearsal list starts from the cases that end up in support tickets: double deposits, partial funding, disputes raised mid-release. When the answer to 'what happens if both happen at once?' is a transition table lookup instead of a debate, the review is short.
The code that guards funds should be boring by design. Excitement in an escrow engine is a defect.
MOXAT has run in production with this shape since launch. Disputes take the arbitrated path we designed instead of a support ticket, and the ledger answers audits without archaeology. If you're building anything that holds other people's money: write the five words first.