awslabs / awslabs/agentcore-samples
AgentCore Payments signs Base USDC EIP-3009 authorizations under domain name "USDC" (contract domain is "USD Coin"), so MPP evm/charge payments on Base fail verification
- Dominant language
- Python
- Stars
- 3.4k
- Forks
- 1.3k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 30
Description
**Summary.** When AgentCore Payments answers an MPP `evm`/`charge` challenge on Base (eip155:8453, USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`), the EIP-3009 `TransferWithAuthorization` signature is produced under EIP-712 domain `{ name: "USDC", version: "2" }`. The Base USDC contract's domain is `{ name: "USD Coin", version: "2" }`. The signature therefore fails facilitator verification (`invalid_exact_evm_payload_signature`) and would fail on-chain, so every MPP evm/charge payment on Base is rejected. The same instrument settles correctly over x402.
**Environment.** `bedrock-agentcore` Python SDK (`PaymentManager.generate_payment_header`), Stripe/Privy credential provider, Base mainnet, 2026-08-28. Seller: a dual-stack (x402 + MPP) endpoint that also accepts stock `mppx` clients on the same challenge.
**Repro.**
1. `POST` a paid route that returns `402` with both `PAYMENT-REQUIRED` (x402) and `WWW-Authenticate: Payment` (MPP `evm`/`charge`, chainId 8453, currency = Base USDC).
2. `generate_payment_header(..., network_preferences=["eip155:8453"])` returns an `Authorization: Payment ` header (MPP path chosen).
3. Decode the credential payload and recover the signer:
```js
import { recoverTypedDataAddress } from "viem";
const types = { TransferWithAuthorization: [ {name:"from",type:"address"},{name:"to",type:"address"},{name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},{name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"} ] };
for (const name of ["USD Coin", "USDC"]) console.log(name, await recoverTypedDataAddress({ domain: { name, version: "2", chainId: 8453, verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }, types, primaryType: "TransferWithAuthorization", message, signature }));
```
Result: `"USD Coin"` recovers an unrelated address; `"USDC"` recovers the instrument's wallet. The `v` byte is canonical (0x1c).
4. Retry with the header: `402`, facilitator reason `invalid_exact_evm_payload_signature: invalid signature`.
**Control.** Hand the SDK the same 402 with the `WWW-Authenticate` header removed: it takes the x402 path, signs `PAYMENT-SIGNATURE`, and the payment settles (Base tx `0x9b48b7feb40d294afc579d5edc87383fdd0ac4826738ee744fc4bfb57864fc02`). So the instrument, the wallet and the seller are all fine; only the MPP evm signer's domain name differs.
**Expected.** The evm/charge signer uses the token's actual EIP-712 domain (`USD Coin`/`2` on Base; the name varies per chain, e.g. Celo USDC is `USDC`), matching what `mppx` clients sign and what facilitators and the contract verify.
**Also worth noting.** `generate_payment_header` falls back from MPP to x402 only on a challenge *selection* error; a failed verify is not retried over x402, so a dual-stack seller's Base route is unpayable from AgentCore until this is fixed, even though its x402 option works.
Contributor guide
Research direction
Start at PaymentManager.generate_payment_header and trace the MPP evm/charge signing path for the Base network preference. Reproduce the provided 402 challenge and compare the signed EIP-712 domain with the Base USDC contract domain; done means facilitator verification succeeds for Base while x402 behavior and other chain-specific domains remain correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- blockchain, payments
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100