Account abstraction as core infrastructure, not an accessory
For eight years every Ethereum transaction started with an externally owned account. ERC-4337, ERC-7579 and EIP-7702 change that — here is the architecture, a production verifying paymaster in Solidity, and why 7702 matters more than it first appears.
Smart contract accounts · paymasters · bundlers · session keys
For the first eight years of Ethereum, account architecture was rigid. Every transaction was initiated by an externally owned account holding a private key. EOAs cannot execute code before validating a signature, cannot pay gas in anything but native ETH, and cannot authorise automated actions without exposing the key itself.
To take Web3 products to mainstream usage, smart contract accounts stop being an optional accessory and become core infrastructure.
The ERC-4337 architecture, demystified
ERC-4337 achieves account abstraction without consensus-layer changes, by creating an off-chain mempool for custom objects called UserOperations.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ UserOperation │ ──► │ Bundler Mempool │ ──► │ EntryPoint.sol │
│ (sig, calldata) │ │ (packages ops) │ │ (0x000000007172)│
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│
┌──────────────────────────────┴───────┐
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Paymaster.sol │ │ SmartAccount.sol │
│ (validates, pays gas) │ │ (executes calldata) │
└───────────────────────┘ └───────────────────────┘- UserOperation — instead of signing a raw EVM transaction, the user signs a pseudo-transaction specifying target address, calldata, gas limits and paymaster data.
- Bundler — a specialised node picks operations off the AA mempool, simulates them off-chain, bundles them into one standard Ethereum transaction and submits it to the EntryPoint.
- EntryPoint — the on-chain singleton that runs validation loops, verifies gas payment via paymasters, and executes against the smart account.
- Paymaster — a contract that sponsors gas outright, or accepts ERC-20 tokens such as USDC in place of ETH.
A verifying paymaster in production
This is the shape we use for a custom verifying paymaster: it validates an off-chain signature issued by your own backend, so gasless transactions are granted only to users you have authorised. The signature covers the chain ID and the paymaster address, which is what stops a signature issued for one deployment being replayed against another.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "@account-abstraction/contracts/core/BasePaymaster.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
contract StondelVerifyingPaymaster is BasePaymaster {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
address public immutable verifyingSigner;
constructor(IEntryPoint _entryPoint, address _verifyingSigner)
BasePaymaster(_entryPoint)
{
verifyingSigner = _verifyingSigner;
}
function _validatePaymasterUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) internal override returns (bytes memory context, uint256 validationData) {
(uint48 validUntil, uint48 validAfter, bytes calldata signature) =
_parsePaymasterData(userOp.paymasterAndData);
bytes32 hash = keccak256(
abi.encode(
userOpHash,
block.chainid,
address(this),
validUntil,
validAfter
)
).toEthSignedMessageHash();
if (verifyingSigner != hash.recover(signature)) {
return ("", _packValidationData(true, validUntil, validAfter));
}
return ("", _packValidationData(false, validUntil, validAfter));
}
function _parsePaymasterData(bytes calldata paymasterAndData)
internal pure returns (uint48 validUntil, uint48 validAfter, bytes calldata signature)
{
validUntil = uint48(bytes6(paymasterAndData[20:26]));
validAfter = uint48(bytes6(paymasterAndData[26:32]));
signature = paymasterAndData[32:];
}
}EIP-7702 and the migration problem
ERC-4337 gives you a clean smart account standard, but it asks existing users to deploy a new wallet contract and migrate their assets into it. In practice that migration is where adoption stalls — people do not move a portfolio to try a feature.
The practical consequence for product teams: you can design for smart-account capabilities without segmenting your users into those who migrated and those who did not.