ERC-3009:驱动 x402 支付的底层协议
深入解析 ERC-3009 授权转账标准——这一加密协议如何在 x402 中实现无 Gas、基于签名的代币转账。

x402 支付机制的核心是 ERC-3009,一个通过加密签名实现无 Gas 代币转账的标准。理解这一协议是掌握 x402 如何在用户无需持有原生代币支付 Gas 的情况下实现无缝支付的关键。
问题:传统 ERC-20 的局限
标准 ERC-20 的 approve/transferFrom 模式存在严重的用户体验问题:
- 需要两笔交易:先
approve,再transferFrom - 两次 Gas 费用:用户支付两次
- 依赖原生代币:仅为转移 USDC 就必须持有 ETH(或链的原生代币)
- 顺序 nonce:交易必须有序执行,造成瓶颈
对于 AI Agent 和自治系统,这些限制更为严峻——一个 Agent 需要在多条网络上管理多种代币余额才能进行支付。
ERC-3009:授权转账
ERC-3009 优雅地解决了这些问题。它不需要链上授权,付款方只需签署一条链下授权消息,任何人都可以提交执行。
核心函数
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;
工作原理
┌─────────────┐ 签署 EIP-712 ┌─────────────┐
│ 付款方 │ ─────────────────► │ 消息 │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐ 提交到链上 ┌─────────────┐
│ 中继者/ │ ◄───────────────── │ 签名 │
│ Facilitator │ └─────────────┘
└──────┬──────┘
│
▼
┌─────────────┐
│ USDC │ transferWithAuthorization()
│ 合约 │ - 验证签名
└──────┬──────┘ - 检查余额
│ - 执行转账
▼
┌─────────────┐
│ 收款方 │ 收到 USDC
└─────────────┘
- 付款方签署消息,符合 EIP-712 类型化数据规范
- 消息发送给中继者或 Facilitator(链下)
- 中继者提交签名到链上,调用
transferWithAuthorization - 合约验证签名并执行转账
- 中继者支付 Gas,付款方无需付费
随机 Nonce 的重要性
ERC-3009 的关键创新之一是使用随机 32 字节 nonce 而非顺序 nonce:
| 方案 | ERC-2612(顺序) | ERC-3009(随机) |
|---|---|---|
| Nonce | 0, 1, 2, 3… | 随机 bytes32 |
| 并行操作 | ❌ 必须有序 | ✅ 完全独立 |
| 高频场景 | ❌ 瓶颈 | ✅ 无限扩展 |
| 恢复处理 | 复杂 | 简单 |
对于 x402 和 AI Agent,这是革命性的。一个 Agent 可以生成数千个并发支付授权,无需考虑冲突或排序。
ERC-3009 在 x402 中的应用
在 x402 协议中,ERC-3009 是 EVM 支付的基础:
支付流程
- 客户端请求受保护资源
- 服务器返回 402 及支付要求
- 客户端构建 ERC-3009 授权消息
- 客户端用钱包签名
- 客户端重试请求,携带包含签名载荷的
X-PAYMENT头部 - Facilitator 验证签名和余额
- 服务器响应内容
- Facilitator 结算,调用
transferWithAuthorization
X-PAYMENT 载荷
{
"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 类型化数据结构
签名消息遵循 EIP-712 格式:
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()
};
代币支持情况
ERC-3009 并非被普遍实现。目前支持该标准的主要代币有:
| 代币 | 发行方 | ERC-3009 支持 |
|---|---|---|
| USDC | Circle | ✅ 支持(v2+) |
| EURC | Circle | ✅ 支持 |
| USDT | Tether | ❌ 不支持 |
| DAI | MakerDAO | ❌ 不支持 |
这正是 x402 专注于 USDC 而非 USDT 的原因——底层智能合约必须支持授权模式。
安全注意事项
1. 防止抢跑攻击
当收款方是智能合约时,使用 receiveWithAuthorization 而非 transferWithAuthorization:
// 易受抢跑攻击
transferWithAuthorization(from, to, value, ...);
// 受保护 - 要求 caller == to
receiveWithAuthorization(from, to, value, ...);
攻击者可能从内存池中提取授权,在目标收款合约之前执行,可能导致充值处理问题。
2. 时间限定有效期
始终设置合理的 validAfter 和 validBefore 窗口:
const authorization = {
validAfter: Math.floor(Date.now() / 1000) - 60, // 1 分钟前
validBefore: Math.floor(Date.now() / 1000) + 3600, // 1 小时后
};
3. Nonce 管理
每个 nonce 只能使用一次。合约会追踪已使用的 nonce:
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
生成真正的随机 nonce 以避免碰撞:
import { randomBytes } from "crypto";
const nonce = "0x" + randomBytes(32).toString("hex");
与 ERC-2612 (Permit) 的对比
| 特性 | ERC-2612 | ERC-3009 |
|---|---|---|
| 用途 | 授权额度 | 直接转账 |
| 步骤 | 签名 → 授权 → 转账 | 签名 → 转账 |
| Nonce 类型 | 顺序 | 随机 |
| 应用场景 | DeFi 协议 | 支付、x402 |
| 采用者 | DAI 等众多代币 | USDC、EURC |
ERC-3009 更适合支付场景,因为它:
- 授权精确的转账(而非可能被耗尽的额度)
- 通过随机 nonce 支持并行操作
- 提供时间限定的有效期窗口
实现示例
使用 viem 创建和签署 ERC-3009 授权:
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 };
}
相关文章
- x402 开发者指南 - 构建你的第一个付费 API
- x402 Facilitator - 理解结算基础设施
- 为什么 x402 不支持 USDT - 代币兼容性解析
- Solana 的授权转账机制 - 非 EVM 链的等效方案
参考资料
ERC-3009 重新定义了代币转账的方式。通过将授权移至链下,它实现了无摩擦、无 Gas 的支付体验,这正是 x402 得以实现的基础。