ERC-3009: The Protocol Powering x402 Payments
A deep dive into ERC-3009 Transfer With Authorization - the cryptographic standard that enables gasless, signature-based token transfers in the x402 protocol.

At the heart of x402’s payment mechanism lies ERC-3009, a standard that enables gasless token transfers through cryptographic signatures. Understanding this protocol is key to grasping how x402 achieves frictionless payments without requiring users to hold native tokens for gas.
The Problem: Traditional ERC-20 Limitations
The standard ERC-20 approve/transferFrom pattern has significant UX problems:
- Two transactions required: First
approve, thentransferFrom - Two gas fees: Users pay twice
- Native token dependency: Must hold ETH (or the chain’s native token) just to move USDC
- Sequential nonces: Transactions must be ordered, creating bottlenecks
For AI agents and autonomous systems, these limitations are even more severe—an agent would need to manage multiple token balances across networks just to make payments.
ERC-3009: Transfer With Authorization
ERC-3009 solves these problems elegantly. Instead of on-chain approvals, the payer signs an off-chain authorization message that can be submitted by anyone.
The Core Functions
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
How It Works
┌─────────────┐ Sign EIP-712 ┌─────────────┐
│ Payer │ ─────────────────► │ Message │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐ Submit to chain ┌─────────────┐
│ Relayer/ │ ◄───────────────── │ Signature │
│ Facilitator │ └─────────────┘
└──────┬──────┘
│
▼
┌─────────────┐
│ USDC │ transferWithAuthorization()
│ Contract │ - Verify signature
└──────┬──────┘ - Check balance
│ - Execute transfer
▼
┌─────────────┐
│ Recipient │ Receives USDC
└─────────────┘
- Payer signs a message conforming to EIP-712 typed data
- Message is sent to a relayer or facilitator (off-chain)
- Relayer submits the signature on-chain via
transferWithAuthorization - Contract verifies the signature and executes the transfer
- Relayer pays gas, not the payer
Why Random Nonces Matter
One of ERC-3009’s key innovations is the use of random 32-byte nonces instead of sequential ones:
| Approach | ERC-2612 (Sequential) | ERC-3009 (Random) |
|---|---|---|
| Nonce | 0, 1, 2, 3… | Random bytes32 |
| Parallel ops | ❌ Must be ordered | ✅ Fully independent |
| High-frequency | ❌ Bottleneck | ✅ Scales infinitely |
| Recovery | Complex | Simple |
For x402 and AI agents, this is transformative. An agent can generate thousands of concurrent payment authorizations without any conflicts or ordering requirements.
ERC-3009 in x402
In the x402 protocol, ERC-3009 is the foundation for EVM payments:
Payment Flow
- Client requests a protected resource
- Server returns 402 with payment requirements
- Client constructs an ERC-3009 authorization message
- Client signs with their wallet
- Client retries with
X-PAYMENTheader containing the signed payload - Facilitator verifies the signature and balance
- Server responds with the content
- Facilitator settles by calling
transferWithAuthorization
The X-PAYMENT Payload
{
"x402Version": 1,
"scheme": "exact",
"network": "base-sepolia",
"payload": {
"signature": "0x...",
"authorization": {
"from": "0xPayerAddress",
"to": "0xRecipientAddress",
"value": "1000000",
"validAfter": 0,
"validBefore": 1734567890,
"nonce": "0x7a8b9c..."
}
}
}
EIP-712 Typed Data Structure
The signed message follows EIP-712 format:
const domain = {
name: "USD Coin",
version: "2",
chainId: 84532, // Base Sepolia
verifyingContract: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
};
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" }
]
};
const message = {
from: payerAddress,
to: recipientAddress,
value: amount,
validAfter: 0,
validBefore: Math.floor(Date.now() / 1000) + 3600,
nonce: randomBytes32()
};
Token Support
ERC-3009 is not universally implemented. Currently, the major tokens supporting it are:
| Token | Issuer | ERC-3009 Support |
|---|---|---|
| USDC | Circle | ✅ Yes (v2+) |
| EURC | Circle | ✅ Yes |
| USDT | Tether | ❌ No |
| DAI | MakerDAO | ❌ No |
This is precisely why x402 focuses on USDC rather than USDT—the underlying smart contract must support the authorization pattern.
Security Considerations
1. Front-Running Protection
Use receiveWithAuthorization instead of transferWithAuthorization when the recipient is a smart contract:
// Vulnerable to front-running
transferWithAuthorization(from, to, value, ...);
// Protected - requires caller == to
receiveWithAuthorization(from, to, value, ...);
An attacker could extract the authorization from the mempool and execute it before the intended recipient’s wrapper contract, potentially causing deposit processing issues.
2. Time-Bound Validity
Always set reasonable validAfter and validBefore windows:
const authorization = {
validAfter: Math.floor(Date.now() / 1000) - 60, // 1 minute ago
validBefore: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
};
3. Nonce Management
Each nonce can only be used once. The contract tracks used nonces:
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
Generate truly random nonces to avoid collisions:
import { randomBytes } from "crypto";
const nonce = "0x" + randomBytes(32).toString("hex");
Comparison with ERC-2612 (Permit)
| Feature | ERC-2612 | ERC-3009 |
|---|---|---|
| Purpose | Approve allowance | Direct transfer |
| Steps | Sign → Approve → Transfer | Sign → Transfer |
| Nonce type | Sequential | Random |
| Use case | DeFi protocols | Payments, x402 |
| Adopted by | DAI, many tokens | USDC, EURC |
ERC-3009 is more suitable for payment scenarios because it:
- Authorizes the exact transfer (not an allowance that could be drained)
- Supports parallel operations via random nonces
- Provides time-bounded validity windows
Implementation Example
Using viem to create and sign an ERC-3009 authorization:
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
import { randomBytes } from "crypto";
const account = privateKeyToAccount(PRIVATE_KEY);
const client = createWalletClient({
account,
chain: baseSepolia,
transport: http(),
});
const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
async function createAuthorization(to: string, value: bigint) {
const nonce = "0x" + randomBytes(32).toString("hex");
const validBefore = Math.floor(Date.now() / 1000) + 3600;
const signature = await client.signTypedData({
domain: {
name: "USD Coin",
version: "2",
chainId: baseSepolia.id,
verifyingContract: USDC_ADDRESS,
},
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" },
],
},
primaryType: "TransferWithAuthorization",
message: {
from: account.address,
to,
value,
validAfter: 0n,
validBefore: BigInt(validBefore),
nonce,
},
});
return { signature, nonce, validBefore };
}
Related Articles
- x402 Developer’s Guide - Build your first paid API
- x402 Facilitators - Understanding the settlement infrastructure
- Why x402 Doesn’t Support USDT - Token compatibility explained
- Solana’s Authorization Mechanism - The non-EVM equivalent
References
- EIP-3009 Specification
- EIP-712: Typed Data Signing
- Circle’s USDC Implementation
- x402 Protocol Specification
ERC-3009 transforms how we think about token transfers. By moving authorization off-chain, it enables the frictionless, gasless payments that make x402 possible.