Overview
AnySpend x402 uses cryptographic signatures instead of traditional token approvals, enabling gasless payments for users. The facilitator pays all gas fees while users simply sign authorization messages.
There are two main signature standards supported, each optimized for different token types:
- EIP-3009 (transferWithAuthorization) - Used by USDC, one-step direct transfers
- EIP-2612 (permit) - Used by most modern ERC-20 tokens, two-step approval + transfer
Quick Comparison
| Feature | EIP-2612 (Permit) | EIP-3009 (transferWithAuthorization) |
| Used by | Most modern ERC-20s | USDC (all networks) |
| Execution | Two-step: approve + transfer | One-step: direct transfer |
| Nonce Type | Sequential (auto-increments) | Random bytes32 |
| Dependency | Must wait for previous nonce | No ordering required |
| Ideal for | General ERC-20 tokens | Stablecoin payments |
| Replay Protection | Sequential nonce | Random nonce tracking |
| Gas Efficiency | 2 transactions (permit + transferFrom) | 1 transaction (direct transfer) |
EIP-3009: transferWithAuthorization (USDC)
Overview
Direct transfer authorization - the signature authorizes an immediate transfer from sender to recipient without a separate approval step.
Used by: USDC on all networks (Base, Ethereum, Arbitrum, Optimism, Polygon, etc.)
Key Advantages
- ✅ One-step execution - Direct transfer, no approval needed
- ✅ Random nonce - No sequential dependency, parallel transactions possible
- ✅ Immediate settlement - Executes in single transaction
- ✅ Gasless for payer - Facilitator pays gas
- ✅ No front-running - Random nonce prevents MEV attacks
Message Structure
transferWithAuthorization(
address from, // Payer address
address to, // Recipient address
uint256 value, // Amount to transfer
uint256 validAfter, // 0 (valid immediately)
uint256 validBefore, // Deadline timestamp
bytes32 nonce, // Random nonce (prevents replay)
bytes signature // EIP-712 signature
)
Usage Example
import { signTransferWithAuthorization } from 'anyspend-x402-client';
const signature = await signTransferWithAuthorization({
from: payerAddress,
to: recipientAddress,
value: '1000000', // 1 USDC (6 decimals)
validBefore: deadline,
nonce: randomBytes32() // Generate random nonce
});
How It Works
User Signs Authorization
User signs an EIP-712 message authorizing the transfer with a random nonce
Facilitator Executes Transfer
Facilitator calls receiveWithAuthorization() with the signature
USDC Transferred
USDC is transferred directly from user to facilitator in one atomic transaction
Nonce Invalidated
The random nonce is marked as used, preventing replay attacks
EIP-712 Typed Data Structure
const typedData = {
domain: {
name: 'USD Coin',
version: '2',
chainId: 8453, // Base
verifyingContract: usdcAddress
},
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' }
]
},
message: {
from: payerAddress,
to: recipientAddress,
value: '1000000',
validAfter: 0,
validBefore: deadline,
nonce: randomNonce
}
};
EIP-2612: Permit (Standard ERC-20)
Overview
Signature-based approval that sets an allowance, followed by a separate transferFrom() call. This is the standard method for most modern ERC-20 tokens.
Used by: Most modern ERC-20 tokens with permit support
Key Advantages
- ✅ Widely adopted - Standard across many tokens
- ✅ Time-limited approvals - Deadline-based expiration
- ✅ ERC-20 compatible - Works with existing infrastructure
- ✅ Gasless for payer - Facilitator pays gas
- ✅ Ecosystem support - Supported by major wallets and dapps
Message Structure
permit(
address owner, // Token owner
address spender, // Approved spender (facilitator)
uint256 value, // Approval amount
uint256 deadline, // Expiration timestamp
uint8 v, bytes32 r, bytes32 s // Signature components
)
Usage Example
import { signPermit } from 'anyspend-x402-client';
const nonce = await token.nonces(ownerAddress); // Get current nonce
const signature = await signPermit({
token: tokenAddress,
owner: ownerAddress,
spender: facilitatorAddress,
value: '1000000', // 1 USDC (6 decimals)
deadline: deadline,
nonce: nonce // Sequential nonce
});
How It Works
User Signs Permit
User signs an EIP-712 permit message with current sequential nonce
Facilitator Calls Permit
Facilitator calls permit() to set the allowance on-chain
Nonce Auto-Increments
The token contract automatically increments the user’s nonce
Facilitator Transfers Tokens
Facilitator calls transferFrom() to transfer tokens using the approval
EIP-712 Typed Data Structure
const typedData = {
domain: {
name: 'Dai Stablecoin',
version: '1',
chainId: 8453, // Base
verifyingContract: daiAddress
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
},
message: {
owner: ownerAddress,
spender: facilitatorAddress,
value: '1000000000000000000',
nonce: currentNonce,
deadline: deadline
}
};
Which Method Does My Token Use?
Checking Token Support
// Check if token supports EIP-3009 (USDC-style)
const hasTransferWithAuth = await token.read.transferWithAuthorization !== undefined;
// Check if token supports EIP-2612 (Standard permit)
const hasPermit = await token.read.permit !== undefined;
const hasDomainSeparator = await token.read.DOMAIN_SEPARATOR !== undefined;
Common Tokens by Method
EIP-3009 (transferWithAuthorization):
- USDC (all chains)
- USDC.e (bridged versions)
EIP-2612 (permit):
- Most modern ERC-20s
- USDT (on some chains - Base, Arbitrum, Optimism)
- Many DeFi tokens
- Check compatibility: anyspend.com/x402-tokens
No Gasless Support:
- USDT on Ethereum mainnet (no permit)
- USDT on Polygon (no permit)
- Legacy ERC-20 tokens
The AnySpend x402 client automatically detects which signature method to use based on the token contract. You don’t need to specify this manually.
Nonce Management
Random Nonce (EIP-3009)
Advantages:
- No ordering dependency - multiple signatures can be used in any order
- Parallel transactions possible
- No blocked state if one transaction fails
Implementation:
import { randomBytes } from 'crypto';
// Generate cryptographically secure random nonce
const nonce = '0x' + randomBytes(32).toString('hex');
Nonce Tracking:
// Check if nonce has been used
mapping(address => mapping(bytes32 => bool)) public authorizationState;
function isAuthorizationUsed(address authorizer, bytes32 nonce)
external
view
returns (bool)
{
return authorizationState[authorizer][nonce];
}
Sequential Nonce (EIP-2612)
Advantages:
- Simple and predictable
- Gas efficient (single storage slot)
- Standard across all permit implementations
Implementation:
// Get current nonce from token contract
const currentNonce = await token.read.nonces([ownerAddress]);
// Sign with current nonce
const signature = await signPermit({
// ...
nonce: currentNonce
});
Nonce Auto-Increment:
// Token contract automatically increments
mapping(address => uint256) public nonces;
function permit(/* ... */) external {
require(nonce == nonces[owner], "Invalid nonce");
nonces[owner]++; // Auto-increment
// ... rest of permit logic
}
Security Considerations
Replay Protection
EIP-3009:
- Random nonce prevents replay across chains and contracts
- Each nonce can only be used once per address
- Nonce state stored on-chain in mapping
EIP-2612:
- Sequential nonce prevents replay
- Must use current nonce (auto-increments)
- Failed transactions block subsequent signatures until re-signed
Deadline Enforcement
Both methods enforce deadlines to prevent stale signatures:
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes from now
Best Practices:
- Use short deadlines (5-10 minutes) for security
- Longer deadlines (30-60 minutes) for better UX if needed
- Never use
type(uint256).max for infinite approvals
Signature Validation
Both methods validate signatures using EIP-712:
// Recover signer from signature
const recoveredAddress = recoverTypedDataAddress({
domain,
types,
primaryType,
message,
signature
});
// Verify signer matches expected address
if (recoveredAddress !== expectedSigner) {
throw new Error('Invalid signature');
}
Client SDK Integration
The AnySpend x402 client handles all signature complexity automatically:
import { X402Client } from 'anyspend-x402-client';
const client = new X402Client({
walletClient,
preferredToken: tokenAddress // USDC or any compatible token
});
// Client automatically:
// 1. Detects if token uses permit or transferWithAuthorization
// 2. Gets current nonce (for permit) or generates random nonce
// 3. Constructs correct EIP-712 typed data
// 4. Prompts user to sign
// 5. Includes signature in X-PAYMENT header
const response = await client.request('https://api.example.com/data');
Gas Cost Comparison
| Method | User Gas | Facilitator Gas | Total Transactions |
| EIP-3009 | 0 | ~45,000 | 1 |
| EIP-2612 | 0 | ~70,000 (permit) + ~45,000 (transfer) | 2 |
All gas costs are paid by the facilitator and included in the 0.25% AnySpend fee. Users never pay gas directly.
Further Reading