# Account Abstraction (AA) Practical Guide ## EIP-4337 Core Concepts ``` User → UserOperation → Bundler → EntryPoint → Smart Account (contract wallet) ↓ Paymaster (can sponsor gas) ``` | Component | Role | |------|------| | **Smart Account** | User's contract wallet (programmable logic) | | **Bundler** | Packages UserOps and submits to chain, similar to a miner | | **Paymaster** | Sponsors gas / pays gas with ERC-20 | | **EntryPoint** | Official standard contract (validation + execution) | --- ## Choosing a Solution | SDK | Characteristics | Recommended For | |-----|------|---------| | **Biconomy** | Battle-tested, simplest Gasless API | Quick gasless integration | | **ZeroDev** | Kernel architecture, plugin-based session keys | When you need session keys | | **Pimlico** | Infrastructure layer, permissionless.js | Custom AA | | **Safe** | Most mature multi-sig smart wallet | Enterprise/DAO multi-sig | --- ## Biconomy (Fastest to Get Started) ```bash npm install @biconomy/account @biconomy/bundler @biconomy/paymaster ``` ```typescript import { createSmartAccountClient } from "@biconomy/account" import { createWalletClient, http } from "viem" import { privateKeyToAccount } from "viem/accounts" import { polygonAmoy } from "viem/chains" // 1. EOA signer (can be a wallet connected via wagmi) const account = privateKeyToAccount("0x...") const signer = createWalletClient({ account, chain: polygonAmoy, transport: http() }) // 2. Create Smart Account const smartAccount = await createSmartAccountClient({ signer, bundlerUrl: "https://bundler.biconomy.io/api/v2/80002/YOUR_API_KEY", biconomyPaymasterApiKey: "YOUR_PAYMASTER_KEY", // optional: gasless }) const address = await smartAccount.getAccountAddress() // 3. Send transaction (gasless) const tx = await smartAccount.sendTransaction({ to: contractAddress, data: encodedCallData, }) console.log("tx hash:", tx.transactionHash) ``` --- ## ZeroDev + Session Keys ```bash npm install @zerodev/sdk @zerodev/ecdsa-validator permissionless ``` ```typescript import { createKernelAccount, createKernelAccountClient } from "@zerodev/sdk" import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" import { ENTRYPOINT_ADDRESS_V07 } from "permissionless" // Create Kernel Smart Account const ecdsaValidator = await signerToEcdsaValidator(publicClient, { signer, entryPoint: ENTRYPOINT_ADDRESS_V07, }) const account = await createKernelAccount(publicClient, { plugins: { sudo: ecdsaValidator }, entryPoint: ENTRYPOINT_ADDRESS_V07, }) // Session Key (user signs once, game/app executes automatically) import { toPermissionValidator } from "@zerodev/permissions" import { toECDSASigner } from "@zerodev/permissions/signers" import { toCallPolicy } from "@zerodev/permissions/policies" const sessionKeySigner = privateKeyToAccount(generatePrivateKey()) // temporary key const sessionKeyValidator = await toPermissionValidator(publicClient, { entryPoint: ENTRYPOINT_ADDRESS_V07, signer: await toECDSASigner({ signer: sessionKeySigner }), policies: [ toCallPolicy({ permissions: [{ target: gameContract, // can only call the specified contract valueLimit: parseEther("0"), functionName: "move", // can only call the move function }] }) ], }) ``` --- ## Pimlico + permissionless.js (Infrastructure Layer) ```typescript import { createSmartAccountClient } from "permissionless" import { signerToSimpleSmartAccount } from "permissionless/accounts" import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" const bundlerClient = createPimlicoBundlerClient({ transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), entryPoint: ENTRYPOINT_ADDRESS_V07, }) const account = await signerToSimpleSmartAccount(publicClient, { signer, entryPoint: ENTRYPOINT_ADDRESS_V07, factoryAddress: "0x..." }) const smartAccountClient = createSmartAccountClient({ account, entryPoint: ENTRYPOINT_ADDRESS_V07, chain: sepolia, bundlerTransport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), middleware: { sponsorUserOperation: paymasterClient.sponsorUserOperation, // gasless }, }) ``` --- ## Common AA Use Cases | Use Case | Implementation | |------|---------| | **Gasless (zero gas for users)** | Paymaster sponsors gas, Biconomy/Pimlico | | **ERC-20 gas payment** | ERC-20 Paymaster, pay with USDC | | **Batch transactions** | Execute multiple calls in a single UserOp | | **Game Session Keys** | ZeroDev plugin, scoped to specific contract + function | | **Social Recovery** | Set up a guardian list, multi-sig recovery | | **Automated Execution** | Integrate with Chainlink Automation | ## Common Pitfalls - Smart Account address is determined by factory + salt — the address is the same across chains, but must be deployed separately on each - The `nonce` in a UserOp is not the same as an EOA nonce — it is managed by the EntryPoint - Paymaster requires a top-up of ETH/USDC via the Pimlico/Biconomy dashboard # Account Abstraction (AA) Practical Guide ## EIP-4337 Core Concepts ``` User → UserOperation → Bundler → EntryPoint → Smart Account (contract wallet) ↓ Paymaster (can sponsor gas) ``` | Component | Role | |------|------| | **Smart Account** | User's contract wallet (programmable logic) | | **Bundler** | Packages UserOps and submits to chain, similar to a miner | | **Paymaster** | Sponsors gas / pays gas with ERC-20 | | **EntryPoint** | Official standard contract (validation + execution) | --- ## Choosing a Solution | SDK | Characteristics | Recommended For | |-----|------|---------| | **Biconomy** | Battle-tested, simplest Gasless API | Quick gasless integration | | **ZeroDev** | Kernel architecture, plugin-based session keys | When you need session keys | | **Pimlico** | Infrastructure layer, permissionless.js | Custom AA | | **Safe** | Most mature multi-sig smart wallet | Enterprise/DAO multi-sig | --- ## Biconomy (Fastest to Get Started) ```bash npm install @biconomy/account @biconomy/bundler @biconomy/paymaster ``` ```typescript import { createSmartAccountClient } from "@biconomy/account" import { createWalletClient, http } from "viem" import { privateKeyToAccount } from "viem/accounts" import { polygonAmoy } from "viem/chains" // 1. EOA signer (can be a wallet connected via wagmi) const account = privateKeyToAccount("0x...") const signer = createWalletClient({ account, chain: polygonAmoy, transport: http() }) // 2. Create Smart Account const smartAccount = await createSmartAccountClient({ signer, bundlerUrl: "https://bundler.biconomy.io/api/v2/80002/YOUR_API_KEY", biconomyPaymasterApiKey: "YOUR_PAYMASTER_KEY", // optional: gasless }) const address = await smartAccount.getAccountAddress() // 3. Send transaction (gasless) const tx = await smartAccount.sendTransaction({ to: contractAddress, data: encodedCallData, }) console.log("tx hash:", tx.transactionHash) ``` --- ## ZeroDev + Session Keys ```bash npm install @zerodev/sdk @zerodev/ecdsa-validator permissionless ``` ```typescript import { createKernelAccount, createKernelAccountClient } from "@zerodev/sdk" import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" import { ENTRYPOINT_ADDRESS_V07 } from "permissionless" // Create Kernel Smart Account const ecdsaValidator = await signerToEcdsaValidator(publicClient, { signer, entryPoint: ENTRYPOINT_ADDRESS_V07, }) const account = await createKernelAccount(publicClient, { plugins: { sudo: ecdsaValidator }, entryPoint: ENTRYPOINT_ADDRESS_V07, }) // Session Key (user signs once, game/app executes automatically) import { toPermissionValidator } from "@zerodev/permissions" import { toECDSASigner } from "@zerodev/permissions/signers" import { toCallPolicy } from "@zerodev/permissions/policies" const sessionKeySigner = privateKeyToAccount(generatePrivateKey()) // temporary key const sessionKeyValidator = await toPermissionValidator(publicClient, { entryPoint: ENTRYPOINT_ADDRESS_V07, signer: await toECDSASigner({ signer: sessionKeySigner }), policies: [ toCallPolicy({ permissions: [{ target: gameContract, // can only call the specified contract valueLimit: parseEther("0"), functionName: "move", // can only call the move function }] }) ], }) ``` --- ## Pimlico + permissionless.js (Infrastructure Layer) ```typescript import { createSmartAccountClient } from "permissionless" import { signerToSimpleSmartAccount } from "permissionless/accounts" import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" const bundlerClient = createPimlicoBundlerClient({ transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), entryPoint: ENTRYPOINT_ADDRESS_V07, }) const account = await signerToSimpleSmartAccount(publicClient, { signer, entryPoint: ENTRYPOINT_ADDRESS_V07, factoryAddress: "0x..." }) const smartAccountClient = createSmartAccountClient({ account, entryPoint: ENTRYPOINT_ADDRESS_V07, chain: sepolia, bundlerTransport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), middleware: { sponsorUserOperation: paymasterClient.sponsorUserOperation, // gasless }, }) ``` --- ## Common AA Use Cases | Use Case | Implementation | |------|---------| | **Gasless (zero gas for users)** | Paymaster sponsors gas, Biconomy/Pimlico | | **ERC-20 gas payment** | ERC-20 Paymaster, pay with USDC | | **Batch transactions** | Execute multiple calls in a single UserOp | | **Game Session Keys** | ZeroDev plugin, scoped to specific contract + function | | **Social Recovery** | Set up a guardian list, multi-sig recovery | | **Automated Execution** | Integrate with Chainlink Automation | ## Common Pitfalls - Smart Account address is determined by factory + salt — the address is the same across chains, but must be deployed separately on each - The `nonce` in a UserOp is not the same as an EOA nonce — it is managed by the EntryPoint - Paymaster requires a top-up of ETH/USDC via the Pimlico/Biconomy dashboard - The first Smart Account deployment incurs extra gas (non-empty `initCode`)