一键转移权限:当地址异常时,智能规则自动接管

5 阅读6分钟

前言

本文将围绕 EIP-7702 展开,系统介绍该提案的核心概念、设计目标(即解决了什么问题),并结合具体场景探讨其实际应用方式与代码落地的可行性。

一、是什么

EIP-7702 是一项让你的普通钱包(EOA,如 MetaMask)在交易执行期间瞬间"变身"为智能合约的技术方案。

1.1 技术本质:临时代码委托

以往,你的钱包地址里只有"钱(余额)"和"计数(Nonce)",没有"代码"。

EIP-7702 引入了一种新交易类型(Type 4) ,允许你签署一个授权,告诉链:"在处理这笔交易时,请让我的地址运行这段指定的智能合约代码。"

  • 交易开始:你的地址变成智能账户。
  • 执行中:可以执行批量转账、代付 Gas、自动止损等复杂逻辑。
  • 交易结束:你的地址变回普通钱包(或根据你的设置保持委托状态)。

1.2 它和以前有什么不同?

在它出现之前,账户体系是割裂的:

方案特点缺陷
传统方式(EIP-3074)引入"调用者"合约风险较高,容易被黑客利用
纯合约账户(ERC-4337)功能强大必须换新地址,老地址资产迁移麻烦
EIP-7702结合两者优点无需换地址,比 3074 更安全,完美兼容 4337 的安全逻辑

1.3 它解决了什么实际问题?

  • 一键操作:以前要先 ApproveSwap,现在一笔交易搞定,省时省 Gas。
  • Gas 自由:你可以用钱包里的 USDC 支付手续费,或者让 DApp 帮你付——钱包里没 ETH 也能转账。
  • 安全加固:发现私钥泄露时,你可以利用 7702 迅速给地址"套"上一层多签锁或限额逻辑。

二、解决什么

2.1 彻底解决了"存量 EOA 升级难"的问题

在 EIP-7702 之前,如果你想使用智能账户(如社交恢复、自动交易),必须创建一个新地址(合约地址),并将资产从原来的 MetaMask 迁移过去。

解决方法:7702 允许你原地升级。用了十年的老地址,通过一个签名就能瞬间具备合约功能,资产无需移动

2.2 消灭了"授权(Approve)"带来的安全隐患与繁琐

以前在 Uniswap 卖币,必须先发一笔 Approve 交易,再发一笔 Swap 交易。这不仅费钱,留下的"无限授权"更是资产被盗的主因。

解决方法:利用 7702 的原子捆绑(Atomic Batching) 。在同一笔交易里,你的 EOA 临时变成合约,直接把币"转"给协议并换回新币。交易结束,授权即刻失效,零留存风险

2.3 解决了"没油(ETH/MATIC)动不了"的尴尬

以前如果你的 Polygon 钱包里只有 USDC 但没有 MATIC,资产就锁死了。

解决方法代付 Gas(Sponsorship) 。你可以签署一个 7702 授权,让机器人地址帮你支付 Gas,或者让合约从你的 USDC 里扣除等值费用作为手续费。这对资产救援机器人至关重要。

2.4 为"账户抽象(AA)"提供了统一入口

以前 ERC-4337(账户抽象)很难在老用户中普及,因为入口太复杂。

解决方法:EIP-7702 成为 ERC-4337 的适配器。普通钱包(EOA)通过签名即可直接接入 4337 的成熟生态(如 Paymaster 和 Bundler),实现技术的完美合流。


三、代码实例

3.1 项目说明

  • 3.1.1 借助 Hardhat v3 启动本地网络节点:npx hardhat node
  • 3.1.2 编译智能合约生成对应的 abi.jsonnpx hardhat compile

hardhat.config.ts 配置

import "@nomicfoundation/hardhat-viem";
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
  plugins: [hardhatToolboxViemPlugin],
  solidity: {
    version: "0.8.28",
    settings: {
      optimizer: {
        enabled: true,
        runs: 1,
      },
      viaIR: true,
      evmVersion: "cancun",
    },
  },
  networks: {
    hardhatMainnet: {
      type: "edr-simulated",
      chainType: "l1",
    },
    hardhatOp: {
      type: "edr-simulated",
      chainType: "op",
    },
    hardhat: {
      type: "edr-simulated",
      chainId: 1337, // 将默认的 31337 修改为 1337
    },
    sepolia: {
      type: "http",
      chainType: "l1",
      url: configVariable("SEPOLIA_RPC_URL"),
      accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
    },
  },
});

修正说明:原配置中 sepoliaurl 参数值为空字符串 configVariable(" "),已修正为 configVariable("SEPOLIA_RPC_URL")

3.2 智能合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title EIP7702ProRescue
 * @dev 针对 2026 年 Pectra 升级环境优化的救援合约
 */
contract EIP7702ProRescue {
    using SafeERC20 for IERC20;

    // 机器人地址(守护者),防止黑客利用你的 7702 授权自行调用 rescue
    address public immutable guardian;

    constructor(address _guardian) {
        guardian = _guardian;
    }

    /**
     * @notice 一键清空资产
     * @param tokens 待救援代币列表
     * @param recipient 接收地址
     */
    function rescueAssets(address[] calldata tokens, address recipient) external {
        // 核心安全检查:只有机器人可以触发此逻辑
        // 在 EIP-7702 中,msg.sender 是发起交易的机器人,address(this) 是你的 EOA
        require(msg.sender == guardian, "EIP7702: Unauthorized guardian");

        // 1. 处理 ERC-20
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 balance = IERC20(tokens[i]).balanceOf(address(this));
            if (balance > 0) {
                // 使用 OZ 的 SafeERC20 防止因转账失败导致整批任务回滚
                IERC20(tokens[i]).safeTransfer(recipient, balance);
            }
        }

        // 2. 处理原生代币(ETH/MATIC)
        uint256 ethBalance = address(this).balance;
        if (ethBalance > 0) {
            // 使用 OZ 的 Address 库安全发送
            Address.sendValue(payable(recipient), ethBalance);
        }
    }
}

3.3 完整测试流程

import {
  createWalletClient,
  createPublicClient,
  http,
  formatEther,
  defineChain,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import rescueContract from "./EIP7702ProRescue.json" with { type: "json" };

const { abi, bytecode } = rescueContract;

// 注意:chainId 需与 hardhat.config 保持一致
const hardhatLocal = defineChain({
  id: 1337, // 与 hardhat.config 中 hardhat 网络的 chainId 一致
  name: "Hardhat Local",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: {
    default: { http: ["http://127.0.0.1:8545"] },
  },
});

async function testRescue() {
  // 账户初始化(Hardhat 默认账户)
  const guardianAccount = privateKeyToAccount(
    "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
  );
  const eoaAccount = privateKeyToAccount(
    "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
  );
  const safeRecipient = "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc";

  console.log("--- 验证地址加载 ---");
  console.log(`Guardian Address: ${guardianAccount.address}`);
  console.log(`EOA Address: ${eoaAccount.address}`);

  const publicClient = createPublicClient({
    chain: hardhatLocal,
    transport: http(),
  });
  const walletClient = createWalletClient({
    chain: hardhatLocal,
    transport: http(),
  });

  try {
    // --- 1. 部署逻辑合约 ---
    console.log("\n[1/3] 部署逻辑合约...");
    const deployHash = await walletClient.deployContract({
      abi,
      bytecode,
      account: guardianAccount,
      args: [guardianAccount.address],
    });
    const receipt = await publicClient.waitForTransactionReceipt({
      hash: deployHash,
    });
    const contractAddress = receipt.contractAddress;
    console.log(`✅ 合约部署于: ${contractAddress}`);

    // --- 2. EIP-7702 授权 ---
    console.log("\n[2/3] 签署 7702 授权...");
    const authorization = await eoaAccount.signAuthorization({
      contractAddress,
    });
    console.log("✅ 授权已签署");

    // --- 3. 执行救援 ---
    console.log("\n[3/3] 准备执行救援交易...");

    let txHash;
    try {
      // 尝试模拟
      const { request } = await publicClient.simulateContract({
        account: guardianAccount,
        address: eoaAccount.address,
        abi,
        functionName: "rescueAssets",
        args: [[], safeRecipient],
        authorizationList: [authorization],
        gas: 1_000_000n, // 显式设置高额 Gas
      });
      txHash = await walletClient.writeContract(request);
    } catch (simError) {
      console.log(
        "⚠️ 模拟执行返回错误(可能是引擎估算问题),尝试直接发送交易..."
      );
      // 绕过 simulate 直接发送
      txHash = await walletClient.writeContract({
        account: guardianAccount,
        address: eoaAccount.address,
        abi,
        functionName: "rescueAssets",
        args: [[], safeRecipient],
        authorizationList: [authorization],
        gas: 1_000_000n,
        chain: hardhatLocal,
      });
    }

    console.log(`🚀 交易已发出! Hash: ${txHash}`);

    const receiptRescue = await publicClient.waitForTransactionReceipt({
      hash: txHash,
    });

    if (receiptRescue.status === "success") {
      const finalBal = await publicClient.getBalance({
        address: safeRecipient,
      });
      console.log(`\n🎉 救援成功!`);
      console.log(`安全地址最终余额: ${formatEther(finalBal)} ETH`);
    } else {
      console.log(
        "\n❌ 交易打包成功但执行回滚。请确认合约中的 guardian 地址校验逻辑。"
      );
    }
  } catch (error) {
    console.error("\n❌ 执行过程中发生错误:");
    console.error(error.shortMessage || error.message);
  }
}

testRescue();

总结

至此,本文关于 EIP-7702 的理论知识讲解及最小可行性落地方案已全部介绍完毕。