Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

使用 EIP-7702 实现账户抽象

本指南将 EIP-7702 应用于 EOA,并使用委托将多个 USDT0 传输批量处理为一笔原子交易。EOA 在整个过程中保持其地址和私钥不变。对于会话密钥和消费限额,请参阅使用 EIP-7702 的会话密钥,该密钥使用经过审计的智能账户而不是自定义委托。

先决条件

  • 了解 EOA 与智能合约账户(EOA 默认没有代码)。
  • 熟悉 EVM 交易类型(EIP-2718)。
  • Foundry 用于部署委托合约。

概览

EIP-7702 引入了一种新的交易类型(0x04),它包含一个 authorizationList。每个授权都指定一个智能合约,EOA 执行该智能合约的代码。流程如下:

  1. 选择或部署委托合约:一个标准的 Solidity 合约,实现您希望 EOA 使用的逻辑。尽可能使用经过审计的合约。
  2. 签署授权:EOA 所有者签署一条消息,授权委托合约。
  3. 提交 EIP-7702 交易:交易包含授权,EOA 在执行期间运行委托的代码。

用例:批量交易

批量处理将多个调用(代币传输、批准或任何组合)在一个原子交易中运行。批量支付就是一个例子:EOA 不会为工资发放发送十笔单独的交易,而是同时执行所有交易。

步骤 1:部署批量执行器

BatchExecutor 按顺序运行一系列调用,如果任何调用失败,则回滚整个批次。它只接受来自 EOA 本身的调用,因此只有所有者的密钥才能触发批次。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
/// @title BatchExecutor (example, unaudited)
/// @notice EIP-7702 delegate that executes a batch of calls from the EOA.
contract BatchExecutor {
    struct Call {
        address target;
        uint256 value;
        bytes data;
    }
 
    function execute(Call[] calldata calls) external payable {
        // SAFE: only the EOA itself, signing with its own key, can run a batch.
        require(msg.sender == address(this), "only self");
 
        for (uint256 i = 0; i < calls.length; i++) {
            (bool success,) = calls[i].target.call{value: calls[i].value}(calls[i].data);
            require(success, "call failed");
        }
    }
}

部署一次。任意数量的 EOA 都可以委托给同一个部署。

forge create src/BatchExecutor.sol:BatchExecutor \
  --rpc-url https://rpc.testnet.stable.xyz \
  --private-key $PRIVATE_KEY \
  --broadcast
Deployer: 0xYourAddress...
Deployed to: 0xBatchExecutor...
Transaction hash: 0x...

步骤 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 = "0xBatchExecutor..."; // from Step 1
 
export const provider = new ethers.JsonRpcProvider(STABLE_TESTNET_RPC);
export const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// signAuthorization.ts
import { DELEGATE_ADDRESS, STABLE_TESTNET_CHAIN_ID, provider, wallet } from "./config";
 
export async function signAuthorization() {
  const txNonce = await provider.getTransactionCount(wallet.address);
 
  return wallet.authorize({
    chainId: STABLE_TESTNET_CHAIN_ID,
    address: DELEGATE_ADDRESS,
    // The EOA sends this transaction itself. Its nonce increments before the
    // authorization is checked, so the authorization must use the next nonce.
    nonce: txNonce + 1,
  });
}

步骤 3:提交 EIP-7702 交易

将授权与对 execute 的调用结合起来。此示例在一个交易中批量处理三个 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 execute((address target, uint256 value, bytes data)[] calls)",
]);
 
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("execute", [
    recipients.map(({ to, amount }) => ({
      target: USDT0_ADDRESS,
      value: 0n,
      data: 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,                  // execute() call to run
    authorizationList: [signedAuth],
    maxPriorityFeePerGas: 0n,
  });
 
  const receipt = await tx.wait(1);
  console.log("Batch transactions executed in tx:", receipt.hash);
}
 
main();
Batch transactions executed in tx: 0x...

EOA 在一个原子交易中执行所有三个传输。委托会持续存在,因此后续批次只需向 wallet.address 发送常规交易,其中包含 execute calldata 且没有 authorizationList。

消费限额和会话密钥

消费限额和会话密钥允许第二个密钥在所有者设定的规则内为 EOA 操作。使用经过审计的智能账户而不是自定义委托:

  • 访问控制是整个功能。 自定义委托中遗漏的调用者检查会将 EOA 暴露给任何人,而不仅仅是会话密钥。
  • 存储比委托更持久。 委托状态存在于 EOA 的存储中。重新委托给具有不同存储布局的合约可能会误读或覆盖它。

使用 EIP-7702 的会话密钥 将 EOA 委托给 Modular Account v2 并授予密钥过期的 USDT0 限额。该限额就是消费限额。

重要注意事项

  • 持久委托:委托会持续存在,直到 EOA 明确更改或清除它。它不限于单个交易。
  • Gas 成本:由于授权处理,EIP-7702 交易的基本 Gas 略高,当委托批量处理多个调用时,这会得到抵消。
  • 使用经过审计的委托:恶意或不受保护的委托合约可能会耗尽 EOA 的资产。上面的 BatchExecutor 是一个未经审计的示例。在生产中只委托给经过审计的合约。

主要收获

  • EIP-7702 允许 EOA 执行智能合约逻辑,而无需迁移到新的账户类型。
  • 在 Stable 上,EIP-7702 允许在现有 EOA 上进行批量支付、消费限额和范围会话密钥。
  • 委托上的每个函数都可以被任何人通过 EOA 调用,因此委托必须检查调用者。
  • 委托会持续存在,直到明确更改。始终使用经过审计的委托合约。

接下来推荐