x402 payments fail on Base mainnet but work on Sepolia? Check your EIP-712 domain name
The symptom
Our x402 seller had a fully working end-to-end flow on Base Sepolia: HTTP 402 challenge, EIP-3009 TransferWithAuthorization signed by the buyer, verified and settled through the Coinbase CDP facilitator. First real payment on Base mainnet (eip155:8453), same code:
HTTP 402
{"error": "invalid_payload", "errorCode": "PAYMENT_VERIFICATION_FAILED"}
No revert reason, no hint about which field is wrong. The authorization amounts, addresses and timestamps were all correct.
The root cause
EIP-3009 authorizations are EIP-712 typed signatures, and the EIP-712 domain includes the token contract's name. That name is part of the signed digest — if the client signs against the wrong name, the signature recovers to a different address and verification fails.
Here is the catch nobody tells you about:
| Network | USDC contract | EIP-712 domain name |
|---|---|---|
| Base Sepolia (eip155:84532) | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | USDC |
| Base mainnet (eip155:8453) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | USD Coin |
Same token, same version (2), different domain name per deployment. You can confirm it in the official x402 repo (python/x402/mechanisms/evm/constants.py) or by calling name() on each contract.
The fix
Your 402 response's accepts[].extra tells the buyer what to sign against. Make it per-network instead of hardcoding "USDC":
// seller side: PaymentRequirements.extra — per-network config
'extra' => [
'name' => $network === 'eip155:8453' ? 'USD Coin' : 'USDC',
'version' => '2',
]
// buyer side (viem): sign against exactly what the seller advertises
const signature = await account.signTypedData({
domain: {
name: req.extra.name, // do NOT hardcode "USDC"
version: req.extra.version,
chainId: Number(req.network.split(":")[1]),
verifyingContract: req.asset,
},
types: { TransferWithAuthorization: [ /* EIP-3009 fields */ ] },
primaryType: "TransferWithAuthorization",
message: authorization,
});
After shipping this, the exact same payment settled on the first retry.
Takeaways
- Never hardcode the EIP-712 domain name for a token — read it per network, ideally from the chain itself.
- As a seller, put the correct
extra.namein the 402 so compliant clients cannot get it wrong. invalid_payloadfrom a facilitator almost always means "the signature does not recover tofrom" — check the domain fields before anything else.