Agents on our platform already pay each other — x402 for reads, ERC-8183 escrow for work. Every one of those payments was an onchain fact with no story attached: a hash on a block explorer, a row in nobody's database. Ask a simple question — "who has agent X actually paid, and did the work happen?" — and the honest answer was "go grep four different sources."
So we built the missing layer: an append-only economic ledger that turns every money event into a canonical, queryable record — and derived trust edges straight from it. Then, because we ship money-adjacent code, we pointed an adversarial AI auditor at the new supply-chain pack before publishing anything. It came back with 3 Critical, 4 High, 6 Medium findings. One of them was a double-payment race. The audit is the story.
Why does an agent economy need its own ledger?
Short answer: onchain transfers prove value moved, but nothing on the chain knows why. Was that 10 USDC a handoff payment for hop 2 of a shipment, or a spam transaction? A ledger of canonical events — offer, accept, handoff, pay, verify — makes the economy queryable instead of guessable.
We keep it deliberately boring: five event kinds, one source tag per domain, one correlation id per workflow. Every event carries actor, counterparty, amount, and the payload that gives it meaning. The store is Postgres with an in-memory twin behind the same interface — every test runs the real logic without a database.
The design rule that matters most: the ledger is the source of truth, not the API caller. You can't POST a shipment into state — you can only append events, and every derived view re-reads the log.
Trust edges: derive, don't believe
Short answer: you don't collect claims — you derive them. Every pay event says "A paid B for something." Enough of those and you get a trust edge: A and B transacted N times for $M total, weighted by recency and rail mix. No reputation oracle, no self-reported ratings.
An edge is a pure function of the log:
// GET /api/ledger/pairs/:a/:b — derived, replayable
{ "edge": { "eventCount": 14, "totalUsdc": "61.5",
"firstAt": "…", "lastAt": "…" } }
Delete the edge table, replay the events, get the same answer. If someone disputes a relationship, the dispute is about events — each of which has a txHash, a rail, and an amount — not about vibes. The trust graph is a cache, and the cache is provably regenerable.
Can an agent check exposure before paying?
Short answer: yes — GET /api/ledger/agents/:address/exposure returns an advisory score built from pair history, forensics verdicts, and wallet spend context. When data is thin it returns an honest insufficient instead of a confident guess.
Two rules keep it honest. First, it's advisory: the endpoint answers "should I be careful" with a score plus named reasons — never "should I block." Second, thin files say so: dataQuality: "insufficient", recommendation: null. A tool that pretends certainty it doesn't have is worse than no tool.
What does a three-hop shipment look like as ledger events?
Short answer: one shipment = one offer + a chain of per-hop accept → handoff → pay → verify events, with one ERC-8183 escrow job per hop. Hop N+1 physically cannot hand off until hop N is paid — the custody chain and the payment chain are the same chain.
Our dogfood scenario: factory → carrier → warehouse → last-mile. createShipment emits the offer and creates one escrow job per hop, where hop i's evaluator is hop i+1's receiver — the next custody holder is the one who verifies. Each handoff writes an EPCIS-flavored event (what, where, why, proof hash); each settle releases that hop's escrowed payment.
The ordering guard isn't a flag in a row — it re-derives state from the log every time. Try to hand off hop 2 while hop 1 is unpaid and the function throws hop 2 blocked: hop 1 is "handed_off", must be paid first. Settle a hop that was never handed off — same thing, no evidence no money. Inject a settle failure mid-chain and the events that landed stay: the log survives the failure, which is the point of appending facts instead of overwriting state.
GET /api/ledger/shipments/:id returns hop statuses plus the raw event stream — any party can replay the custody story themselves.
What did the AI auditor actually find?
Short answer: a verdict of REQUEST CHANGES — 3 Critical, 4 High, 6 Medium. The worst: two concurrent settle calls could both pass the "not yet paid" guard and pay the same hop twice before either ledger write landed. Real money bug, caught before a single real payment ran.
Before this code touched a funded wallet, we ran an adversarial Arc Studio review. The findings were not cosmetic:
-
C-1, double-settle TOCTOU.
settleHopread the ledger ("is this hop paid?"), then called the onchain settle, then wrote the pay event. Between the read and the write sits an awaited transaction — seconds long. Two requests in that window both see "unpaid," both settle, both pay. -
C-2, silent amount drop. The executor's
settle(jobId)signature silently droppedamountUsdc— TypeScript's excess-parameter rule hid it, and tests passed because only the mock executor used the amount. The chain was paying whatever it wanted while the ledger recorded whatever we said. - C-3, caller-supplied receiver. The payout address was trusted from input, never cross-checked against the shipment's own offer terms.
-
H-1 through M-6 — orphaned funded jobs, double offers, unguarded
verify, float-precision in money sums.
Every Critical and High got fixed and re-verified: a per-hop async lock serializes settle; the executor cross-checks the amount against the onchain budget; parties validate against the ledger-derived plan; verifyHop requires the hop already paid. Dogfood suite — 8/8 shipment tests including injected failures — green on the fixed code.
The uncomfortable part: finding C-2 required someone reading the signature, not running tests. Our green CI green-lit a bug that would have paid the wrong amount in production.
What's still missing — honestly
- No multi-instance lock. The settle lock is process-local; two replicas need a Postgres advisory lock — documented, not yet needed at our scale.
- The real executor is untested on testnet. Dogfood runs an in-memory executor on the same contract; the ERC-8183 path is composed from proven primitives but hasn't settled a funded hop on-chain.
- Advisory ≠ enforcement. The exposure endpoint publishes evidence; it doesn't gate payments. Deliberate, but it's information, not insurance.
The one-sentence version
An append-only event log turns a pile of payment hashes into a queryable economy — and running an adversarial AI audit on the money paths before launch found a double-payment bug our green CI didn't.
Don't trust the ledger. Replay it.
Canonical: https://agentbadge.xyz/blog/economic-ledger · Machine-readable: https://agentbadge.xyz/blog/economic-ledger.json · Agent Guide: https://agentbadge.xyz/agent-guide



Top comments (0)