A USDC authorisation signed with the EIP-712 values your code uses on Base Sepolia will fail on Base mainnet. The domain name is "USDC" on the testnet and "USD Coin" on mainnet, so the signature recovers to a different address and the contract reverts. Nothing in the user's wallet shows the difference.
Three standards let someone move stablecoins by signing a message instead of paying gas: EIP-3009, EIP-2612 and Uniswap's Permit2. The pages that rank for them explain one at a time. None puts the three side by side on nonces, token coverage and gas, and none covers the per-chain domain values that break real integrations. This guide does, using the specs, the token source code and on-chain reads I ran on 20 October 2026.
What Is EIP-3009 Transfer With Authorization?
EIP-3009 lets a token holder sign a transfer that anyone else can submit and pay gas for. The signed message is the transfer itself:
TransferWithAuthorization(
address from, address to, uint256 value,
uint256 validAfter, uint256 validBefore, bytes32 nonce
)
A relayer calls transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s). The token checks the signature, checks the time window and moves the funds in one call. No allowance is created and nothing is left behind for a spender to reuse.
Three details matter in practice:
- Nonces are random 32-byte values, not a counter. The spec's rationale is that sequential nonces force ordering: if a relayer submits nonce 5 before 4, it fails and wastes gas. With random nonces a payer can have many authorisations outstanding at once, and each settles independently.
-
The time window is strict on both ends. Circle's implementation requires
now > validAfterandnow < validBefore. -
authorizationState(authorizer, nonce)returns whether a nonce has been used, and each use emitsAuthorizationUsed(address indexed authorizer, bytes32 indexed nonce).cancelAuthorizationis listed as optional.
EIP-3009 was created in September 2020 and is still a Draft. It sat as Stagnant until October 2025, when it was moved back to Draft. That revision also added a line saying the standard "does not apply to smart contract accounts". Hold that thought, because USDC disagrees.
How Does EIP-2612 permit Work?
EIP-2612 is older (April 2020) and Final. The holder signs an approval, not a transfer:
Permit(address owner, address spender, uint256 value, uint256 nonce, uint256 deadline)
Someone calls permit(...) to set the allowance, and then the spender calls transferFrom. That is two state changes, usually in one transaction from a contract that wraps both. Nonces are sequential per owner (nonces(owner)), and the deadline is inclusive: the permit is valid while block.timestamp <= deadline.
Read the full article on tomcn.uk →
About the Author
I'm Tom Wang, an AI Developer & Fintech Developer — building AI agents, crypto payment infrastructure, and cross-border payout systems with Rust, Go, and TypeScript. Based in London, UK.
Currently open to new opportunities in fintech, crypto payments, and AI agent engineering.
Top comments (1)
The Base Sepolia versus mainnet domain mismatch is the kind of bug that eats a whole sprint and never shows up in test coverage, precisely because your fixtures carry the testnet domain values and the wallet signs them happily. What makes it nastier is that the failure is silent on the integration side too: the signature is well-formed, the contract just recovers to a nobody and reverts, so the stack trace looks like a user balance problem rather than a metadata one. On nonces, the random-value choice in 3009 is underrated for agents specifically. A purchasing agent that retries a failed payment with a fresh authorization should never depend on submission order across relayers, and sequential nonces punish exactly the at-least-once delivery semantics that real relay networks have.