Pectra之后的EIP-7702:以太坊应用开发者实用指南
· 阅读需 9 分钟
在2025年5月7日,以太坊的Pectra升级(Prague + Electra)在主网上线。其中对开发者最明显的变化之一是EIP-7702,它允许外部拥有账户(EOA)"挂载"智能合约逻辑——无需迁移资金或更改地址。如果您构建钱包、dapp或中继器,这为智能账户UX开启了一条更简单的道路。
以下是一份简洁的、实现优先的指南:实际发布了什么,7702如何工作,何时选择它而不是纯ERC-4337,以及您今天可以适配的复制粘贴脚手架。
实际发布的内容
- EIP-7702在Pectra的最终范围内。 Pectra硬分叉的meta-EIP正式列出7702为包含的变更之一。
- 激活详情: Pectra在2025年5月7日的纪元364032在主网激活,此前在所有主要测试网成功激活。
- 工具链说明: Solidity v0.8.30将其默认EVM目标更新为prague以兼容Pectra。您需要升级编译器和CI管道,特别是如果您固定特定版本的话。
EIP-7702—工作原理(技术细节)
EIP-7702引入了一种新的交易类型和EOA将其执行逻辑委托给智能合约的机制。
- 新交易类型(0x04): 类型4交易包括一个名为
authorization_list
的新字段。此列表包含一个或多个授权元组—(chain_id, address, nonce, y_parity, r, s)
—每个都由EOA的私钥签名。当处理此交易时,协议向EOA的代码字段写入委托指示符:0xef0100 || address
。从那时起,对EOA的任何调用都会代理到指定的address
(实现),但在EOA的存储和余额上下文中执行。此委托保持活动状态,直到明确更改。 - 链范围: 授权可以通过提供
chain_id
来特定于链,或者如果chain_id
设置为0
,则可以适用于所有链。这允许您在多个网络上部署相同的实现合约,而无需用户为每个网络签署新的授权。 - 撤销: 要将EOA恢复到其原始的不可编程行为,您只需发送另一个7702交易,其中实现
address
设置为零地址。这会清除委托指示符。 - 自赞助vs.中继: EOA可以自己提交类型4交易,或者第三方中继器可以代表EOA提交。后者常用于创建无gas用户体验。nonce处理根据方法略有不同,因此使用正确处理此区别的库很重要。
安全模型转变: 因为原始EOA私钥仍然存在,它总是可以通过提交新的7702交易来更改委托,从而覆盖任何智能合约规则(如社交恢复或支出限制)。这是根本性的变化。依赖
tx.origin
验证调用来自EOA的合约必须重新审核,因为7702可能破坏这些假设。相应地审核您的流程。
7702还是ERC-4337?(何时组合)
EIP-7702和ERC-4337都能实现账户抽象,但它们服务于不同的需求。
- 选择EIP-7702当…
- 您想为现有EOA提供即时智能账户UX,而不强迫用户迁移资金或更改地址。
- 您需要可以逐步升级新功能的跨链一致地址。
- 您想分阶段过渡到账户抽象,从简单功能开始,随时间添加复杂性。
- 选择纯ERC-4337当…
- 您的产品从第一天起就需要完全可编程性和复杂的策略引擎(如多重签名、高级恢复)。
- 您为没有现有EOA的新用户构建,使新智能账户地址和相关设置可接受。
- 组合它们: 最强大的模式是使用两者。EOA可以使用7702交易指定ERC-4337钱包实现作为其逻辑。这使EOA表现得像4337账户,允许它被打包、由付款主赞助,并由现有4337基础设施处理——所有这些都无需用户需要新地址。这是EIP作者明确鼓励的前向兼容路径。
您可以适配的最小7702脚手架
这里是实现合约和激活它的客户端代码的实际示例。
1. 小型可审核实现合约
一旦指定,此合约代码将在EOA的上下文中执行。保持其小型、可审核,并考虑添加升级机制。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @notice 通过EIP-7702指定时从EOA上下文执行调用。
contract DelegatedAccount {
// 避免与其他合约冲突的唯一存储槽。
bytes32 private constant INIT_SLOT =
0x3fb93b3d3dcd1d1f4b4a1a8db6f4c5d55a1b7f9ac01dfe8e53b1b0f35f0c1a01;
event Initialized(address indexed account);
event Executed(address indexed to, uint256 value, bytes data, bytes result);
modifier onlyEOA() {
// 可选:添加检查以限制谁可以调用某些函数。
_;
}
function initialize() external payable onlyEOA {
// 在EOA的存储中设置简单的一次性初始化标志。
bytes32 slot = INIT_SLOT;
assembly {
if iszero(iszero(sload(slot))) { revert(0, 0) } // 如果已经初始化则回滚
sstore(slot, 1)
}
emit Initialized(address(this));
}
function execute(address to, uint256 value, bytes calldata data)
external
payable
onlyEOA
returns (bytes memory result)
{
(bool ok, bytes memory ret) = to.call{value: value}(data);
require(ok, "CALL_FAILED");
emit Executed(to, value, data, ret);
return ret;
}
function executeBatch(address[] calldata to, uint256[] calldata value, bytes[] calldata data)
external
payable
onlyEOA
{
uint256 n = to.length;
require(n == value.length && n == data.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < n; i++) {
(bool ok, ) = to[i].call{value: value[i]}(data[i]);
require(ok, "CALL_FAILED");
}
}
}
2. 使用viem在EOA上指定合约(类型4交易)
像viem
这样的现代客户端有内置助手来签署授权并发送类型4交易。在此示例中,relayer
账户为升级eoa
支付gas。
import { createWalletClient, http, encodeFunctionData } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { abi, implementationAddress } from "./DelegatedAccountABI";
// 1. 定义中继器(赞助gas)和要升级的EOA
const relayer = privateKeyToAccount(process.env.RELAYER_PK as `0x${string}`);
const eoa = privateKeyToAccount(process.env.EOA_PK as `0x${string}`);
const client = createWalletClient({
account: relayer,
chain: sepolia,
transport: http(),
});
// 2. EOA签署指向实现合约的授权
const authorization = await client.signAuthorization({
account: eoa,
contractAddress: implementationAddress,
// 如果EOA自己要发送这个,您会添加:executor: 'self'
});
// 3. 中继器发送类型4交易来设置EOA的代码并调用initialize()
const hash = await client.sendTransaction({
to: eoa.address, // 目标是EOA本身
authorizationList: [authorization], // 新的EIP-7702字段
data: encodeFunctionData({ abi, functionName: "initialize" }),
});
// 4. 现在,EOA可以通过其新逻辑控制,无需进一步授权
// 例如,执行交易:
// await client.sendTransaction({
// to: eoa.address,
// data: encodeFunctionData({ abi, functionName: 'execute', args: [...] })
// });
3. 撤销委托(返回到普通EOA)
要撤销升级,让EOA签署将零地址指定为实现的授权并发送另一个类型4交易。之后,对eth_getCode(eoa.address)
的调用应该返回空字节。
在生产中工作的集成模式
- 为现有用户就地升级: 在您的dapp中,检测用户是否在Pectra兼容网络上。如果是,显示可选的"升级账户"按钮,触发一次性授权签名。为使用旧钱包的用户维护回退路径(如经典
approve
+swap
)。 - 无gas入门: 使用中继器(您的后端或服务)来赞助初始类型4交易。对于持续的无gas交易,通过ERC-4337打包器路由用户操作以利用现有的付款主和公共内存池。
- 跨链推出: 使用
chain_id = 0
授权在所有链上指定相同的实现合约。然后您可以在应用逻辑中按链启用或禁用功能。 - 可观察性: 您的后端应该索引类型4交易并解析
authorization_list
以跟踪哪些EOA已升级。交易后,通过调用eth_getCode
并确认EOA的代码现在匹配委托指示符(0xef0100 || implementationAddress
)来验证更改。
威胁模型和陷阱(不要跳过这个)
- 委托是持久的: 像处理标准智能合约升级一样严肃地对待EOA实现合约的更改。这需要审核、清晰的用户沟通,理想情况下是选择加入流程。永远不要悄悄地向用户推送新逻辑。
tx.origin
地雷: 任何使用msg.sender == tx.origin
来确保调用直接来自EOA的逻辑现在可能易受攻击。必须用更robust的检查(如EIP-712签名或明确的白名单)替换此模式。- Nonce数学: 当EOA赞助自己的7702交易(
executor: 'self'
)时,其授权nonce和交易nonce以特定方式交互。始终使用正确处理此问题的库以避免重放问题。 - 钱包UX责任: EIP-7702规范警告dapp不应该要求用户签署任意指定。验证建议的实现并确保它们安全是钱包的责任。设计您的UX以符合这种钱包介导的安全原则。
何时7702是明确胜利
- DEX流程: 多步骤
approve
和swap
可以使用executeBatch
函数合并为单次点击。 - 游戏和会话: 在有限时间或范围内授予类似会话密钥的权限,而无需用户创建和资助新钱包。