Module 4: Account Abstraction
Difficulty: Intermediate
Estimated reading time: ~40 minutes | Exercises: ~4-5 hours
π Table of Contents
ERC-4337 Architecture
- The Problem Account Abstraction Solves
- ERC-4337 Components
- The Flow
- Reading SimpleAccount and BaseAccount
- Build Exercise: SimpleSmartAccount
EIP-7702 and DeFi Implications
- EIP-7702 β How It Differs from ERC-4337
- DeFi Protocol Implications
- EIP-1271 β Contract Signature Verification
- Build Exercise: SmartAccountEIP1271
Paymasters and Gas Abstraction
- Paymaster Design Patterns
- Paymaster Flow in Detail
- Reading Paymaster Implementations
- Build Exercise: Paymasters
π‘ ERC-4337 Architecture
π‘ Concept: The Problem Account Abstraction Solves
Why this matters: As of 2025, over 40 million smart accounts are deployed on EVM chains. Major wallets (Coinbase Smart Wallet, Safe, Argent, Ambire) have migrated to ERC-4337. If your DeFi protocol doesnβt support account abstraction, youβre cutting off a massive and growing user base.
π The fundamental limitations of EOAs:
Ethereumβs account model has two types: EOAs (controlled by private keys) and smart contracts. EOAs are the only accounts that can initiate transactions. This creates severe UX limitations:
| Limitation | Impact | Real-World Cost |
|---|---|---|
| Must hold ETH for gas | Users with USDC but no ETH canβt transact | Massive onboarding friction |
| Lost key = lost funds | No recovery mechanism | Billions in lost crypto (estimates vary widely) |
| Single signature only | No multisig, no social recovery | Enterprise users forced to use external multisig |
| No batch operations | Separate tx for approve + swap | 2x gas costs, poor UX |
First proposed in EIP-4337 (September 2021), deployed to mainnet (March 2023)
β¨ The promise of account abstraction:
Make smart contracts the primary account type, with programmable validation logic. ERC-4337 achieves this without any changes to the Ethereum protocol itselfβeverything is implemented at a higher layer.
π Deep dive: Cyfrin Updraft - Account Abstraction Course provides hands-on Foundry tutorials. QuickNode - ERC-4337 Guide covers fundamentals and implementation patterns.
π DeFi Pattern Connection
Why DeFi protocol developers must understand account abstraction:
-
User Onboarding β Lending protocols (Aave, Compound) and DEXes lose users at the βneed ETH for gasβ step. Paymasters eliminate this entirely β new users deposit USDC without ever holding ETH.
-
Batch DeFi Operations β Smart accounts can atomically: approve + deposit + borrow + swap in one UserOperation. Your protocol must handle these composite calls without reentrancy issues.
-
Institutional DeFi β Enterprise users require multisig (3-of-5 signers to execute a trade). ERC-4337 makes this native instead of requiring external multisig contracts like Safe wrapping every interaction.
-
Cross-Chain UX β Smart accounts + paymasters enable βswap on Arbitrum, pay gas in USDC on mainnetβ patterns. Bridge protocols and aggregators are building this now.
The shift: DeFi is moving from βuser manages gas and approvals manuallyβ to βprotocol handles everything under the hood.β Understanding this shift is essential for designing modern protocols.
πΌ Job Market Context
Interview question you WILL be asked:
βWhat is account abstraction and why does it matter for DeFi?β
What to say (30-second answer): βAccount abstraction makes smart contracts the primary account type, replacing EOA limitations with programmable validation. ERC-4337 achieves this without protocol changes through a system of UserOperations, Bundlers, an EntryPoint contract, and Paymasters. For DeFi, it means gasless onboarding, batch operations, custom signature schemes, and institutional-grade access controls. Over 40 million smart accounts are deployed β protocols that donβt support them are losing users.β
Follow-up question:
βWhatβs the difference between ERC-4337 and EIP-7702?β
What to say: βERC-4337 deploys new smart contract accounts β full flexibility but requires asset migration. EIP-7702, activated with Pectra in May 2025, lets existing EOAs delegate to smart contract code β same address, no migration. Delegation persists until explicitly revoked. Theyβre complementary: an EOA can use EIP-7702 to delegate to an ERC-4337-compatible implementation, getting the full bundler/paymaster ecosystem without changing addresses.β
Interview Red Flags:
- π© βAccount abstraction requires a hard forkβ β ERC-4337 is entirely at the application layer
- π© Not knowing that
msg.sender == tx.originbreaks with smart accounts - π© Canβt name the ERC-4337 components (EntryPoint, Bundler, Paymaster)
Pro tip: Mention real adoption numbers β 40M+ smart accounts, Coinbase Smart Wallet, Safe migration to 4337. Show you track the ecosystem, not just the spec.
β οΈ Common Mistakes
// β WRONG: Blocking smart accounts with EOA-only checks
function deposit() external {
require(msg.sender == tx.origin, "No contracts"); // Breaks all smart wallets!
}
// β
CORRECT: Allow both EOAs and smart accounts
function deposit() external {
// No msg.sender == tx.origin check β smart accounts welcome
}
π‘ Concept: The ERC-4337 Components
The actors in the system:
1. UserOperation
A pseudo-transaction object that describes what the user wants to do. It includes all the fields of a regular transaction (sender, calldata, gas limits) plus additional fields for smart account deployment, paymaster integration, and signature data.
Think of it as a βtransaction intentβ rather than an actual transaction.
struct PackedUserOperation {
address sender; // The smart account
uint256 nonce;
bytes initCode; // For deploying account if it doesn't exist
bytes callData; // The actual operation to execute
bytes32 accountGasLimits; // Packed: verificationGas | callGas
uint256 preVerificationGas; // Gas to compensate bundler
bytes32 gasFees; // Packed: maxPriorityFee | maxFeePerGas
bytes paymasterAndData; // Paymaster address + data (if sponsored)
bytes signature; // Smart account's signature
}
2. Bundler
An off-chain service that collects UserOperations from an alternative mempool, validates them, and bundles multiple UserOperations into a single real Ethereum transaction.
Bundlers compete with each otherβitβs a decentralized market. They call handleOps() on the EntryPoint contract and get reimbursed for gas.
Who runs bundlers: Flashbots, Alchemy, Pimlico, Biconomy, and any party willing to operate one. Public bundler endpoints.
3. EntryPoint
A singleton contract (one per network, shared by all smart accounts) that orchestrates the entire flow. It receives bundled UserOperations, validates each one by calling the smart accountβs validation function, executes the operations, and handles gas payment.
Deployed addresses:
- EntryPoint v0.7:
0x0000000071727De22E5E9d8BAf0edAc6f37da032 - (v0.6 at
0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789- deprecated)
4. Smart Account (Sender)
The userβs smart contract wallet. Must implement validateUserOp() which the EntryPoint calls during validation. This is where custom logic livesβmultisig, passkey verification, social recovery, spending limits.
ποΈ Popular implementations:
- Safe β most widely deployed, enterprise-grade multisig
- Kernel (ZeroDev) β modular plugins, session keys
- Biconomy β optimized for gas
- SimpleAccount β reference implementation
π Modular Account Standards (2024-2025):
The ecosystem is converging on standardized module interfaces so plugins can work across different smart accounts:
- ERC-6900 β Modular Smart Contract Accounts. Defines a standard plugin interface (validation, execution, hooks) so modules are portable across account implementations. Led by Alchemy (Modular Account).
- ERC-7579 β Minimal Modular Smart Accounts. A lighter alternative to ERC-6900 with fewer constraints, adopted by Rhinestone and Biconomy. Defines four module types: validators, executors, hooks, and fallback handlers.
Why this matters for DeFi: Modular accounts enable session keys (temporary authorization for specific protocols), spending limits (auto-DCA without full key access), and recovery modules. Your protocol may need to interact with these modules for advanced integrations.
5. Paymaster
An optional contract that sponsors gas on behalf of users. When a UserOperation includes paymaster data, the EntryPoint calls the paymaster to verify it agrees to pay, then charges the paymaster instead of the user.
This enables gasless interactionsβa dApp can pay its usersβ gas costs, or accept ERC-20 tokens as gas payment. β¨
6. Aggregator
An optional component for signature aggregationβmultiple UserOperations can share a single aggregate signature (e.g., BLS signatures), reducing on-chain verification cost.
π Deep Dive: Packed Fields in ERC-4337
Why this matters: ERC-4337 v0.7 aggressively packs data to minimize calldata costs (which dominate L2 gas). If you misunderstand the packing, your smart account wonβt work.
PackedUserOperation β accountGasLimits (bytes32):
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β verificationGasLimit β callGasLimit β
β (128 bits / 16 bytes) β (128 bits / 16 bytes) β
β Gas for validateUserOp() β Gas for execution phase β
ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β high 128 bits β low 128 bits β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
bytes32 (256 bits)
PackedUserOperation β gasFees (bytes32):
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β maxPriorityFeePerGas β maxFeePerGas β
β (128 bits / 16 bytes) β (128 bits / 16 bytes) β
β Tip for the bundler β Max total gas price β
ββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β high 128 bits β low 128 bits β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
bytes32 (256 bits)
validationData return value β the trickiest packing:
ββββββββββββββββββββββββ¬βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β validAfter β validUntil β aggregator / sigFailed β
β (48 bits) β (48 bits) β (160 bits) β
β Not-before timestampβ Expiration timestampβ 0 = no aggregator, valid β
β 0 = immediately β 0 = no expiration β 1 = SIG_VALIDATION_FAILED β
ββββββββββββββββββββββββΌβββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ€
β bits 208-255 β bits 160-207 β bits 0-159 β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββ
uint256 (256 bits)
Common return values:
return 0; // β
Signature valid, no time bounds, no aggregator
return 1; // β Signature invalid (SIG_VALIDATION_FAILED in aggregator field)
// With time bounds:
uint256 validAfter = block.timestamp;
uint256 validUntil = block.timestamp + 1 hours;
return (validUntil << 160) | (validAfter << 208);
// This creates a 1-hour validity window
π Deep Dive: validationData Packing β Worked Example
Letβs trace through a concrete example. Say your smart account wants to return: βsignature valid, usable from timestamp 1700000000, expires at 1700003600 (1 hour later).β
Given:
sigFailed = 0 (valid signature)
validAfter = 1700000000 = 0x6553_F100
validUntil = 1700003600 = 0x6554_0110
Step 1: Pack sigFailed into bits 0-159
Since sigFailed = 0, the low 160 bits are all zeros.
Result so far: 0x00000000...0000 (160 bits)
Step 2: Shift validUntil left by 160 bits (into bits 160-207)
0x65540110 << 160
= 0x0000006554_0110_000000000000000000000000000000000000000000
Step 3: Shift validAfter left by 208 bits (into bits 208-255)
0x6553F100 << 208
= 0x6553F100_0000000000000000000000000000000000000000000000000000
Step 4: OR them together:
ββββββββββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β 0x6553F100 β 0x65540110 β 0x00...00 β
β validAfter β validUntil β sigFailed (0 = valid) β
β bits 208-255 β bits 160-207 β bits 0-159 β
ββββββββββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββββββββββββββββββ
In Solidity:
// Packing:
uint256 validationData = (uint256(1700003600) << 160) | (uint256(1700000000) << 208);
// Unpacking (how EntryPoint reads it):
address aggregator = address(uint160(validationData)); // bits 0-159
uint48 validUntil = uint48(validationData >> 160); // bits 160-207
uint48 validAfter = uint48(validationData >> 208); // bits 208-255
bool sigFailed = aggregator == address(1); // special sentinel
// If validUntil == 0, EntryPoint treats it as "no expiration" (type(uint48).max)
Common mistake: Swapping validAfter and validUntil positions. The layout is validAfter | validUntil | sigFailed from high to low bits β counterintuitive because youβd expect βuntilβ (the upper bound) at higher bits, but the packing follows the EntryPointβs _parseValidationData order.
Connection to Module 1: This is the same bit-packing pattern as BalanceDelta (Module 1) and PackedAllowance (Module 3) β multiple values squeezed into a single uint256 to save gas. The pattern is everywhere in production DeFi.
π» Quick Try:
Check the EntryPoint contract on Etherscan to see ERC-4337 in action:
- Go to EntryPoint v0.7 on Etherscan
- Click βInternal Txnsβ β each one is a UserOperation being executed
- Click any transaction β βLogsβ tab β look for
UserOperationEvent - Youβll see: sender (smart account), paymaster (who paid gas), actualGasCost, success
- Compare a sponsored tx (paymaster β 0x0) vs a self-paid one (paymaster = 0x0)
This gives you a concrete feel for how the system works in production.
π‘ Concept: The Flow
1. User creates UserOperation (off-chain)
2. User sends UserOp to Bundler (off-chain, via RPC)
3. Bundler validates UserOp locally (simulation)
4. Bundler batches multiple UserOps into one tx
5. Bundler calls EntryPoint.handleOps(userOps[])
6. For each UserOp:
a. EntryPoint calls SmartAccount.validateUserOp() β validation
b. If paymaster: EntryPoint calls Paymaster.validatePaymasterUserOp() β funding check
c. EntryPoint calls SmartAccount with the operation callData β execution
d. If paymaster: EntryPoint calls Paymaster.postOp() β post-execution accounting
7. EntryPoint reimburses Bundler for gas spent
The critical insight: Validation and execution are separated. Validation runs first for ALL UserOps in the bundle, then execution runs. This prevents one UserOpβs execution from invalidating another UserOpβs validation (which would waste the bundlerβs gas).
π Deep dive: Read the ERC-4337 spec section on the validation/execution split and the βforbidden opcodesβ during validation. The restricted opcodes include
GASPRICE,GASLIMIT,DIFFICULTY/PREVRANDAO,TIMESTAMP,BASEFEE,BLOCKHASH,NUMBER,SELFBALANCE,BALANCE,ORIGIN, andCOINBASEβ essentially anything environment-dependent that could change between simulation and execution. Storage access is restricted (accounts can read/write their own storage; staked entities get broader access).CREATEis forbidden during validation except for account deployment via factories. The full rules are in the validation rules spec.
π Read: SimpleAccount and BaseAccount
Source: eth-infinitism/account-abstraction
Read these contracts in order:
contracts/interfaces/IAccount.solβ the minimal interface a smart account must implementcontracts/core/BaseAccount.solβ helper base contract with validation logiccontracts/samples/SimpleAccount.solβ a basic implementation with single-owner validationcontracts/core/EntryPoint.solβ focus onhandleOps,_validatePrepayment, and_executeUserOp(itβs complex, but understanding the flow is essential)contracts/core/BasePaymaster.solβ the interface for gas sponsorship
β‘ Common pitfall: The validation function returns a packed
validationDatauint256 that encodes three values:sigFailed(1 bit),validUntil(48 bits),validAfter(48 bits). Returning 0 means βsignature valid, no time bounds.β Returning 1 means βsignature invalid.β Get the packing wrong and your account wonβt work. See the Deep Dive above for the bit layout.
π How to Study ERC-4337 Source Code
Start here β the 5-step approach:
-
Start with
IAccount.solβ just one function:validateUserOp- Understand the inputs: PackedUserOperation, userOpHash, missingAccountFunds
- Understand the return: packed validationData (draw the bit layout!)
-
Read
SimpleAccount.solβ the simplest implementation- How it stores the owner
- How
validateUserOpverifies the ECDSA signature - How
executeandexecuteBatchhandle the execution phase - Note the
onlyOwnerOrEntryPointpattern
-
Skim
EntryPoint.handleOpsβ the orchestrator- Donβt try to understand every line β focus on the flow
- Find where it calls
validateUserOpon each account - Find where it calls the execution calldata
- Find where it handles paymaster logic
-
Read
BasePaymaster.solβ the paymaster interfacevalidatePaymasterUserOpβ decide whether to sponsorpostOpβ post-execution accounting- How context bytes flow between validate and postOp
-
Study a production account (Safe or Kernel)
- Compare to SimpleAccount β whatβs different?
- Look for: module systems, plugin hooks, access control
- These represent where the industry is heading
Donβt get stuck on: The gas accounting internals in EntryPoint. Understand the flow first (validate β execute β postOp), then revisit the gas math later.
π― Build Exercise: SimpleSmartAccount
Workspace: workspace/src/part1/module4/exercise1-simple-smart-account/ β starter file: SimpleSmartAccount.sol, tests: SimpleSmartAccount.t.sol
- Create a minimal smart account that implements
IAccount(justvalidateUserOp) - The account should validate that the UserOperation was signed by a single owner (ECDSA signature via
ecrecover) - Implement basic
execute(address dest, uint256 value, bytes calldata func)for the execution phase - Test against the provided MockEntryPoint (simplified for learning)
Note on UserOperation versions: The exercise uses a simplified
UserOperationstruct with separate gas fields (inspired by v0.6). Production ERC-4337 v0.7 usesPackedUserOperationwith packedbytes32 accountGasLimitsandbytes32 gasFees(see the bit-packing diagrams above). The core flow (validate β execute β postOp) is identical β only the struct encoding differs.
Key concepts to implement:
- Extract
r,s,vfromuserOp.signature(65 bytes packed asr|s|v) - Recover signer using
ecrecover(userOpHash, v, r, s)β raw hash, no EthSign prefix - Return
0for valid signature,1forSIG_VALIDATION_FAILED - If
missingAccountFunds > 0, pay the EntryPoint via low-level call
β οΈ Note: The exercise uses raw
ecrecoveragainst theuserOpHashdirectly (no"\x19Ethereum Signed Message:\n32"prefix). This matches the simplified MockEntryPoint. Production ERC-4337 implementations typically useECDSA.recoverwith the EthSign prefix or a typed data hash, but the raw approach keeps the exercise focused on the account abstraction flow rather than signature encoding details.
π― Goal: Understand the smart account contract interface from the builderβs perspective. Youβre not building a wallet productβyouβre understanding how these accounts interact with DeFi protocols youβll design.
π Summary: ERC-4337 Architecture
β Covered:
- EOA limitations β gas requirements, single key, no batch operations
- ERC-4337 architecture β UserOperation, Bundler, EntryPoint, Smart Account, Paymaster
- Validation/execution split β why it matters for security
- SimpleAccount implementation β ECDSA validation and execution
Next: EIP-7702 and how smart accounts change DeFi protocol design
π‘ EIP-7702 and DeFi Implications
π‘ Concept: EIP-7702 β How It Differs from ERC-4337
Why this matters: EIP-7702 (Pectra upgrade, May 2025) unlocks account abstraction for the ~200 million existing EOAs without requiring migration. Your DeFi protocol will interact with both βnativeβ smart accounts (ERC-4337) and βupgradedβ EOAs (EIP-7702).
Introduced in EIP-7702, activated with Pectra (May 2025)
π The two paths to account abstraction:
| Aspect | ERC-4337 | EIP-7702 |
|---|---|---|
| Account type | Full smart account with new address | EOA keeps its address |
| Migration | Requires moving assets | No migration needed |
| Flexibility | Maximum (custom validation, storage) | Limited (persistent delegation until revoked) |
| Adoption | ~40M+ deployed as of 2025 | Native to protocol (all EOAs) |
| Use case | New users, enterprises | Existing users, wallets |
Combined approach:
An EOA can use EIP-7702 to delegate to an ERC-4337-compatible smart account implementation, gaining access to the full bundler/paymaster ecosystem without changing addresses. β¨
ποΈ Real adoption:
- Coinbase Smart Wallet uses this approach
- Trust Wallet planning migration
- Metamask exploring integration
β οΈ Common Mistakes
// β WRONG: Confusing EIP-7702 and ERC-4337 in your integration
// EIP-7702 = EOA delegates to code (no new account needed)
// ERC-4337 = deploy a new smart account contract
// β WRONG: Assuming delegation is temporary per-transaction
// Delegation PERSISTS across transactions until explicitly revoked!
// Don't assume a user's EOA will behave like a plain EOA next block
// β
CORRECT: Design protocols to handle both transparently
// Check neither msg.sender.code.length nor tx.origin β just work with msg.sender
π‘ Concept: DeFi Protocol Implications
Why this matters: As a DeFi protocol designer, account abstraction changes your core assumptions. Code that worked for 5 years breaks with smart accounts.
1. msg.sender is now a contract
When interacting with your protocol, msg.sender might be a smart account, not an EOA. If your protocol assumes msg.sender == tx.origin (to check for EOA), this breaks.
Example of broken code:
// β DON'T DO THIS
function deposit() external {
require(msg.sender == tx.origin, "Only EOAs"); // BREAKS with smart accounts
// ...
}
Some older protocols used this as a βreentrancy guardββitβs no longer reliable.
β‘ Common pitfall: Protocols that whitelist βknown EOAsβ or blacklist contracts. With EIP-7702, the same address can be an EOA one block and a contract the next.
2. tx.origin is unreliable
With bundlers submitting transactions, tx.origin is the bundlerβs address, not the userβs. Never use tx.origin for authentication.
Example of broken code:
// β DON'T DO THIS
function withdraw() external {
require(tx.origin == owner, "Not owner"); // tx.origin is the bundler!
// ...
}
3. Gas patterns change
Paymasters mean users donβt need ETH for gas. If your protocol requires users to hold ETH (e.g., for refund mechanisms), consider that smart account users might not have any.
4. Batch transactions are common
Smart accounts naturally batch operations. A single handleOps call might:
- Deposit collateral
- Borrow USDC
- Swap USDC for ETH
- All atomically β¨
Your protocol should handle this gracefully (no unexpected reentrancy, proper event emissions).
5. Signatures are non-standard
Smart accounts can use any signature scheme:
- Passkeys (WebAuthn)
- Multisig (m-of-n threshold)
- MPC (distributed key generation)
- Session keys (temporary authorization)
If your protocol requires EIP-712 signatures from users (e.g., for permit or off-chain orders), you need to support EIP-1271 (contract signature verification) in addition to ecrecover.
π DeFi Pattern Connection
Where these implications hit real protocols:
-
Uniswap V4 + Smart Accounts
- Permit2βs
SignatureVerificationalready handles EIP-1271 β smart accounts can sign Permit2 permits - Flash accounting (Module 2) works identically for EOAs and smart accounts
- But custom hooks might assume EOA behavior β audit carefully
- Permit2βs
-
Aave V3 + Batch Liquidations
- Smart accounts enable atomic batch liquidations: scan undercollateralized positions β liquidate multiple β swap rewards β all in one UserOp
- This creates a new class of liquidation MEV thatβs more efficient than current flashbot bundles
-
Curve/Balancer + Gas Abstraction
- LP providers who hold only stablecoins can now add/remove liquidity without ETH
- Protocol-sponsored paymasters can subsidize LP actions to attract TVL
-
Governance + Multisig
- DAOs using smart accounts can vote with m-of-n signatures natively
- No more wrapping governance calls through external Safe contracts
The pattern: Every require(msg.sender == tx.origin) and ecrecover-only validation is now a compatibility bug. Modern DeFi protocols must be account-abstraction-aware from day one.
β οΈ Common Mistakes
Mistakes that break with smart accounts:
-
Using
msg.sender == tx.originas a security check// β BREAKS: Smart accounts have msg.sender β tx.origin always require(msg.sender == tx.origin, "No contracts"); // β If you need reentrancy protection, use a proper guard // (ReentrancyGuard or transient storage from Module 1) -
Assuming all signatures are ECDSA
// β BREAKS: Smart accounts use EIP-1271, not ecrecover address signer = ecrecover(hash, v, r, s); require(signer == expectedSigner); // β Use SignatureChecker that handles both // (see EIP-1271 section below) -
Assuming
msg.sender.code.length == 0means EOA// β BREAKS: With EIP-7702, an EOA can have code temporarily // And during construction, contracts also have code.length == 0 require(msg.sender.code.length == 0, "Only EOAs"); -
Hardcoding gas refund to
tx.origin// β BREAKS: tx.origin is the bundler, not the user payable(tx.origin).transfer(refund); // β Refund to msg.sender (the smart account) payable(msg.sender).transfer(refund);
πΌ Job Market Context
Interview question you WILL be asked:
βHow does account abstraction affect DeFi protocol design?β
What to say (30-second answer): βFive major changes: msg.sender can be a contract, so tx.origin checks break; tx.origin is the bundler, so authentication must use msg.sender; gas patterns change because paymasters mean users might not hold ETH; batch transactions are common so reentrancy protection matters more; and signatures are non-standard because smart accounts use passkeys, multisig, or session keys instead of ECDSA, requiring EIP-1271 support for any signature verification.β
Follow-up question:
βHow would you audit a protocol for smart account compatibility?β
What to say:
βIβd search for three red flags: any msg.sender == tx.origin checks, any ecrecover-only signature verification without EIP-1271 fallback, and any assumption that msg.sender canβt be a contract. Then Iβd verify reentrancy guards work correctly with batch operations, and check that gas refund patterns send to msg.sender, not tx.origin.β
Interview Red Flags:
- π© Using
tx.originfor any authentication purpose - π© βWe only support EOAsβ β excludes 40M+ smart accounts
- π© Not knowing what EIP-1271 is
Pro tip: If you can articulate the five protocol design changes fluently, you signal deep understanding. Most candidates know βaccount abstraction existsβ but canβt explain concrete protocol implications.
π‘ Concept: EIP-1271 β Contract Signature Verification
Why this matters: Every protocol that uses signatures (Permit2, OpenSea, Uniswap limit orders, governance proposals) must support EIP-1271 for smart account compatibility.
Defined in EIP-1271 (April 2019)
The interface:
interface IERC1271 {
// Standard method name
function isValidSignature(
bytes32 hash, // The hash of the data that was signed
bytes memory signature
) external view returns (bytes4 magicValue);
}
How it works:
- Instead of calling
ecrecover(hash, signature), you check ifmsg.senderis a contract - If itβs a contract, call
IERC1271(msg.sender).isValidSignature(hash, signature) - If the return value is
0x1626ba7e(the function selector itself), the signature is valid β - If itβs anything else, the signature is invalid β
Standard pattern:
// β
CORRECT: Supports both EOA and smart account signatures
function verifySignature(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
// Check if signer is a contract
if (signer.code.length > 0) {
// EIP-1271 contract signature verification
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magicValue) {
return magicValue == 0x1626ba7e;
} catch {
return false;
}
} else {
// EOA signature verification
address recovered = ECDSA.recover(hash, signature);
return recovered == signer;
}
}
π Deep dive: Permit2βs SignatureVerification.sol is the production reference for handling both EOA and EIP-1271 signatures. Ethereum.org - EIP-1271 Tutorial provides step-by-step implementation. Alchemy - Smart Contract Wallet Compatibility covers dApp integration patterns.
π» Quick Try:
See EIP-1271 in action with a Safe multisig:
- Go to any Safe wallet on Etherscan (the Safe singleton implementation)
- Search for the
isValidSignaturefunction in the βRead Contractβ tab - Notice the function signature β this is the EIP-1271 interface that every protocol calls
- Now look at OpenZeppelinβs SignatureChecker.sol β see how it branches between
ecrecoverandisValidSignaturebased onsigner.code.length
π Intermediate Example: Universal Signature Verification
Before the exercise, hereβs a reusable pattern that handles both EOA and smart account signatures β the same approach used by OpenZeppelinβs SignatureChecker:
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
library UniversalSigVerifier {
bytes4 constant EIP1271_MAGIC = 0x1626ba7e;
function isValidSignature(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
// Path 1: Smart account β EIP-1271
if (signer.code.length > 0) {
try IERC1271(signer).isValidSignature(hash, signature) returns (bytes4 magic) {
return magic == EIP1271_MAGIC;
} catch {
return false;
}
}
// Path 2: EOA β ECDSA
(address recovered, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
return error == ECDSA.RecoverError.NoError && recovered == signer;
}
}
Key decisions in this pattern:
signer.code.length > 0β check if itβs a contract (imperfect with EIP-7702, but standard practice)try/catchβ protect against maliciousisValidSignatureimplementations that revert or consume gastryRecoverβ safer thanrecoverbecause it doesnβt revert on bad signatures0x1626ba7eβ this magic value is theisValidSignaturefunction selector itself
Where youβll use this:
- Any protocol that accepts off-chain signatures (permits, orders, votes)
- Any protocol that integrates with Permit2 (which handles this internally)
- Governance systems that accept delegated votes
Connection to Module 3: This is exactly what Permit2βs
SignatureVerification.soldoes internally. The pattern you learned in Module 3 (Permit2 source code reading) connects directly here βSignatureVerificationis the bridge between permit signatures and smart accounts.
π DeFi Pattern Connection
Where EIP-1271 is required across DeFi:
-
Permit2 β already supports EIP-1271 via
SignatureVerification.sol- Smart accounts can sign Permit2 permits
- Your vault from Module 3 works with smart accounts out of the box (if using Permit2)
-
OpenSea / NFT Marketplaces β order signatures must support contract wallets
- Safe users listing NFTs sign via EIP-1271
- Marketplaces that only support
ecrecoverexclude enterprise users
-
Governance (Compound Governor, OpenZeppelin Governor)
castVoteBySigmust verify both EOA and contract signatures- DAOs with Safe treasuries need EIP-1271 to vote
-
UniswapX / Intent Systems
- Swap orders signed by smart accounts β verified via EIP-1271
- Witness data (Module 3) + EIP-1271 = smart accounts participating in intent-based trading
The pattern: If your protocol accepts any kind of off-chain signature, add EIP-1271 support. Use OpenZeppelinβs SignatureChecker library β itβs a one-line change that makes your protocol compatible with all smart accounts.
πΌ Job Market Context
Interview question you WILL be asked:
βHow do you verify signatures from smart contract wallets?β
What to say (30-second answer):
βUse EIP-1271. Check if the signer has code β if yes, call isValidSignature(hash, signature) on the signer contract and verify it returns the magic value 0x1626ba7e. If no code, fall back to standard ECDSA recovery with ecrecover. Wrap the EIP-1271 call in try/catch to handle malicious implementations. OpenZeppelinβs SignatureChecker library implements this pattern, and Permit2 uses it internally.β
Follow-up question:
βWhatβs the security risk of EIP-1271?β
What to say:
βThe main risk is that isValidSignature is an external call to an arbitrary contract. A malicious implementation could: consume all gas (griefing), return the magic value for any input (always-valid), or have side effects. Thatβs why you always use try/catch with a gas limit, and never trust that a valid EIP-1271 response means the signer actually authorized the action β it only means the contract says it did.β
Interview Red Flags:
- π© Only using
ecrecoverwithout EIP-1271 fallback - π© Not knowing the magic value
0x1626ba7e - π© Calling
isValidSignaturewithout try/catch
Pro tip: Mention that EIP-1271 enables passkey-based wallets (WebAuthn signatures verified on-chain). Coinbase Smart Wallet uses this β passkey signs, wallet contract verifies via isValidSignature. This is the future of DeFi UX.
β οΈ Common Mistakes
// β WRONG: Only supporting EOA signatures (ecrecover)
function verifySignature(bytes32 hash, bytes memory sig) internal view returns (address) {
return ECDSA.recover(hash, sig); // Fails for ALL smart wallets!
}
// β
CORRECT: Support both EOA and contract signatures
function verifySignature(address signer, bytes32 hash, bytes memory sig) internal view returns (bool) {
if (signer.code.length > 0) {
// Smart account β use EIP-1271
try IERC1271(signer).isValidSignature(hash, sig) returns (bytes4 magic) {
return magic == IERC1271.isValidSignature.selector;
} catch {
return false;
}
} else {
// EOA β use ecrecover
return ECDSA.recover(hash, sig) == signer;
}
}
// β WRONG: Calling isValidSignature without gas limit
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, sig))
); // Malicious contract could consume ALL remaining gas!
// β
CORRECT: Set a gas limit for the external call
(bool success, bytes memory result) = signer.staticcall{gas: 50_000}(
abi.encodeCall(IERC1271.isValidSignature, (hash, sig))
);
π― Build Exercise: SmartAccountEIP1271
Workspace: workspace/src/part1/module4/exercise2-smart-account-eip1271/ β starter file: SmartAccountEIP1271.sol, tests: SmartAccountEIP1271.t.sol
- Extend your SimpleSmartAccount to support EIP-1271:
- Implement
isValidSignature(bytes32 hash, bytes signature)that verifies the ownerβs ECDSA signature - Return
0x1626ba7eif valid β ,0xffffffffif invalid β - Handle edge cases: invalid signature length, recovery to
address(0)
- Implement
Note: This exercise depends on completing Exercise 1 first.
SmartAccountEIP1271inherits fromSimpleSmartAccount.
π― Goal: Understand how EIP-1271 bridges smart accounts and signature-based DeFi protocols. The isValidSignature function is what Permit2, OpenSea, and governance systems call to verify signatures from contract wallets.
π Stretch goal (Permit2 integration): After completing the tests, consider how youβd modify the Permit2 Vault from Module 3 to support contract signatures β check signer.code.length > 0, then call isValidSignature instead of ecrecover. Permit2 already does this internally via its SignatureVerification library.
π Summary: EIP-7702 and DeFi Implications
β Covered:
- EIP-7702 vs ERC-4337 β persistent delegation vs full smart accounts
- DeFi protocol implications β
msg.sender,tx.origin, batch transactions - EIP-1271 β contract signature verification for smart account compatibility
- Real-world patterns β Permit2 integration with smart accounts
Next: Paymasters and how to sponsor gas for users
π‘ Paymasters and Gas Abstraction
π‘ Concept: Paymaster Design Patterns
Why this matters: Paymasters are where DeFi and account abstraction intersect most directly. Protocols can subsidize onboarding (Coinbase pays gas for new users), accept stablecoins for gas (pay in USDC instead of ETH), or implement novel gas markets.
π Three common patterns:
1. Verifying Paymaster
Requires an off-chain signature from a trusted signer authorizing the sponsorship. The dAppβs backend signs each UserOperation it wants to sponsor.
Use case: βFree gasβ onboarding flows. New users interact with your DeFi protocol without needing ETH first. β¨
Implementation:
contract VerifyingPaymaster is BasePaymaster {
address public verifyingSigner;
function validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) external override returns (bytes memory context, uint256 validationData) {
// Extract signature from paymasterAndData
// v0.7 layout: [0:20] paymaster addr, [20:36] verificationGasLimit,
// [36:52] postOpGasLimit, [52:] custom data
bytes memory signature = userOp.paymasterAndData[52:];
// Verify backend signed this UserOp
bytes32 hash = keccak256(abi.encodePacked(userOpHash, block.chainid, address(this)));
address recovered = ECDSA.recover(hash, signature);
if (recovered != verifyingSigner) return ("", 1); // β Signature failed
return ("", 0); // β
Will sponsor this UserOp
}
}
2. ERC-20 Paymaster
Accepts ERC-20 tokens as gas payment. The user pays in USDC or the protocolβs native token, and the paymaster converts to ETH to reimburse the bundler.
Use case: Users hold stablecoins but no ETH. Protocol accepts USDC for gas.
Requires a price oracle (Chainlink or similar) to determine the exchange rate.
Implementation sketch:
contract ERC20Paymaster is BasePaymaster {
IERC20 public token;
IChainlinkOracle public oracle;
function validatePaymasterUserOp(...)
external override returns (bytes memory context, uint256 validationData)
{
uint256 tokenPrice = oracle.getPrice(); // Token/ETH price
uint256 tokenCost = (maxCost * 1e18) / tokenPrice;
// Check user has enough tokens
require(token.balanceOf(userOp.sender) >= tokenCost, "Insufficient token balance");
// Return context with tokenCost for postOp
return (abi.encode(userOp.sender, tokenCost), 0);
}
function postOp(
PostOpMode mode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) external override {
(address user, uint256 estimatedTokenCost) = abi.decode(context, (address, uint256));
// Calculate actual token cost based on actual gas used
uint256 tokenPrice = oracle.getPrice();
uint256 actualTokenCost = (actualGasCost * 1e18) / tokenPrice;
// Transfer tokens from user to paymaster
token.transferFrom(user, address(this), actualTokenCost);
}
}
β‘ Common pitfall: Oracle price updates can lag, leading to over/underpayment. Add a buffer (e.g., charge 105% of oracle price) and refund excess in
postOp.
π» Quick Try:
See paymaster-sponsored transactions live:
- Go to JiffyScan β an ERC-4337 UserOperation explorer
- Pick any recent UserOperation on a supported chain
- Look at the βPaymasterβ field β if non-zero, the paymaster sponsored gas
- Compare gas costs between sponsored (paymaster β 0x0) and self-paid (paymaster = 0x0) UserOperations
- Click into a paymaster address to see how many UserOps it has sponsored β some have sponsored millions
π Deep dive: OSEC - ERC-4337 Paymasters: Better UX, Hidden Risks analyzes security vulnerabilities including post-execution charging risks. Encrypthos - Security Risks of EIP-4337 covers common attack vectors. OpenZeppelin - Account Abstraction Impact on Security provides security best practices.
3. Deposit Paymaster
Users pre-deposit ETH or tokens into the paymaster contract. Gas is deducted from the deposit.
Use case: Subscription-like models. Users deposit once, protocol deducts gas over time.
π DeFi Pattern Connection
How paymasters transform DeFi economics:
-
Protocol-Subsidized Onboarding
- Aave could sponsor first-time deposits: user deposits USDC, Aave pays gas
- Cost to protocol: ~$0.50 per new user on L2s
- ROI: retained TVL from users who would have abandoned at βneed ETHβ step
-
Token-Gated Gas Markets
- Protocol tokens as gas: hold $UNI β pay gas in $UNI for Uniswap swaps
- Creates native demand for the protocol token
- Pimlico and Alchemy already offer this as a service
-
Cross-Protocol Gas Sponsorship
- Aggregators (1inch, Paraswap) can sponsor gas for users routing through them
- βFree gasβ becomes a competitive advantage for attracting order flow
- Similar to how CEXes offer zero-fee trading
-
Conditional Sponsorship
- Sponsor gas only for trades above $1000 (whale onboarding)
- Sponsor gas only during low-activity hours (incentivize off-peak usage)
- Sponsor gas for LP deposits but not withdrawals (encourage TVL)
The pattern: Paymasters turn gas from a user cost into a protocol design lever. The question isnβt βdoes your protocol support paymasters?β β itβs βwhatβs your gas sponsorship strategy?β
πΌ Job Market Context
Interview question you WILL be asked:
βHow would you implement gasless DeFi interactions?β
What to say (30-second answer):
βUsing ERC-4337 paymasters. Three patterns: a verifying paymaster where the protocol backend signs each UserOperation it wants to sponsor β good for controlled onboarding. An ERC-20 paymaster that accepts stablecoins for gas, using a Chainlink oracle for the exchange rate β good for users who hold tokens but not ETH. Or a deposit paymaster where users pre-fund a gas balance. The paymasterβs validatePaymasterUserOp decides whether to sponsor, and postOp handles accounting after execution.β
Follow-up question:
βWhat are the security risks of paymasters?β
What to say: βGriefing is the main risk β a malicious user could submit expensive UserOperations that the paymaster sponsors, draining its balance. Mitigations include: off-chain validation before signing (verifying paymaster), rate limiting per user, gas caps per UserOp, and requiring token pre-approval before sponsoring (ERC-20 paymaster). Also, oracle manipulation for ERC-20 paymasters β if the price feed is stale, the paymaster could underprice gas and lose money.β
Interview Red Flags:
- π© βJust use meta-transactionsβ β ERC-4337 paymasters are the modern standard
- π© Not understanding the validate β execute β postOp flow
- π© Canβt explain paymaster griefing risks
Pro tip: Knowing specific paymaster services (Pimlico, Alchemy Gas Manager, Biconomy) shows youβve worked with the ecosystem practically, not just theoretically.
β οΈ Common Mistakes
// β WRONG: Paymaster without griefing protection
function _validatePaymasterUserOp(PackedUserOperation calldata userOp, ...)
internal returns (bytes memory, uint256) {
return ("", 0); // Sponsors everything β will be drained!
}
// β
CORRECT: Validate user eligibility and set limits
function _validatePaymasterUserOp(PackedUserOperation calldata userOp, ...)
internal returns (bytes memory, uint256) {
address sender = userOp.getSender();
require(isWhitelisted[sender], "Not eligible");
require(dailyUsage[sender] < MAX_DAILY_GAS, "Daily limit reached");
return (abi.encode(sender), 0);
}
// β WRONG: ERC-20 paymaster with no oracle staleness check
uint256 tokenAmount = gasUsed * gasPrice / tokenPrice; // tokenPrice could be stale!
// β
CORRECT: Check oracle freshness
(, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt < 1 hours, "Stale price feed");
π‘ Concept: Paymaster Flow in Detail
validatePaymasterUserOp(userOp, userOpHash, maxCost)
β Paymaster checks if it will sponsor this UserOp
β Returns context (arbitrary bytes) and validationData
β EntryPoint locks paymaster's deposit for maxCost
// ... UserOp executes ...
postOp(mode, context, actualGasCost, actualUserOpFeePerGas)
β Paymaster performs post-execution accounting
β mode indicates: success, execution revert, or postOp revert
β Can charge user in ERC-20, update internal accounting, etc.
β οΈ Critical detail: The postOp is called even if the UserOp execution reverts (in PostOpMode.opReverted), giving the paymaster a chance to still charge the user for the gas consumed.
π Read: Paymaster Implementations
Source: eth-infinitism/account-abstraction
contracts/samples/VerifyingPaymaster.solβ reference verifying paymastercontracts/samples/TokenPaymaster.solβ ERC-20 gas payment
ποΈ Production paymasters:
π How to Study Paymaster Implementations:
-
Start with
BasePaymaster.solβ the abstract base- Two functions to understand:
validatePaymasterUserOpandpostOp - The
contextbytes are the bridge between them β data from validation flows to post-execution - Notice:
postOpis called even on execution revert (the paymaster still gets to charge)
- Two functions to understand:
-
Read
VerifyingPaymaster.solβ the simpler implementation- Focus on: how
paymasterAndDatais unpacked (paymaster address + custom data) - The validation logic: extract signature, verify against trusted signer
- Notice: no
postOpoverride β the simplest paymaster doesnβt need post-execution logic
- Focus on: how
-
Read
TokenPaymaster.solβ the complex implementation- Follow the flow: validate β estimate token cost β store in context β execute β postOp charges actual cost
- The oracle integration: how does it get the ETH/token exchange rate?
- The refund mechanism: estimated cost vs actual cost, refund difference
-
Compare with production paymasters β Pimlico and Alchemy
- These add: rate limiting, gas caps, off-chain pre-validation
- Notice whatβs missing from the reference implementations (griefing protection, fee margins)
- This gap between reference and production is where security bugs hide
-
Trace one complete sponsored transaction
- UserOp submitted β Bundler validates β EntryPoint calls
validatePaymasterUserOpβ execution β EntryPoint callspostOpβ gas reimbursement - Key question at each step: who pays, and how much?
- UserOp submitted β Bundler validates β EntryPoint calls
Donβt get stuck on: The PostOpMode enum details initially. Just know that opSucceeded = everything worked, opReverted = userβs call failed but paymaster still charges, postOpReverted = rare edge case.
π― Build Exercise: Paymasters
Workspace: workspace/src/part1/module4/exercise3-paymasters/ β starter file: Paymasters.sol, tests: Paymasters.t.sol
-
Implement a simple verifying paymaster that sponsors UserOperations if they carry a valid signature from a trusted signer:
- Add a
verifyingSigneraddress - In
validatePaymasterUserOp, verify the signature inuserOp.paymasterAndData - Return 0 for valid β , 1 for invalid β
- Add a
-
Implement an ERC-20 paymaster that accepts a mock stablecoin as gas payment:
- In
validatePaymasterUserOp:- Verify the user has sufficient token balance
- Return context with user address and estimated token cost
- In
postOp:- Calculate actual token cost based on
actualGasCost - Transfer tokens from user to paymaster
- Calculate actual token cost based on
- Use a simple fixed exchange rate for now (1 USDC = 0.0005 ETH as mock rate)
- In Part 2, youβll integrate Chainlink for real pricing
- In
-
Write tests demonstrating the full flow:
- User submits UserOp with no ETH
- Paymaster sponsors gas
- User pays in tokens
- Verify userβs token balance decreased by correct amount
-
Test edge cases:
- User has insufficient tokens (paymaster should reject in validation)
- UserOp execution reverts (paymaster should still charge in postOp)
- Different gas prices (verify postOp correctly adjusts token cost)
π― Goal: Understand paymaster economics and how DeFi protocols can use them to remove gas friction for users.
π Summary: Paymasters and Gas Abstraction
β Covered:
- Paymaster patterns β verifying, ERC-20, deposit models
- Paymaster flow β validation, context passing, postOp accounting
- Real implementations β Pimlico, Alchemy gas managers
- Edge cases β reverted UserOps, oracle pricing, insufficient balances
Key takeaway: Paymasters enable gasless DeFi interactions, making protocols accessible to users without ETH. Understanding paymaster economics is essential for modern protocol design.
π Cross-Module Concept Links
Backward references (β concepts from earlier modules):
| Module 4 Concept | Builds on | Where |
|---|---|---|
| PackedUserOperation + validationData packing | BalanceDelta bit-packing, uint256 slot layout | M1 β BalanceDelta |
| UserOp validation errors | Custom errors for clear revert reasons | M1 β Custom Errors |
| Type-safe EntryPoint calls | abi.encodeCall for compile-time type checking | M1 β abi.encodeCall |
| EIP-7702 + ERC-4337 combined approach | Delegation designator format, DELEGATECALL semantics | M2 β EIP-7702 |
| EIP-1271 signature verification | Permit2βs SignatureVerification handles EOA + contract sigs | M3 β Permit2 Source Code |
| Smart account permit support | Permit2 works with smart accounts via EIP-1271 | M3 β EIP-2612 Permit |
Forward references (β concepts youβll use later):
| Module 4 Concept | Used in | Where |
|---|---|---|
| UserOp signature testing | vm.sign, vm.addr, fork testing for EntryPoint | M5 β Foundry |
| Smart account upgradeability | UUPS proxy pattern β Kernel, Safe are upgradeable proxies | M6 β Proxy Patterns |
| EntryPoint singleton deployment | CREATE2 deterministic addresses across chains | M7 β Deployment |
Part 2 connections:
| Module 4 Concept | Part 2 Module | How it connects |
|---|---|---|
| EIP-1271 + smart account signatures | M2 β AMMs | Smart accounts using Permit2 for swaps β EIP-1271 verifies the permit signature |
| Paymaster oracle pricing | M3 β Oracles | ERC-20 paymasters need Chainlink feeds for ETH/token exchange rates |
| Batch liquidations via smart accounts | M4 β Lending | Atomic batch liquidation: scan β liquidate multiple β swap rewards in one UserOp |
| Gasless flash loan execution | M5 β Flash Loans | Paymasters can sponsor flash loan arb execution for users |
| Gas sponsorship for vault deposits | M7 β Vaults & Yield | Protocol-sponsored gasless deposits to attract TVL |
| AA security implications | M8 β DeFi Security | msg.sender == tx.origin checks, EIP-1271 griefing, paymaster draining |
| Full AA integration | M9 β Integration Capstone | Capstone should support smart account users with paymaster option |
π Production Study Order
Read these files in order to build progressive understanding of account abstraction in production:
| # | File | Why | Lines |
|---|---|---|---|
| 1 | IAccount.sol | One function: validateUserOp β the minimal smart account interface | ~15 |
| 2 | BaseAccount.sol | Validation helper β see how _validateSignature is separated from nonce/payment handling | ~50 |
| 3 | SimpleAccount.sol | Reference implementation β ECDSA owner validation, execute/executeBatch | ~100 |
| 4 | EntryPoint.sol β handleOps | The orchestrator β follow validate β execute β postOp flow (skim, donβt deep-read) | ~500 |
| 5 | BasePaymaster.sol | Paymaster interface β validatePaymasterUserOp + postOp with context passing | ~60 |
| 6 | VerifyingPaymaster.sol | Simplest paymaster β off-chain signature verification | ~80 |
| 7 | TokenPaymaster.sol | ERC-20 gas payment β oracle integration, postOp accounting | ~200 |
| 8 | OZ SignatureChecker.sol | Universal sig verification β the bridge between EOA and smart account signatures | ~30 |
| 9 | Kernel (ZeroDev) | Production modular account β plugins, session keys, how the industry builds on top of ERC-4337 | ~300 |
Reading strategy: Files 1β3 build the smart account from interface β reference implementation. File 4 is the orchestrator (skim the flow, donβt memorize). Files 5β7 cover paymasters from simple β complex. File 8 is the EIP-1271 bridge. File 9 shows where the industry is heading β modular, pluggable account architecture.
π Resources
ERC-4337
- EIP-4337 specification β full technical spec
- eth-infinitism/account-abstraction β reference implementation
- ERC-4337 docs β Alchemyβs guide
- Bundler endpoints β public bundler services
Smart Account Implementations
- Safe Smart Account β most widely deployed
- Kernel by ZeroDev β modular plugins
- Biconomy Smart Accounts β gas-optimized
- SimpleAccount β reference
EIP-7702
- EIP-7702 specification β EOA code delegation
- Vitalikβs account abstraction roadmap β how EIP-7702 fits
EIP-1271
- EIP-1271 specification β contract signature verification
- Permit2 SignatureVerification.sol β production implementation
- OpenZeppelin SignatureChecker β helper library
Paymasters
- Pimlico paymaster docs
- Alchemy Gas Manager
- eth-infinitism VerifyingPaymaster
- eth-infinitism TokenPaymaster
Modular Accounts
- ERC-6900 specification β modular smart contract accounts (Alchemy)
- ERC-7579 specification β minimal modular accounts (Rhinestone)
- Rhinestone Module Registry β reusable account modules
Deployment Data
- 4337 Stats β account abstraction adoption metrics
- Dune: Smart Account Growth β deployment trends
Navigation: β Module 3: Token Approvals & Permits | Module 5: Foundry Testing β