Skip to content
On this page

L4-1: Smart Wallets + Paymaster(智能钱包 + Gas 代付)

1. 问题

传统以太坊账户(EOA,外部拥有账户)由单个私钥控制。这种模型有根本性的局限:(1) 私钥丢失 = 资金永久丢失,无恢复机制;(2) 每次操作都需要 ETH 支付 Gas,新用户面临"先有鸡还是先有蛋"的难题;(3) 无法实现复杂的权限策略——如日常消费限额、时间锁、或多人共签;(4) 用户体验笨拙——每笔交易都需要确认和等待。

ERC-4337(账户抽象,Account Abstraction)提出了一套不修改共识层的解决方案:引入智能合约钱包(代替 EOA)、Bundler(打包交易)、以及 Paymaster(代付 Gas)。用户操作不再是传统的以太坊交易,而是"用户操作"(UserOperation)——一个包含操作意图的数据结构,由智能钱包签名,由 Bundler 打包提交。

这个挑战构建了完整 ERC-4337 栈的核心组件:一个智能钱包合约(SmartWallet.sol)和一个代币支付 Gas 的 Paymaster(TokenPaymaster.sol)。用户可以用 ERC-20 代币而不是 ETH 来支付 Gas 费,同时享受智能钱包带来的所有优势。

2. 原因

ERC-4337 已经被超过 3000 万个智能账户采用,是以太坊账户抽象的实际标准。Vitalik Buterin 多次强调,账户抽象是实现主流 Web3 采用的必要路径——它消除了新用户入门的技术摩擦。2026 年,EntryPoint v0.7 已在所有主要 EVM 链上以相同地址 (0x0000000071727De22E5E9d8BAf0edAc6f37da032) 通过 CREATE2 部署,成为以太坊基础设施的核心组件。

理解 ERC-4337 对整个 Web3 生态系统至关重要:

  • 前端开发:需要理解如何构建 UserOperation 并用智能钱包签名
  • 合约开发:需要理解 validateUserOpexecute 的接口,以及 EntryPoint 的生命周期
  • 基础设施开发:Bundler 和 Paymaster 的构建是新兴的基础设施领域
  • 安全工程:智能钱包引入了不同于 EOA 的攻击面,需要不同的安全考量

不同于 EIP-7702(通过在 EOA 中委托代码到智能合约来实现账户抽象——一种"EOA 升级"方案),ERC-4337 是完全的智能合约钱包方案,不依赖 EOA 的存在。两者是互补的技术路径:EIP-7702 给现有 EOA 用户提供升级路径,ERC-4337 面向原生智能钱包的新用户。

3. 方案

完整的 ERC-4337 架构包含四个协作的组件。这个挑战实现了其中两个核心合约:

3.1 架构总览

用户 (可能无 ETH)
  |
  ├─→ 签名 UserOperation
  |    {sender, nonce, callData, paymasterAndData, signature, ...}
  |

Bundler (监听 alt mempool 中的 UserOperation)
  |
  ├─→ simulateValidation() — 模拟验证阶段
  ├─→ 打包多个 UserOperation 成 bundle
  ├─→ handleOps([userOp1, userOp2, ...]) — 提交到 EntryPoint
  |

EntryPoint (0x0000000071727De22E5E9d8BAf0edAc6f37da032)
  |
  ├─→ 验证阶段:
  │     Wallet._validateSignature(userOp, userOpHash)
  │     Paymaster._validatePaymasterUserOp(userOp, userOpHash, maxCost)
  |
  ├─→ 执行阶段:
  │     Wallet.execute(to, value, data) 或 executeBatch(...)
  |     如果验证阶段成功,Bundler 垫付 Gas
  |
  └─→ 后执行阶段:
        Paymaster._postOp(mode, context, actualGasCost)
        从用户扣取 ERC-20 代币补偿 Bundler

3.2 SmartWallet(智能钱包合约)

SmartWallet.sol 是 ERC-4337 兼容的智能合约钱包,实现两个核心接口:

签名验证(_validateSignature):由 EntryPoint 在验证阶段调用。验证逻辑:

  1. userOp.signature(65 字节 ECDSA 签名)中恢复签名者地址
  2. 检查恢复的地址是否匹配 owner
  3. 检查 userOp.nonce 是否匹配钱包当前的 nonce(防止重放)
  4. 返回 0 表示验证成功,返回 1 表示失败(SIG_VALIDATION_FAILED)

签名恢复的实现支持标准的 v 值规范化(如果 v < 27 则加 27),同时兼容 EIP-155 重放保护的 v 值编码(chainId * 2 + 35)。

交易执行(execute / executeBatch)

  • execute():执行单笔调用。只能由 EntryPoint 调用(onlyEntryPoint 修饰符)。成功执行后递增 nonce。
  • executeBatch():原子化批量执行。所有调用必须成功,否则全部回滚。传入的三个数组(to、values、datas)必须长度一致。

安全门控:所有执行函数使用 onlyEntryPoint 修饰符,确保只有 EntryPoint 可以触发执行。这是 ERC-4337 的核心安全模型——用户操作必须通过完整的验证流水线(包括 Gas 支付、签名验证和 nonce 检查)才能执行。

接收 ETHreceive() 函数允许钱包直接接收 ETH 转账(如从其他用户或合约转入资金)。

3.3 TokenPaymaster(代币 Gas 代付合约)

TokenPaymaster.sol 实现了允许用户用 ERC-20 代币支付 Gas 的 Paymaster。核心机制:

验证(_validatePaymasterUserOp):在 EntryPoint 的验证阶段被调用,接收三个参数:

  1. userOp:完整的用户操作,paymasterAndData 字段包含编码的 maxTokenCost(用户授权支付的最大代币数量)
  2. userOpHash:用户操作的哈希(虽然在此实现中未被使用,但完整实现中用于额外的签名验证)
  3. maxCost:EntryPoint 估算的此操作最大 Gas 成本

验证检查:

  • 解析 paymasterAndData 中的 maxTokenCost(从第 20 字节开始取 32 字节的 uint256)
  • 根据固定汇率 TOKENS_PER_ETH = 2000 计算 tokenCost = maxCost * 2000
  • 验证用户是否批准了足够的代币额度(token.allowance(userOp.sender, address(this))
  • 验证 Paymaster 在 EntryPoint 中是否有足够的 ETH 存款(entryPoint.balanceOf(address(this))
  • 返回 context(编码的 (sender, tokenCost)),供 _postOp 使用

后执行(_postOp):在用户操作执行后由 EntryPoint 调用:

  • 只在操作成功时(mode == PostOpMode.opSucceeded)扣费
  • 根据实际消耗的 Gas 计算最终代币费用:actualTokenCost = actualGasCost * TOKENS_PER_ETH
  • 不超过预授权的 tokenCost 上限
  • 从用户账户 transferFrom 代币到 Paymaster 合约

存款管理:Paymaster 需要在 EntryPoint 中存入 ETH 来支付 Bundler 的 Gas:

  • deposit():任何人都可以存入 ETH 到 EntryPoint(标注为 Paymaster 的存款)
  • withdraw():只有 owner 可以将 ETH 从 EntryPoint 提取回 owner 地址

汇率模型:当前使用硬编码汇率 TOKENS_PER_ETH = 2000。这是一个简化——生产 Paymaster 需要集成 Chainlink 或其他预言机来获取实时 ETH/代币汇率。

参考源文件:src/level4/SmartWallet.solsrc/level4/TokenPaymaster.sol

3.4 Bundler 逻辑(概念层)

Bundler 是 ERC-4337 中的关键基础设施组件,负责:

  1. 监听 alt mempool:收集 UserOperation(不同于传统交易 mempool)
  2. 模拟验证:调用 EntryPoint.simulateValidation() 检查每个 UserOperation 是否会成功
  3. Gas 排序:按 Gas 价格(maxPriorityFeePerGas)对 UserOperation 降序排列(优先费高的优先)
  4. 打包提交:调用 EntryPoint.handleOps() 将所有 UserOperation 批量提交
  5. Gas 补偿:Bundler 垫付所有 Gas,然后从 Paymaster 或钱包的 EntryPoint 存款中获得补偿

3.5 UserOperation 数据结构

struct UserOperation {
    address sender;              // 智能钱包地址
    uint256 nonce;               // 防重放计数器
    bytes   initCode;            // 工厂部署代码(新钱包首次操作)
    bytes   callData;            // 要执行的 calldata
    uint256 callGasLimit;        // 执行阶段的 Gas 限制
    uint256 verificationGasLimit;// 验证阶段的 Gas 限制
    uint256 preVerificationGas;  // Bundler 补偿
    uint256 maxFeePerGas;        // EIP-1559 最大费用
    uint256 maxPriorityFeePerGas;// EIP-1559 优先费
    bytes   paymasterAndData;    // paymaster 地址(20B) + 自定义数据
    bytes   signature;           // 钱包签名的 userOpHash
}

4. 遭遇的陷阱

  • 验证和执行阶段的 Gas 分离:ERC-4337 将验证阶段(签名检查、Paymaster 验证)与执行阶段分开,如果验证阶段消耗过多 Gas 导致 verificationGasLimit 超出,整个操作直接失败——且 Bundler 得不到 Gas 补偿
  • Paymaster 的 ETH 存款不足:如果在 _validatePaymasterUserOp 中存款检查失败,Bundler 会被拒绝补偿——但 _validatePaymasterUserOp 本身消耗的 Gas 也无法收回
  • 代币汇率波动TokenPaymaster.sol 使用硬编码汇率 TOKENS_PER_ETH = 2000,如果 ETH/代币价格大幅波动,用户可能支付过多或过少的代币
  • paymasterAndData 解码_decodeMaxTokenCost() 使用内联汇编从 calldata 偏移 20 字节处读取 uint256,如果 paymasterAndData 格式不正确(长度不足 52 字节),会读取到垃圾数据或越界数据
  • nonce 同步问题:Bundler 提交多个 UserOperation 时,如果某个 UserOperation 的 nonce 与其他操作不连续,整个 handleOps 调用可能失败
  • 智能钱包无恢复机制:当前实现只有单一 owner,如果 owner 私钥丢失,钱包中的资金将永久锁定——没有社交恢复或时间锁机制
  • EntryPoint 地址硬编码:如果 EntryPoint 升级(从 v0.6 到 v0.7),钱包和 Paymaster 需要重新部署以指向新的 EntryPoint

5. 陷阱的原因

验证阶段 Gas 管理是 ERC-4337 最特殊的挑战。在传统交易中,Gas 是一个连续的消耗过程。在 ERC-4337 中,验证阶段的 Gas 按 verificationGasLimit 独立计价:Bundler 在执行任何操作前先支付验证 Gas,如果验证失败,Bundler 不仅得不到补偿,还要自己承担验证阶段的 Gas 成本。这创造了一个激励机制问题:Bundler 不愿意接受验证逻辑复杂(高 Gas)的 UserOperation。

更具体地说,_validatePaymasterUserOp 中的 token.allowance()entryPoint.balanceOf() 调用都是外部合约调用,消耗的 Gas 比简单的存储读取多得多。如果 Paymaster 的验证逻辑过于复杂,Bundler 可能因为潜在的亏损而拒绝打包这些 UserOperation。这迫使 Paymaster 开发者在验证效率和功能完整性之间做出权衡。

Paymaster 存款不足是一个连锁故障:如果存款在 _validatePaymasterUserOp 中检查通过,但在实际执行时(可能几秒到几分钟后)因为其他 UserOperation 消耗了存款而变得不足,Bundler 在 _postOp 阶段将无法获得补偿。ERC-4337 通过要求 Paymaster 在 EntryPoint 中持有足够的存款来解决这个问题——存款在验证阶段被"锁定",不会被其他 UserOperation 消耗。

硬编码汇率的危险在于套利:如果 TOKENS_PER_ETH = 2000 但市场价格是 2500,用户可以用低于市场价的代币支付 Gas,Paymaster 承担损失。如果市场价格是 1500,用户需要多付代币,可能导致用户体验恶化。生产 Paymaster 必须使用实时预言机价格。

6. 如何解决陷阱

验证 Gas 优化

  • _validatePaymasterUserOp 中尽量减少外部调用——考虑缓存 entryPoint.balanceOf() 的结果
  • 使用 SLOAD 而非 CALL 来检查白名单等信息
  • 对于复杂验证逻辑,将部分检查移到执行阶段(由钱包合约自行承担)
  • 为 Bundler 设置盈利预期模型——模拟验证后计算补偿是否覆盖成本

汇率预言机集成(生产代码示例):

solidity
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract TokenPaymaster {
    AggregatorV3Interface public tokenPriceFeed; // Token/ETH price

    function getTokenCost(uint256 ethCost) public view returns (uint256) {
        (, int256 price,,,) = tokenPriceFeed.latestRoundData();
        // price 是 token/ETH(缩放 1e8),计算需要多少 token 来支付 ethCost
        return (ethCost * uint256(price)) / 1e8;
    }
}

nonce 管理改进:使用 ERC-4337 的 key-based nonce 管理(EntryPoint v0.7 支持二维 nonce:(key, sequence))。这允许多个独立的 nonce 序列,支持并发 UserOperation:

钱包恢复机制:在 SmartWallet 中添加 owner 转移功能(通过 EntryPoint 或 owner 自身调用):

solidity
function transferOwnership(address newOwner) external {
    require(msg.sender == owner || msg.sender == entryPoint, "Not authorized");
    owner = newOwner;
}

更高级的实现可以添加社交恢复模块,允许多个 guardian 通过投票来替换 owner。

存款监控:构建自动化存款补充脚本——当 Paymaster 在 EntryPoint 中的余额低于安全阈值时自动调用 deposit()

javascript
async function monitorPaymasterDeposit(paymaster, entryPoint, minBalance) {
  const balance = await entryPoint.balanceOf(paymaster.address);
  if (balance < minBalance) {
    await paymaster.deposit({ value: topUpAmount });
  }
}

7. 技术要点

技术点说明
ERC-4337账户抽象标准,UserOperation 替代传统交易
EntryPoint全局单例合约,所有 EVM 链同一地址 (CREATE2)
UserOperation包含 sender/nonce/callData/signature/paymasterAndData
签名验证 (_validateSignature)由 EntryPoint 在验证阶段调用,返回 0=成功 1=失败
批量执行 (executeBatch)原子化批量调用,全成功或全回滚
Paymaster代付 Gas,用户可以用 ERC-20 代币支付
验证→执行→后执行三阶段流水线:validate → execute → postOp
验证阶段 GasverificationGasLimit 独立计价,失败则 Bundler 承担
paymasterAndData20 字节 Paymaster 地址 + 自定义数据(如 maxTokenCost)
EntryPoint 存款Paymaster 在 EntryPoint 中存入 ETH,用于补偿 Bundler
PostOpModeopSucceeded/opReverted/postOpReverted,控制后执行扣费
ECDSA 签名恢复v 值规范化(v<27 则 +27),ecrecover 预编译

Built with AiAda