账户抽象 SDK 集成
使用 Alchemy AA SDK 或 ZeroDev 实现无 Gas 交易体验
概述
账户抽象(Account Abstraction)是 Web3 用户体验革命的核心技术。通过 ERC-4337 标准,我们可以实现无 Gas 交易、社交恢复、批量操作等功能,大幅降低用户进入 Web3 的门槛。本章将介绍如何使用 Alchemy AA SDK 和 ZeroDev 快速集成账户抽象功能。
为什么需要账户抽象?
传统钱包(EOA)存在诸多痛点: 1. Gas 费用门槛:用户必须持有 ETH 才能执行交易 2. 助记词安全:丢失助记词 = 丢失资产 3. 单一签名:无法实现多签、社交恢复等高级功能 4. 交互体验差:每次操作都需要签名确认 ERC-4337 账户抽象解决方案通过智能合约钱包替代传统 EOA: - 可编程验证逻辑(支持多签、生物识别) - Paymaster 代付 Gas(用户无需持有 ETH) - 批量原子操作(多笔交易一次签名) - 可升级(账户逻辑可以迭代)
核心概念
ERC-4337 引入了几个关键组件: • UserOperation:替代传统 transaction 的用户意图结构,包含 sender、nonce、callData、签名等字段 • Bundler:打包多个 UserOp 并提交到链上的服务 • EntryPoint:验证和执行 UserOp 的入口合约(单例部署) • Paymaster:代付 Gas 费用的合约,可以实现 ERC-20 支付、赞助等策略 • Account:智能合约钱包,实现自定义验证逻辑
Alchemy AA SDK 配置
Alchemy AA SDK 提供了完整的账户抽象基础设施: - 智能账户管理 - Gas 估算与优化 - Paymaster 集成 - Bundler 服务 安装依赖: pnpm add @alchemy/aa-core @alchemy/aa-alchemy viem@2.x
ZeroDev SDK 配置
ZeroDev 是另一个流行的 AA SDK,提供: - Kernel:模块化智能账户 - Session Keys:临时授权密钥 - 多种签名方式:Passkey、社交登录 - 灵活的 Paymaster 策略 安装: pnpm add @zerodev/sdk @zerodev/passkey-sdk permissionless viem@2.x
React Hooks 封装
为了提供更好的开发体验,我们可以将账户抽象操作封装为 React Hooks: - useAAAccount: 管理智能账户状态和操作 - useBatchUserOp: 批量执行多笔交易 - usePaymaster: 管理 Gas 赞助策略
Session Keys 实现
Session Keys 是 AA 的高级功能,适用于: - 游戏:玩家在游戏中无需每次操作都签名 - 自动化:定期执行某些操作 - 限制授权:只允许调用特定函数 Session Key 可以设置权限范围、有效期、调用次数等限制,在不牺牲安全性的前提下提供流畅体验。
代码示例
Alchemy AA 客户端配置
使用 Alchemy AA SDK 创建智能账户客户端并发送 UserOperation
1import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";2import { LocalAccountSigner } from "@alchemy/aa-core";3import { sepolia } from "@alchemy/aa-core";4 5// 创建签名者(可以是 MetaMask、Passkey 等)6const signer = LocalAccountSigner.mnemonicToAccountSigner(7 process.env.MNEMONIC!8);9 10// 创建 AA 客户端11const client = await createModularAccountAlchemyClient({12 apiKey: process.env.ALCHEMY_API_KEY!,13 chain: sepolia,14 signer,15 // Gas 策略(可选:使用 Paymaster)16 gasManagerConfig: {17 policyId: process.env.ALCHEMY_GAS_POLICY_ID!,18 },19});20 21// 获取智能账户地址22const smartAccountAddress = client.getAddress();23console.log("Smart Account:", smartAccountAddress);24 25// 发送 UserOp26const { hash } = await client.sendUserOperation({27 uo: {28 target: "0x...", // 目标合约29 data: "0x...", // calldata30 value: 0n, // ETH 数量31 },32});33 34// 等待交易完成35const receipt = await client.waitForUserOperationTransaction({ hash });ZeroDev Kernel 账户
使用 ZeroDev SDK 创建 Kernel 智能账户
1import { createKernelAccount, createKernelAccountClient } from "@zerodev/sdk";2import { KERNEL_V3_1 } from "@zerodev/sdk/constants";3import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";4import { http } from "viem";5import { sepolia } from "viem/chains";6import { privateKeyToAccount } from "viem/accounts";7 8// 创建 ECDSA 验证器9const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);10const ecdsaValidator = await signerToEcdsaValidator(publicClient, {11 signer,12 entryPoint: ENTRYPOINT_ADDRESS_V07,13 kernelVersion: KERNEL_V3_1,14});15 16// 创建 Kernel 账户17const account = await createKernelAccount(publicClient, {18 plugins: {19 sudo: ecdsaValidator,20 },21 entryPoint: ENTRYPOINT_ADDRESS_V07,22 kernelVersion: KERNEL_V3_1,23});24 25console.log("Kernel Account:", account.address);26 27// 创建客户端28const kernelClient = createKernelAccountClient({29 account,30 chain: sepolia,31 bundlerTransport: http(process.env.BUNDLER_URL!),32 entryPoint: ENTRYPOINT_ADDRESS_V07,33});34 35// 发送交易36const txHash = await kernelClient.sendTransaction({37 to: "0x...",38 data: "0x...",39 value: 0n,40});React useAAAccount Hook
封装 AA 客户端为 React Hook,提供更好的开发体验
1'use client';2 3import { useState, useCallback, useEffect } from 'react';4import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";5import type { Address } from 'viem';6 7interface UseAAAccountResult {8 address: Address | null;9 isLoading: boolean;10 error: Error | null;11 sendUserOp: (target: Address, data: `0x${string}`, value?: bigint) => Promise<string>;12}13 14export function useAAAccount(): UseAAAccountResult {15 const [client, setClient] = useState<any>(null);16 const [address, setAddress] = useState<Address | null>(null);17 const [isLoading, setIsLoading] = useState(true);18 const [error, setError] = useState<Error | null>(null);19 20 useEffect(() => {21 async function initClient() {22 try {23 const aaClient = await createModularAccountAlchemyClient({24 // ... config25 });26 setClient(aaClient);27 setAddress(aaClient.getAddress());28 } catch (err) {29 setError(err as Error);30 } finally {31 setIsLoading(false);32 }33 }34 initClient();35 }, []);36 37 const sendUserOp = useCallback(38 async (target: Address, data: `0x${string}`, value: bigint = 0n) => {39 if (!client) throw new Error("Client not initialized");40 41 const { hash } = await client.sendUserOperation({42 uo: { target, data, value },43 });44 45 await client.waitForUserOperationTransaction({ hash });46 return hash;47 },48 [client]49 );50 51 return { address, isLoading, error, sendUserOp };52}无 Gas 交易组件
实现一个用户无需持有 ETH 即可执行的 NFT Mint 操作
1'use client';2 3import { useState } from 'react';4import { useAAAccount } from '@/hooks/useAAAccount';5import { encodeFunctionData } from 'viem';6 7const NFT_CONTRACT = '0x...' as const;8const NFT_ABI = [9 {10 name: 'mint',11 type: 'function',12 inputs: [{ name: 'to', type: 'address' }],13 outputs: [],14 },15] as const;16 17export function GaslessMint() {18 const { address, sendUserOp, isLoading } = useAAAccount();19 const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');20 const [txHash, setTxHash] = useState<string>();21 22 const handleMint = async () => {23 if (!address) return;24 setStatus('pending');25 26 try {27 const callData = encodeFunctionData({28 abi: NFT_ABI,29 functionName: 'mint',30 args: [address],31 });32 33 // 发送 UserOp(Gas 由 Paymaster 代付)34 const hash = await sendUserOp(NFT_CONTRACT, callData);35 setTxHash(hash);36 setStatus('success');37 } catch (err) {38 console.error(err);39 setStatus('error');40 }41 };42 43 return (44 <div className="p-6 bg-card border border-border rounded-xl">45 <h2 className="text-xl font-bold mb-4">🎨 免 Gas Mint NFT</h2>46 {isLoading ? (47 <p>初始化智能账户...</p>48 ) : (49 <>50 <p className="text-sm text-muted-foreground mb-4">51 智能账户: {address}52 </p>53 <button54 onClick={handleMint}55 disabled={status === 'pending'}56 className="px-6 py-3 bg-primary text-primary-foreground rounded-lg"57 >58 {status === 'pending' ? 'Minting...' : 'Mint NFT(免 Gas)'}59 </button>60 {status === 'success' && <p className="mt-4 text-green-400">✅ 成功!</p>}61 </>62 )}63 </div>64 );65}