# 理解并实现 EIP-7702

> 假设你要执行一笔复杂的一体化原子交易，需要兑换代币、添加流动性、质押奖励。可问题是，你的普通 Ethereum 钱包 EOA 做不了批量交易。

作者 Shodipo Ayomide，发布于 2025年9月27日。网页版本：https://blog.shodipoayomide.com/zh/understanding-and-implementing-eip-7702

![理解并实现 EIP-7702](https://blog.shodipoayomide.com/banners/understanding-and-implementing-eip-7702.png)

假设你要执行一笔复杂的一体化原子交易，需要兑换代币、添加流动性、质押奖励。可问题是，你的普通 Ethereum 钱包 EOA 做不了批量交易。所以，你得签三笔独立的交易，付三次 gas，这可万万不行，然后还得祈祷 MEV 机器人别在步骤之间把你夹成三明治。

这是最近 `EVM` 生态里讨论最激烈的话题之一，我们到底要怎样才能赋予普通钱包超能力，同时又不把所有东西一下子全搞砸？

这个挑战催生了两个 `EIPs`，先是 `EIP-3074`，随后是 `EIP-7702`。两者都想弥合简单的 `EOA` 与 `smart contract accounts` 之间的差距，但它们在委托权限上采取了不同的方式。一个提供持久的权力，事情可能会变得非常糟糕，你的钱包会被掏空。另一个既能给你持久委托，也能给你临时委托，后者可以在你的交易上链执行的那一刻就消失。

我们来聊聊为什么这个区别很重要，以及 Vitalik 的提案是如何可能让 Ethereum 躲过一场潜在的安全噩梦的。

## EIP-2718 - 类型化交易信封

在进入委托这个话题之前，有一块重要的基础设施让这一切成为可能，那就是 `EIP-2718`。可以把它看作 Ethereum 为交易打造的集装箱系统。

```typescript
// before eip-2718 all transactions looked the same
const legacyTx = rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]);

// after eip-2718 transactions can now be typed
const typedTx = TransactionType || TransactionPayload;
```

`EIP-2718` 引入了 `Typed Transaction Envelopes`，其中 `TransactionType || TransactionPayload` 就是一笔有效的交易。`TransactionType` 标识交易的格式，而 `Payload` 包含交易的内容。这套包装系统意味着，我们可以在不破坏现有功能的情况下引入全新的交易行为。

引入信封交易类型之后，我们只需要保证与现有交易的向后兼容，从那以后，只需解决一个简单得多的问题：确保各个 `TransactionTypes` 之间没有编号冲突。所以当 `EIP-7702` 需要引入它的临时委托机制时，它只需定义一个新的交易类型，例如 `0x04`，而不需要对 `EVM` 本身做任何改动。

## EIP-3074 - 持久委托方案

[EIP-3074](https://eips.ethereum.org/EIPS/eip-3074) 在委托上采取了大胆的做法，通过两个新的 `EVM` `opcodes`，也就是 `AUTH` 和 `AUTHCALL`，让 `EOAs` 能够把权限永久委托给智能合约。

### AUTH 和 AUTHCALL 是如何工作的

机制很直接

```solidity
// user signs an auth message
bytes32 commit = keccak256(abi.encode(invokerAddress, batchActions));
bytes memory signature = signMessage(userPrivateKey, commit);

// invoker contract calls AUTH opcode
AUTH(commit, signature.v, signature.r, signature.s);
// This sets the 'authorized' context variable to user's address

// now invoker can make calls as the user
AUTHCALL(gasLimit, targetContract, 0, 0, calldata, calldataLength, 0, 0);
```

`AUTH` 接收四个参数，`commit` 以及签名的 `yParity`、`r` 和 `s`，如果签名有效，并且签名者地址等于 authority，上下文变量 authorized 就会被设置为该 authority。随后，`AUTHCALL` 允许被授权的 invoker 合约发送交易，此时 `CALLER` 被设置为被授权的地址，而不是 invoker。

现在，想象一个能处理批量交易的 invoker 合约

```solidity
contract BatchInvoker {
    struct BatchCall {
        address target;
        bytes data;
        uint256 value;
    }
    
    function executeBatch(
        BatchCall[] calldata calls,
        bytes32 commit,
        uint8 v, bytes32 r, bytes32 s
    ) external {
        // authorize the user
        AUTH(commit, v, r, s);
        
        // Execute all calls as the user
        for (uint i = 0; i < calls.length; i++) {
            AUTHCALL(
                gasleft(),
                calls[i].target,
                calls[i].value,
                0, // valueExt (must be 0 for now)
                // ... calldata parameters
            );
        }
    }
}
```

尽管这很令人兴奋，但这两个 `opcodes` 在 `EVM` 社区引发了一系列安全担忧。上下文变量 `authorized` 在合约的单个执行帧内一直存在，但不会传递给任何调用，包括 `DELEGATECALL`。这基本上意味着授权在整个交易范围内都是有效的。

真正的问题不在于它在单笔交易内的临时性，而在于当 invoker 合约被广泛使用时会发生什么。如果最终每个人都会用上智能账户，那么 `AUTH` 和 `AUTHCALL` 这两个 `opcodes` 可能会成为巨大的技术负担，并增加 `EVM` 本身的复杂度。

如果像上面那样一个热门的 invoker 合约出了 bug，会怎么样？突然之间，每一个曾经授权过这个合约的用户都可能面临风险。一次恶意更新，一个被遗漏的边界情况，一次整数溢出，就这样，大规模的资产掏空开始了。

`EVM` 生态意识到，这造成了一种奇怪的局面。`EIP-3074` 的安全模型严重依赖 invoker 合约。由于多个用户可能依赖同一套 invoker 逻辑，一个 invoker 中的漏洞或攻击可能一下子危及许多用户的资产或权限，这就是 `EIP-3074` 被撤回的原因。

## EIP-7702 - 灵活的委托方案

现在来看 [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)，它由 Vitalik 在 2024 年 5 月提出，于 2025 年 5 月通过 `pectra upgrade` 上了 `mainnet`，它引入了一种新的精简方案，让 `EOAs` 能在单笔交易期间临时像智能合约一样运作，这可是件大事！

`EIP-7702` 没有引入新的 `opcodes`，而是利用 `EIP-2718's` 交易信封系统引入了一种新的交易类型

```typescript
interface EIP7702Transaction {
    chainId: number;
    nonce: number;
    maxPriorityFeePerGas: bigint;
    maxFeePerGas: bigint;
    gasLimit: bigint;
    to: string;
    value: bigint;
    data: Uint8Array;
    accessList: AccessList;
    authorizationList: Authorization[];
}

interface Authorization {
    chainId: number;
    address: string;  // the address of the contract code to delegate to
    nonce: number;
    yParity: 0 | 1;
    r: Uint8Array;
    s: Uint8Array;
}
```

对于每个授权对象，都会有一个委托标识 `(0xef0100 || address)` 被写入授权账户的代码中。所有执行代码的操作都必须加载并执行该委托所指向的代码。

### 委托的持久性究竟是如何运作的
这正是很多人搞不清楚的地方。`EIP-7702` 自最初的草案以来一直在演进。起初，这个提案的设计是让 `EOAs` 只在一笔交易内临时变得“智能”。但最终版本让你可以控制委托持续多久。

```javascript
function processEIP7702Transaction(tx) {
    // verify all auth signatures
    for (const auth of tx.authorizationList) {
        const signer = recoverSigner(auth);
        if (signer !== getAddressFromAuth(auth)) {
            throw new Error("Invalid authorization");
        }
    }
    
    // set delegation pointers (these persist after transaction)
    for (const auth of tx.authorizationList) {
        const eoaAddress = getAddressFromAuth(auth);
        
        // set delegation pointer - 0xef0100 || contract_address
        const delegationPointer = concat(['0xef0100', auth.address]);
        setCode(eoaAddress, delegationPointer);
        
        // nowm this delegation PERSISTS unless explicitly cleared
    }
    
    // execute the transaction normally
    executeTransaction(tx);
    
    // no auto cleanup, delegation stays active
}
```

**注意**：如果一笔 `7702` 交易执行失败，已经处理的委托标识不会回滚，而是会一直保留，除非在后续的一笔 `7702` 交易中把委托地址设置为 `0x0`，显式地清除它。

这意味着你有三种委托模式：

- 持久委托：设置委托，并在多笔交易中持续使用它
- 临时委托：为特定用例设置委托，比如在一天之内委托给某个 `Batching Contract`，委托本身会一直存在，直到用户把它清除。

### 临时和持久，两种选择都有

`EIP-7702` 的好处在于它让你自己选。想为一次性的复杂交易做临时委托？可以。想把你的 `EOA` 持久升级成像智能合约钱包一样工作？也可以。

临时委托的例子如下

```typescript
// one off delegation
const tempDelegationTx = {
    type: 0x04,
    authorizationList: [{
        address: batchExecutorContract,
        // ... auth details
    }],
    to: userAddress,
    data: encodeBatchCall([
        // execute your complex operation
        swapTokens(),
        addLiquidity(), 
        stakeRewards(),
    ])
};
```
而持久委托的例子如下

```typescript
// upgrade EOA to smart wallet
const persistentDelegationTx = {
    type: 0x04,
    authorizationList: [{
        address: smartWalletImplementation,
        // ... auth details  
    }],
    to: userAddress,
    data: encodeCall("initializeSmartFeatures", [])
    // this delegation persists for future txns
};

// later txns can now use smart wallet features
const futureTransaction = {
    to: userAddress, // your EOA will now execute smart wallet code
    data: encodeCall("batchExecute", [multipleOperations])
    // no new auth needed
};
```

### 为什么 7702 更安全

`EIP-3074` 和 `EIP-7702` 的关键区别不只是临时与持久，而在于风险的分布与控制。`EIP-3074` 制造了一个共享的故障点，一个热门合约的问题可能波及成千上万的用户。`EIP-7702` 隔离了每一笔交易，所以 bug 只会影响那一笔具体的交易。`EIP-7702` 与现有的 `ERC-4337` 完全兼容，不需要新的 `opcodes`。这基本上意味着，钱包可以逐步采用它，而不会破坏任何现有系统。

关键的洞见在于，管理委托的是 `wallets`，而不是 `dApps`。所以普遍的共识是只允许钱包管理委托，这意味着：

- dApp 不能直接控制你的委托
- 钱包服务商实现经过审查和审计的委托逻辑
- 风险分散在众多实现之中，而不是集中在少数几个热门的 invoker 合约里

## 开发者需要知道的事

如果你在构建一个将要支持 `EIP-7702` 的应用，有几个技术点需要注意

1. **交易构造的变化**
你的交易构建逻辑需要处理授权列表

```typescript
// old: simple EOA transaction
const tx = {
    to: contractAddress,
    data: encodedCall,
    // ... standard fields
};

// new way with eip-7702 delegation
const tx = {
    type: 0x04,
    to: userAddress, // calling user's own address
    data: encodeBatchCall([
        contract.interface.encodeFunctionData("swap", [tokenA, tokenB, amount]),
        contract.interface.encodeFunctionData("addLiquidity", [lpTokens]),
        contract.interface.encodeFunctionData("stake", [lpAmount])
    ]),
    authorizationList: [{
        address: batchExecutorImplementation,
        // ... authorization signature
    }],
    // ... standard fields
};
```

2. **Gas 估算的复杂性**
如果 EOA 决定撤回智能合约的控制权，就必须再发送一笔 `7702` 交易，把委托重新设置为 `0x0`。这意味着你的 gas 估算需要考虑到

- 授权签名验证
- 代码指针设置
- 实际执行
- 清理开销

3. **智能合约签名支持**
`EIP-7702` 为 `EOAs` 启用了 `EIP-1271` 签名验证。这意味着你**升级后**的 `EOA` 可以使用 `passkeys` 这类替代方式来签名消息，同时依然保持向后兼容。

```solidity
// your EOA can now implement smart signature verification
function isValidSignature(bytes32 hash, bytes signature) external view returns (bytes4) {
    // can verify passkey signatures, multisig, etc.
    if (verifyPasskeySignature(hash, signature)) {
        return 0x1626ba7e; // EIP-1271 magic value
    }
    return 0xffffffff;
}
```

## 必读链接

- [EIP-2718：类型化交易信封](https://eips.ethereum.org/EIPS/eip-2718) - 奠定基础的交易信封系统
- [EIP-3074：AUTH 和 AUTHCALL 操作码](https://eips.ethereum.org/EIPS/eip-3074) - 持久委托提案
- [EIP-7702：为单笔交易设置 EOA 账户代码](https://eips.ethereum.org/EIPS/eip-7702) - 临时委托提案
- [Ethereum Magicians 讨论帖](https://ethereum-magicians.org/t/eip-set-eoa-account-code-for-one-transaction/19923) - 社区关于 EIP-7702 的辩论
- [Vitalik 的账户抽象路线图](https://ethereum-magicians.org/t/eip-set-eoa-account-code-for-one-transaction/19923)
