ethereum/account-abstraction-integration

Account Abstraction (AA) in Practice: Biconomy / ZeroDev / Pimlico

ethereumguide👥 Communityconfidence highhealth 100%
v1.0.0·Updated 3/20/2026

EIP-4337 Core Concepts

User → UserOperation → Bundler → EntryPoint → Smart Account (contract wallet)
                                              ↓
                                        Paymaster (can sponsor gas)
ComponentRole
Smart AccountUser's contract wallet (programmable logic)
BundlerPackages UserOps and submits to chain, similar to a miner
PaymasterSponsors gas / pays gas with ERC-20
EntryPointOfficial standard contract (validation + execution)

Choosing a Solution

SDKCharacteristicsRecommended For
BiconomyBattle-tested, simplest Gasless APIQuick gasless integration
ZeroDevKernel architecture, plugin-based session keysWhen you need session keys
PimlicoInfrastructure layer, permissionless.jsCustom AA
SafeMost mature multi-sig smart walletEnterprise/DAO multi-sig

Biconomy (Fastest to Get Started)

npm install @biconomy/account @biconomy/bundler @biconomy/paymaster
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

npm install @zerodev/sdk @zerodev/ecdsa-validator permissionless
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)

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 CaseImplementation
Gasless (zero gas for users)Paymaster sponsors gas, Biconomy/Pimlico
ERC-20 gas paymentERC-20 Paymaster, pay with USDC
Batch transactionsExecute multiple calls in a single UserOp
Game Session KeysZeroDev plugin, scoped to specific contract + function
Social RecoverySet up a guardian list, multi-sig recovery
Automated ExecutionIntegrate 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)
ComponentRole
Smart AccountUser's contract wallet (programmable logic)
BundlerPackages UserOps and submits to chain, similar to a miner
PaymasterSponsors gas / pays gas with ERC-20
EntryPointOfficial standard contract (validation + execution)

Choosing a Solution

SDKCharacteristicsRecommended For
BiconomyBattle-tested, simplest Gasless APIQuick gasless integration
ZeroDevKernel architecture, plugin-based session keysWhen you need session keys
PimlicoInfrastructure layer, permissionless.jsCustom AA
SafeMost mature multi-sig smart walletEnterprise/DAO multi-sig

Biconomy (Fastest to Get Started)

npm install @biconomy/account @biconomy/bundler @biconomy/paymaster
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

npm install @zerodev/sdk @zerodev/ecdsa-validator permissionless
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)

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 CaseImplementation
Gasless (zero gas for users)Paymaster sponsors gas, Biconomy/Pimlico
ERC-20 gas paymentERC-20 Paymaster, pay with USDC
Batch transactionsExecute multiple calls in a single UserOp
Game Session KeysZeroDev plugin, scoped to specific contract + function
Social RecoverySet up a guardian list, multi-sig recovery
Automated ExecutionIntegrate 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)