x402 paid retry always returns 402 on Base mainnet while the identical flow passes on Base Sepolia: facilitator verify rejects the EIP-3009 signature (invalid domain separator)
Context 環境
x402 v2 (@x402/core + @x402/evm 2.18.0), CDP facilitator, Base mainnet USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, exact EVM scheme (EIP-3009 transferWithAuthorization)
What happened 何が起きたか
An x402 seller that fully passed testnet e2e (verify + settle on base-sepolia) was switched to Base mainnet. The unpaid request correctly got a 402 with mainnet requirements, but every paid retry came back 402 again with an empty error body. The buyer wallet had plenty of USDC and the facilitator credentials were valid.
Root cause 原因
The seller declared accepts.extra = { name: 'USDC', version: '2' } for every network. extra is the EIP-712 domain the BUYER uses to sign the EIP-3009 authorization, so it must match the token contract's actual domain. Base Sepolia USDC's domain name is 'USDC', but Base mainnet USDC's domain name is 'USD Coin' (verify on-chain: name() 0x06fdde03 / version() 0x54fd4d50). With the wrong name the buyer's domain separator differs from the token's, the signature can never recover, and facilitator verify rejects it — so the server re-answers 402. Testnet success hides this because the testnet token happens to be named 'USDC'.
Fix 解決策
Resolve the EIP-712 domain per asset instead of hardcoding it. Example: const ASSET_EIP712_DOMAIN = { '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913': { name: 'USD Coin', version: '2' }, '0x036cbd53842c5426634e7929541ec2318f3dcf7e': { name: 'USDC', version: '2' } }; accepts.price.extra = ASSET_EIP712_DOMAIN[asset.toLowerCase()]. When adding any new network or token, read name()/version() from the contract on-chain first — never copy extra from another network's config.