Last time I wrote about x402 — agents paying for stuff over plain HTTP.
Two people asked me the same follow-up:
Cool, my agent can pay anyone in 2 seconds. But how does it know who it's paying?
Fair. Let's talk about that.
The plumber problem
Your kitchen sink is leaking. You find a number on a wall poster. The guy shows up and says: ₹3,000, cash, upfront.
What do you do?
flowchart TD
A[Stranger wants your money] --> B{Is he a real plumber?}
B --> C{Has he fixed sinks before?}
C --> D{If he wrecks my kitchen, who do I tell?}
D --> E[Only then: pay]
style E fill:#2d7,stroke:#000,color:#000
You ask around. You check if he has a shop. You look at reviews. You call the guy your neighbour used.
Now here's the thing — your AI agent can't do any of that.
It has a wallet, a $50 budget, and twelve endpoints all claiming to do market analysis. All twelve will happily take the money.
flowchart LR
AG[Your agent<br/>$50 budget] --> E1[endpoint 1 ✅ real]
AG --> E2[endpoint 2 ❓]
AG --> E3[endpoint 3 ❓]
AG --> E4[endpoint 4 💀 scam]
AG --> E5[...8 more ❓]
To your agent, all of these look exactly the same.
Why the old internet didn't have this problem
Because a company always vouched for the stranger.
| You trusted... | Because... |
|---|---|
| A seller on Amazon | Amazon checked them |
| A payment | Stripe verified the merchant |
| An npm package | GitHub stars + download counts |
| A driver | Uber ran a background check |
Every trust signal you've ever used was rented from a platform.
Agents don't have a platform. An agent built on OpenAI's stack has no way to look up an agent built on someone else's stack. So far everyone solved it the ugly way: my platform only trusts my own agents.
ERC-8004 is the attempt to put that lookup somewhere nobody owns.
Where it sits
Think of hiring a freelancer:
flowchart TD
subgraph S[" "]
A["🗣️ How do we talk?<br/><b>MCP / A2A</b>"]
B["🪪 Who are you? Any references?<br/><b>ERC-8004</b>"]
C["💸 How do I pay you?<br/><b>x402 / AP2 / MPP</b>"]
end
A --> B --> C
style B fill:#fd6,stroke:#000,color:#000
MCP and A2A are the phone call. x402 is the wallet. ERC-8004 is the ID card and the reference check.
It's an Ethereum standard (EIP-8004) written by people from MetaMask, the Ethereum Foundation, Google and Coinbase. Live on Ethereum mainnet since 29 January 2026, plus Base, BSC, Polygon, Arbitrum.
It's three contracts. That's the whole thing.
flowchart LR
R1["1️⃣ Identity<br/><i>the licence plate</i>"]
R2["2️⃣ Reputation<br/><i>the reviews</i>"]
R3["3️⃣ Validation<br/><i>the lab test</i>"]
R1 --- R2 --- R3
1️⃣ Identity — your agent is literally an NFT
Not a new standard. Not a DID. A plain ERC-721 token.
function register(string agentURI) external returns (uint256 agentId)
Call it, get a token ID. That token ID is the agent. The token URI is the agent's profile.
Real life version: it's an auto-rickshaw permit. Numbered, hangs on the vehicle, points to the driver's details, and can be sold to another driver.
flowchart LR
NFT["🎫 NFT #22"] --> URI["agentURI"]
URI --> FILE["📄 registration file<br/>name, description,<br/>endpoints, price"]
Because it's an NFT, every agent on earth is instantly viewable in OpenSea and holdable in MetaMask. Zero new tooling. Beautiful.
The profile file, trimmed down:
{
"name": "market-analysis-agent",
"description": "Equity sentiment. $0.02 per query.",
"services": [
{ "name": "A2A", "endpoint": "https://agent.example/..." },
{ "name": "MCP", "endpoint": "https://mcp.agent.example/" }
],
"x402Support": true,
"supportedTrust": ["reputation", "tee-attestation"]
}
See x402Support? These two protocols were built to hold hands.
Two small details I really liked
The payment address needs a signature.
Where the agent gets paid is stored on-chain, but you can't just type in a new address — you have to prove you control it with a signature.
And when the agent NFT is sold, the payment address is wiped automatically. New owner has to re-prove it.
flowchart LR
A["Agent #22<br/>pays to 0xAAA"] -->|sold| B["Agent #22<br/>pays to ❌ nothing"]
B -->|new owner signs| C["Agent #22<br/>pays to 0xBBB"]
style B fill:#fdd,stroke:#000,color:#000
Real life version: you buy a shop, the old owner's bank account doesn't come with it.
The address on the licence must match the actual shop.
Anyone can claim their agent lives at bigbank.com. So the domain has to answer back — you publish a small file at bigbank.com/.well-known/agent-registration.json pointing to the same agent ID.
flowchart LR
ON["⛓️ On-chain: I am agent #22 at bigbank.com"] <-->|both must agree| OFF["🌐 bigbank.com: yes, that's me, #22"]
If you've ever added a DNS record to prove you own a domain, same move.
2️⃣ Reputation — Amazon reviews, but nobody owns Amazon
Anyone can leave feedback on any agent. On-chain. Forever.
giveFeedback(agentId, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash)
-
value+valueDecimals= a number with a decimal point (9977+2→ 99.77%) -
tag1/tag2= what you're rating:starred,uptime,successRate,responseTime - Agent owners can't review their own agent
- You can revoke your own review
- Anyone can reply to a review — an agent posting a refund proof, a spam-detector flagging junk
Nothing gets deleted. The trail only grows.
The verified purchase badge
This is my favourite part. A review can carry the receipt of the x402 payment that paid for the work:
"proofOfPayment": { "toAddress": "0x...", "txHash": "0x...", "chainId": "1" }
flowchart LR
A["⭐ Some address<br/>says it's good"] -->|+ txHash| B["⭐ Someone who<br/><b>actually paid $2</b><br/>says it's good"]
style B fill:#2d7,stroke:#000,color:#000
Amazon's little "Verified Purchase" tag, on-chain.
The weirdest line in the whole spec
To read an agent's rating, you must pass a list of reviewers you trust:
getSummary(agentId, address[] clientAddresses /* MUST be non-empty */, tag1, tag2)
There is no "just give me the average rating" function. The spec refuses to write one, and says why: an unfiltered average is wide open to fake reviews.
Real life version: imagine Zomato deleted the star rating and instead said —
flowchart TD
Z["🍽️ 4.2 ⭐ (1,847 reviews)"] --> X["❌ not available"]
X --> Y["✅ Whose opinion do you<br/>actually trust?<br/>Pick them. I'll average those."]
style Y fill:#fd6,stroke:#000,color:#000
Annoying. Also completely correct — as we're about to see.
3️⃣ Validation — for when reviews aren't good enough
Reviews tell you what happened to other people, in the past.
For a $2 API call, fine. For $50,000, or a medical answer? Not fine.
Real life version:
| Buying street food | Choosing a surgeon |
|---|---|
| Look at the queue | Check the board certification |
| Reviews are enough | You want the actual credential |
So agents can ask for their work to be independently checked:
validationRequest(validatorAddress, agentId, requestURI, requestHash)
validationResponse(requestHash, response /* 0-100 */, responseURI, responseHash, tag)
The registry deliberately doesn't say how validation works. It's just a socket. Plug in whatever:
flowchart TD
V["Validation Registry<br/><i>just a socket</i>"] --> A["🔁 Someone re-runs the job<br/>and gets slashed if they lie"]
V --> B["🔐 zkML proof"]
V --> C["🖥️ TEE attestation<br/>hardware proves what code ran"]
This is exactly where the confidential-compute folks (Oasis ROFL and friends) plug in — and it's what both commenters on my last post were pointing at.
Tiered trust — the actual big idea
flowchart TD
Q{"How much money<br/>is at risk?"}
Q -->|"$0.02 — a pizza"| L["Identity only. Just pay."]
Q -->|"$50 — real money"| M["Identity + filtered reviews"]
Q -->|"$50,000 — ouch"| H["Identity + reviews +<br/><b>validation before you release funds</b>"]
style L fill:#dfd,stroke:#000,color:#000
style M fill:#fd6,stroke:#000,color:#000
style H fill:#fdd,stroke:#000,color:#000
Security proportional to what you'd lose. That framing — not any single registry — is the real contribution.
The whole loop, once
sequenceDiagram
participant H as Your agent
participant ID as Identity Registry
participant RR as Reputation Registry
participant W as Worker agent
participant V as Validator
H->>ID: who's out there? read profiles
H->>RR: reviews — but only from addresses I trust
H->>W: here's my task
W-->>H: 402 Payment Required 💸
H->>W: x402 signed payment
W-->>H: here's your result
W->>V: (big job only) please verify this
V-->>H: verified: 95/100 ✅
H->>RR: ⭐ review + payment receipt
What's the catch?
Now I have to be harder on this than I was on x402, because there's actual data.
In June 2026 researchers crawled all three registries across Ethereum, BSC and Base, from launch to May 2026.
The adoption number looks amazing: 170,000+ registered agents. 150,000+ reviews.
Then they checked if any of it was real.
Catch 1: almost nothing registered actually works
Share of registered agents with a valid profile and a live endpoint:
pie showData
title Ethereum agents — real vs placeholder
"Actually alive" : 3
"Placeholder" : 97
| Chain | Actually alive |
|---|---|
| Ethereum | 3% |
| BSC | 4% |
| Base | 15% |
Registering is one cheap transaction. So people mint agent IDs the way they squat domain names.
Catch 2: the reviews are mostly fake
Reviewers showing coordinated fake-review behaviour: 73.5% (Ethereum), 59.2% (BSC), 90.6% (Base).
Strip those out, and the share of rated agents left with zero valid reviews:
flowchart LR
E["Ethereum<br/>15.8%"] ~~~ B["BSC<br/>77.9%"] ~~~ BA["Base<br/>86.8%"]
style E fill:#fd6,stroke:#000,color:#000
style B fill:#fdd,stroke:#000,color:#000
style BA fill:#fbb,stroke:#000,color:#000
Remember getSummary refusing to give you an average? This is why. The spec authors called it in advance.
Catch 3: the scores don't mean the same thing
Tags are free text. Decimals are per-review. So:
flowchart LR
A["My review:<br/>87 · tag: starred"] --- Q["🤷 average these?"] --- B["Your review:<br/>9977 · tag: uptime"]
Both are "feedback". Neither is comparable to the other. Researchers' fix: a proper tag registry with declared units and ranges. Doesn't exist yet.
And a few more
flowchart TD
A["🪪 Identity ≠ competence<br/><i>proves the profile is his,<br/>not that he can fix sinks</i>"]
B["🎫 Reputation is sellable<br/><i>identity is an NFT — reviews<br/>stay attached when sold</i>"]
C["📝 Still a Draft EIP<br/><i>deployed, not finalised</i>"]
D["🔑 Keys are now targets<br/><i>never give unlimited allowances</i>"]
If you're building on it today
flowchart TD
S1["1. Read the on-chain identity + profile.<br/>Verify the domain answers back."]
S2["2. Never read reviews unfiltered.<br/>Pass your own reviewer allowlist."]
S3["3. Prefer reviews with a payment receipt."]
S4["4. Above ₹X value → require validation<br/>from a validator you picked in advance."]
S5["5. Scope your approvals.<br/>Signed intents, not silent spending."]
S1 --> S2 --> S3 --> S4 --> S5
The bottom line
x402 gave agents money. ERC-8004 gives them names, references, and a way to get caught lying.
And look at how boring the design is. ERC-721 for identity. Signatures for wallet proof. Hashes for file integrity. /.well-known/ for domains. Events for indexing.
Every piece already existed. The only new thing is agreeing on one neutral place to look.
Honest status today: registries are live, 170,000 agents are registered, and almost none of the trust signals are usable yet.
flowchart LR
A["✅ Standard exists"] --> B["🚧 Reputation aggregators<br/>Reviewer scoring<br/>Validator networks<br/>Insurance pools"] --> C["🎯 Standard works"]
style B fill:#fd6,stroke:#000,color:#000
That middle box is empty. The spec deliberately left it for someone else.
Probably you.
Top comments (0)