← Back to Blog

ERC-4337 Account Abstraction Security: New Attack Surfaces in 2026

2026-04-16 erc-4337 account-abstraction solidity security paymaster bundler smart-wallet 2026

ERC-4337 Account Abstraction is now mainstream. Smart wallets, session keys, and gasless transactions are standard features in 2026 DeFi applications. The major AA wallets (Safe{Wallet}, Biconomy, ZeroDev, Alchemy's LightAccount) collectively custody billions of dollars of assets.

But AA introduces fundamentally new security primitives — Paymasters, Bundlers, UserOperations, EntryPoints — each with vulnerability classes that traditional security tools are not designed to detect. This post covers the most critical attack surfaces with concrete code examples.


Background: How ERC-4337 Works

Instead of EOA transactions, AA uses a separate mempool of UserOperation objects:

struct UserOperation {
    address sender;          // the smart account
    uint256 nonce;
    bytes initCode;          // for counterfactual deployment
    bytes callData;          // the actual call to execute
    uint256 callGasLimit;
    uint256 verificationGasLimit;
    uint256 preVerificationGas;
    uint256 maxFeePerGas;
    uint256 maxPriorityFeePerGas;
    bytes paymasterAndData;  // optional: paymaster address + data
    bytes signature;
}

The flow:
1. User signs a UserOperation
2. A Bundler picks it up from the alt mempool and submits a batch to the EntryPoint
3. EntryPoint calls validateUserOp() on the smart account
4. If a Paymaster is set, EntryPoint calls validatePaymasterUserOp()
5. EntryPoint executes callData against the account

This architecture creates four distinct trust boundaries, each exploitable.


Attack Vector 1: Paymaster Draining

Paymasters hold ETH deposits in the EntryPoint to sponsor gas for users. A malicious actor can craft UserOperations specifically designed to drain a Paymaster's deposit.

Vulnerable pattern — unlimited sponsorship without caller validation:

// VULNERABLE: Paymaster sponsors any UserOperation — no restrictions
contract OpenPaymaster is BasePaymaster {
    function _validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 /*userOpHash*/,
        uint256 requiredPreFund
    ) internal override returns (bytes memory context, uint256 validationData) {
        // No validation — will sponsor any UserOp from any account
        return ("", 0);
    }
}

An attacker can spam arbitrary accounts' UserOperations through this Paymaster, draining its deposit with zero-value calls.

Secure pattern — whitelist + per-user rate limiting:

// SAFE: restricts sponsorship to approved accounts with rate limits
contract RestrictedPaymaster is BasePaymaster {
    mapping(address => bool) public approvedAccounts;
    mapping(address => uint256) public lastSponsored;
    uint256 public constant COOLDOWN = 1 hours;

    function _validatePaymasterUserOp(
        UserOperation calldata userOp,
        bytes32 /*userOpHash*/,
        uint256 /*requiredPreFund*/
    ) internal override returns (bytes memory context, uint256 validationData) {
        require(approvedAccounts[userOp.sender], "Account not approved");
        require(
            block.timestamp - lastSponsored[userOp.sender] >= COOLDOWN,
            "Rate limit exceeded"
        );
        lastSponsored[userOp.sender] = block.timestamp;
        return (abi.encode(userOp.sender), 0);
    }
}

For token-gated Paymasters (sponsor users who hold a specific NFT or ERC-20), validate the token balance in _validatePaymasterUserOp — not in _postOp, where the balance may have changed after execution.


Attack Vector 2: Signature Validation Bypass

The smart account's validateUserOp() must correctly verify that the signature authorizes the specific UserOperation. Missing checks create signature reuse or replay vulnerabilities.

Vulnerable pattern — no chainId in signed hash:

// VULNERABLE: signature doesn't bind to chainId — cross-chain replay possible
contract WeakWallet is BaseAccount {
    address public owner;

    function _validateSignature(
        UserOperation calldata userOp,
        bytes32 /*userOpHash*/   // ignoring the EntryPoint-provided hash!
    ) internal override returns (uint256 validationData) {
        // Constructing own hash WITHOUT chainId
        bytes32 customHash = keccak256(abi.encodePacked(
            userOp.sender,
            userOp.nonce,
            userOp.callData
        ));
        address recovered = ECDSA.recover(
            ECDSA.toEthSignedMessageHash(customHash),
            userOp.signature
        );
        return recovered == owner ? 0 : SIG_VALIDATION_FAILED;
    }
}

A signature for a UserOp on Ethereum mainnet can be replayed on any chain where this wallet exists.

Fix: always validate against userOpHash, which the EntryPoint computes including block.chainid:

// SAFE: use the EntryPoint-provided userOpHash (includes chainId)
function _validateSignature(
    UserOperation calldata userOp,
    bytes32 userOpHash  // already includes chain, entryPoint, nonce
) internal override returns (uint256 validationData) {
    address recovered = ECDSA.recover(
        ECDSA.toEthSignedMessageHash(userOpHash),
        userOp.signature
    );
    return recovered == owner ? 0 : SIG_VALIDATION_FAILED;
}

Also verify nonce handling — if your account uses custom nonces rather than the EntryPoint's built-in nonce manager, ensure they're tracked per-chain and per-key.


Attack Vector 3: Validation Phase Storage Restriction Violations

The ERC-4337 spec places strict restrictions on storage access during the validation phase (validateUserOp and validatePaymasterUserOp). Bundlers enforce these rules during simulation to prevent griefing.

The spec allows:
- Reading/writing the account's own storage (sender's slots)
- Reading the Paymaster's associated storage
- Reading immutable global state

Violation pattern — reading another account's storage:

// VULNERABLE: validation reads from external contract storage
contract ViolatingWallet is BaseAccount {
    IRegistry public registry;  // external contract

    function _validateSignature(
        UserOperation calldata userOp,
        bytes32 userOpHash
    ) internal override returns (uint256 validationData) {
        // Reading registry.isBlocked() accesses external storage —
        // bundlers will reject this UserOp during simulation
        require(!registry.isBlocked(userOp.sender), "Blocked");
        // ...
    }
}

Bundlers will reject UserOperations that violate storage rules during simulation (via the debug_traceCall simulation). This creates a griefing vector: an attacker can craft UserOps that pass validation on-chain but fail simulation, wasting bundler resources.

Fix: move external storage reads out of the validation phase into callData execution, or use storage that's associated with the sender (e.g., keccak256(abi.encodePacked(sender, storageKey))).


Attack Vector 4: Wallet Factory Frontrunning (Counterfactual Deployment)

ERC-4337 supports counterfactual wallet deployment: a user can receive funds at their predicted wallet address before the wallet is deployed. The factory deploys it atomically on first use.

Vulnerable pattern — deterministic address depends on mutable state:

// VULNERABLE: salt derived from something the attacker can influence
contract WalletFactory {
    function createWallet(
        address owner,
        uint256 saltNonce
    ) external returns (address wallet) {
        bytes32 salt = keccak256(abi.encodePacked(owner, saltNonce));
        wallet = address(new SmartWallet{salt: salt}(owner));
    }

    function getAddress(
        address owner,
        uint256 saltNonce
    ) public view returns (address) {
        bytes32 salt = keccak256(abi.encodePacked(owner, saltNonce));
        return Create2.computeAddress(salt, keccak256(type(SmartWallet).creationCode), address(this));
    }
}

If saltNonce is predictable (e.g., always 0), an attacker can deploy a different contract to the predicted address before the user's first transaction — potentially draining pre-funded assets.

Fix: include an immutable registry-checked nonce, ensure the factory validates that initCode deployer is trusted, and consider including the full wallet initialization calldata in the salt.


Attack Vector 5: Session Key Scope Violations

Session keys are a key AA feature: limited-permission signing keys for delegated operations. Improperly scoped session keys allow privilege escalation.

// VULNERABLE: session key can call arbitrary targets
contract SessionKeyWallet is BaseAccount {
    struct SessionKey {
        address key;
        uint256 validUntil;
        // Missing: allowed targets, allowed selectors, value limits
    }
    mapping(address => SessionKey) public sessionKeys;

    function _validateSignature(
        UserOperation calldata userOp,
        bytes32 userOpHash
    ) internal override returns (uint256 validationData) {
        address signer = ECDSA.recover(
            ECDSA.toEthSignedMessageHash(userOpHash),
            userOp.signature
        );
        SessionKey memory sk = sessionKeys[signer];
        if (sk.validUntil >= block.timestamp) {
            return 0;  // allows ANY callData!
        }
        // fallback to owner...
    }
}

A session key intended for "swap tokens on Uniswap" can call transfer() on any ERC-20 if the target and selector aren't explicitly restricted.

Secure session key validation:

// SAFE: enforce target, selector, and value constraints
struct SessionKey {
    address key;
    uint256 validUntil;
    address allowedTarget;      // only this contract
    bytes4 allowedSelector;     // only this function
    uint256 maxValuePerOp;      // ETH value cap
}

function _validateSessionKey(
    UserOperation calldata userOp,
    SessionKey memory sk
) internal pure returns (bool) {
    if (block.timestamp > sk.validUntil) return false;
    // Decode the execute() call to check the inner target and calldata
    (address target, uint256 value, bytes memory innerCallData) =
        abi.decode(userOp.callData[4:], (address, uint256, bytes));
    if (target != sk.allowedTarget) return false;
    if (value > sk.maxValuePerOp) return false;
    if (bytes4(innerCallData) != sk.allowedSelector) return false;
    return true;
}

What Automated Scanners Catch

Vulnerability Slither Mythril Semgrep AI
Paymaster without rate limits ⚠️ Partial
Missing chainId in signature validation ⚠️ Partial
Storage restriction violations ✅ (context)
Underconstrained session keys
Factory address griefing

AA vulnerabilities are almost entirely semantic — they require understanding ERC-4337's architecture, the validation/execution phase split, and the trust model between Bundlers, EntryPoints, and accounts. Pattern-based tools have almost no coverage here.


ERC-4337 Security Audit Checklist

Before deploying an AA wallet or Paymaster:

Account Contract:
- [ ] validateUserOp() uses userOpHash (includes chainId) — not a custom hash
- [ ] Nonce validation is per-chain and prevents replay
- [ ] Upgrade functions are properly access-controlled
- [ ] execute() / executeBatch() don't allow calls to the EntryPoint itself
- [ ] Storage accessed in validation phase is limited to sender's slots

Paymaster:
- [ ] _validatePaymasterUserOp() enforces caller restrictions (whitelist, token gate)
- [ ] Per-user rate limiting prevents deposit draining
- [ ] Token balance checks happen in validation, not _postOp
- [ ] postOp handles opSucceeded = false cases (the op failed but gas was spent)

Session Keys:
- [ ] allowedTarget explicitly set per session key
- [ ] allowedSelector enforced (restrict which functions can be called)
- [ ] ETH value cap enforced
- [ ] Expiration enforced in validation phase

Factory:
- [ ] Salt is non-predictable or non-influenceable by attackers
- [ ] Counterfactual addresses can't be hijacked before first use


Real-World Incidents

ERC-4337 is relatively new, but several pre-standard smart wallet exploits provide directional evidence:

The AA ecosystem is maturing fast, but production deployments are outpacing security tooling. Most ERC-4337-specific vulnerabilities are invisible to Slither and Mythril because they require understanding the EntryPoint contract interaction model.

Run a free scan on ContractScan to analyze your AA wallet or Paymaster. The AI engine understands ERC-4337 architecture and can identify scope violations, missing rate limits, and signature validation gaps that static analysis misses.

Important Notes

This post is for informational and educational purposes only. It does not constitute financial, legal, or investment advice. The security analysis provided is based on available data and automated tools, which may not capture all potential vulnerabilities. Always conduct a professional audit before deploying smart contracts.

Scan your contract now
Slither + AI analysis — Unlimited quick scans. No signup required.
Try Free Scan →