跳转到内容
123xiao | 无名键客

《Web3 中级实战:基于 EIP-4337 实现智能账户与 Gas 代付的钱包接入方案》

字数: 0 阅读时长: 1 分钟

Web3 中级实战:基于 EIP-4337 实现智能账户与 Gas 代付的钱包接入方案

EIP-4337 这两年几乎成了“钱包体验升级”的代名词。很多团队想做的事情其实很朴素:让用户别再一上来就被 Gas、助记词、原生币充值这些问题劝退。而 EIP-4337 提供了一条不改共识层、能渐进接入的路线:把账户逻辑从传统 EOA 外置账户,迁移到更可编程的智能账户(Smart Account)。

这篇文章我不打算只讲概念,而是从接入方视角来做一遍:
你会看到一个最小可运行方案,完成这几件事:

  • 创建智能账户
  • 通过 UserOperation 发起交易
  • 使用 Paymaster 实现 Gas 代付
  • 理解 Bundler / EntryPoint / Factory 的协作关系
  • 排查最常见的 4337 接入问题

如果你已经会写基础 Solidity,了解 ethers.js,并且知道 ERC-20 / 合约调用是怎么回事,这篇内容会比较适合你。


前置知识

在开始之前,先确认你至少熟悉这些概念:

  • EOA 与合约账户的区别
  • Solidity 基础合约开发
  • ethers.js 基本调用
  • Ethereum 交易的 gas、nonce、签名
  • JSON-RPC 的基本使用

如果你没接触过 EIP-4337,也没关系,我会先把它拆开讲。


背景与问题

传统 Web3 钱包体验,问题经常卡在第一步:

  1. 用户必须持有原生币(如 ETH)支付 Gas
  2. 新用户要先创建 EOA,再妥善保存助记词
  3. 钱包逻辑固定,难以做权限控制、社交恢复、批量执行
  4. dApp 接入时,常常只能“让用户自己处理链上细节”

这些限制不是产品想象力不够,而是EOA 天生功能有限
EOA 本质上就是“由私钥控制的账户”,签什么、发什么,全靠外部客户端拼交易。

而智能账户的思路是:
把账户本身做成一个合约,让验证规则、执行逻辑、权限体系都变成可编程。

EIP-4337 的价值就在这里:
它不要求你修改以太坊底层协议,而是在应用层引入一套新交易流程,让“账户抽象”能先跑起来。


核心原理

先给一个全景图。

flowchart LR
    U[用户 / 钱包前端] --> A[Smart Account SDK]
    A --> B[UserOperation]
    B --> C[Bundler]
    C --> D[EntryPoint]
    D --> E[Smart Account]
    D --> F[Paymaster]
    E --> G[目标合约]

1. 关键角色

Smart Account

一个智能合约账户。
它不再像 EOA 那样只能由私钥直接发交易,而是由合约中的验证逻辑决定:谁能签、怎么验、能不能批量执行、是否允许 session key 等。

UserOperation

4337 不是直接发传统交易,而是提交一个 UserOperation
你可以把它理解成“用户意图 + 执行参数 + 验证材料”的结构体。

Bundler

Bundler 类似“4337 交易打包服务”。
它收集多个 UserOperation,调用 EntryPoint.handleOps() 上链。

EntryPoint

4337 的核心入口合约。
负责统一校验与执行 UserOperation

Paymaster

Gas 代付服务方。
它可以为用户支付 gas,常见场景有:

  • 新用户免首笔 Gas
  • 平台代付
  • 用 ERC-20 代替原生币支付费用
  • 风控后有条件放行

Factory

用于按需部署智能账户。
很多实现会用“Counterfactual Address”思路:用户账户地址先算出来,第一次操作时再真正部署。


2. 一次调用是怎么发生的

sequenceDiagram
    participant User as 用户
    participant App as dApp/前端
    participant SDK as 4337 SDK
    participant PM as Paymaster
    participant Bundler as Bundler
    participant EP as EntryPoint
    participant SA as Smart Account
    participant Target as 目标合约

    User->>App: 点击发起操作
    App->>SDK: 构造 UserOperation
    SDK->>PM: 请求 paymasterData
    PM-->>SDK: 返回签名/额度许可
    SDK->>Bundler: eth_sendUserOperation
    Bundler->>EP: handleOps()
    EP->>SA: validateUserOp()
    EP->>PM: validatePaymasterUserOp()
    EP->>SA: execute()
    SA->>Target: 调用目标合约
    Bundler-->>App: 返回 userOpHash

3. 验证与执行分离

EIP-4337 的一个重要思想是:
先验证,再执行。

  • 验证阶段:校验签名、nonce、余额、Paymaster 资助资格等
  • 执行阶段:真正调用账户或目标合约

这带来的好处是:

  • 钱包逻辑可以灵活定义
  • Gas 赞助逻辑可以单独扩展
  • Bundler 能提前模拟,降低失败率

环境准备

下面我给一个“最小可运行”的示例工程思路。为了尽量贴近真实接入,我采用:

  • Solidity:实现简单智能账户、Factory、Paymaster
  • Node.js + ethers:构造并发送调用
  • Hardhat:本地开发与测试

依赖安装

mkdir eip4337-smart-wallet-demo
cd eip4337-smart-wallet-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npm install ethers dotenv

初始化 Hardhat:

npx hardhat

选择一个基础 JavaScript 项目即可。


实战代码(可运行)

说明:
为了让示例更容易理解,下面的代码实现的是简化版智能账户模型
它体现的是接入思路与关键机制,不是生产级完整 4337 钱包。
真正接入主网或测试网时,建议基于成熟实现,例如 ZeroDev、Biconomy、Stackup、Alchemy AA SDK,或者参考 eth-infinitism 的账户实现。


第一步:实现一个最小智能账户

新建 contracts/SimpleSmartAccount.sol

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

contract SimpleSmartAccount {
    address public owner;
    uint256 public nonce;
    address public entryPoint;

    event Executed(address indexed target, uint256 value, bytes data);
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);

    modifier onlyEntryPoint() {
        require(msg.sender == entryPoint, "only entrypoint");
        _;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "only owner");
        _;
    }

    constructor(address _owner, address _entryPoint) {
        owner = _owner;
        entryPoint = _entryPoint;
    }

    function validateUserOp(
        bytes32 userOpHash,
        bytes calldata signature,
        uint256 expectedNonce
    ) external view onlyEntryPoint returns (bool) {
        require(expectedNonce == nonce, "bad nonce");

        bytes32 ethSigned = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", userOpHash)
        );

        return recoverSigner(ethSigned, signature) == owner;
    }

    function execute(
        address target,
        uint256 value,
        bytes calldata data
    ) external onlyEntryPoint {
        nonce++;

        (bool ok, ) = target.call{value: value}(data);
        require(ok, "call failed");

        emit Executed(target, value, data);
    }

    function executeByOwner(
        address target,
        uint256 value,
        bytes calldata data
    ) external onlyOwner {
        nonce++;

        (bool ok, ) = target.call{value: value}(data);
        require(ok, "call failed");

        emit Executed(target, value, data);
    }

    function changeOwner(address newOwner) external onlyOwner {
        require(newOwner != address(0), "zero addr");
        emit OwnerChanged(owner, newOwner);
        owner = newOwner;
    }

    receive() external payable {}

    function recoverSigner(bytes32 hash, bytes memory sig) internal pure returns (address) {
        require(sig.length == 65, "bad sig length");

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }

        if (v < 27) {
            v += 27;
        }

        require(v == 27 || v == 28, "bad v");
        return ecrecover(hash, v, r, s);
    }
}

这个账户做了三件事:

  • 记录 owner
  • 校验签名和 nonce
  • entryPoint 触发执行

这里我保留了 executeByOwner(),便于本地验证。生产里通常会更加严格。


第二步:实现账户工厂

新建 contracts/SimpleAccountFactory.sol

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

import "./SimpleSmartAccount.sol";

contract SimpleAccountFactory {
    event AccountCreated(address indexed owner, address account);

    function createAccount(address owner, address entryPoint) external returns (address) {
        SimpleSmartAccount account = new SimpleSmartAccount(owner, entryPoint);
        emit AccountCreated(owner, address(account));
        return address(account);
    }
}

Factory 的作用很直观:帮你部署账户。


第三步:实现一个简化 EntryPoint

新建 contracts/SimpleEntryPoint.sol

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

interface ISimpleSmartAccount {
    function validateUserOp(
        bytes32 userOpHash,
        bytes calldata signature,
        uint256 expectedNonce
    ) external view returns (bool);

    function execute(
        address target,
        uint256 value,
        bytes calldata data
    ) external;
}

interface ISimplePaymaster {
    function validateSponsor(
        address sender,
        address target,
        bytes calldata data
    ) external view returns (bool);

    function postOp(address bundler, uint256 gasCost) external;
}

contract SimpleEntryPoint {
    struct UserOperation {
        address sender;
        address target;
        uint256 value;
        bytes data;
        uint256 nonce;
        bytes signature;
        address paymaster;
    }

    event UserOperationHandled(bytes32 indexed userOpHash, address indexed sender, bool success);

    function getUserOpHash(UserOperation calldata op) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                op.sender,
                op.target,
                op.value,
                keccak256(op.data),
                op.nonce,
                op.paymaster
            )
        );
    }

    function handleOp(UserOperation calldata op) external {
        bytes32 userOpHash = getUserOpHash(op);

        bool valid = ISimpleSmartAccount(op.sender).validateUserOp(
            userOpHash,
            op.signature,
            op.nonce
        );
        require(valid, "invalid userop");

        if (op.paymaster != address(0)) {
            bool sponsorOk = ISimplePaymaster(op.paymaster).validateSponsor(
                op.sender,
                op.target,
                op.data
            );
            require(sponsorOk, "paymaster rejected");
        }

        ISimpleSmartAccount(op.sender).execute(op.target, op.value, op.data);

        if (op.paymaster != address(0)) {
            ISimplePaymaster(op.paymaster).postOp(msg.sender, 0);
        }

        emit UserOperationHandled(userOpHash, op.sender, true);
    }
}

这里我们模拟了最核心的流程:

  • 校验签名
  • 可选调用 Paymaster 审核
  • 执行目标调用
  • 触发 postOp

注意:真实 EIP-4337 的 EntryPoint 比这个复杂很多,包含 gas accounting、prefund、simulation、aggregator 等。这个版本的目标是“把机制跑通”。


第四步:实现一个最小 Paymaster

新建 contracts/SimplePaymaster.sol

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

contract SimplePaymaster {
    address public owner;
    mapping(address => bool) public whitelist;

    event Sponsored(address indexed sender, address indexed bundler, uint256 gasCost);

    modifier onlyOwner() {
        require(msg.sender == owner, "only owner");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function setWhitelist(address user, bool allowed) external onlyOwner {
        whitelist[user] = allowed;
    }

    function validateSponsor(
        address sender,
        address,
        bytes calldata
    ) external view returns (bool) {
        return whitelist[sender];
    }

    function postOp(address bundler, uint256 gasCost) external {
        emit Sponsored(tx.origin, bundler, gasCost);
    }
}

这个 Paymaster 做得很简单:
白名单用户可以被代付。

真实业务里,你可能会加这些策略:

  • 每日额度
  • 首笔免 Gas
  • 仅允许指定合约调用
  • ERC-20 扣费结算
  • 风控签名校验

第五步:部署一个目标合约用于验证

新建 contracts/Counter.sol

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

contract Counter {
    uint256 public number;

    event Increased(uint256 newNumber);

    function increment() external {
        number += 1;
        emit Increased(number);
    }
}

第六步:编写部署脚本

新建 scripts/deploy.js

const hre = require("hardhat");

async function main() {
  const [deployer, user] = await hre.ethers.getSigners();

  const EntryPoint = await hre.ethers.getContractFactory("SimpleEntryPoint");
  const entryPoint = await EntryPoint.deploy();
  await entryPoint.waitForDeployment();

  const Factory = await hre.ethers.getContractFactory("SimpleAccountFactory");
  const factory = await Factory.deploy();
  await factory.waitForDeployment();

  const Paymaster = await hre.ethers.getContractFactory("SimplePaymaster");
  const paymaster = await Paymaster.deploy();
  await paymaster.waitForDeployment();

  const Counter = await hre.ethers.getContractFactory("Counter");
  const counter = await Counter.deploy();
  await counter.waitForDeployment();

  const tx = await factory.createAccount(user.address, await entryPoint.getAddress());
  await tx.wait();

  console.log("deployer:", deployer.address);
  console.log("user:", user.address);
  console.log("entryPoint:", await entryPoint.getAddress());
  console.log("factory:", await factory.getAddress());
  console.log("paymaster:", await paymaster.getAddress());
  console.log("counter:", await counter.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

执行:

npx hardhat compile
npx hardhat run scripts/deploy.js

第七步:构造并提交一个“用户操作”

为了方便本地演示,我们不引入真正 Bundler,而是直接由一个脚本模拟 Bundler 调用 EntryPoint.handleOp()

新建 scripts/sendUserOp.js

const hre = require("hardhat");
const { ethers } = hre;

async function main() {
  const [bundler, user] = await ethers.getSigners();

  const entryPointAddr = process.env.ENTRY_POINT;
  const accountAddr = process.env.ACCOUNT;
  const counterAddr = process.env.COUNTER;
  const paymasterAddr = process.env.PAYMASTER;

  const entryPoint = await ethers.getContractAt("SimpleEntryPoint", entryPointAddr);
  const counter = await ethers.getContractAt("Counter", counterAddr);
  const paymaster = await ethers.getContractAt("SimplePaymaster", paymasterAddr);

  // 白名单代付
  let tx = await paymaster.connect(bundler).setWhitelist(accountAddr, true);
  await tx.wait();

  const data = counter.interface.encodeFunctionData("increment");

  const userOp = {
    sender: accountAddr,
    target: counterAddr,
    value: 0,
    data: data,
    nonce: 0,
    signature: "0x",
    paymaster: paymasterAddr,
  };

  const userOpHash = await entryPoint.getUserOpHash(userOp);

  const signature = await user.signMessage(ethers.getBytes(userOpHash));
  userOp.signature = signature;

  tx = await entryPoint.connect(bundler).handleOp(userOp);
  const receipt = await tx.wait();

  const number = await counter.number();

  console.log("userOpHash:", userOpHash);
  console.log("txHash:", receipt.hash);
  console.log("counter.number:", number.toString());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

这里有一个关键细节:
SimpleSmartAccount 的 owner 必须是 user.address,而不是 accountAddr 本身。所以创建账户时,owner 应该传入用户 EOA 地址。


第八步:拿到账户地址并执行

由于上面的 Factory 没有直接返回部署结果到脚本变量,实际操作中最方便的方法是:

  1. 从部署日志或链上事件里取出账户地址
  2. 设置环境变量

创建 .env

ENTRY_POINT=0xYourEntryPointAddress
ACCOUNT=0xYourSmartAccountAddress
COUNTER=0xYourCounterAddress
PAYMASTER=0xYourPaymasterAddress

执行:

npx hardhat run scripts/sendUserOp.js

如果一切正常,你会看到:

userOpHash: 0x...
txHash: 0x...
counter.number: 1

这就表示:

  • 用户没有直接发传统交易
  • 而是签了一个 UserOperation
  • 由 EntryPoint 统一执行
  • Paymaster 参与了“代付许可”

接入真实 4337 SDK 的思路

上面的示例偏“机制演示”。如果你要给产品做正式钱包接入,通常不会自己从零实现 EntryPoint,而是走成熟基础设施。

更现实的架构通常长这样:

flowchart TD
    FE[前端 dApp] --> SDK[4337 钱包 SDK]
    SDK --> RPC[普通 RPC 节点]
    SDK --> BDL[Bundler API]
    SDK --> PM[Paymaster API]
    BDL --> EP[官方 EntryPoint]
    EP --> SA[智能账户实现]
    PM --> EP

前端典型接入步骤

  1. 用户登录
    • EOA
    • 社交登录
    • MPC / Passkey
  2. 生成或绑定 smart account
  3. 构造目标调用数据
  4. 请求 Paymaster sponsorship
  5. 发送 eth_sendUserOperation
  6. 轮询 userOpHash 执行状态
  7. 在 UI 中展示“已提交 / 打包中 / 已上链 / 失败原因”

用 SDK 的伪代码示例

下面这个例子是抽象写法,用来帮助你理解接口层次,不绑定特定厂商:

import { ethers } from "ethers";

async function sendWith4337({
  provider,
  ownerSigner,
  smartAccount,
  bundlerClient,
  paymasterClient,
  target,
  data,
}) {
  const nonce = await smartAccount.getNonce();

  let userOp = {
    sender: await smartAccount.getAddress(),
    callData: await smartAccount.encodeExecute(target, 0, data),
    nonce,
    callGasLimit: 300000,
    verificationGasLimit: 200000,
    preVerificationGas: 50000,
    maxFeePerGas: ethers.parseUnits("20", "gwei"),
    maxPriorityFeePerGas: ethers.parseUnits("1", "gwei"),
    paymasterAndData: "0x",
    signature: "0x",
  };

  const sponsored = await paymasterClient.sponsorUserOperation(userOp);
  userOp.paymasterAndData = sponsored.paymasterAndData;

  const userOpHash = await smartAccount.getUserOpHash(userOp);
  userOp.signature = await ownerSigner.signMessage(ethers.getBytes(userOpHash));

  const res = await bundlerClient.sendUserOperation(userOp);
  return res.userOpHash;
}

真正接入时,你要重点看 SDK 是否封装了以下能力:

  • 自动估算 gas
  • 自动构造 initCode
  • 多链 EntryPoint 管理
  • paymasterAndData 填充
  • session key / batched calls
  • userOp 状态查询

逐步验证清单

我建议你按这个顺序验,不容易乱:

验证 1:目标合约能被普通交易调用

先确认 Counter.increment() 本身没问题。

验证 2:智能账户 owner 配置正确

最常见错误就是部署账户时 owner 传错,导致签名永远过不了。

验证 3:userOpHash 前后端计算一致

如果前端签的是 A,合约验的是 B,那一定失败。

验证 4:nonce 从 0 开始、单调递增

重复 nonce 会直接失败。

验证 5:Paymaster 放行逻辑正确

白名单、调用目标、额度限制要逐项验证。

验证 6:最终目标调用 data 编码正确

很多失败看起来像签名错,实际上是 abi.encodeFunctionData 编错了。


常见坑与排查

这部分非常重要。我自己第一次调 4337 时,真正花时间的地方不是“写代码”,而是“到底哪一步没对上”。

1. 签名正确但仍然校验失败

现象

validateUserOp() 返回 false,或者 EntryPoint 报 invalid userop

常见原因

  • 用了不同的 userOpHash 计算方式
  • signMessage() 自动加了 EIP-191 前缀,但合约没按同样方式恢复
  • nonce 不一致
  • owner 地址不是实际签名人

排查建议

  • 把合约侧 userOpHash 打印出来
  • 把脚本侧 hash 打印出来
  • 明确是否使用 eth_sign / personal_sign / EIP-712
  • 用最简单签名路径先跑通,再升级到 typed data

2. Paymaster 明明白名单了,但还是拒绝

现象

报错 paymaster rejected

常见原因

  • 你白名单的是 EOA,但 sender 实际上是 Smart Account 地址
  • Paymaster 策略限制了目标合约或 calldata
  • 前端构造 paymasterAndData 时数据版本不对

排查建议

  • 明确 sponsor 的对象是谁:owner 还是 smart account
  • 把校验条件拆开记录日志
  • 先去掉复杂风控,只保留白名单判断

这个坑我很想强调:
4337 里的 sender 是智能账户地址,不是用户 EOA。
很多团队第一次做代付时,白名单配错对象,结果怎么看都“不应该失败”。


3. Bundler 模拟通过,但链上执行失败

现象

本地/预执行正常,上链却 revert

常见原因

  • 链上状态变化,模拟时的条件失效
  • gas 估算不足
  • Paymaster 额度在两个区块间被其他请求消耗
  • 目标合约内部有依赖 msg.senderblock.timestamp、余额等敏感逻辑

排查建议

  • 记录 simulation 时的区块号
  • 给关键逻辑留更保守的 gas buffer
  • 避免 sponsor 额度被并发抢占
  • 检查目标合约是否假定调用方一定是 EOA

4. 智能账户首次使用时部署失败

现象

第一次发操作失败,后续地址也不对

常见原因

  • initCode 编码不对
  • Factory 地址错
  • create2 salt 不一致
  • 预计算地址和实际部署参数不一致

排查建议

  • 先不做 counterfactual,直接部署一个账户跑通
  • 再加入 Factory + initCode
  • 每一步都对比预期地址和链上地址

安全/性能最佳实践

4337 的灵活性很强,但也意味着“你可以很容易把系统做复杂”。下面这些建议比较实用。

安全最佳实践

1. 不要自己魔改官方 EntryPoint 语义

生产环境建议尽量贴近主流实现。
EntryPoint 是整个安全边界的核心,随意改动会让 SDK、Bundler、审计经验全部失效。

2. 签名域必须明确

推荐逐步升级到更规范的签名方案,例如 EIP-712。
简单的 signMessage 虽然便于演示,但生产中容易出现跨链、跨合约重放风险。

3. nonce 设计不要过于单一

如果账户支持批量操作、并行 session、插件执行,单一 nonce 很快会成为性能瓶颈。
可以考虑 key-based nonce 或多维 nonce 结构。

4. Paymaster 必须做额度与目标限制

不要只凭“用户在白名单”就无限代付。
至少限制:

  • 每日/每周额度
  • 允许调用的合约范围
  • 允许的方法签名
  • 单笔 gas 上限
  • 黑名单风控

5. 谨慎开放 execute

账户合约里如果执行入口过于宽松,很容易被构造恶意调用。
建议:

  • 所有入口都走统一验证
  • 明确只允许 EntryPoint 触发
  • 变更 owner / guardian 的操作单独做更严格保护

性能最佳实践

1. 减少验证阶段的存储读取

validateUserOp() 越重,Bundler 越不喜欢,gas 也越高。
尽量减少复杂逻辑和外部调用。

2. 批量操作适合合并

智能账户很适合做 batched calls。
如果用户一次要授权 + swap + stake,尽量合并,提升体验并减少总成本。

3. 代付审核尽量前置到链下

Paymaster 很多风控应在链下完成,只把必要校验信息上链。
否则 sponsor 成本和复杂度都会膨胀。

4. 为失败设计观测性

至少打通这些日志:

  • userOpHash
  • sender
  • paymaster decision
  • simulation result
  • bundler response
  • on-chain revert reason

没有这些日志时,4337 问题真的很难追。


方案落地建议

如果你是 dApp 团队,不一定要自己造整套钱包基础设施。更现实的分层思路是:

适合自研的部分

  • 钱包产品体验
  • 智能账户权限模型
  • Paymaster 赞助策略
  • 风控与计费体系

适合复用基础设施的部分

  • Bundler 服务
  • 标准 EntryPoint 对接
  • 多链 gas 估算
  • userOp 状态追踪
  • 兼容性测试

一个常见组合

  • 前端:自定义登录和交易体验
  • 智能账户:基于成熟实现扩展权限
  • Bundler:采购现成服务
  • Paymaster:自研,绑定你的业务规则

这通常是成本、风险、可控性比较均衡的路线。


总结

EIP-4337 最值得你抓住的,不是“它很新”,而是它真正解决了钱包接入中的几个老问题:

  • 用户不必先准备原生币
  • 账户能力从固定变成可编程
  • dApp 可以更主动地设计交易体验
  • Gas 代付、批量执行、权限分级都有了统一入口

这篇文章我们完成了一个从零到一的最小实践:

  1. 写了一个简化版 Smart Account
  2. 用 EntryPoint 模拟 4337 调度流程
  3. 用 Paymaster 做白名单式 Gas 代付
  4. 用脚本构造并执行 UserOperation
  5. 复盘了接入中最容易踩的坑

如果你接下来准备上真实测试网,我的建议是:

  • 先用成熟 SDK 跑通端到端流程
  • 再把 Paymaster 策略替换成你的业务规则
  • 最后再考虑账户权限扩展,比如 session key、批量执行、恢复机制

边界条件也要明确:

  • 如果你的产品只需要普通签名和简单转账,4337 不一定是第一优先级
  • 如果你关注新用户转化、免 Gas、复杂权限,4337 非常值得投入
  • 如果你要上生产,不要把本文的简化合约直接用于主网

一句话总结:
EIP-4337 不是单纯换一种钱包,而是在把“账户”变成你的产品能力。

如果你正在做钱包、游戏、社交、交易聚合器,越早理解这一点,后面的架构选择就越不容易走弯路。


分享到:

上一篇
《前端性能实战:基于 Web Vitals 的首屏加载优化与排查方案》
下一篇
《从源码到部署:用 Docker Compose 搭建并二次开发一套开源日志采集与分析平台实战》