A buyer-controlled, non-custodial payment escrow. Every claim in this document maps to a receipt you can verify yourself — a line of code, a passing test, or an on-chain transaction. Don't trust us. Check it.
A smart-contract escrow for ETH or any ERC-20. A buyer locks funds earmarked for a seller, with a refund window:
There is no operator, no admin, no pause, no upgrade. No key held by anyone — including the deployer — can move a user's money. The buyer, and only the buyer, controls their deposit.
| The claim | How the code enforces it | Receipt |
|---|---|---|
| Non-custodial — only the buyer can move their funds | release / refund require msg.sender == payer; no operator role exists | test_onlyPayer_can_release |
| Buyer controls release | release(id) is payer-only | test_release_splits_99_02_08 |
| 1% fee, split 0.2 / 0.8, exact to the wei | toRecipient + feePersonal + feeReinvest == amount, always | test_release_splits_99_02_08 + on-chain release tx |
| Funds always recoverable — even if the seller's address rejects payment | Buyer can refund after the window, unconditionally | test_revertingRecipient_fundsStillRecoverable |
| Reentrancy-safe | EIP-1153 transient guard + checks-effects-interactions | test_reentrancy_cannotDoubleWithdraw |
| A hostile fee wallet can't freeze the escrow | Fees accrue (pull-payment), withdrawn separately — a reverting fee wallet only blocks itself | test_revertingFeeWallet_no_longer_bricks |
| No value leaks on tiny amounts | Fee rounds down; split still conserves exactly | test_dustDeposit_conserves_noRevert |
| It works on-chain | Deployed and exercised on Base Sepolia | deploy / deposit / release txs below |
9 tests, all passing. Run them yourself: forge test.
An honest document names its own edges. This escrow is deliberately simple, and here is what it does not yet do:
This is the entire point. Nothing here asks for your trust — it offers you proof.
src/NonCustodialEscrow.sol (~180 lines).forge test -vv → 9 passing, including the adversarial attacks.On-chain — Base Sepolia (initial deploy):
0x7bBF02D2E71B6e22153c3b75c09e7446AD6b9a1f0xb3b4e283…883e990xc171e015…6aaae10x78db3ebb…751526Verified split (release tx, read back from chain):