ERC-4337: Smart Wallets Without Changing Ethereum
Ethereum wallets are still weirdly primitive.
For most users, a wallet means:
- One private key
- One seed phrase
- One account
- One scary signing popup
- Zero forgiveness
Lose the key?
Gone.
Sign the wrong thing?
Pain.
Need to approve, swap, and stake?
Enjoy three transactions and a small identity crisis.
ERC-4337 is one of Ethereum’s cleaner answers to this problem.
It brings account abstraction to Ethereum without changing the base protocol.
Translation:
Users can use smart contract wallets as their main accounts.
And smart contracts can do more than “one private key signed this, therefore ship it.”
The Old Model: EOAs
Normal Ethereum wallets are Externally Owned Accounts, or EOAs.
An EOA is controlled by a private key.
The rule is simple:
Valid signature from private key = transaction allowed
That works.
But it is limited.
EOAs do not natively support:
- Social recovery
- Multisig rules
- Gas sponsorship
- Batched transactions
- Session keys
- Spending limits
- Custom signature schemes
EOAs are basically keys with balances.
Very powerful.
Very unforgiving.
Very “hope you wrote down those 12 words correctly.”
The New Model: Smart Accounts
A smart account is a wallet built as a smart contract.
That means the wallet can have programmable rules.
For example:
2 of 3 guardians can recover this wallet
Or:
This app can spend up to 20 USDC per day
Or:
This game session key works for 2 hours and cannot move NFTs
Or:
The dapp sponsors gas for this new user
That is the big shift.
EOA = account controlled by a key
Smart account = account controlled by code
More flexible.
Also easier to mess up if the code is bad.
Because of course 🥀
So What Is ERC-4337?
ERC-4337 is a standard that lets smart accounts behave like first-class wallets.
The clever part:
It does this without requiring Ethereum consensus changes.
Instead of creating a new native transaction type, ERC-4337 creates a new flow using:
UserOperation- Bundlers
EntryPoint- Smart accounts
- Paymasters
- Factories
The user does not directly send a normal Ethereum transaction.
The user signs a UserOperation.
Then a bundler submits it onchain through the EntryPoint smart contract.
The ERC-4337 Flow
High level:
- User wants to do something
- Wallet creates a
UserOperation - User signs it
- Bundler receives it
- Bundler simulates it
- Bundler calls
EntryPoint.handleOps(...) - EntryPoint asks the smart account to validate it
- Smart account executes the action
- Bundler gets paid
Diagram:
User
-> signs UserOperation
-> Bundler
-> EntryPoint
-> Smart Account
-> Target Contract
That is the whole machine.
A little more plumbing than EOAs.
A lot more flexibility.
UserOperation: The Almost-Transaction
A UserOperation is like a transaction, but for smart accounts.
A simplified version:
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
The main fields:
sender: the smart accountnonce: replay protectioninitCode: optional account deployment data.callData: what the account should executepaymasterAndData: optional gas sponsorship datasignature: proof the operation is authorized
So the user signs this object.
The smart account decides whether the signature and operation are valid.
EntryPoint: The Traffic Controller
The EntryPoint is the central contract in ERC-4337.
It:
- Receives UserOperations from bundlers
- Validates them
- Deploys accounts if needed
- Executes calls
- Handles gas accounting
- Pays the bundler
Simplified interface:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IEntryPoint {
function handleOps(
PackedUserOperation[] calldata ops,
address payable beneficiary
) external;
function getUserOpHash(
PackedUserOperation calldata userOp
) external view returns (bytes32);
function depositTo(address account) external payable;
function balanceOf(address account) external view returns (uint256);
}
The bundler calls:
entryPoint.handleOps(userOps, bundlerAddress);
The smart account trusts a specific EntryPoint.
That version matters.
Wrong EntryPoint, wrong day.
Bundlers
A bundler is a relayer.
It takes UserOperations and submits them to Ethereum.
The bundler:
- Receives UserOperations
- Simulates them offchain
- Bundles valid ones together
- Sends a normal transaction to the EntryPoint
- Gets reimbursed for gas
So users do not need to submit normal Ethereum transactions directly.
They sign intents.
Bundlers get them onchain.
Very useful.
Slightly more moving parts.
Welcome to crypto.
Smart Accounts
The smart account is the user’s actual wallet contract.
It validates UserOperations.
A common validation function looks like this:
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData);
The EntryPoint calls this.
The account checks things like:
- Is the caller the trusted EntryPoint?
- Is the signature valid?
- Is the nonce correct?
- Is this operation allowed?
- Does the account need to send ETH to EntryPoint for gas?
If valid, execution continues.
If not, the operation fails.
Basically, this is where the programmability lives
Tiny Simple Account Example
This is a stripped-down educational account.
Do not ship this directly.
Please do not become a postmortem🙂
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SimpleAccount {
using ECDSA for bytes32;
address public owner;
IEntryPoint public immutable entryPoint;
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;
uint256 internal constant SIG_VALIDATION_FAILED = 1;
modifier onlyEntryPoint() {
require(msg.sender == address(entryPoint), "not EntryPoint");
_;
}
constructor(address _owner, IEntryPoint _entryPoint) {
owner = _owner;
entryPoint = _entryPoint;
}
receive() external payable {}
function execute(
address target,
uint256 value,
bytes calldata data
) external onlyEntryPoint {
(bool ok, bytes memory result) = target.call{value: value}(data);
if (!ok) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external onlyEntryPoint returns (uint256 validationData) {
bytes32 hash = ECDSA.toEthSignedMessageHash(userOpHash);
address recovered = ECDSA.recover(hash, userOp.signature);
if (recovered != owner) {
return SIG_VALIDATION_FAILED;
}
if (missingAccountFunds > 0) {
(bool ok, ) = payable(msg.sender).call{
value: missingAccountFunds
}("");
require(ok, "prefund failed");
}
return SIG_VALIDATION_SUCCESS;
}
}
This account is still simple:
Valid owner signature = allowed
But because it is a contract, you can replace that logic with something better.
Multisig.
Guardians.
Passkeys.
Session keys.
Spending limits.
Whatever your wallet needs.
Factories
Smart accounts are contracts, so they need to be deployed.
A factory handles that.
With CREATE2, a factory can compute the account address before deployment.
That means a user can have a wallet address before the smart account exists onchain.
Very weird.
Very useful.
Remember the initCode field in a userop? it's useful with CREATE2 since we compute the address then the entrypoint will call the factory with the initcode that deploys to the address
Simplified idea:
contract SimpleAccountFactory {
IEntryPoint public immutable entryPoint;
constructor(IEntryPoint _entryPoint) {
entryPoint = _entryPoint;
}
function createAccount(address owner, bytes32 salt)
external
returns (SimpleAccount account)
{
account = new SimpleAccount{salt: salt}(owner, entryPoint);
}
}
The first UserOperation can deploy the account and perform an action.
That removes a lot of onboarding friction.
Paymasters
Paymasters let someone else pay gas.
That someone might be:
- A dapp
- A protocol
- A sponsor
- A service that accepts ERC-20 payment instead
This enables:
User has no ETH
User signs action
Paymaster sponsors gas
User still uses the app
That is a big deal.
Because “please go buy ETH first” is terrible onboarding.
A paymaster can choose when to sponsor:
- New users only
- Certain contracts only
- Certain actions only
- Users with valid offchain approval
- Users paying fees in tokens
Gas abstraction is one of ERC-4337’s sharpest UX wins.
Why ERC-4337 Matters
ERC-4337 unlocks better wallet behavior.
Things like:
Gas sponsorship
Users can use an app without already holding ETH.
Batched transactions
One signature can trigger multiple actions.
approve + swap + stake
Much better than three popups and a prayer.
Social recovery
Lose your key?
Guardians can help recover the account.
No seed phrase treasure hunt required.
Session keys
Give temporary permissions to an app.
Useful for games, trading, subscriptions, and anything that should not require a wallet popup every 12 seconds.
Custom security
Smart accounts can support:
- Multisig
- Spending limits
- Passkeys
- Hardware policies
- Different signature schemes
The account defines the rules.
Not the protocol.
What ERC-4337 Does Not Fix
ERC-4337 is not magic dust.
It does not automatically solve:
- Phishing
- Bad wallet design
- Buggy smart accounts
- Bad paymaster rules
- Centralized infrastructure
- Users signing things they do not understand
It gives builders better tools.
You still have to build carefully.
A smart wallet bug is not a normal bug.
It is often a “funds are gone” bug.
Different flavor of sadness.
Quick Recap
ERC-4337 gives Ethereum account abstraction without changing Ethereum consensus.
The main pieces:
- UserOperation: what the user signs
- Bundler: submits UserOperations onchain
- EntryPoint: validates and executes operations
- Smart account: the user’s programmable wallet
- Factory: deploys smart accounts
- Paymaster: optionally pays gas
The mental model:
EOA:
key signs transaction
Ethereum validates it
ERC-4337:
user signs UserOperation
EntryPoint asks smart account to validate it
smart account executes if allowed
Shorter:
EOA = key-controlled account
Smart account = code-controlled account
Final Thoughts
ERC-4337 matters because wallet UX matters.
If the account layer is painful, every crypto app inherits that pain.
Smart accounts give us room to build better defaults:
- Recovery
- Gas sponsorship
- Safer permissions
- Fewer popups
- Better onboarding
It is not perfect.
It adds new infrastructure and new risks.
But it gives Ethereum a practical path beyond private-key survival mode.
And honestly, that is overdue.
Users should not need to understand gas, nonces, seed phrases, approvals, and mempool drama just to try an app.
ERC-4337 does not remove all the complexity.
But it lets builders hide more of it behind better wallet logic.
That is the point.
Less raw crypto pain.
More usable accounts.
Finally.
Further reading:
