← Protocols
ERC-3009 — Transfer With Authorization
Standard / EIP·EVM

ERC-3009 — Transfer With Authorization

01Description

Draft ERC defining EIP-712-signed off-chain authorizations for ERC-20 transfers, enabling gasless / meta-transaction flows where any relayer can submit a holder's signed transfer. The basis of Circle's USDC `transferWithAuthorization` on every chain USDC is deployed. Status: Draft (despite Draft, it is one of the most production-deployed ERCs — every USDC contract implements it).

02Best for
  • 01gasless USDC transfers
  • 02meta-transactions / relayer-submitted payments
  • 03scheduled or time-windowed payments
  • 04merchant payment flows where the payer signs but a relayer pays gas
  • 05atomic cross-protocol composition with USDC
03Install
  • pnpm add viem
  • # Circle USDC implements ERC-3009 natively on every supported chain
  • # reference impl: https://github.com/centrehq/centre-tokens
05Prompt snippet
Use ERC-3009 for gasless ERC-20 transfers. EIP-712 typed data fields: `TransferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce)` (and `ReceiveWithAuthorization` for receiver-pull flows, plus `CancelAuthorization`). The holder signs off-chain; any relayer calls `transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s)` on-chain. The contract verifies the signature, checks `block.timestamp` is within `[validAfter, validBefore]`, marks `nonce` used (per-from), then transfers. `receiveWithAuthorization` adds the protection that `msg.sender == to`, preventing front-running by replay relayers. Use viem's `signTypedData` with the contract's domain (name, version, chainId, verifyingContract). Differs from EIP-2612 permit: ERC-3009 nonces are RANDOM bytes32 (any unused value), not sequential — enabling parallel signed authorizations.
06Gotchas
  • Nonces are random bytes32 (NOT sequential like EIP-2612) — a single signed authorization can be cancelled via `cancelAuthorization` but cannot be replaced by signing the same nonce again. Generate fresh `crypto.randomBytes(32)` per authorization.
  • `validBefore` is in chain time (`block.timestamp`), not wall clock — clock drift on testnets and L2 reorg windows can invalidate authorizations that look fine to the user. Add a 60+ second buffer.
  • `transferWithAuthorization` is FRONT-RUNNABLE — any observer of the mempool can submit the signed tx and pay gas (which is usually fine, since the transfer is to a fixed recipient). For receiver-bound flows, use `receiveWithAuthorization` which requires `msg.sender == to`.
  • Different USDC deployments use DIFFERENT EIP-712 domain `version` strings ("1", "2") and chain-specific contract addresses — always read `eip712Domain()` (ERC-5267) at runtime instead of hard-coding domain values, or signatures will silently fail.
  • Bridged USDC (USDC.e on Polygon, Arbitrum legacy) often does NOT implement ERC-3009 — only native Circle-issued USDC does. Check `transferWithAuthorization` selector existence before falling back to standard `permit` or `approve`+`transferFrom`.
  • Replay across chains: signatures are scoped by `chainId` in the domain separator, but if a token is deployed at the same address on multiple chains, ALWAYS verify `chainId` in the domain matches the user's intended chain.
07Alternatives