Skip to Content
指南主权账户抽象指南 (ERC-4337)

去神秘化 ERC-4337:构建无专有 WaaS 锁定的自主智能账户

账户抽象(ERC-4337)极大改善了 Web3 用户体验,但商业化 WaaS 厂商带来了供应商锁定与闭源黑盒风险。


1. 账户抽象的光谱:签名者 vs 智能账户

┌────────────────────────────────────────────────────────────────────────┐│ Decoupled Account Abstraction Model │├───────────────────────────────────┬────────────────────────────────────┤│ Signer (Cryptographic Authority) │ Smart Account (On-Chain Execution) │├───────────────────────────────────┼────────────────────────────────────┤│ • Local Secp256k1 EOA Key │ • Solady ERC-4337 (Single Owner) ││ • Hardware Wallet (Ledger/Trezor) │ • ERC-7579 Modular Account (Kernel)││ • SSS / MPC Key Share (Non-cust.) │ • Atomic Multi-Call Batching ││ • WebAuthn P-256 (via Verifier) │ • Canonical EntryPoint v0.7 ││ • Sole Job: Sign arbitrary bytes │ • Asset Custody & Nonce Validation │└───────────────────────────────────┴────────────────────────────────────┘

解构 WaaS 的权衡陷阱

签名者负责提供密码学凭证,智能账户负责链上资产托管与执行验证。


2. 两阶段生命周期:userOpHash 与 txHash

双标识符两难困境

  • userOpHash:内存池意图凭据,向 Bundler RPC 轮询;
  • txHash:最终链上结算凭据,向标准以太坊节点或区块浏览器查询。

朴素前端的三个生产级崩溃陷阱

1. RPC 节点轮询与 Bundler 内存池不匹配

标准 RPC 节点无法识别 userOpHash,强行轮询会导致超时报错并丢失执行状态追踪。

2. Bundle 调用数据混淆

多个 UserOperation 被合并至单一 handleOps 批处理事务中,导致前端难以定位具体单笔操作。

3. EntryPoint.handleOps 内部隐式执行回滚

外层 Bundler 交易在链上执行成功(status 1),但合约内部的实际调用发生回滚,造成虚假成功提示。


3. 智能账户架构:Solady vs ERC-7579

┌────────────────────────────────────────────────────────────────────────┐│ Smart Account Architectural Matrix │├───────────────────────────────────┬────────────────────────────────────┤│ Solady ERC4337.sol (Minimalist) │ ERC-7579 (Modular Standard) │├───────────────────────────────────┼────────────────────────────────────┤│ • Pure Yul Assembly implementation│ • Modular Proxy (Kernel v3, Safe) ││ • Lowest possible deployment gas │ • Dynamic Module Installation ││ • Minimal runtime overhead │ • Native Session Keys & Subscriptions││ • Strict Single-Owner (secp256k1) │ • Multi-Validator (WebAuthn, ECDSA)││ • Immutably fixed logic │ • Social Recovery & Spending Limits│└───────────────────────────────────┴────────────────────────────────────┘

Solady 与 Passkeys 的密码学现实

极简高效推荐 Solady;动态插件与原生生物识别 Passkey 推荐模块化 ERC-7579 规范。


4. 方案落地:标准 Viem AA vs TUWA 栈

4.1 方案 A:标准原生 Viem 链下编排

// src/services/canonicalSmartAccount.tsimport { http, createPublicClient, encodeFunctionData, parseUnits, erc20Abi, pad, hexToBigInt, type Hex } from 'viem';import { sepolia } from 'viem/chains';import { createBundlerClient, createPaymasterClient, toSoladySmartAccount, type BundlerClient, type ToSoladySmartAccountReturnType,} from 'viem/account-abstraction';import { toAccount } from 'viem/accounts';import type { WalletClient, HttpTransport } from 'viem'; /** * Initializes a Solady smart account and binds a canonical Viem Bundler client * with native Pimlico paymaster sponsorship. */export async function initializeCanonicalAccount(walletClient: WalletClient) { const apiKey = process.env.NEXT_PUBLIC_PIMLICO_API_KEY; if (!apiKey) throw new Error('Missing NEXT_PUBLIC_PIMLICO_API_KEY'); const bundlerRpcUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`; const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); if (!walletClient.account) { throw new Error('WalletClient must have an active account.'); } const ownerAccount = walletClient.account; // 1. Wrap the JSON-RPC account to ensure signing delegation methods are available const owner = toAccount({ address: ownerAccount.address, async signMessage({ message }) { return walletClient.signMessage({ account: ownerAccount, message }); }, async signTransaction(tx) { return walletClient.signTransaction({ account: ownerAccount, ...tx } as any); }, async signTypedData(typedData) { return walletClient.signTypedData({ account: ownerAccount, ...typedData } as any); }, }); // 2. Compute Solady factory-compliant deterministic salt: // Solady's ERC4337Factory packs the owner address directly into the top 160 bits of ownSalt (extracted via shr(96, ownSalt)). const salt = pad(ownerAccount.address, { dir: 'right', size: 32 }); // 3. Create Solady Smart Account with explicit owner delegation and salt const account = await toSoladySmartAccount({ client: publicClient, owner, salt, }); // 4. Instantiate Viem Paymaster Client configured for Pimlico const paymasterClient = createPaymasterClient({ transport: http(bundlerRpcUrl), }); // 5. Instantiate canonical Viem Bundler Client with paymaster sponsorship const bundlerClient = createBundlerClient({ account, client: publicClient, transport: http(bundlerRpcUrl), paymaster: paymasterClient, paymasterContext: { sponsorshipPolicyId: 'sp_my_policy_id', // ERC-7677 / Pimlico sponsorship policy }, // Pimlico refuses UserOps priced at the chain's fees ("maxFeePerGas must be at least …"): price them at its own // gas price userOperation: { estimateFeesPerGas: async ({ bundlerClient }) => { const { fast } = (await bundlerClient.request({ method: 'pimlico_getUserOperationGasPrice' } as never)) as { fast: { maxFeePerGas: Hex; maxPriorityFeePerGas: Hex }; }; return { maxFeePerGas: hexToBigInt(fast.maxFeePerGas), maxPriorityFeePerGas: hexToBigInt(fast.maxPriorityFeePerGas), }; }, }, }); return { account, bundlerClient, publicClient, paymasterClient };} /** * Dispatches a UserOp, tracks the mempool stage, and verifies internal execution success. */export async function executeCanonicalTransfer({ bundlerClient, account, tokenAddress, recipient, amount,}: { bundlerClient: BundlerClient<HttpTransport>; account: ToSoladySmartAccountReturnType; tokenAddress: `0x${string}`; recipient: `0x${string}`; amount: string;}) { const callData = encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [recipient, parseUnits(amount, 18)], }); // 1. Dispatch UserOperation to Pimlico Bundler const userOpHash = await bundlerClient.sendUserOperation({ account, calls: [{ to: tokenAddress, value: 0n, data: callData }], }); // 2. Poll eth_getUserOperationReceipt on Bundler RPC (Stage 1 tracking) const userOpReceipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash, pollingInterval: 1_500, timeout: 60_000, }); // 3. Prevent silent execution revert: verify inner success flag if (!userOpReceipt.success) { const reason = userOpReceipt.reason || 'UserOperation reverted inside EntryPoint.handleOps'; throw new Error(`Execution reverted: ${reason}`); } // 4. Capture canonical settlement hash for Stage 2 const settledTxHash = userOpReceipt.receipt.transactionHash; const explorerUrl = `https://sepolia.etherscan.io/tx/${settledTxHash}`; return { userOpHash, settledTxHash, explorerUrl };}

4.2 方案 B:TUWA 开源工具链组合

┌────────────────────────────────────────────────────────────────────────┐│ The Open-Source TUWA Stack │├────────────────────────────────────────────────────────────────────────┤│ • @tuwaio/orbit-evm : Bundler/Paymaster caching, Solady salt padding ││ • @tuwaio/pulsar-core : Headless, framework-agnostic transaction FSM ││ • @tuwaio/pulsar-evm : Native Two-Stage tracker (Mempool ➔ Settlement)││ • @tuwaio/nova-uikit : Unstyled / Tailwind presentation layer │└────────────────────────────────────────────────────────────────────────┘

步骤 1:使用 Orbit EVM 编排智能账户

初始化封装了 ERC-4337 标准以及 Pimlico Paymaster 赞助能力的智能账户客户端:

// src/services/orbitSmartAccount.tsimport { createPimlicoSmartAccountClient } from '@tuwaio/orbit-evm';import { sepolia } from 'viem/chains';import type { Config } from '@wagmi/core'; export async function getOrbitSmartAccountClient(wagmiConfig: Config) { // Resolves walletClient from Wagmi, attaches cached Pimlico Paymaster, // and configures EntryPoint v0.7 compliant Bundler client const { account, bundlerClient, publicClient, paymasterClient } = await createPimlicoSmartAccountClient({ chain: sepolia, wagmiConfig, apiKey: process.env.NEXT_PUBLIC_PIMLICO_API_KEY, sponsor: true, }); return { account, bundlerClient, publicClient, paymasterClient };}

步骤 2:使用 Pulsar 进行响应式两阶段追踪

在生产前端中,将纯粹的无状态链上事务逻辑与响应式 React UI 分发清晰解耦:

部分 A:纯事务动作执行 (incrementPimlico.ts)

此动作函数保持完全无头(headless)、纯粹且与 UI 状态解耦。它通过 Orbit 初始化智能客户端,编码 calldata,并将 UserOperation 发送至 bundler:

// src/transactions/evm/incrementPimlico.tsimport { createPimlicoSmartAccountClient } from '@tuwaio/orbit-evm';import { type Config } from '@wagmi/core';import { encodeFunctionData, type Hex } from 'viem';import { sepolia } from 'viem/chains'; import { CounterAbi } from '@/abis/CounterAbi';import { COUNTER_ADDRESS } from '@/constants'; /** * Executes a counter increment via an ERC-4337 UserOperation using a Pimlico bundler. * Returns the raw userOpHash for lifecycle tracking. */export async function incrementPimlico({ wagmiConfig }: { wagmiConfig?: Config }): Promise<Hex | undefined> { if (!wagmiConfig) return undefined; // 1. Initialize Solady Smart Account & Bundler client via Orbit EVM const { account, bundlerClient } = await createPimlicoSmartAccountClient({ chain: sepolia, wagmiConfig, apiKey: process.env.NEXT_PUBLIC_PIMLICO_API_KEY, rpcUrl: process.env.NEXT_PUBLIC_ALCHEMY_KEY ? `https://eth-sepolia.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_KEY}` : undefined, }); // 2. Encode contract calldata const data = encodeFunctionData({ abi: CounterAbi, functionName: 'increment', }); // 3. Dispatch UserOperation to alternative mempool and return userOpHash return bundlerClient.sendUserOperation({ account, calls: [ { to: COUNTER_ADDRESS, data, value: 0n, }, ], });}
部分 B:响应式 UI 组件派发 (CounterButton.tsx)

在 React 视图层,组件使用 Hook 选择器连接到中心 Pulsar Store(usePulsarStore((state) => state.executeTxAction))。它以生命周期元数据、自动追踪器选择(TransactionTracker.ERC4337)和四状态用户通知派发该动作:

// src/components/evm/CounterButton.tsx'use client'; import { OrbitAdapter } from '@tuwaio/orbit-core';import { TransactionTracker } from '@tuwaio/pulsar-core';import { useConfig } from 'wagmi';import { sepolia } from 'viem/chains'; import { usePulsarStore } from '@/hooks/pulsarStoreHook';import { incrementPimlico } from '@/transactions/evm/incrementPimlico';import { COUNTER_ADDRESS } from '@/constants'; export const CounterButton = () => { const wagmiConfig = useConfig(); // Select the action dispatcher from the centralized Pulsar store const executeTxAction = usePulsarStore((state) => state.executeTxAction); const handleIncrement = async () => { await executeTxAction({ actionFunction: () => incrementPimlico({ wagmiConfig }), onSuccess: (tx) => { console.log('ERC-4337 UserOperation confirmed on-chain:', tx); }, params: { type: 'INCREMENT_PIMLICO', adapter: OrbitAdapter.EVM, tracker: TransactionTracker.ERC4337, desiredChainID: sepolia.id, pimlicoApiKey: process.env.NEXT_PUBLIC_PIMLICO_API_KEY, title: [ 'Incrementing (ERC-4337)', 'Incremented (ERC-4337)', 'Error when increment (ERC-4337)', 'Increment tx replaced', ], description: [ 'Dispatching sponsored UserOperation to Pimlico bundler...', 'Counter successfully incremented on-chain!', 'UserOperation reverted or failed during execution.', 'UserOperation was replaced with higher gas fees.', ], payload: { contractAddress: COUNTER_ADDRESS, }, withTrackedModal: true, }, }); }; return ( <button onClick={handleIncrement} className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow-md transition-colors" > Sponsored Increment (+1) </button> );};

Pulsar 两阶段流水线底层工作原理

第一阶段轮询 Bundler 的 eth_getUserOperationReceipt 并校验 receipt.success,第二阶段将外层 txHash 移交标准 EVM 追踪器直至区块终局确认。


5. 跨设备持久化:自建索引器还是采用 Quasar

前端面临的一个普遍挑战是“关闭标签页两难”:用户在批准交易后往往立即关闭页面,导致客户端 JS 轮询中止。工程团队可通过两种方式解决持久化问题:

┌────────────────────────────────────────────────────────────────────────┐│ Cross-Device Architecture Options │├───────────────────────────────────┬────────────────────────────────────┤│ Option A: Build Your Own Indexer │ Option B: Quasar Engine │├───────────────────────────────────┼────────────────────────────────────┤│ • Ponder or Envio event indexer │ • Cloud: managed, usage-based ││ • Custom PostgreSQL schema │ • Community Edition: self-hosted ││ • Your own sync API & webhooks │ • Same @tuwaio/quasar-sdk for both ││ • Full control of data model │ • Two-phase EVM & Solana tracking ││ • You build and operate it all │ • HMAC-signed webhook delivery │└───────────────────────────────────┴────────────────────────────────────┘

方案 A:自建轻量级事件索引器 (Ponder / Envio)

需要完全自定义数据模型的团队可以监听 EntryPoint v0.7 合约事件并将状态同步至数据库:

event UserOperationEvent( bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed);

方案 B:采用 Quasar 非托管云端/自托管同步

对于不愿自建事件索引器的团队,Quasar 提供了开箱即用的非托管同步与 Webhook 引擎。两种发行版(Quasar Cloud 与开源自托管 Quasar Community Edition)提供完全一致的 API:

// src/app/actions/syncTransaction.ts'use server'; import { Quasar, type Transaction } from '@tuwaio/quasar-sdk';import { getSiwxServerSession, isSessionMatchingTarget } from '@tuwaio/siwx-server';import { cookies } from 'next/headers';import { createPublicClient, http } from 'viem';import { sepolia } from 'viem/chains'; const quasar = new Quasar({ // Quasar Cloud by default; set to your Community Edition node, e.g. https://api.your-domain.com baseUrl: process.env.NEXT_PUBLIC_QUASAR_BASE_URL || 'https://api.tuwa.io', secretKey: process.env.QUASAR_SECRET_KEY ?? '',}); /** * Syncs an ERC-4337 UserOperation transaction to Quasar for cross-device persistence and webhooks. * Authenticates the caller via SIWX session before anything reaches Quasar. * Accurately validates the cryptographic relationship between EOA Signer and Smart Account Sender. */export async function syncTransaction(tx: Transaction) { const session = await getSiwxServerSession({ cookieSource: await cookies(), cookieName: 'siwx-session', }); if (!session?.address) { return { success: false, reason: 'unauthenticated' }; } // Authorization Check: // 1. Direct EOA match (Standard transactions where tx.from is the signing wallet) const isDirectMatch = isSessionMatchingTarget(session, tx.from, tx.chainId); // 2. Smart Account validation (ERC-4337): // In ERC-4337, tx.from is the Smart Account contract address, while session.address is the EOA Signer. // Validate that the authenticated session signer is the verified on-chain owner of tx.from: let isAuthorized = isDirectMatch; if (!isAuthorized && tx.from) { const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); try { // Query the owner() view function on the Solady smart account contract const accountOwner = await publicClient.readContract({ address: tx.from as `0x${string}`, abi: [ { type: 'function', name: 'owner', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view', }, ], functionName: 'owner', }); if (accountOwner.toLowerCase() === session.address.toLowerCase()) { isAuthorized = true; } } catch { // If the account is counterfactual (not yet deployed on-chain), // the server verifies that the deterministic salt embeds session.address. isAuthorized = false; } } if (!isAuthorized) { return { success: false, reason: 'unauthorized_account_signer' }; } try { // Registers the complete transaction state including UI payload with Quasar await quasar.pulsar.syncCreate(tx, 'MyDApp'); return { success: true }; } catch (error) { console.error('[Quasar Sync Error]', error); return { success: false, error: error instanceof Error ? error.message : String(error) }; }}

6. 综合架构对比矩阵

技术维度经典 EOA (Viem / Wagmi)嵌入式 WaaS (Privy / Dynamic)标准开源方案 (Viem AA + ERC-7579 / Solady)TUWA 模块化栈 (Orbit + Pulsar + 可选 Quasar)
架构与分层标准低层 JSON-RPC 客户端单体供应商 SDK 与云端中继器基础协议原语(前端需编写客户端缓存胶水代码)人体工学客户端抽象层(orbit + pulsar)
私钥所有权100% 用户(硬件 / 浏览器 EOA)非托管 2-of-3 SSS 部署于供应商 TEE 飞地(可导出)100% 用户(硬件 / 本地 / 带模块的 Passkey)100% 用户(硬件 / 本地 / EOA)
执行原语每次签名仅支持单笔独立调用专有中继器或标准 AA原生 ERC-4337 v0.7 批量多重调用 (Multi-Call)原生 ERC-4337 v0.7 批量多重调用 (Multi-Call)
Gas 策略 (Paymaster)仅原生代币 ETH/MATIC(用户自行支付)供应商托管赞助策略规则开放 ERC-4337 Paymaster(Pimlico、Biconomy 等)通过 @tuwaio/orbit-evm 可拔插 Paymaster 支持
智能账户架构无(标准外部拥有账户)专有合约实现或模块化代理自主选择:极简汇编 Solady 或模块化 ERC-7579通过 @tuwaio/orbit-evm 编排 Solady Single-Owner
Passkey 支持不支持供应商专有生物识别认证原生支持 ERC-7579 / EIP-7212(需对应验证模块)需外部 Passkey 验证器或签名模块支持
交易状态模型内存中基础轮询 txHash供应商私有云端 WebhooksViem waitForUserOperationReceipt(需自定义状态机)确定性两阶段(userOp ➔ txHash)状态机
Mempool 追踪与浏览器不适用(标准节点内存池)专有弹窗状态视图内存池:Bundler RPC / 确认后:区块浏览器两阶段:Bundler 状态 ➔ 规范区块浏览器直达
跨设备持久化关闭标签页即丢失状态供应商账户中心云端同步自建开源事件索引器(Ponder / Envio + 数据库)Local-First 本地优先 + 可选 Quasar(云端或自托管)
后端基础设施不适用(纯客户端执行)仅限供应商托管平台完全自主开发与自运维Quasar Cloud 云端服务或自托管 Community Edition
开源许可与可扩展性开源 (MIT)闭源 SDK 与商业阶梯计费100% 开源 (MIT / Apache-2.0)Apache-2.0 SDK 与可自托管 Quasar;托管 Quasar Cloud 可选

总结:设计主权智能账户

通过解耦签名者与智能账户,开发者能够在保证绝对非托管安全的同时提供现代化用户体验。


📚 后续指引

最后更新于