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)
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"
const account = privateKeyToAccount("0x...")
const signer = createWalletClient({ account, chain: polygonAmoy, transport: http() })
const smartAccount = await createSmartAccountClient({
signer,
bundlerUrl: "https://bundler.biconomy.io/api/v2/80002/YOUR_API_KEY",
biconomyPaymasterApiKey: "YOUR_PAYMASTER_KEY",
})
const address = await smartAccount.getAccountAddress()
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"
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
const account = await createKernelAccount(publicClient, {
plugins: { sudo: ecdsaValidator },
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
import { toPermissionValidator } from "@zerodev/permissions"
import { toECDSASigner } from "@zerodev/permissions/signers"
import { toCallPolicy } from "@zerodev/permissions/policies"
const sessionKeySigner = privateKeyToAccount(generatePrivateKey())
const sessionKeyValidator = await toPermissionValidator(publicClient, {
entryPoint: ENTRYPOINT_ADDRESS_V07,
signer: await toECDSASigner({ signer: sessionKeySigner }),
policies: [
toCallPolicy({
permissions: [{
target: gameContract,
valueLimit: parseEther("0"),
functionName: "move",
}]
})
],
})
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,
},
})
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)
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"
const account = privateKeyToAccount("0x...")
const signer = createWalletClient({ account, chain: polygonAmoy, transport: http() })
const smartAccount = await createSmartAccountClient({
signer,
bundlerUrl: "https://bundler.biconomy.io/api/v2/80002/YOUR_API_KEY",
biconomyPaymasterApiKey: "YOUR_PAYMASTER_KEY",
})
const address = await smartAccount.getAccountAddress()
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"
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
const account = await createKernelAccount(publicClient, {
plugins: { sudo: ecdsaValidator },
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
import { toPermissionValidator } from "@zerodev/permissions"
import { toECDSASigner } from "@zerodev/permissions/signers"
import { toCallPolicy } from "@zerodev/permissions/policies"
const sessionKeySigner = privateKeyToAccount(generatePrivateKey())
const sessionKeyValidator = await toPermissionValidator(publicClient, {
entryPoint: ENTRYPOINT_ADDRESS_V07,
signer: await toECDSASigner({ signer: sessionKeySigner }),
policies: [
toCallPolicy({
permissions: [{
target: gameContract,
valueLimit: parseEther("0"),
functionName: "move",
}]
})
],
})
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,
},
})
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)