使用 EIP-7702 实现账户抽象
本指南将通过 EIP-7702 应用于 EOA,并使用委托实现三种模式:批量支付、消费限额和会话密钥。在此过程中,EOA 保持其地址和私钥不变。
先决条件
- 了解 EOA 与智能合约账户的区别(EOA 默认不包含代码)。
- 熟悉 EVM 交易类型(EIP-2718)。
概述
EIP-7702 引入了一种新的交易类型(0x04),它携带一个 authorizationList。每个授权都指定了一个智能合约,EOA 将在该交易中执行该智能合约的代码。流程如下:
- 选择或部署委托合约:一个标准的 Solidity 合约,实现了您希望 EOA 使用的逻辑。您可以使用已部署的合约或部署自己的合约。尽可能使用经过审计的合约。
- 签署授权:EOA 所有者签署一条消息,授权委托合约。
- 提交 EIP-7702 交易:交易包含授权,EOA 在执行期间运行委托的代码。
用例:批量交易
以下步骤将使用 Multicall3 作为委托合约来演示此流程。Multicall3 是一个广泛部署的实用合约,它将多个调用聚合到一个交易中。通过将 Multicall3 指定为 EIP-7702 委托,EOA 可以将任意合约交互(代币转账、批准、合约读取或任意组合)批量处理为一笔原子交易。批量支付就是一个例子:EOA 无需为一次工资支付发送十笔单独的交易,而是可以一次性执行所有交易。
步骤 1:使用 Multicall3 作为委托合约
Multicall3 部署在 Stable 上的 0xcA11bde05977b3631167028862bE2a173976CA11。由于它已经部署并且被广泛使用,您无需部署自己的委托。签署 EIP-7702 授权会授予委托对您的 EOA 的完全执行权限。
// Multicall3 interface (relevant functions only)
interface IMulticall3 {
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
/// @notice Aggregate calls, allowing each to succeed or fail independently
function aggregate3(Call3[] calldata calls)
external payable returns (Result[] memory returnData);
}步骤 2:签署授权
EOA 所有者签署一份授权,指定委托合约。此授权将包含在 EIP-7702 交易中。
// config.ts
import { ethers } from "ethers";
export const STABLE_TESTNET_RPC = "https://rpc.testnet.stable.xyz";
export const STABLE_TESTNET_CHAIN_ID = 2201;
export const USDT0_ADDRESS = "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9";
export const DELEGATE_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
export const provider = new ethers.JsonRpcProvider(STABLE_TESTNET_RPC);
export const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);// signAuthorization.ts
import { ethers } from "ethers";
import { DELEGATE_ADDRESS, STABLE_TESTNET_CHAIN_ID, provider, wallet } from "./config";
export async function signAuthorization() {
const authorization = {
chainId: STABLE_TESTNET_CHAIN_ID,
address: DELEGATE_ADDRESS,
nonce: await provider.getTransactionCount(wallet.address),
};
return wallet.signAuthorization(authorization);
}步骤 3:提交 EIP-7702 交易
将授权与对 Multicall3.aggregate3 的调用结合起来。此示例将三个 USDT0 转账批处理到一个交易中。
import { ethers } from "ethers";
import { wallet, USDT0_ADDRESS } from "./config";
import { signAuthorization } from "./signAuthorization";
const usdt0Interface = new ethers.Interface([
"function transfer(address to, uint256 amount)",
]);
const batchInterface = new ethers.Interface([
"function aggregate3((address target, bool allowFailure, bytes callData)[] calls) returns ((bool success, bytes returnData)[])",
]);
async function main() {
const recipients = [
{ to: "0xAlice...", amount: ethers.parseUnits("100", 6) },
{ to: "0xBob...", amount: ethers.parseUnits("200", 6) },
{ to: "0xCarol...", amount: ethers.parseUnits("150", 6) },
];
const batchData = batchInterface.encodeFunctionData("aggregate3", [
recipients.map(({ to, amount }) => ({
target: USDT0_ADDRESS,
allowFailure: false,
callData: usdt0Interface.encodeFunctionData("transfer", [to, amount]),
})),
]);
const signedAuth = await signAuthorization();
const tx = await wallet.sendTransaction({
type: 4, // EIP-7702 transaction type
to: wallet.address, // call is directed at the EOA itself
data: batchData, // aggregate3 call to execute
authorizationList: [signedAuth],
maxPriorityFeePerGas: 0n,
});
const receipt = await tx.wait(1);
console.log("Batch transactions executed in tx:", receipt.hash);
}Batch transactions executed in tx: 0x...EOA 通过 Multicall3.aggregate3 在一个原子交易中执行所有三个调用。委托将持续存在,直到明确更改或清除。虽然此示例展示了批量支付,但相同的模式适用于任何合约调用组合。
用例:消费限额
委托合约可以强制对 EOA 实行每笔交易或每日限额,而无需进行账户迁移。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title SpendingLimitExecutor
/// @notice Delegate contract that enforces daily spending caps
contract SpendingLimitExecutor {
mapping(address => uint256) public dailyLimit;
mapping(address => uint256) public spentToday;
mapping(address => uint256) public lastResetDay;
function setDailyLimit(uint256 limit) external {
dailyLimit[msg.sender] = limit;
}
function executeWithLimit(
address target,
uint256 value,
bytes calldata data
) external payable {
uint256 today = block.timestamp / 1 days;
if (today > lastResetDay[msg.sender]) {
spentToday[msg.sender] = 0;
lastResetDay[msg.sender] = today;
}
spentToday[msg.sender] += value;
require(
spentToday[msg.sender] <= dailyLimit[msg.sender],
"daily limit exceeded"
);
(bool success,) = target.call{value: value}(data);
require(success, "call failed");
}
}用例:会话密钥
会话密钥允许 dApp 在具有限定权限(时间窗口和允许的目标合约集)的情况下代表 EOA 执行交易。这对于需要频繁链上交互的 dApp 很有用,否则会需要重复的钱包批准。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title SessionKeyExecutor
/// @notice Delegate contract that grants scoped, time-limited access to a session key
contract SessionKeyExecutor {
struct Session {
address key;
uint256 validUntil;
uint256 spendingLimit;
uint256 spent;
}
mapping(address => Session) public sessions;
mapping(address => mapping(address => bool)) public allowedTargets;
/// @notice Register a session key with scoped permissions
function startSession(
address key,
uint256 validUntil,
uint256 spendingLimit,
address[] calldata targets
) external {
sessions[msg.sender] = Session({
key: key,
validUntil: validUntil,
spendingLimit: spendingLimit,
spent: 0
});
for (uint256 i = 0; i < targets.length; i++) {
allowedTargets[msg.sender][targets[i]] = true;
}
}
/// @notice Execute a call using the session key
function executeAsSessionKey(
address owner,
address target,
uint256 value,
bytes calldata data
) external {
Session storage session = sessions[owner];
require(msg.sender == session.key, "not session key");
require(block.timestamp <= session.validUntil, "session expired");
require(allowedTargets[owner][target], "target not allowed");
uint256 beforeBalance = owner.balance;
(bool success,) = target.call{value: value}(data);
require(success, "call failed");
session.spent += owner.balance - beforeBalance;
require(session.spent <= session.spendingLimit, "budget exceeded");
}
/// @notice Revoke the active session
function revokeSession() external {
delete sessions[msg.sender];
}
}重要注意事项
- 持久委托:委托将一直持续,直到 EOA 明确更改或清除它。它不限于单笔交易。
- gas 成本:EIP-7702 交易由于授权处理,基础 gas 费略高,但当委托批量处理多个调用时,可以抵消这部分成本。
- 使用经过审计的委托:恶意的委托合约可能会耗尽 EOA 的资产。仅委托给经过审计的合约。
主要收获
- EIP-7702 允许 EOA 执行智能合约逻辑,而无需迁移到新的账户类型。
- 在 Stable 上,EIP-7702 使得现有 EOA 能够实现批量支付、消费限额和受限会话密钥。
- 委托将持续存在,直到明确更改。始终使用经过审计的委托合约。
接下来推荐
- 订阅与收款:将 EIP-7702 应用于使用 SubscriptionManager 的定期订阅支付。
- EIP-7702 概念:在发布之前了解委托模型。
- EIP-7702 参考资料:查找
0x04交易格式和授权字段。

