LayerZeroFault
ERC-4337 & Safe CODE: AA20 Reviewed & Verified:

ERC-4337 Error AA20: Account Not Deployed EntryPoint Revert

Verified by Victor Vance • Lead Technical Analyst & Systems Researcher
Quick Diagnostic Answer (Executive Summary) GSC Snippet Verified

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 Resolution
01

Inspect UserOperation initCode Formatting

Verify that initCode is populated with exactly 20 bytes of factory address followed by the ABI-encoded creation function parameters.

02

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.

03

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.

04

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
assemble-initcode.ts
// 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!
Zero-Secrets Client Sandbox

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.

Open Sandbox →
Comprehensive In-Depth Guide

Read Full Investigation & Recovery Workflow

Explore complete architectural diagrams, edge-case failure trees, and detailed post-mortem analysis in our verified deep guide.

Read Guide →

# Related Diagnostic Codes in ERC-4337 & Safe

Related Hex Statuses

# Frequently Asked Questions

FAQ Schema

What 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.