前言
本文将围绕 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 它解决了什么实际问题?
- 一键操作:以前要先
Approve再Swap,现在一笔交易搞定,省时省 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.json:npx 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")],
},
},
});
修正说明:原配置中
sepolia的url参数值为空字符串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 的理论知识讲解及最小可行性落地方案已全部介绍完毕。