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)