EOA 委托

EIP-7702 引入了一种新的交易类型 (0x4),它授予 外部拥有账户 (EOAs) 将执行委托给智能合约的能力。这对于使传统的 EVM 账户能够做到以下几点特别有用:

  • 在单个交易中批量处理多个操作(例如 approve + transfer, 太棒了!)

  • 为其他用户赞助交易。

  • 实施权限降级(例如,具有有限权限的子密钥)。

本节将引导您完成遵循 ERC-7702 将 EOA 委托给合约的过程。这允许您使用 EOA 的私钥来签名并执行具有自定义执行逻辑的操作。结合 ERC-4337 基础设施,用户可以通过 paymasters 实现 gas 赞助。

委托执行

EIP-7702 使 EOA 能够将其执行能力委托给智能合约,从而有效地弥合了传统账户和 智能账户 之间的差距。SignerERC7702 实用程序通过验证针对 EOA 地址 (address(this)) 的签名来促进这种委托,从而更容易在智能合约账户中实现 EIP-7702。

Unresolved include directive in modules/ROOT/pages/eoa-delegation.adoc - include::api:example$account/MyAccountERC7702.sol[]
用户可以委托给 ERC-7821 的实例,以获得一个不使用 ERC-4337 相关代码的最小批量执行器。

签名授权

要授权委托,EOA 所有者需要对包含链 ID、nonce、委托地址和签名组件的消息进行签名 (即 [chain_id, address, nonce, y_parity, r, s])。此签名授权有两个目的:它将执行限制为仅委托合约,并防止重放攻击。

EOA 为每个链上的每个授权地址维护一个委托指示符,该指示符指向合约,该合约的代码将在 EOA 的上下文中执行以处理委托的操作。

以下是如何使用 viem 构建授权:

// 记住不要硬编码你的私钥!
const eoa = privateKeyToAccount('<YOUR_PRIVATE_KEY>');
const eoaClient = createWalletClient({
  account: eoa,
  chain: publicClient.chain,
  transport: http(),
});

const walletClient = createWalletClient({
  account, // 参考 Viem 的 `privateKeyToAccount`
  chain, // import { ... } from 'viem/chains';
  transport: http(),
})

const authorization = await eoaClient.signAuthorization({
  account: walletClient.account.address,
  contractAddress: '0x<YOUR_DELEGATE_CONTRACT_ADDRESS>',
  // 如果你的 walletClient 的 account 是发送交易的 account,
  // 则使用此字段代替 `account`
  // executor: "self",
});
在实施委托合约时,请确保它们需要的签名可以避免重放性(例如,域分隔符,nonce)。 实施不佳的委托可能会让恶意行为者几乎完全控制签名者的 EOA。

发送设置代码交易

签署授权后,您需要发送一个 SET_CODE_TX_TYPE (0x04) 交易,将委托指示符 (即 0xef0100 || address) 写入您的 EOA 代码,这将告诉 EVM 加载和执行来自指定地址的代码,当在您的 EOA 上执行操作时。

// 发送包含 `authorization` 的 `data`
const receipt = await walletClient
  .sendTransaction({
    authorizationList: [authorization],
    data: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
    to: eoa.address,
  })
  .then((txHash) =>
    publicClient.waitForTransactionReceipt({
      hash: txHash,
    })
  );

// 打印收据
console.log(userOpReceipt);

要删除委托并将您的 EOA 恢复到原始状态,您可以发送一个授权元组指向零地址 (0x0000000000000000000000000000000000000000) 的 SET_CODE_TX_TYPE 交易。这将清除帐户的代码并将其代码哈希重置为空哈希,但是,请注意,它不会自动清理 EOA 存储。

在更改帐户的委托时,请确保新委托的代码经过专门设计和测试,可以作为对旧代码的升级。为了确保委托合约之间安全迁移,请遵循 ERC-7201 使用避免意外冲突的命名空间存储。

由于潜在的存储冲突,更新委托指示符可能会导致您的 EOA 无法使用。我们建议遵循与 编写可升级智能合约 相似的做法。

与 ERC-4337 一起使用

在 EOA 上设置代码以执行逻辑的能力允许用户利用 ERC-4337 基础设施来处理用户操作。开发人员只需要将 Account 合约与 SignerERC7702 结合起来,即可实现开箱即用的 ERC-4337 兼容性。

发送 UserOp

一旦您的 EOA 委托给 ERC-4337 兼容帐户,您就可以通过入口点合约发送用户操作。用户操作包括所有必要的执行字段,包括 gas 限制、费用以及要执行的实际调用数据。入口点将验证操作并在您委托帐户的上下文中执行它。

类似于 发送 UserOp 是如何为工厂帐户实现的,以下是如何为委托给 Account 合约的 EOA 构建 UserOp。

const userOp = {
  sender: eoa.address,
  nonce: await entrypoint.read.getNonce([eoa.address, 0n]),
  initCode: "0x" as Hex,
  callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
  accountGasLimits: encodePacked(
    ["uint128", "uint128"],
    [
      100_000n, // verificationGas
      300_000n, // callGas
    ]
  ),
  preVerificationGas: 50_000n,
  gasFees: encodePacked(
    ["uint128", "uint128"],
    [
      0n, // maxPriorityFeePerGas
      0n, // maxFeePerGas
    ]
  ),
  paymasterAndData: "0x" as Hex,
  signature: "0x" as Hex,
};

const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
userOp.signature = await eoa.sign({ hash: userOpHash });

const userOpReceipt = await eoaClient
  .writeContract({
    abi: EntrypointV08Abi,
    address: entrypoint.address,
    authorizationList: [authorization],
    functionName: "handleOps",
    args: [[userOp], eoa.address],
  })
  .then((txHash) =>
    publicClient.waitForTransactionReceipt({
      hash: txHash,
    })
  );

console.log(userOpReceipt);
当使用赞助交易中继器时,请注意,授权帐户可能会导致中继器花费 gas 而未获得报销,方法是使授权无效(增加帐户的 nonce)或将相关资产从帐户中扫出。中继器可以实施保护措施,例如要求保证金或使用声誉系统。