Web3

How to Build a Smart-Contract Escrow (and Ship It Safely)

8 min readAbsolute Foundry

A smart-contract escrow is a program that holds funds and releases them only when pre-agreed conditions are met — no bank, no middleman, every step provable on-chain. Done right it turns "trust me" into "verify it." Done wrong it loses customer money in a single transaction. So before we write a line of Solidity, we argue about the design.

This is how our team actually works through an escrow build — the questions we ask, the options we put on the whiteboard, and where we land. We've shipped this ourselves (AbsoluteSafe), so this is the real process, not the demo version.

01 First fight: what is the contract really holding?

The opening discussion is never about code — it's about the deal. Escrow is a state machine, and most failures are design failures, not bugs. We map every state and every transition: agree → fund → deliver → release, plus the unhappy paths that actually cause losses — partial delivery, no-show, dispute, abandonment.

The biggest early decision is release granularity. The simple version releases everything at the end. We almost always reject that.

The trade-off we weighedSingle release vs. milestones. One final release is trivial to build but brutal in practice: a buyer who's 90% happy still has to choose all-or-nothing, and disputes become total war. Milestones cost more code but de-risk the relationship — money moves as trust is earned. We land on milestones for anything beyond a trivial one-shot deal.

02 The invariant that matters more than any feature

Once the contract is the custodian, its correctness is the product. We define one rule the contract can never violate: the funds held always cover everything owed. Every function is then judged against that invariant — if a path could let withdrawals exceed the balance, it doesn't ship.

This reframes the whole build. Features are negotiable; the solvency invariant is not. We write it as an explicit assertion and test against it relentlessly, because "looks fine" is how escrows get drained.

03 Disputes without inventing a referee problem

Every team hits the same wall here: who decides a contested release? We put three options up and pressure-test each.

For most real deals we land on the 2-of-3 with a time-boxed process — propose, respond, arbiter decides — and a timeout so funds can never be trapped by a party who simply goes silent. The principle we keep repeating in the room: no path should ever permanently lock money.

04 Treat the audit as a gate, not a trophy

Before mainnet we run our own adversarial pass first — re-entrancy, rounding, griefing, gas-griefing, fee-on-transfer tokens — then a fork test against real token behaviour, then an independent audit. We budget more time for hardening than for the happy-path contract, because that ratio is where safe escrows and exploited ones diverge.

Where we'd landOne EVM chain, USDC/USDT first, milestone releases, a 2-of-3 dispute path with timeouts, a solvency invariant enforced in code, and launch gated on an external audit. Boring on purpose — boring is what holds funds.

Key takeaways

FAQ

How long does escrow take to build?

A milestone MVP with disputes is a few weeks of design and build; hardening and the audit usually take longer than the contract itself — and that's the right ratio.

Which chains and tokens first?

One EVM chain and the stablecoins your users hold (USDC/USDT). Multi-chain is a later optimisation, never a launch requirement.

Do we really need an audit?

For anything holding real funds, yes — an internal adversarial review plus an independent audit is the floor.

We designed and shipped AbsoluteSafe — on-chain escrow, audited and live.

Build escrow with us