An externally owned account has one security property, enforced by the protocol: nothing moves unless the private key signs. A smart account under ERC-4337 replaces that with code. Whatever validateUserOp accepts is a valid transaction from your account. An attacker reading a smart account is reading the new definition of "authorised", looking for any input the author did not anticipate.

Security is now a property of a contract, its modules, the factory that deployed it, the paymaster that sponsors it, and the infrastructure that carries operations to the EntryPoint. This article is for wallet teams, module authors, paymaster operators and anyone integrating account abstraction. We walk through the lifecycle, then each layer where authorisation can be subverted or an attacker can make someone else pay.

The UserOperation lifecycle

A UserOperation is a struct, not a transaction. It names the sender, a nonce, optional initCode to deploy the account, the callData to execute, gas limits for validation and execution, fee parameters, optional paymaster data, and a signature. The user signs its hash together with the EntryPoint address and chain ID, and submits it to a bundler.

The bundler simulates it against the EntryPoint, checks that validation succeeds and obeys the rules below, and includes it in a batch submitted via handleOps. For each operation the EntryPoint deploys the account if initCode is present, calls validateUserOp on the account and validatePaymasterUserOp on any paymaster, collects prefunded gas, and only then executes callData. Afterwards it calls the paymaster's postOp and refunds unused gas.

Two properties drive the security model: gas is charged whether or not execution succeeds, and the EntryPoint is the only legitimate caller of validateUserOp and the execution functions, so every function the account exposes must check msg.sender.

The validation function is now your perimeter

validateUserOp receives the operation, its hash, and the prefund amount. It must verify authorisation, pay the prefund, and return validation data encoding success, an optional aggregator, and a validity window. Everything about the account's security reduces to what this function accepts.

Signature checks that do not bind what they should

The signature must cover the userOpHash, which commits to the EntryPoint and chain ID. Accounts that hash their own subset of fields have omitted the paymaster, the nonce or the gas parameters, so a signed operation can be resubmitted with a different paymaster, higher fees or a replayed nonce. We check that the signed message is the userOpHash or a domain-separated wrapper of it, and nothing less.

ecrecover and malleability

Raw ecrecover returns the zero address on invalid signatures. If the owner slot is ever zero, during initialisation or after a botched transfer, a garbage signature matches. Use a hardened recovery library, reject the zero address explicitly, and treat high-s signatures as invalid.

ERC-1271 and contract owners

Accounts owned by contracts verify via isValidSignature. The failure is a trust chain ending in something attacker-controlled: an owner whose isValidSignature returns the magic value for any input, or one upgradeable by a weaker key than the account itself. The account's security is the minimum over the chain.

Nonce handling

The EntryPoint manages nonces, so an account does not need its own. But session keys and modules that sign their own payloads must bind a nonce of their own, because the EntryPoint nonce covers the operation, not the inner authorisation.

Returning instead of reverting

Validation signals signature failure by returning a specific value, not by reverting. An account that returns success on a path it meant to fail, through a mis-ordered condition or an unchecked return from a module, has no perimeter at all. We trace every return path.

Signature aggregation

An account may return an aggregator address from validation, in which case the EntryPoint calls the aggregator once for all participating operations in the bundle instead of checking each signature. This is how BLS-style schemes amortise verification, and it makes the aggregator part of every participating account's perimeter.

We check that validateSignatures cannot succeed for a bundle containing an operation whose signature was not in the aggregate, that each signature is bound to its operation's hash, and that each public key is bound to the sender that claimed it. An aggregator that skips the last check lets one signer authorise operations for every account in the bundle.

Validation rules, banned opcodes and storage access

Bundlers include operations they have simulated. If validation could behave differently at inclusion than at simulation, an attacker could submit operations that pass simulation and fail on-chain, and the bundler would pay for every failure. The validation rules, specified separately from ERC-4337 itself, make validation's outcome depend only on the operation and on state its own sender controls.

Banned opcodes

During validation, the account, paymaster and factory may not use opcodes whose result varies between simulation and inclusion: block number, timestamp, base fee, gas price, gas remaining, coinbase, block hash, prevrandao, and balance queries, among others. Contract creation is banned except for the factory deploying the account via CREATE2. Calls are allowed only to contracts that already have code. Time windows replace timestamp checks: validation returns validAfter and validUntil, and the EntryPoint enforces them.

Storage access

The account may access its own storage, and storage in other contracts only in slots associated with its address, in practice mapping entries keyed by the account, so one operation's validation cannot be invalidated by another sender's state change. Any entity needing broader access, such as a paymaster reading a shared allowance, must stake with the EntryPoint, which gives bundlers a reputation system to throttle abuse.

Why this matters for auditors

These rules are enforced by bundlers, not the chain; a violating account still works via a direct handleOps call, but the public mempool rejects it. The security consequence runs the other way: accounts and modules that read unstaked shared storage during validation enable mass invalidation of a bundle, which griefs every other user in it. We flag anything in validation that reaches outside the sender's own storage.

Paymasters and gas griefing

A paymaster pays for operations it approves. That makes it a pool of funds with a function that decides who may drain it.

Verifying paymasters

An off-chain service signs an approval that validatePaymasterUserOp checks. It must bind the sender, nonce, call data, every gas field, validity window and chain ID. An approval that omits gas limits lets the user inflate them and bill the paymaster. One without a nonce replays until the deposit is empty.

Execution failure still costs the paymaster

Validation succeeds, execution reverts, and the paymaster pays anyway. An attacker crafts operations that pass the paymaster's checks and revert at execution, burning the deposit at one maximum-gas operation each. Mitigations: tight per-user and per-operation limits, call data checked against known targets and selectors, and a postOp that records usage so the signer can cut off abusers. Newer EntryPoint versions penalise unused execution gas, which reduces the incentive to overstate limits but does not remove this vector.

postOp reverts

If postOp reverts, execution is reverted but gas is still charged to the paymaster. A user who can force that, for instance by moving a token balance during execution so a post-payment transfer fails, gets free gas. Token paymasters should pre-charge in validation and refund in postOp, not the reverse.

Deposit and stake management

Deposit pays for gas; stake backs reputation. We check who can withdraw each and whether the unstake delay is honoured.

Session keys: convenience versus blast radius

A session key is a secondary signer with restricted permissions: these targets, these selectors, up to this value, until this time. The policy must be enforced in validation by decoding callData and checking every call, including every inner call of a batch.

Failures we see: a policy that checks the first call of a batch and not the rest; one that checks the target but not the selector, so a key scoped to a swap router can call its arbitrary-execution function; a spend limit that counts value but not approve or permit calls that move the same funds later; and a key that cannot be revoked without the owner key online. Revocation must be a cheap single call, checked live in validation rather than cached.

Our test: for each policy, we enumerate every call shape that reaches the execution function, including batches and fallback paths, and try to construct one the policy accepts and the owner would not.

Bundlers and the mempool

Operations travel through an alternative mempool maintained by bundlers. Bundle invalidation, where one operation changes state so another in the same bundle fails at inclusion, is what the storage rules limit, which is why violations are a security finding rather than a style issue.

Operations are visible to the bundler before inclusion, and a bundler can reorder, delay or sandwich them. High-value operations need either a trusted bundler or a design insensitive to ordering, such as slippage bounds inside the call data. We treat the bundler as an untrusted relayer with full visibility.

Account factories and initCode

The factory deploys accounts with CREATE2 so the address is known before deployment. The address must be derivable only from parameters the owner controls, initialisation must be atomic with deployment, and the implementation must be immutable or behind a timelock.

Factory bugs we see: salts that omit the owner, so an attacker deploys at the user's expected address with themselves as owner before the first legitimate operation lands; proxies deployed and returned before initialize, so the first caller becomes owner; and implementations switchable by a single key, so every account deployed afterwards runs attacker code. A factory that reads shared storage must be staked or bundlers reject its operations.

Modules, plugins and modular accounts

Modular standards such as ERC-6900 and ERC-7579 split the account into a minimal core and installable modules: validators that decide whether an operation is authorised, executors that act on the account's behalf, hooks that run around execution, and fallback handlers that extend the interface. Installing a module is granting it a role. A buggy validator is a full compromise; an executor can call anything from the account's context; a hook can revert every operation, which is a lock-out. The account's security is the minimum over its modules and the install path.

What we check: only the owner path can install or uninstall; installation is not reachable from a session key or from a module unless intended; a module cannot block its own removal; modules are storage-isolated; and the account verifies a module's type before trusting its return values. We also review the registries and attestation schemes a wallet relies on, because "this module is attested" is a trust assumption with a key behind it.

Recovery mechanisms

Social recovery lets guardians replace the owner key after a delay. It is a second authorisation path and gets the same scrutiny as the first.

The failures: guardians changeable by a single guardian; a delay too short for the owner to cancel; a cancellation path that requires the key being replaced; recovery with no threshold; and guardians that are contracts weaker than the account they protect. Guardians who are the same entity under different addresses are one guardian. Recovery strictly stronger than the key it recovers is a design error: the guardians become the real owners.

EIP-7702 context

EIP-7702, activated on Ethereum mainnet in 2025, lets an externally owned account delegate to a contract's code. Transactions to the EOA then execute that code in the EOA's context, so an existing key-based account can behave like a smart account, including as an ERC-4337 sender, without migrating assets.

The security model differs from a deployed smart account. The EOA key retains full authority and can sign a new delegation at any time, so it remains a single point of failure whatever the delegated code enforces. A user who signs a delegation to a malicious implementation has handed over the account, so wallets must treat delegation signatures as the most sensitive thing a user signs. The delegated contract's storage lives at the EOA's address, so re-delegating to an implementation with a different layout can corrupt or expose state. And initialisation of the delegated code is a race if it is open.

For teams adopting 7702 we check the authorisation flow, initialisation of the delegated code, storage layout across implementations the user may switch between, and how the EOA key interacts with any recovery or session-key mechanism.

How we test smart accounts

  • Validation trace: every path through validateUserOp and each validator module, the exact fields the signature binds, every return path.
  • Rules check: validation simulated against the bundler rules, flagging banned opcodes and out-of-scope storage.
  • Paymaster economics: operations that pass validation and fail execution, replayed approvals and postOp reverts, measured as deposit drained per operation.
  • Session key enumeration: every call shape against every policy.
  • Factory and address derivation: attempts to deploy at a user's address with attacker parameters.
  • Module lifecycle: install, uninstall and cross-module isolation from every caller.
  • Recovery and 7702 flows: threshold, delay, cancellation and key-authority analysis.

High and Critical findings include a working operation against a fork, submitted through a local bundler, that demonstrates the unauthorised outcome.

Next steps

Before shipping an account, write down what validateUserOp accepts, in full, including every module and session key path. If the document is longer than you expected, the perimeter is larger than you intended. Then run the bundler rules against validation, enumerate session policies, and review recovery as a second owner. For the contract-level issues beneath all of this, see the 2026 smart contract audit checklist.

Our account abstraction audit scope is on the services page, and you can reach the team on Telegram.