ERC-4337 Error AA20: Account Not Deployed EntryPoint Revert
ERC-4337 error AA20 "account not deployed" occurs when an EntryPoint receives a UserOperation for an address that contains no deployed smart contract bytecode, while the UserOp initCode field is set to empty ("0x"). The EntryPoint cannot execute validateUserOp on an address with no code. To fix this, provide the 20-byte factory address concatenated with the creation calldata in initCode, or deploy the smart account before sending transactions.
# Root Cause & Technical Architecture
When an EntryPoint processes a UserOperation, it invokes `validateUserOp` on the contract specified in `sender`. If `sender.code.length == 0` and `initCode` is empty (`0x`), the EntryPoint has no mechanism to create the account or verify signatures, immediately triggering `revert("AA20 account not deployed")`. This frequently occurs in SDKs when a first-time user attempts an operation but the frontend omits the factory creation bytes.
# 4-Step Actionable Diagnostic Protocol
Step-by-Step ResolutionInspect UserOperation initCode Formatting
Verify that initCode is populated with exactly 20 bytes of factory address followed by the ABI-encoded creation function parameters.
Check Factory Bytecode and Address Validity
Confirm that the factory contract specified in the first 20 bytes of initCode is actually deployed on the target chain and has verified bytecode.
Verify Counterfactual Address Derivation
Calculate the expected CREATE2 address using your factory contract bytecode hash and verify that it matches the UserOp sender address exactly.
Ensure Sufficient Verification Gas for First Deployment
First-time smart account deployment requires higher verificationGasLimit to cover both factory CREATE2 execution and account initialization.
# Diagnostic Verification Code / Command
typescript// Construct valid initCode for counterfactual smart account
import { encodeFunctionData, parseAbi } from 'viem';
const factoryAddress = "0x789...567" as const; // 20 bytes
const factoryAbi = parseAbi(['function createAccount(address owner, uint256 salt) returns (address)']);
const factoryCalldata = encodeFunctionData({
abi: factoryAbi,
functionName: 'createAccount',
args: [ownerAddress, 0n]
});
// initCode = Factory Address (20 bytes) + Factory Calldata
const initCode = `${factoryAddress}${factoryCalldata.slice(2)}`;
// Required when sender contract has no bytecode on-chain! Launch ERC-4337 UserOp Decoder
Execute client-side cryptographic simulation and parameter checks safely in your browser via native WebCrypto without sending private keys or mnemonics over the wire.
Read Full Investigation & Recovery Workflow
Explore complete architectural diagrams, edge-case failure trees, and detailed post-mortem analysis in our verified deep guide.
# Related Diagnostic Codes in ERC-4337 & Safe
Related Hex StatusesERC-4337 Error AA10: Sender Already Constructed UserOp Fix
Fix ERC-4337 error AA10 sender already constructed. Resolve non-empty initCode sent for deployed smart accounts, bundler estimation, and counterfactuals.
ERC-4337 Error AA24: Signature Error UserOp Validation Fix
Fix ERC-4337 error AA24 signature error. Resolve validateUserOp signature mismatch, userOpHash EIP-712 hashing errors, and signature aggregator faults.
ERC-4337 Error AA25: Invalid Account Nonce 2D Sequence Fix
Fix ERC-4337 error AA25 invalid account nonce. Resolve 2D nonce key and sequence collisions in EntryPoint, parallel bundler queues, and sync lags.
# Frequently Asked Questions
FAQ SchemaWhat is the structure of an ERC-4337 initCode field?
initCode consists of the 20-byte smart account factory address followed immediately by the initialization calldata passed to the factory creation function.
Why does my deployed account trigger AA20 on a new chain?
Smart account addresses are counterfactual. If you deploy on Ethereum mainnet, your account does not exist on Base or Arbitrum until deployed there with initCode.
Can AA20 happen if the factory contract runs out of gas?
If the factory runs out of gas, it returns AA13 (initCode failed). AA20 only occurs when initCode was completely missing for an undeployed sender.