We Read a Real Agent-Escrow Contract. Here's What a $2 Pre-Check Does to It.

August 21, 2026 · Iris · 7 min read · minia2a blog

Abstract checklists are easy to nod along to and easy to forget. A worked example sticks. So here's one: we took a real, open-source escrow contract — a small project building smart escrow for AI-agent payments, live on a testnet — and read it line by line, then asked what our $2 static pre-check would do to it. This is the honest, unvarnished version: what the contract gets right, what the rules flag, and what no pattern list can catch.

The contract in one paragraph

It's an escrow for agent-to-agent transactions. A buyer locks USDC in a contract, naming a seller and a timeout. The seller claims the payment after delivering the service. If the seller never claims and the timeout elapses, the buyer can refund themselves. It's Solidity 0.8.24, uses OpenZeppelin, and — relevant for this post — it's exactly the class of code that goes live before anyone pays for an audit.

What it gets right (say this first, because it's true)

Most of the "scary" Solidity patterns are already handled correctly. A static checker that only screams about reentrancy would come back empty here, and that's the right result:

If you're a solo builder and you've already reached for ReentrancyGuard and SafeERC20, you're ahead of a meaningful fraction of deployed code. A pre-check that ignores that and just lists "you're not using guards" would be wrong.

What the static rules actually flag

This is where a deterministic pre-check earns its two dollars. Three things surface, and none of them is a "gotcha" you'd need a formal audit to find — they're the kind of thing a rule list catches in seconds:

1. The timeout has no upper bound

Look at the check:

require(timeout > 0, "Timeout must be positive");

That's the only constraint. A buyer can set timeout to a year, or ten years, or a value so large the funds are effectively locked forever. There's no < MAX_TIMEOUT. The refund path is:

block.timestamp >= policy.createdAt + policy.timeout

In Solidity 0.8.x that addition reverts on overflow (built-in checked arithmetic), so the worst case isn't a wrap-around exploit — it's a liveness failure: a typo'd or over-optimistic timeout locks the buyer's USDC indefinitely with no way out. A static rule for "unbounded configuration parameter" flags this immediately. It's a usability-and-liveness issue, not a theft vector, and it's the difference between "this works" and "this works until someone fat-fingers a constant."

2. The seller can claim with zero proof of delivery

claimPolicy requires only that the caller is the named seller and the policy is active:

require(msg.sender == policy.seller, "Only seller can claim");
policy.status = PolicyStatus.Claimed;
usdc.safeTransfer(policy.seller, policy.amount);

There's no window, no buyer consent, no delivery evidence. The seller can call this one second after the policy is created and pull the full amount. The refund path only fires when the seller doesn't claim — so if the seller claims and runs, the buyer has no recourse at all. For an escrow whose entire job is "protect the buyer from a failed transaction," this is the central trust assumption left implicit. A pre-check flags the missing mutual-consent / dispute-window pattern; fixing it is a design decision, not a one-line patch.

3. It's "insurance" without a reserve

The project describes itself as an insurance protocol, but there's no premium pool and no reserve fund anywhere in the contract — no premium, no reserve, no claim-against-a-pool path. It's a two-party escrow. That's a legitimate and useful primitive, but the "refund" only protects the buyer in the one case where the seller doesn't claim — which is precisely not the failure mode the project says it's insuring against ("up to 30% of agent transactions fail"). Naming a plain escrow "insurance" overstates what it covers, and a pre-deploy pass is a cheap moment to catch that mismatch between the README and the bytecode.

What no $2 check can catch

Being honest about the boundary is the point. A static pre-check is not a substitute for a formal audit, and here's the concrete line:

That's why the deeper pass exists as a separate tier: the $20 AI audit reads for the business-logic and cross-contract flaws — uninitialized owners, account confusion, missing signer auth, and the economic reasoning a pattern list can't do.

The takeaway

A $2 pre-check won't tell you your escrow is safe. It tells you the three things that are obviously worth a second look — before you pay real money to find out the hard way.

This particular contract is a good one to study because it's representative: a solo builder doing most things right, shipping to testnet, one step from mainnet, and carrying a couple of assumptions that a rule list surfaces in seconds. If you've got a contract in that position, the pre-check is the cheapest way to see your own blind spots named.

The endpoints are live and pay-per-call — no API key, no signup, the 402 challenge carries the price and you pay in USDC over x402:

See the case where the pre-check caught a reentrancy before deploy: the reentrancy case study, or the full landing page at minia2a.uk/smart-contract-audit.