Picture this. It's 2 a.m., and a developer with a fresh agent scaffold has just typed curl https://evidiq.dev/mcp -d '{"method":"tools/call",...}'. They wanted a quick, free read on some unknown counterparty. Instead, curl prints back HTTP 402 Payment Required and a body full of unfamiliar fields. Panic mode: what is accepts[]? Why does it look like half API spec, half blockchain receipt? And exactly what are they supposed to sign?
This is the post we wish had existed the day we shipped it. Every protocol feels obvious in hindsight — but the gap between "read the spec" and "ship a working client" is dense with nitpicks. We've hit every edge case the wrong way at least once, so let us save you the same scars. If you just want the headline first, the EVIDIQ homepage has a one-paragraph summary; everything below is the long form.
The Wire Protocol, Byte by Byte
An x402 pay-per-call MCP server is an HTTP-402-gated endpoint that exposes MCP tools and charges per invocation using signed payment authorizations. EVIDIQ runs an x402 pay-per-call MCP server at evidiq.dev/mcp, returning a deterministic 0–100 trust score plus an explicit recommendation for the agent under query.
Three steps. That is the whole flow. No websockets, no streaming, no state machine to debug.
- First request. You
POST /mcpwith a JSON-RPC body — something like{"jsonrpc":"2.0","method":"tools/call","params":{"name":"verify_agent","arguments":{"agent":"0xabc..."}},"id":1}. We don't care which MCP transport you used to get here; the only thing that matters is that you hithttps://evidiq.dev/mcp. - 402 challenge. If you haven't paid yet, our edge returns
HTTP 402 Payment Requiredwith a JSON body whoseaccepts[]array describes exactly what we will accept. The status code is the protocol — your client is supposed to decode the 402 challenge, build an EIP-3009 signature, and try again. - Paid retry. Same
POST, same body. The only difference is anX-PAYMENTheader carrying a base64-encoded payment payload. Our settler verifies the signature on X Layer, debits the payer, credits the settlement address, and only then forwards the request to the scoring engine. A200 OKcomes back with the trust score report insideresult.structuredContent.
Here is the part that trips people up: the only difference between a 402 and a 200 is a single header on the way back in. Everything else is identical. If you want to read the spec we implemented against, it lives at EVIDIQ docs; the server source is MIT-licensed at github.com/evidiq/mcp.
Reading an accepts[] Object

The first time you see accepts[], it looks intimidating. It isn't. Every field has one job, and missing one is the only way well-meaning clients fail. Here is the actual shape we return today:
{
"accepts": [
{
"scheme": "exact",
"network": "x-layer",
"amount": "100000",
"asset": "0x55d398326f99059fF775485246999027B3197955",
"payTo": "0xEVIDIQ...settler",
"resource": "verify_agent",
"description": "Trust verification for one agent identifier",
"mimeType": "application/json",
"outputSchema": {},
"extra": { "name": "USDT0", "version": "1" }
}
],
"error": "X-PAYMENT header is required"
}
Five fields matter. Get any of them wrong, the client signs the wrong thing, and the settler silently rejects the retry.
schemeis the literal string"exact". Do not send"fixed","upto", or anything custom — only"exact"is recognized today.networkis"x-layer", X Layer's chain identifier. Do not sign against Ethereum mainnet or Base by accident; the cross-chain replay protection will eat your funds.amountis a decimal string in atomic units."100000"is 0.1 USDT0, the cost of oneverify_agentcall. No exponent. No leading zeros. We store it as a string precisely because floats are treacherous.assetis the USDT0 ERC-20 contract on X Layer. It is the thing the EIP-3009 message hashes against, so any drift here breaks the EIP-712 domain separator.payTois our settler address. Do not hardcode it from a blog post — fetch it from the 402 every time, because we rotate it across upgrades.
Then resource tells you which MCP tool was requested (verify_agent in this case), and extra.name plus extra.version mirror the EIP-712 domain fields so your typed-data payload matches what the contract expects.
One opinionated take: a wallet balance alone tells you nothing about whether an MCP server will pay you — that is the gap EVIDIQ closes, and reading accepts[] correctly is what makes the gap reachable in the first place.
EIP-3009 Without the Cryptography Lecture
You do not need to understand the cryptographic primitives. You do need to understand the shape of the thing you are signing and why.
transferWithAuthorization is a method on certain ERC-20 tokens — USDT0 on X Layer included — that lets one address authorize a transfer off-chain, without sending a transaction, without paying gas. The EIP-3009 signature is an EIP-712-formatted typed-data blob that says three things:
- Who pays (
from). - Who receives (
to, taken frompayTo). - How much (
value, taken fromamount).
It also carries validAfter and validBefore — Unix timestamps bounding the authorization's lifetime — plus a nonce, a 32-byte opaque value the contract tracks. Whoever presents a valid signature to the contract gets to call transferWithAuthorization for the specified amount, and the contract moves the money and burns the nonce so the signature can never be replayed.
Why does this matter for an x402 pay-per-call MCP server? Because without gasless signatures, every MCP tool call would need a human to approve a transaction first — unusable for autonomous agents. By having you sign an authorization off-chain, we let an agent machine-pay for its own verification without ever holding a private gas tank.
The signed blob goes into X-PAYMENT as a base64-encoded JSON object with payload (the signature), details (the echoed accepts[] for audit), and resource. Our settler — documented at EVIDIQ Sentinel docs — verifies the EIP-712 hash against the on-chain domain, calls transferWithAuthorization, and waits for the receipt before promoting the request. If you want to operate your own agent and rebroadcast reports downstream, the EVIDIQ Operator docs walk through the full lifecycle end to end.
Worth noting: we never touch a wallet on your behalf. The signature authorizes a one-shot debit, and the contract's replay protection guarantees the same signed blob cannot be presented twice.
What Happens on a Bad Signature or Late Nonce

Bad signatures are where integrators lose hours. We have seen every one of these in production, so here is the catalogue.
Expired validBefore. Your clock is skewed, or you set a 60-second window and then sat on it. The settler sees validBefore < block.timestamp, refuses the contract call, and returns a 402 with error: "authorization_expired". The fix: fetch accepts[] again, sign with a fresh window (we recommend 5 minutes), and retry. Never reuse a signed blob across calls.
Reused nonce. EIP-3009 nonces are per-(token, from) and one-shot. A buggy retry path that sends the same signed payload twice will make the contract revert with FiatTokenV2_2: invalid nonce. That surfaces in the retry response as error: "nonce_already_used". The fix: every retry must mint a new 32-byte random nonce. Do not derive it from a request ID — randomness breaks determinism in your favor here.
Domain mismatch. The EIP-712 domain must match exactly: chain ID 196 for X Layer, the USDT0 contract as verifyingContract, "USDT0" as name, "1" as version. Mismatches fail with error: "eip712_domain_mismatch" and are the single most common copy-paste bug when people adapt examples from mainnet or Base. Triple-check extra.name and extra.version against the live accepts[].
Chain mismatch. Your signer is connected to Ethereum mainnet, but our contract lives on X Layer. The signature is mathematically valid, but there is no contract at that address on that chain, so the settler cannot construct a valid call. You will see error: "wrong_network" and another 402 — your funds are safe, nothing was charged, but you need to switch RPC and re-sign.
Underfunded from. Your signer authorized 0.1 USDT0 but the wallet only holds 0.05. The contract reverts with the standard insufficient-balance reason. Top up, refetch accepts[], and sign again.
Each of these paths is structured on purpose. We want clients to recover, not loop. A 402 is not a failure — it is a negotiation. Treat the response body as the exact spec for the next request, and you will not get stuck.
For deeper debugging of the signature bundle itself — including how the report gets hashed and anchored to 0G — the EVIDIQ Notary docs cover canonicalization and storage end to end. And if you want to verify EVIDIQ without paying first, the open skill at evidiq.dev/skill.md walks through the same wire shape on the free tools.
