EIP-7702实战:Pectra之后构建智能账户流程
Engineering Team
EIP-7702带来了什么
EIP-7702随Ethereum的Pectra升级于2025年3月激活,引入了一种新的交易类型,允许任何外部拥有账户(EOA)设置委托指示器——指向智能合约的指针,EOA在交易期间临时采用其代码。这意味着您现有的MetaMask钱包地址可以执行任意智能合约逻辑(批量调用、自定义验证、gas赞助),而无需部署新的合约钱包或更改您的地址。
这是Ethereum历史上最重大的用户体验改进。用户不再需要在EOA的简单性和智能账户的强大功能之间做选择。
EIP-7702 vs ERC-4337:何时使用哪个
两者都实现账户抽象,但在不同层级运作:
| 特性 | EIP-7702 | ERC-4337 |
|---|---|---|
| 层级 | 协议(新tx类型) | 应用(智能合约) |
| 账户类型 | 升级现有EOA | 需要新智能合约钱包 |
| 地址 | 保留现有EOA地址 | 新地址(反事实) |
| 持久性 | 每笔交易委托 | 永久智能合约 |
| 需要Bundler | 否(标准tx流程) | 是(独立内存池) |
| Gas开销 | ~20,000 gas委托 | ~42,000 gas UserOp验证 |
| 支持的钱包 | MetaMask, Coinbase Wallet, Rainbow | 专用(Safe, ZeroDev, Biconomy) |
| 最适合 | 升级现有用户 | 新用户、复杂账户逻辑 |
决策框架
使用EIP-7702当:
- 您的用户已有EOA钱包
- 您需要批量交易或gas赞助
- 您希望最小化集成复杂性
使用ERC-4337当:
- 您正在构建新钱包或引导新用户
- 您需要持久的账户逻辑
- 您需要高级功能如账户模块、插件或守护者
代码示例
批量交易
最常见的EIP-7702用例:在单笔交易中执行多个操作。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
/// @title BatchExecutor — EIP-7702批量调用委托目标
contract BatchExecutor {
struct Call {
address target;
uint256 value;
bytes data;
}
/// @notice 从委托EOA执行批量调用
function executeBatch(Call[] calldata calls) external payable {
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory result) = calls[i].target.call{
value: calls[i].value
}(calls[i].data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
}
}
使用ethers.js v6的客户端用法:
import { ethers } from "ethers";
const batchTx = {
type: 4,
to: userAddress,
authorizationList: [{
chainId: 1,
address: BATCH_EXECUTOR_ADDRESS,
nonce: await provider.getTransactionCount(userAddress),
yParity: 0, r: "0x...", s: "0x..."
}],
data: batchExecutorInterface.encodeFunctionData("executeBatch", [[
{
target: USDC_ADDRESS,
value: 0,
data: usdcInterface.encodeFunctionData("approve", [
UNISWAP_ROUTER, ethers.parseUnits("1000", 6)
])
},
{
target: UNISWAP_ROUTER,
value: 0,
data: routerInterface.encodeFunctionData("exactInputSingle", [{
tokenIn: USDC_ADDRESS,
tokenOut: WETH_ADDRESS,
fee: 3000,
recipient: userAddress,
amountIn: ethers.parseUnits("1000", 6),
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
}])
}
]])
};
const tx = await signer.sendTransaction(batchTx);
Gas赞助
/// @title SponsoredExecutor — 通过EIP-7702的gas赞助
contract SponsoredExecutor {
mapping(address => uint256) public nonces;
function sponsoredExecute(
address user,
address target,
bytes calldata data,
uint256 userNonce,
uint256 deadline,
bytes calldata signature
) external {
require(block.timestamp <= deadline, "Expired");
require(nonces[user] == userNonce, "Invalid nonce");
nonces[user]++;
bytes32 digest = _hashTypedData(user, target, data, userNonce, deadline);
address signer = ECDSA.recover(digest, signature);
require(signer == user, "Invalid signature");
(bool success, bytes memory result) = target.call(data);
require(success, "Call failed");
}
}
社交恢复
/// @title RecoveryModule — 通过EIP-7702的社交恢复
contract RecoveryModule {
struct RecoveryConfig {
address[] guardians;
uint256 threshold;
uint256 delay;
}
mapping(address => RecoveryConfig) public configs;
mapping(bytes32 => uint256) public recoveryTimestamps;
mapping(bytes32 => uint256) public approvalCounts;
mapping(bytes32 => mapping(address => bool)) public hasApproved;
function setupRecovery(
address[] calldata guardians,
uint256 threshold,
uint256 delay
) external {
require(guardians.length >= threshold, "Invalid threshold");
require(threshold >= 2, "Min 2 guardians");
configs[msg.sender] = RecoveryConfig(guardians, threshold, delay);
}
function approveRecovery(
address account,
address newOwner
) external {
RecoveryConfig memory config = configs[account];
require(_isGuardian(config, msg.sender), "Not a guardian");
bytes32 recoveryId = keccak256(abi.encode(account, newOwner));
require(!hasApproved[recoveryId][msg.sender], "Already approved");
hasApproved[recoveryId][msg.sender] = true;
approvalCounts[recoveryId]++;
if (approvalCounts[recoveryId] >= config.threshold) {
recoveryTimestamps[recoveryId] = block.timestamp + config.delay;
}
}
function executeRecovery(
address account,
address newOwner
) external {
bytes32 recoveryId = keccak256(abi.encode(account, newOwner));
uint256 timestamp = recoveryTimestamps[recoveryId];
require(timestamp > 0 && block.timestamp >= timestamp, "Not ready");
}
}
钱包集成状态
MetaMask
MetaMask在12.4版本(2025年9月)中添加了EIP-7702支持。用户在dApp请求type-4交易时会看到“智能交易“标识。
Coinbase Wallet
Coinbase Wallet是最积极的采用者,将EIP-7702集成到“Smart Wallet“功能(2025年11月发布)中:
- 自动批处理 — 钱包自动批处理approve+swap序列
- Gas赞助 — Coinbase为低于$1费用的交易赞助gas
- 一键DeFi — 常见DeFi操作的预构建委托目标
Rainbow、Rabby等
Rainbow于2026年1月添加支持。Rabby支持type-4交易但缺少批处理可视化UI。
迁移指南:从EOA到智能账户
步骤1:部署委托目标
创建并审计用户将委托的智能合约。
步骤2:更新前端
async function supportsEIP7702(provider: ethers.Provider): Promise<boolean> {
try {
const capabilities = await provider.send(
"wallet_getCapabilities", []
);
return capabilities?.atomicBatch?.supported === true;
} catch {
return false;
}
}
步骤3:实现优雅降级
async function executeSwap(tokenIn, tokenOut, amount) {
if (await supportsEIP7702(provider)) {
return executeBatchSwap(tokenIn, tokenOut, amount);
} else {
await approve(tokenIn, ROUTER, amount);
return swap(tokenIn, tokenOut, amount);
}
}
安全注意事项
委托目标验证
委托目标合约在交易期间对EOA的资产拥有完全控制权。恶意委托目标可以转移EOA的所有ETH和代币。
缓解措施: 仅委托给经过审计和验证的合约。
重放保护
EIP-7702授权列表包含nonce以防止重放攻击。
通过委托的钓鱼
攻击者可能诱骗用户签署委托给恶意合约的授权列表。
缓解措施: 钱包必须清楚显示EOA委托给什么代码。
批量执行中的重入
bool private locked;
modifier noReentrant() {
require(!locked, "Reentrant");
locked = true;
_;
locked = false;
}
存储冲突
使用EIP-7201(命名空间存储):
bytes32 constant STORAGE_SLOT = keccak256(
abi.encode(uint256(keccak256("batch.executor.storage")) - 1)
) & ~bytes32(uint256(0xff));
常见问题
EIP-7702会改变我的钱包地址吗?
不会。您的EOA地址保持不变。EIP-7702在单笔交易期间临时赋予您的地址智能合约能力。
我可以在L2上使用EIP-7702吗?
是的。所有主要L2(Arbitrum, Base, Optimism, zkSync)已采用EIP-7702。
如果委托目标有bug怎么办?
损害仅限于单笔交易。但在该交易中,委托目标对EOA资产有完全访问权限。
EIP-7702与硬件钱包兼容吗?
是的。Ledger在固件2.3.0版本(2025年12月)中添加了支持。
EIP-7702节省多少gas?
对于典型的approve+swap流程,节省约**40-50%**的gas。复杂的多步DeFi操作可节省60-70%。