Found during an x402 buyer/seller compatibility audit (paid-MCP obol sell mcp ⇄ external buyer).
Summary
internal/x402/chains.go advertises the EIP-712 domain name "USD Coin" for Base Sepolia (eip155:84532), but the on-chain USDC contract's EIP-712 domain name is "USDC". The obol sell http 402 challenge surfaces this wrong name in extra.name, so any payer that signs TransferWithAuthorization against the advertised domain produces a signature the facilitator rejects.
Evidence
internal/x402/chains.go:74 — ChainBaseSepolia.EIP3009Name = "USD Coin"; flows into the 402 extra.name (chains.go:237 EIP712Name = c.EIP3009Name, chains.go:326 "name": asset.EIP712Name).
- On-chain ground truth (
eth_call name() on 0x036CbD53842c5426634e7929541eC2318f3dCF7e, Base Sepolia): returns "USDC", version "2". Base mainnet 0x833589…2913 returns "USD Coin" — the split is real per-contract.
- The vendored
coinbase/x402/go registry agrees with the chain: eip155:84532 → "USDC", eip155:8453 → "USD Coin" (mechanisms/evm/constants.go).
Why it has not surfaced
obol's own buyer (internal/embed/skills/buy-x402/scripts/buy.py) deliberately ignores the seller-advertised extra.name (treats it as a human-readable display string) and signs from its own authoritative per-chain domain table (USDC_EIP712_DOMAIN, base-sepolia → ("USDC","2")). So internal sell http ⇄ buy.py flows on Base Sepolia pass and mask the bug. A spec-conformant external buyer that trusts extra.name will fail every paid request on Base Sepolia.
Fix
- Set
ChainBaseSepolia.EIP3009Name = "USDC".
- Add a regression test asserting the resolved Base Sepolia EIP-712 domain name is
"USDC" (and that the 402 extra.name matches).
- Audit the other testnet entries (
polygon-amoy, avalanche-fuji, arbitrum-sepolia) against each testnet USDC contract's on-chain name() — they currently all hardcode "USD Coin".
Found during an x402 buyer/seller compatibility audit (paid-MCP
obol sell mcp⇄ external buyer).Summary
internal/x402/chains.goadvertises the EIP-712 domain name"USD Coin"for Base Sepolia (eip155:84532), but the on-chain USDC contract's EIP-712 domain name is"USDC". Theobol sell http402 challenge surfaces this wrong name inextra.name, so any payer that signsTransferWithAuthorizationagainst the advertised domain produces a signature the facilitator rejects.Evidence
internal/x402/chains.go:74—ChainBaseSepolia.EIP3009Name = "USD Coin"; flows into the 402extra.name(chains.go:237EIP712Name = c.EIP3009Name,chains.go:326"name": asset.EIP712Name).eth_call name()on0x036CbD53842c5426634e7929541eC2318f3dCF7e, Base Sepolia): returns"USDC", version"2". Base mainnet0x833589…2913returns"USD Coin"— the split is real per-contract.coinbase/x402/goregistry agrees with the chain:eip155:84532 → "USDC",eip155:8453 → "USD Coin"(mechanisms/evm/constants.go).Why it has not surfaced
obol's own buyer (
internal/embed/skills/buy-x402/scripts/buy.py) deliberately ignores the seller-advertisedextra.name(treats it as a human-readable display string) and signs from its own authoritative per-chain domain table (USDC_EIP712_DOMAIN, base-sepolia →("USDC","2")). So internalsell http⇄ buy.py flows on Base Sepolia pass and mask the bug. A spec-conformant external buyer that trustsextra.namewill fail every paid request on Base Sepolia.Fix
ChainBaseSepolia.EIP3009Name = "USDC"."USDC"(and that the 402extra.namematches).polygon-amoy,avalanche-fuji,arbitrum-sepolia) against each testnet USDC contract's on-chainname()— they currently all hardcode"USD Coin".