ERC-4337:通过合约实现账户抽象
账户抽象允许账户用可编程规则决定谁能操作资产、如何执行操作以及如何承担费用。ERC-4337 提供了一条通过合约和链下基础设施实现这些能力的路径,其基本机制不要求修改以太坊的共识规则。
用户提交 UserOperation,打包者(Bundler)把操作放入普通链上交易,EntryPoint 合约协调账户验证、执行和费用结算。用户使用的是合约钱包,但底层区块链仍然在处理普通交易。
经典合约账户流程以 EntryPoint v0.7.0 为基准。v0.6 的 UserOperation、v0.7 的链上打包结构,以及后续版本与 EIP-7702 的集成存在差异,接入时必须同时确认网络、EntryPoint 地址和版本。
为什么需要合约账户
传统 EOA 的交易授权主要由私钥签名决定。合约账户则可以在代码中实现多签、会话密钥、额度限制、恢复流程或批量调用,不必把所有权限交给同一把长期私钥。
ERC-4337 为这些账户提供了提交和结算操作的通用流程,并不自动提供上述钱包功能。恢复是否安全、会话密钥能做什么、批量调用是否原子执行,都由账户实现和相关服务决定。
一次操作如何上链
flowchart TD
User[用户与钱包客户端] -->|生成并授权 UserOperation| Bundler[Bundler:接收、模拟与打包]
Pool[可选:UserOperation P2P 内存池] <-->|传播操作| Bundler
Bundler -->|普通交易调用 handleOps| EntryPoint[选定版本的 EntryPoint]
EntryPoint -->|必要时通过工厂部署账户| Factory[账户工厂]
Factory --> Account[合约账户]
EntryPoint -->|验证授权与资金条件| Account
EntryPoint -->|可选:验证代付资格| Paymaster[Paymaster]
EntryPoint -->|调用账户的 callData| Account
Account -->|执行授权业务| Target[目标合约]
- 钱包准备操作,包括账户地址、nonce、调用数据、费用上限和授权数据。
- Bundler 在链下模拟验证,选择可接受的操作,并用普通交易调用 EntryPoint 的
handleOps()。 - EntryPoint 验证各账户及可选的 Paymaster,检查 nonce、有效时间和预付费用条件。
- 验证通过后,EntryPoint 依次调用账户;账户执行实际业务,随后结算费用。
UserOperation 不等于一笔以太坊协议层交易,也没有协议层必须接纳的独立交易类型。客户端通常通过 Bundler 的 RPC 提交它;开放 P2P 内存池是一种传播方式,并非每个服务都必须采用相同的内存池。
UserOperation 描述什么
v0.7 在链上使用以下打包结构。RPC 可以用拆分字段表示同一操作,不能把 RPC 对象直接当成 Solidity 的 ABI 结构传入。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
| 字段 | 含义 |
|---|---|
sender、nonce | 合约账户地址与防重放序列信息 |
initCode | 首次部署需要的工厂地址及调用数据;账户已部署时通常为空 |
callData | EntryPoint 将发送给账户的调用数据,由账户再执行业务 |
accountGasLimits | 打包后的验证和账户执行 Gas 上限 |
preVerificationGas | 补偿交易数据、打包及其他无法直接计入单个操作测量的链上开销 |
gasFees | 打包后的最高优先费与最高 Gas 单价 |
paymasterAndData | 可选的代付方地址、其 Gas 限额和附加数据 |
signature | 账户理解的授权数据,不限于单个 ECDSA 签名 |
preVerificationGas 不是“链下模拟实际消耗的 Gas”。模拟是 Bundler 的链下工作;该字段主要用于链上交易成本的分摊。Gas 上限、预付金额和实际结算费用也不能混为一谈。
账户、EntryPoint 与工厂各自负责什么
账户通过 validateUserOp() 验证授权,并按接口约定返回签名结果和有效时间等信息。账户应只允许受信任的 EntryPoint 调用这一入口,业务执行入口也需要独立的权限检查,避免绕过授权流程。
EntryPoint 管理验证流程、nonce 和费用记账。它是某条链上、某个版本的协调合约,不是整个生态永久唯一的全局合约。账户通常绑定特定 EntryPoint,签名也应绑定操作内容、链和 EntryPoint,避免跨环境重放。
账户尚未部署时,EntryPoint 通过工厂调用创建账户,再继续验证。很多工厂使用 CREATE2,使钱包能提前算出地址;部署方式由工厂实现决定,不能说 EntryPoint 自己固定用 CREATE2 创建所有账户。
v0.7 的 nonce 分为 192 位 key 和 64 位 sequence。不同 key 可以维护独立序列,减少不同会话之间的顺序依赖;这不表示 EVM 会并行执行这些操作,handleOps() 仍按流程顺序执行。
谁承担 Gas
Bundler 先承担普通交易的链上 Gas 费用,EntryPoint 再从账户或 Paymaster 的资金中结算操作费用,将收集的费用支付给指定的 beneficiary。用户看到的“免 Gas”,通常是其他主体代付,链上执行本身仍然有成本。
没有 Paymaster 时,由账户承担费用:使用其在 EntryPoint 中的 ETH 存款,必要时在验证过程中补足资金。有 Paymaster 时,由 Paymaster 的 ETH 存款承担费用,账户是否符合代付条件由其策略检查。
所谓“使用 ERC-20 支付手续费”,通常是 Paymaster 用 ETH 对 EntryPoint 结算,再按自己的规则向用户收取代币。ERC-4337 没有把以太坊底层 Gas 支付资产改成 ERC-20。
EntryPoint 中的两类资金需要分清:
- Deposit(存款):用于支付操作费用,需要有足够余额支持打包。
- Stake(质押):具有解锁延迟,配合内存池规则和信誉机制约束服务实体;不是每次操作扣费的账户,也不意味着协议会自动罚没。
验证失败与执行失败
链下模拟失败时,Bundler 可以拒绝或移除操作,避免提交已知无效的交易。但模拟成功不保证链上一定成功,因为状态可能在两者之间发生变化。
在 EntryPoint v0.7.0 参考实现中,handleOps() 先验证整批操作。某项验证失败可能使整笔打包交易回滚,并不是链上自动剔除坏操作后继续执行。Bundler 仍可能因此损失交易费用。
进入执行阶段后,账户业务调用的普通回滚通常会被记录为该操作失败,其他操作可以继续,已消耗的 Gas 仍会结算。外层 Gas 不足等异常仍可能影响整批交易,因此也不能保证所有执行错误都只影响单个操作。
安全边界
开放内存池需要防止昂贵或容易失效的验证。相关验证规则会约束操作码和存储访问,细则见 ERC-7562;这些是验证与打包规则,不能理解成整个 EVM 从此禁止读取时间或其他合约的状态。
账户抽象把授权策略交给代码,也把账户实现、升级权限、恢复机制和代付策略纳入了安全边界。评估钱包时,应同时查看这些规则,而不能仅凭“支持 ERC-4337”判断其安全性或功能完整度。