前言
ERC-7579 是目前 Web3 领域最前沿的模块化账户抽象(Modular Account Abstraction)标准。
如果说 ERC-4337 解决了"如何通过 Bundler 发送交易",那么 ERC-7579 解决的就是"智能合约钱包内部如何像乐高一样插拔功能"。它由 Rhinestone、Biconomy 和 ZeroDev 等头部团队共同推动,旨在消除不同钱包厂商(如 Safe, Biconomy, Kernel)之间的模块不兼容问题。
一、 什么是 ERC-7579?
定义: 模块化智能合约账户(MSCA)的轻量级互操作性标准。
核心痛点: 解决不同钱包插件不兼容的问题。
定位: 它是 ERC-4337 的增强插件集。如果 4337 是地基,7579 就是插座标准,让各种功能插件可以"即插即用"。
二、 它能做什么?(三大核心价值)
- 跨钱包通用: 开发一个"自动理财"模块,可在 Safe、Biconomy 等所有兼容钱包中运行。
- 标准化操作: 统一了插件的安装、卸载和调用逻辑,降低安全风险。
- 极度轻量化: 只规定最基础的接口,给钱包开发者留出最大的创新空间。
三、 落地场景
- 全自动链上策略 (Executor 场景) : 你不需要给 AI Agent 提供钱包私钥。你只需安装一个
Executor模块,授权它"只能在 ETH 跌破 2000 时调用 Uniswap 卖出"。这实现了所有权与操作权的分离。 - 企业级风控 (Hook 场景) : 公司账户可以安装一个
SpendingLimitHook。无论管理员如何签名,只要单笔金额超过 10 ETH,Hook 就会直接在合约层 Revert。 - 无缝跨链账户 (Validator 场景) : 利用统一的模块标准,你在 Base 链上安装的"指纹识别验证器",可以轻松同步并部署到 Arbitrum 或 Optimism 的 7579 钱包中。
四、 ERC-7579 核心架构
ERC-7579 定义了四种标准的模块类型,每种类型负责钱包的一个特定维度:
| 模块类型 | 角色 (ID) | 落地场景示例 | 核心功能 |
|---|---|---|---|
| Validator | 验证器 (1) | Passkey 验证、多签、社会恢复 | 决定"谁"有权发起这笔交易。 |
| Executor | 执行器 (2) | 自动定投 (DCA)、网格交易、止损 | 允许第三方或脚本在特定条件下代表钱包执行操作。 |
| Fallback | 回退处理器 (3) | ERC-1271 签名支持、NFT 接收钩子 | 处理钱包原生不支持的函数调用。 |
| Hook | 钩子 (4) | 每日支出限额、黑名单拦截 | 在交易执行前后进行检查(类似 Web2 的中间件)。 |
五、MVP版ERC-7579定时转账:智能合约开发、测试、部署一体化
5.1智能合约
- 5.1.1:Mock7579Account
js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Mock7579Account {
function execute(address target, uint256 value, bytes calldata callData) external payable {
// 简化:生产环境需校验 msg.sender 是否为已安装模块
(bool success, ) = target.call{value: value}(callData);
require(success, "Mock7579Account: Execution failed");
}
receive() external payable {}
}
- 5.1.2:ScheduledOrdersExecutor
js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// 简化版接口,实际生产环境建议通过 npm 安装 @rhinestone/module-bases
interface IERC7579Executor {
function onInstall(bytes calldata data) external;
function onUninstall(bytes calldata data) external;
function isModuleType(uint256 typeID) external pure returns (bool);
}
interface IMockAccount {
function execute(address target, uint256 value, bytes calldata callData) external payable;
}
contract ScheduledOrdersExecutor is IERC7579Executor, ReentrancyGuard {
using EnumerableSet for EnumerableSet.AddressSet;
struct Order {
uint256 interval;
uint256 lastExecution;
address destination;
uint256 amount;
bool active;
}
mapping(address => Order) public orders;
EnumerableSet.AddressSet private _accounts;
error InvalidExecution();
error AlreadyInitialized();
error NotInitialized();
function onInstall(bytes calldata data) external override {
if (orders[msg.sender].active) revert AlreadyInitialized();
(uint256 interval, address destination, uint256 amount) = abi.decode(data, (uint256, address, uint256));
orders[msg.sender] = Order({
interval: interval,
lastExecution: block.timestamp,
destination: destination,
amount: amount,
active: true
});
_accounts.add(msg.sender);
}
function onUninstall(bytes calldata) external override {
delete orders[msg.sender];
_accounts.remove(msg.sender);
}
// 由外部 Keeper 触发,代为执行账户的转账逻辑
function executeOrder(address account) external nonReentrant {
Order storage order = orders[account];
if (!order.active) revert NotInitialized();
if (block.timestamp < order.lastExecution + order.interval) revert InvalidExecution();
order.lastExecution = block.timestamp;
// 回调账户的执行接口
IMockAccount(account).execute(order.destination, order.amount, "");
}
function isModuleType(uint256 typeID) external pure override returns (bool) {
return typeID == 2; // Executor 类型
}
function isInitialized(address account) external view returns (bool) {
return orders[account].active;
}
}
5.2 测试脚本
- ERC-7579 定期转账模块测试
- 场景1:模块安装与配置初始化
- 场景2:未到执行时间尝试执行应失败
- 场景3:到达间隔时间后成功触发转账
- 场景4:卸载模块后无法再次执行
js
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import { parseEther, encodeAbiParameters, parseAbiParameters, getAddress } from 'viem';
import { network } from "hardhat";
describe("ERC-7579 定期转账模块测试", function () {
let executor: any, account: any;
let publicClient: any, owner: any, receiver: any;
beforeEach(async function () {
// @ts-ignore
const { viem } = await (network as any).connect();
publicClient = await viem.getPublicClient();
[owner, receiver] = await viem.getWalletClients();
executor = await viem.deployContract("ScheduledOrdersExecutor");
account = await viem.deployContract("Mock7579Account");
// 注入 ETH
await owner.sendTransaction({
to: account.address,
value: parseEther("10")
});
// 【关键修复】:伪装合约账户,使其能够发送交易
await publicClient.request({
method: "hardhat_impersonateAccount",
params: [account.address],
});
});
it("场景1:模块安装与配置初始化", async function () {
const interval = 3600n;
const amount = parseEther("1");
const installData = encodeAbiParameters(
parseAbiParameters('uint256, address, uint256'),
[interval, receiver.account.address, amount]
);
// 使用 impersonation 发送交易
await executor.write.onInstall([installData], {
account: account.address // 现在 Hardhat 允许以这个地址发送了
});
const order = await executor.read.orders([account.address]);
// order 返回的是一个数组/结构体: [interval, lastExecution, destination, amount, active]
assert.equal(order[0], interval);
assert.equal(getAddress(order[2]), getAddress(receiver.account.address));
assert.equal(order[4], true);
});
it("场景2:未到执行时间尝试执行应失败", async function () {
const installData = encodeAbiParameters(
parseAbiParameters('uint256, address, uint256'),
[3600n, receiver.account.address, parseEther("1")]
);
await executor.write.onInstall([installData], { account: account.address });
// 预期失败:InvalidExecution()
await assert.rejects(
executor.write.executeOrder([account.address]),
(err: any) => err.message.includes("InvalidExecution")
);
});
it("场景3:到达间隔时间后成功触发转账", async function () {
const interval = 3600n;
const amount = parseEther("1");
const installData = encodeAbiParameters(
parseAbiParameters('uint256, address, uint256'),
[interval, receiver.account.address, amount]
);
await executor.write.onInstall([installData], { account: account.address });
const beforeBalance = await publicClient.getBalance({ address: receiver.account.address });
// 快进时间
await publicClient.request({ method: "evm_increaseTime", params: [3601] });
await publicClient.request({ method: "evm_mine" });
// 执行订单
await executor.write.executeOrder([account.address]);
const afterBalance = await publicClient.getBalance({ address: receiver.account.address });
assert.equal(afterBalance - beforeBalance, amount);
});
it("场景4:卸载模块后无法再次执行", async function () {
const installData = encodeAbiParameters(
parseAbiParameters('uint256, address, uint256'),
[3600n, receiver.account.address, parseEther("1")]
);
await executor.write.onInstall([installData], { account: account.address });
// 卸载
await executor.write.onUninstall(["0x"], { account: account.address });
// 预期失败:NotInitialized()
await assert.rejects(
executor.write.executeOrder([account.address]),
(err: any) => err.message.includes("NotInitialized")
);
});
});
5.3 部署脚本
js
// scripts/deploy.js
import { network, artifacts } from "hardhat";
import { parseUnits } from "viem";
async function main() {
// 连接网络
const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端
const [deployer, investor] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address;
console.log("部署者的地址:", deployerAddress);
// 部署ScheduledOrdersExecutor合约
const ScheduledOrdersExecutorArtifact = await artifacts.readArtifact("ScheduledOrdersExecutor");
// 1. 部署合约并获取交易哈希
const ScheduledOrdersExecutorHash = await deployer.deployContract({
abi: ScheduledOrdersExecutorArtifact.abi,
bytecode: ScheduledOrdersExecutorArtifact.bytecode,
args: [],
});
const ScheduledOrdersExecutorReceipt = await publicClient.waitForTransactionReceipt({
hash: ScheduledOrdersExecutorHash
});
console.log("ScheduledOrdersExecutor合约地址:", ScheduledOrdersExecutorReceipt.contractAddress);
// 部署Mock7579Account合约
const Mock7579AccountArtifact = await artifacts.readArtifact("Mock7579Account");
// 1. 部署合约并获取交易哈希
const Mock7579AccountHash = await deployer.deployContract({
abi: Mock7579AccountArtifact.abi,
bytecode: Mock7579AccountArtifact.bytecode,
args: [],
});
const Mock7579AccountReceipt = await publicClient.waitForTransactionReceipt({
hash: Mock7579AccountHash
});
console.log("Mock7579Account合约地址:", Mock7579AccountReceipt.contractAddress);
}
main().catch(console.error);
总结
ERC-7579 的意义在于打破了钱包生态的孤岛。它通过标准化的模块化方案,降低了开发者的开发门槛,同时也给用户带来了极大的自由度。
如果说 ERC-4337 让钱包变成了"账户",那么 ERC-7579 就让账户变成了"操作系统"。 它打破了厂商壁垒,让用户可以像在手机上安装 App 一样,自由定制自己的链上账户功能。