// Engineering · 2026-09-16
Building the payment guard for Arc.
Arc is Circle’s chain where USDC is the gas token. It puts three things in one place that have never been in one place before: agent identity through ERC-8004, job escrow through ERC-8183, and settlement in the same asset an agent spends. An agent can be registered, hired, paid on delivery and rated, and the money never leaves USDC.
That combination is the reason we built for it. It is also the reason the money boundary needs an enforcement layer rather than another content scanner: when identity, instructions and settlement meet, the text an agent reads is one step away from the payment it signs.
This is what we shipped, how we verified it, and what we are building next.
Verification before support
Our payment guard checks that an x402 payment matches the quote it was offered against — offline, with no RPC. To recover the signer of an EIP-3009 authorization it needs the exact EIP-712 domain, and a guessed domain does not fail loudly. It recovers the wrong signer and returns a confident wrong answer.
So we
do not take domains from documentation. For both Arc networks we read DOMAIN_SEPARATOR() off the deployed
contract and confirmed it reproduces from the name and version we were about to ship. For mainnet we did
it across four independent RPC providers so the entry does not rest on one endpoint’s word.
50420025042USDC · 2 · 6
both networks
yesWe
also sent a deliberately malformed authorization and watched it revert FiatTokenV2: invalid signature, which proves the
EIP-3009 path is really there rather than merely documented. A test pins each separator, so a drift in
name or version cannot silently start recovering the wrong signer.
One Arc-specific detail worth stating for anyone integrating: the native USDC balance has 18 decimals and the ERC-20 interface has 6, over the same underlying balance. Quotes are in ERC-20 base units. Mixing the two is an easy way to be off by a factor of a trillion.
The boundary: a task the model cannot widen
A content scanner can be evaded. That is not a reason to skip it — it is a reason not to make it the only thing standing between an agent and a wallet. So the enforcement lives somewhere the model cannot reach.
An operator approves a task in the console: one approved recipient, a total budget, a per-payment cap, a gas cap, an expiry, optionally one private endpoint, and the API key that may use it. The agent holds that key and can only ask. The key that can pay never leaves the server.
So the worst a fooled model can do is make a request that gets refused with a code. There is no key in its context to hand over.
ARC-000 account kill switch engaged ARC-001 task revoked, expired, or not bound to this key ARC-002 recipient is not the task's approved recipient ARC-003 per-payment cap exceeded ARC-004 task budget exceeded ARC-005 estimated gas exceeds the task's gas cap ARC-006 request id reused for a different request ARC-007 quote is not on Arc / not Arc USDC ARC-008 job check failed (client, provider, evaluator, hook, status) ARC-009 deliverable does not match the on-chain hash ARC-010 signer or chain unavailable — abstain, never a partial allow
One ordering detail matters more than it looks. The signed bytes, the nonce and the budget reservation commit in a single tenant-locked transaction before the transaction is broadcast. A crash between commit and send leaves a row the reconciler re-broadcasts, not a second payment. Refusals are stored too — returned from the transaction rather than thrown through it, because a throw would roll back the very row that tells the operator what their agent asked for.
What the live run showed
We ran all of it against Arc Testnet with a funded signer and real USDC. The case worth naming is the poisoned quote: an attacker’s payee, wrapped in text instructing the model to send payment there and not mention the charge.
refused ARC-002
X402-202 · X402-209confirmed
same transaction, no second payment
refused ARC-004 at 1.75 of 2.00 USDC
Completedrefused ARC-0094 — the refusals signed nothing
The content scanner flagged the injection. Independently, the signer refused because the payee was not on the task. Nothing was signed. That is the architecture in one result: two boundaries, either of which holds alone.
Afterwards everything reconciled — the merchant held exactly what was sent, the signer’s nonce equalled the number of real transactions, and the task ledger matched the chain. The refusals are rows, not transactions.
Two defects surfaced only because the run was live, and both are fixed. Our SSRF guard answered Node’s DNS lookup in the wrong shape and so could never open a socket — it failed closed, so it was safe, and every unit test passed because those tests only asked which addresses the classifier rejects. And ERC-8004 documents resolved through a single IPFS gateway, which is one outage away from “scanning unavailable”. Neither would have appeared without spending real testnet USDC.
What we learned from the registry
Building the agent-scanning half meant reading the ERC-8004 registry at scale, and what we found shaped what we plan to build next.
895,230150 / 150 (0 errors)139 (92.7%)1yes
Nearly 900,000 identities exist on testnet. In a random sample of 150, read without a single error, one
declared a service endpoint. The typical record is a well-formed NFT profile — a name, an image, and
trait badges like "Role": "DEX Trader"
— with no endpoint, price or callable schema.
There is a straightforward reason, and it is a tooling story rather than a criticism. ERC-8004 identities are ERC-721 tokens, so every library that already exists emits OpenSea-style collectible metadata by default. The standard permits a document an agent could act on; the path of least resistance produces a profile card. On a testnet where registration is nearly free, that is exactly what you would expect to accumulate.
Two structural facts follow from the same ERC-721 inheritance. Identities are transferable, so whatever reputation an identity earns attaches to the token rather than the operator who earned it. And the registries are upgradeable proxies, so the meaning of a record can change without any address an integrator pinned changing.
None of that is unusual for infrastructure in its first weeks. It is the cheapest possible moment to build the checks, which is the point.
Mainnet, and what we deliberately did not carry over
Arc mainnet opened on September 16. We added support the same day — but because we read the deployed contract, not because the date arrived. The USDC domain verifies across four providers, so payment conformance works on mainnet today.
The registries are the part we did not carry over. ERC-8004 and ERC-8183 are not deployed at their testnet addresses on mainnet, so those fields are null there and our agent-scan and job-settlement endpoints refuse with a message naming exactly what to configure.
Reusing a familiar address on a chain that has not deployed it would point the guard at whatever happens to occupy it, and the failure would present as success. The nullability lives in the type system rather than in a comment, so every call site had to pass a check the compiler enforces.
What it costs, and when we charge
$0.01$0.02$0.005$0.01freeTwo of those rules are commitments rather than implementation details, so they are tested rather than described.
We charge when we sign, never for refusing. If a refusal earned revenue, the incentive would point the wrong way, and an operator could not tell whether a refusal was about their policy or our margin.
A retry is not billed twice. The charge is keyed on the execution’s own request hash, so an identical request reuses the ledger key and is absorbed — the same property that stops a retry from signing a second transaction. The 402 also arrives before any chain work, so short credit never leaves a half-signed transaction behind.
What we are building next
The registry measurements point at one thing more than any other. Reputation is what would let one agent tell a good counterparty from a bad one, and across three mainnets a published study of 173,441 agents found that 98.7–100% of feedback records carry no proof of payment, with the median cost of moving a score between $0.0027 and $0.055.
Reputation bound to settlement receipts. We already issue a signed receipt that binds a payment to a task, a recipient, an amount and a confirmed transaction. Binding a feedback record to one of those receipts makes a rating cost whatever the job cost, and makes Sybil raters pay each other real money to manufacture standing. This is downstream of the payment guard rather than a new foundation, which is why it is rarely built first — you need the receipts before you can require them.
Identity provenance. Transferable identities mean history can change hands. Ownership transfer is an on-chain event; we want to surface it at the moment of decision, so “this agent changed owner last week and kept its history” is visible before you hire it.
Registry and implementation attestation. Which registry is canonical, and has the implementation behind it changed? Both are checkable on chain, and a signed attestation is cheap to publish and free to verify.
Mainnet agent coverage, the moment it is real. When the ERC-8004 and ERC-8183 contracts land on mainnet we will read them the same way we read USDC — from the deployed contract, across multiple providers — and turn the scans on then.
Limits
Testnet registration is close to free, so the 895,230 figure mixes real deployment with airdrop positioning and we cannot separate the two. Our samples are 150–200 agents against roughly 895,000, so the proportions carry a few points of sampling error. The cross-chain reputation figures are cited from the published study and were not re-derived by us. Everything here is a snapshot of an ecosystem in its first weeks, and we expect the numbers to move.
One method note worth repeating because it changed the result: our first sample was rate-limited by the public RPC at 59 of 240 and reported materially different proportions. A truncated sample is a biased sample, so it was discarded rather than published. The reported samples completed 188/200 and 150/150 across four independent providers with paced requests.
Our private gateway is offchain and every receipt says so. Arc’s confidential execution is documented as on the roadmap, so nothing we issue claims a transfer on Arc hides a counterparty or an amount.
Measurements taken
2026-09-16 against Arc Testnet (5042002) and Arc mainnet (5042). Registry size by binary search
on ownerOf; composition by two independent pseudo-random samples
over the full id range; activity by eth_getLogs over 40,000
recent blocks. The payment guard is wormhole-x402 on npm; the
hosted layer is documented at /llms.txt and /api/openapi.json. Negative findings mean “not found with substantial
effort”, never “proven absent”.