智能账户
SimpleAccount 是最小单 Owner ECDSA 参考实现。开发者也可以实现自己的 ERC-4337 智能账户。
SimpleAccount implementation 是所有账户代理共享的实现地址,不是用户自己的账户地址。每个账户实例由 Owner 和 salt 确定。
1. SimpleAccountFactory API
interface ISimpleAccountFactory {
function accountImplementation()
external view returns (address);
function getAddress(
address owner,
uint256 salt
) external view returns (address);
function createAccount(
address owner,
uint256 salt
) external returns (address);
}
| 方法 | 说明 |
|---|---|
accountImplementation | 返回共享 SimpleAccount implementation |
getAddress | 计算 Owner 和 salt 对应的反事实代理地址,不部署账户 |
createAccount | 地址无代码时创建代理;已经部署时返回现有账户 |
首次 UserOperation 的 Factory 数据由 Factory 地址和 createAccount(owner, salt) 的 ABI 编码组成。EntryPoint 要求 Factory 返回的地址与 UserOperation sender 一致,并要求部署后 sender 存在代码。
2. SimpleAccount API
interface ISimpleAccount {
function owner() external view returns (address);
function entryPoint() external view returns (address);
function getNonce() external view returns (uint256);
function initialize(address owner) external;
function execute(
address dest,
uint256 value,
bytes calldata func
) external;
function executeBatch(
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) external;
function getDeposit() external view returns (uint256);
function addDeposit() external payable;
function withdrawDepositTo(
address payable withdrawAddress,
uint256 amount
) external;
}
2.1 账户状态
| 方法 | 说明 |
|---|---|
owner | 当前单一 ECDSA Owner |
entryPoint | 账户绑定的 EntryPoint |
getNonce | EntryPoint 中 key 0 的下一个 nonce |
initialize | 初始化代理 Owner;只允许初始化一次 |
Factory 在创建代理时完成初始化。外部开发者不应把未初始化代理暴露给其他调用者。
2.2 业务执行
| 方法 | 调用权限 | 行为 |
|---|---|---|
execute | Owner 或绑定的 EntryPoint | 执行单个目标调用,并透传回滚数据 |
executeBatch | Owner 或绑定的 EntryPoint | 原子执行多个目标调用 |
executeBatch 要求目标地址数量与 calldata 数量相同;value 数组可以为空,表示全部调用不转 BOT,也可以与目标数组等长。
Owner 直接调用执行接口时使用普通链上交易并自行支付交易 Gas。EntryPoint 调用时使用 UserOperation,Gas 从账户或 Paymaster 的 deposit 结算。
2.3 EntryPoint deposit
| 方法 | 说明 |
|---|---|
getDeposit | 查询该账户在 EntryPoint 中的 Gas deposit |
addDeposit | 为该账户增加 EntryPoint deposit |
withdrawDepositTo | 由 Owner 提取账户 deposit |
账户原生 BOT 余额用于转账和业务调用,EntryPoint deposit 用于支付 UserOperation Gas,两者相互独立。
2.4 Token 接收
SimpleAccount 可以接收原生 BOT、ERC-721 和 ERC-1155,并实现对应的 receiver 接口检测。
3. SimpleAccount 验证行为
SimpleAccount 继承 BaseAccount.validateUserOp:
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
| 步骤 | SimpleAccount 行为 |
|---|---|
| 调用方检查 | 只接受绑定的 EntryPoint |
| 签名 hash | 对 userOpHash 应用 EIP-191 |
| 签名验证 | ECDSA recover 后必须等于 Owner |
| 签名失败 | 返回 SIG_VALIDATION_FAILED,即 1 |
| nonce | 唯一性由 EntryPoint 管理,默认 key 为 0 |
| prefund | 按 missingAccountFunds 向 EntryPoint 补足资金 |
SimpleAccount 不原生提供多签、社交恢复、Session Key、Passkey 或 ERC-7579 模块。需要这些能力时,应实现自定义账户,而不是假设 SimpleAccount 存在未公开接口。
4. 自定义智能账户
开发者可以从零实现 IAccount,也可以继承 BaseAccount。无论采用哪种方式,都必须与当前 EntryPoint v0.7 和 Bundler 验证规则兼容。
4.1 必须实现的接口
interface IAccount {
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
}
| 参数 | 说明 |
|---|---|
userOp | 即将验证和执行的完整 PackedUserOperation |
userOpHash | 已绑定 EntryPoint 和 chainId 的 hash |
missingAccountFunds | 当前账户 deposit 距离 required prefund 的差额 |
validationData 的布局为:
低 160 位:0 表示签名有效,1 表示签名失败,其他值表示聚合器地址
接续 48 位:validUntil,0 表示无限期
高 48 位:validAfter
4.2 必须满足的行为
-
验证
msg.sender是账户信任的 EntryPoint。 -
验证
signature、授权策略和必要的账户状态。 -
签名不匹配时返回
1,其他格式或策略错误可以回滚。 -
按账户策略检查 nonce;nonce 唯一性仍由 EntryPoint 更新。
-
没有 Paymaster 且 deposit 不足时,补足
missingAccountFunds。 -
避免违反 ERC-7562 的 opcode、存储访问和外部调用限制。
-
为估算阶段提供可模拟、确定且不会产生不可控副作用的验证逻辑。
继承 BaseAccount 时,子类至少需要提供:
-
entryPoint():返回绑定的 EntryPoint。 -
_validateSignature(...):返回签名和时间范围验证结果。 -
可选
_validateNonce(...):增加账户自己的 nonce 策略。 -
可选
_payPrefund(...):自定义 prefund 资金管理。
4.3 执行接口设计
账户可以定义自己的 callData 接口,例如单笔执行、批量执行、模块执行或权限分层执行。EntryPoint 默认将 userOp.callData 直接发送给账户。
账户还可以实现可选接口:
interface IAccountExecute {
function executeUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) external;
}
当 callData 使用 executeUserOp selector 时,EntryPoint 会把完整 UserOperation 和 hash 交给账户执行。该接口不是实现 ERC-4337 账户的必选项。
4.4 Factory 与部署
自定义 Factory 应满足:
-
根据确定参数稳定计算账户地址。
-
创建函数返回实际部署的账户地址。
-
首次 UserOperation 的
initCode前 20 字节是 Factory 地址。 -
Factory 调用数据位于
initCode剩余部分。 -
部署结果必须等于 UserOperation
sender。 -
已部署账户不应继续携带 Factory 字段。
Bundler 会模拟 Factory 和账户验证。Factory 或账户行为不符合规则时,UserOperation 会在进入 mempool 前被拒绝。