Appearance
L4-1: Smart Wallets + Paymaster
1. Problem
Traditional Ethereum accounts (EOAs, Externally Owned Accounts) are controlled by a single private key. This model has fundamental limitations: (1) lost private key = permanently lost funds, with no recovery mechanism; (2) every operation requires ETH for gas, creating a "chicken and egg" problem for new users; (3) complex permission policies -- such as daily spending limits, timelocks, or multi-party co-signing -- cannot be implemented; (4) the user experience is clunky -- every transaction requires confirmation and waiting.
ERC-4337 (Account Abstraction) proposes a solution that does not modify the consensus layer: it introduces smart contract wallets (replacing EOAs), Bundlers (batching transactions), and Paymasters (sponsoring gas). User operations are no longer traditional Ethereum transactions but "UserOperations" -- data structures containing the operational intent, signed by the smart wallet and submitted by the Bundler in batches.
This challenge builds the core components of the complete ERC-4337 stack: a smart wallet contract (SmartWallet.sol) and a TokenPaymaster (TokenPaymaster.sol) that enables paying gas with ERC-20 tokens. Users can pay gas fees with ERC-20 tokens instead of ETH, while enjoying all the benefits of smart wallets.
2. Why
ERC-4337 has already been adopted by over 30 million smart accounts and is the de facto standard for Ethereum account abstraction. Vitalik Buterin has repeatedly emphasized that account abstraction is a necessary path to achieving mainstream Web3 adoption -- it eliminates the technical friction for new user onboarding. In 2026, EntryPoint v0.7 has been deployed via CREATE2 at the same address (0x0000000071727De22E5E9d8BAf0edAc6f37da032) on all major EVM chains, becoming a core component of Ethereum infrastructure.
Understanding ERC-4337 is critical for the entire Web3 ecosystem:
- Frontend development: understanding how to construct a UserOperation and sign it with a smart wallet
- Contract development: understanding the
validateUserOpandexecuteinterfaces and the EntryPoint lifecycle - Infrastructure development: building Bundlers and Paymasters is an emerging infrastructure domain
- Security engineering: smart wallets introduce an attack surface different from EOAs, requiring different security considerations
Unlike EIP-7702 (which achieves account abstraction by delegating code from an EOA to a smart contract -- an "EOA upgrade" approach), ERC-4337 is a complete smart contract wallet solution that does not depend on the existence of an EOA. The two are complementary technical paths: EIP-7702 provides an upgrade path for existing EOA users, while ERC-4337 targets new users with native smart wallets.
3. Solution
The complete ERC-4337 architecture consists of four collaborating components. This challenge implements two of the core contracts:
3.1 Architecture Overview
User (potentially without ETH)
|
+--> Sign UserOperation
| {sender, nonce, callData, paymasterAndData, signature, ...}
|
v
Bundler (listens for UserOperations in the alt mempool)
|
+--> simulateValidation() -- simulate validation phase
+--> Package multiple UserOperations into a bundle
+--> handleOps([userOp1, userOp2, ...]) -- submit to EntryPoint
|
v
EntryPoint (0x0000000071727De22E5E9d8BAf0edAc6f37da032)
|
+--> Validation Phase:
| Wallet._validateSignature(userOp, userOpHash)
| Paymaster._validatePaymasterUserOp(userOp, userOpHash, maxCost)
|
+--> Execution Phase:
| Wallet.execute(to, value, data) or executeBatch(...)
| If validation phase succeeds, Bundler fronts the gas
|
+--> Post-Execution Phase:
Paymaster._postOp(mode, context, actualGasCost)
Deduct ERC-20 tokens from user to reimburse Bundler
3.2 SmartWallet
SmartWallet.sol is an ERC-4337-compatible smart contract wallet implementing two core interfaces:
Signature Validation (_validateSignature): Called by the EntryPoint during the validation phase. Validation logic:
- Recover the signer address from
userOp.signature(65-byte ECDSA signature) - Check that the recovered address matches
owner - Check that
userOp.noncematches the wallet's currentnonce(replay prevention) - Return 0 for validation success, 1 for failure (SIG_VALIDATION_FAILED)
The signature recovery implementation supports standard v-value normalization (adding 27 if v < 27) and is also compatible with EIP-155 replay-protected v-value encoding (chainId * 2 + 35).
Transaction Execution (execute / executeBatch):
execute(): Executes a single call. Can only be called by the EntryPoint (onlyEntryPointmodifier). Increments nonce on success.executeBatch(): Atomic batch execution. All calls must succeed or all are reverted. The three arrays passed (to, values, datas) must have equal lengths.
Security Gating: All execution functions use the onlyEntryPoint modifier to ensure only the EntryPoint can trigger execution. This is the core security model of ERC-4337 -- user operations must pass through the complete validation pipeline (including gas payment, signature verification, and nonce checking) before execution.
Receiving ETH: The receive() function allows the wallet to directly receive ETH transfers (e.g., from other users or contracts).
3.3 TokenPaymaster
TokenPaymaster.sol implements a Paymaster that allows users to pay gas with ERC-20 tokens. Core mechanism:
Validation (_validatePaymasterUserOp): Called by the EntryPoint during the validation phase, receiving three parameters:
userOp: The complete user operation; thepaymasterAndDatafield contains the encodedmaxTokenCost(the maximum number of tokens the user authorizes to pay)userOpHash: The hash of the user operation (unused in this implementation, but used for additional signature verification in full implementations)maxCost: The maximum gas cost of this operation as estimated by the EntryPoint
Validation checks:
- Parse
maxTokenCostfrompaymasterAndData(reading 32-byte uint256 starting at byte offset 20) - Calculate
tokenCost = maxCost * 2000based on the fixed exchange rateTOKENS_PER_ETH = 2000 - Verify that the user has approved sufficient token allowance (
token.allowance(userOp.sender, address(this))) - Verify that the Paymaster has sufficient ETH deposited in the EntryPoint (
entryPoint.balanceOf(address(this))) - Return context (encoded
(sender, tokenCost)) for use by_postOp
Post-Execution (_postOp): Called by the EntryPoint after user operation execution:
- Only deducts when the operation succeeded (
mode == PostOpMode.opSucceeded) - Calculates the final token fee based on actual gas consumed:
actualTokenCost = actualGasCost * TOKENS_PER_ETH - Capped at the pre-authorized
tokenCostlimit - Performs
transferFromfrom the user account to the Paymaster contract
Deposit Management: The Paymaster needs to deposit ETH in the EntryPoint to pay the Bundler's gas:
deposit(): Anyone can deposit ETH into the EntryPoint (marked as the Paymaster's deposit)withdraw(): Only the owner can withdraw ETH from the EntryPoint back to the owner address
Exchange Rate Model: Currently uses a hardcoded exchange rate TOKENS_PER_ETH = 2000. This is a simplification -- production Paymasters need to integrate Chainlink or other oracles to obtain real-time ETH/token exchange rates.
Reference source files: src/level4/SmartWallet.sol, src/level4/TokenPaymaster.sol
3.4 Bundler Logic (Conceptual Level)
The Bundler is a critical infrastructure component in ERC-4337, responsible for:
- Listening to the alt mempool: Collecting UserOperations (distinct from the traditional transaction mempool)
- Simulation validation: Calling
EntryPoint.simulateValidation()to check whether each UserOperation will succeed - Gas ordering: Sorting UserOperations by gas price (
maxPriorityFeePerGas) in descending order (higher priority fee first) - Batch submission: Calling
EntryPoint.handleOps()to submit all UserOperations in a batch - Gas reimbursement: The Bundler fronts all gas, then receives compensation from the Paymaster or the wallet's EntryPoint deposit
3.5 UserOperation Data Structure
struct UserOperation {
address sender; // Smart wallet address
uint256 nonce; // Replay protection counter
bytes initCode; // Factory deployment code (first operation for new wallets)
bytes callData; // Calldata to execute
uint256 callGasLimit; // Gas limit for the execution phase
uint256 verificationGasLimit;// Gas limit for the validation phase
uint256 preVerificationGas; // Bundler compensation
uint256 maxFeePerGas; // EIP-1559 max fee
uint256 maxPriorityFeePerGas;// EIP-1559 priority fee
bytes paymasterAndData; // Paymaster address (20B) + custom data
bytes signature; // Wallet's signature of userOpHash
}
4. Pitfalls Encountered
- Gas separation between validation and execution phases: ERC-4337 separates the validation phase (signature checks, Paymaster validation) from the execution phase. If the validation phase consumes too much gas and exceeds
verificationGasLimit, the entire operation fails directly -- and the Bundler receives no gas compensation - Insufficient ETH deposit in Paymaster: If the deposit check in
_validatePaymasterUserOpfails, the Bundler is denied compensation -- and the gas already consumed by_validatePaymasterUserOpitself is also unrecoverable - Token exchange rate volatility:
TokenPaymaster.soluses a hardcoded exchange rateTOKENS_PER_ETH = 2000; if the ETH/token price fluctuates significantly, users may pay too much or too little in tokens - paymasterAndData decoding:
_decodeMaxTokenCost()uses inline assembly to read a uint256 at byte offset 20 from calldata; ifpaymasterAndDatais malformed (shorter than 52 bytes), it will read garbage or out-of-bounds data - Nonce synchronization issues: When the Bundler submits multiple UserOperations, if one UserOperation's nonce is non-sequential with the others, the entire
handleOpscall may fail - Smart wallet without recovery mechanism: The current implementation has only a single owner; if the owner's private key is lost, funds in the wallet are permanently locked -- there is no social recovery or timelock mechanism
- Hardcoded EntryPoint address: If the EntryPoint is upgraded (from v0.6 to v0.7), wallets and Paymasters need to be redeployed to point to the new EntryPoint
5. Why the Pitfalls Occur
Validation phase gas management is the most distinctive challenge of ERC-4337. In traditional transactions, gas is consumed continuously. In ERC-4337, the validation phase gas is independently priced by verificationGasLimit: the Bundler pays the validation gas before performing any operations. If validation fails, the Bundler not only receives no compensation but also bears the validation phase gas cost itself. This creates an incentive problem: Bundlers are unwilling to accept UserOperations with complex (high-gas) validation logic.
More specifically, the token.allowance() and entryPoint.balanceOf() calls in _validatePaymasterUserOp are both external contract calls that consume significantly more gas than simple storage reads. If the Paymaster's validation logic is too complex, Bundlers may refuse to package these UserOperations due to potential losses. This forces Paymaster developers to trade off between validation efficiency and functional completeness.
Insufficient Paymaster deposits represent a cascading failure: if the deposit passes the check in _validatePaymasterUserOp but becomes insufficient during actual execution (which may be seconds to minutes later) because other UserOperations consumed the deposit, the Bundler will be unable to receive compensation during the _postOp phase. ERC-4337 addresses this by requiring the Paymaster to hold sufficient deposits in the EntryPoint -- deposits are effectively "locked" during the validation phase and cannot be consumed by other UserOperations.
The danger of hardcoded exchange rates lies in arbitrage: if TOKENS_PER_ETH = 2000 but the market price is 2500, users can pay gas with tokens below market price, and the Paymaster bears the loss. If the market price is 1500, users must overpay in tokens, potentially degrading user experience. Production Paymasters must use real-time oracle prices.
6. How to Resolve the Pitfalls
Validation Gas Optimization:
- Minimize external calls in
_validatePaymasterUserOp-- consider cachingentryPoint.balanceOf()results - Use
SLOADinstead ofCALLto check whitelist information - For complex validation logic, move some checks to the execution phase (borne by the wallet contract itself)
- Build a profitability expectation model for Bundlers -- calculate whether compensation covers costs after simulation
Exchange Rate Oracle Integration (production code example):
solidity
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract TokenPaymaster {
AggregatorV3Interface public tokenPriceFeed; // Token/ETH price
function getTokenCost(uint256 ethCost) public view returns (uint256) {
(, int256 price,,,) = tokenPriceFeed.latestRoundData();
// price is token/ETH (scaled by 1e8), calculates how many tokens are needed to cover ethCost
return (ethCost * uint256(price)) / 1e8;
}
}
Nonce Management Improvements: Use ERC-4337's key-based nonce management (EntryPoint v0.7 supports two-dimensional nonces: (key, sequence)). This allows multiple independent nonce sequences, supporting concurrent UserOperations:
Wallet Recovery Mechanism: Add an ownership transfer function to SmartWallet (callable through the EntryPoint or by the owner themselves):
solidity
function transferOwnership(address newOwner) external {
require(msg.sender == owner || msg.sender == entryPoint, "Not authorized");
owner = newOwner;
}
More advanced implementations can add social recovery modules, allowing multiple guardians to replace the owner through voting.
Deposit Monitoring: Build automated deposit replenishment scripts -- automatically call deposit() when the Paymaster's balance in the EntryPoint falls below a safe threshold.
javascript
async function monitorPaymasterDeposit(paymaster, entryPoint, minBalance) {
const balance = await entryPoint.balanceOf(paymaster.address);
if (balance < minBalance) {
await paymaster.deposit({ value: topUpAmount });
}
}
7. Technical Highlights
| Technique | Description |
|---|---|
| ERC-4337 | Account abstraction standard; UserOperation replaces traditional transactions |
| EntryPoint | Global singleton contract, same address across all EVM chains (CREATE2) |
| UserOperation | Contains sender/nonce/callData/signature/paymasterAndData |
| Signature Validation (_validateSignature) | Called by EntryPoint during validation phase; returns 0=success, 1=failure |
| Batch Execution (executeBatch) | Atomic batch calls; all succeed or all revert |
| Paymaster | Sponsors gas; users can pay with ERC-20 tokens |
| Validate -> Execute -> PostOp | Three-phase pipeline: validate -> execute -> postOp |
| Validation Phase Gas | verificationGasLimit priced independently; Bundler bears cost on failure |
| paymasterAndData | 20-byte Paymaster address + custom data (e.g., maxTokenCost) |
| EntryPoint Deposit | Paymaster deposits ETH in EntryPoint to compensate Bundlers |
| PostOpMode | opSucceeded/opReverted/postOpReverted; controls post-execution fee deduction |
| ECDSA Signature Recovery | v-value normalization (v<27 then +27), ecrecover precompile |