Just attended the “Account Abstraction in Production” workshop at LA Tech Week 2025, and I’m convinced: ERC-4337 is finally ready for mainstream adoption.
We’re seeing real traction - over 12 million smart accounts deployed across chains, and the UX improvements are massive.
What is Account Abstraction (ERC-4337)?
Traditional EOA (Externally Owned Accounts):
- Controlled by single private key
- Must hold ETH for gas
- No transaction batching
- No social recovery
- Lost key = lost funds forever
Smart Contract Accounts (ERC-4337):
- Programmable account logic
- Gasless transactions (paymasters)
- Batch multiple operations
- Social recovery (guardians)
- Session keys (limited permissions)
- Hardware 2FA support
Key difference: Your wallet is a smart contract, not just a key pair.
The ERC-4337 Architecture
Core Components
1. UserOperation (UserOp)
- Pseudo-transaction object (not a real Ethereum transaction)
- Contains: sender, nonce, calldata, gas limits, paymaster info, signature
2. Bundler
- Collects UserOps from mempool
- Bundles multiple UserOps into single transaction
- Sends to EntryPoint contract
3. EntryPoint Contract
- Single global contract (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789)
- Validates and executes UserOps
- Manages gas accounting
4. Paymaster
- Optional contract that sponsors gas fees
- Allows gasless transactions for users
- Can implement custom gas policies
5. Smart Account
- User’s wallet contract
- Implements validation and execution logic
- Can have custom features (multisig, recovery, etc.)
Transaction Flow
User → Create UserOp → Send to Bundler Pool
↓
Bundler collects UserOps
↓
Bundle → EntryPoint.handleOps()
↓
EntryPoint validates each UserOp
↓
EntryPoint executes account.execute()
↓
Paymaster pays gas (if present)
Real-World Use Cases from LA Tech Week
1. Gasless Onboarding (Coinbase Wallet)
Problem: New users don’t have ETH for gas fees.
Solution: Paymaster sponsors first N transactions.
// Paymaster contract
contract OnboardingPaymaster is BasePaymaster {
mapping(address => uint256) public sponsoredTxCount;
uint256 constant MAX_SPONSORED = 10;
function validatePaymasterUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) external override returns (bytes memory context, uint256 validationData) {
address sender = userOp.sender;
// Check if user still eligible for sponsored gas
require(sponsoredTxCount[sender] < MAX_SPONSORED, "Sponsorship limit reached");
sponsoredTxCount[sender]++;
// Approve payment
return ("", 0);
}
function postOp(
PostOpMode mode,
bytes calldata context,
uint256 actualGasCost
) external override {
// Paymaster pays the gas cost
// (deducted from paymaster deposit in EntryPoint)
}
}
Results (Coinbase Wallet, Q3 2025):
- 2.3M users onboarded with gasless transactions
- 94% retention vs 67% with traditional gas-required flow
- $1.2M in sponsored gas costs → $15M in user deposits
2. Social Recovery (Argent, Safe)
Problem: Lost seed phrase = lost funds.
Solution: Guardians can recover account.
contract SocialRecoveryAccount is BaseAccount {
address public owner;
address[] public guardians;
uint256 public recoveryThreshold; // e.g., 2 of 3 guardians
mapping(address => uint256) public recoveryVotes;
address public proposedOwner;
function proposeRecovery(address newOwner) external {
require(isGuardian(msg.sender), "Not a guardian");
if (proposedOwner != newOwner) {
proposedOwner = newOwner;
recoveryVotes[newOwner] = 0;
}
recoveryVotes[newOwner]++;
if (recoveryVotes[newOwner] >= recoveryThreshold) {
owner = newOwner;
delete proposedOwner;
emit RecoveryExecuted(newOwner);
}
}
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external override returns (uint256 validationData) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);
// Validate signature from current owner
require(signer == owner, "Invalid signature");
return 0; // Valid
}
}
Real data (Argent, 2025):
- 47,000 account recoveries performed
- $180M in assets recovered
- Average recovery time: 48 hours
3. Session Keys (Gaming)
Problem: Users don’t want to sign every transaction in games.
Solution: Grant limited permissions to session key.
contract SessionKeyAccount is BaseAccount {
struct SessionKey {
address key;
uint48 validUntil;
uint48 validAfter;
address allowedContract; // Can only call this contract
uint256 maxGasPrice;
}
mapping(address => SessionKey) public sessionKeys;
function addSessionKey(
address key,
uint48 validUntil,
address allowedContract,
uint256 maxGasPrice
) external onlyOwner {
sessionKeys[key] = SessionKey({
key: key,
validUntil: validUntil,
validAfter: uint48(block.timestamp),
allowedContract: allowedContract,
maxGasPrice: maxGasPrice
});
}
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external override returns (uint256 validationData) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
address signer = hash.recover(userOp.signature);
// Check if signer is session key
SessionKey storage session = sessionKeys[signer];
if (session.key == signer) {
// Validate session constraints
require(block.timestamp >= session.validAfter, "Session not started");
require(block.timestamp <= session.validUntil, "Session expired");
require(userOp.maxFeePerGas <= session.maxGasPrice, "Gas too high");
// Validate target contract
address target = address(bytes20(userOp.callData[0:20]));
require(target == session.allowedContract, "Invalid contract");
return 0; // Valid session key
}
// Fall back to owner validation
require(signer == owner, "Invalid signature");
return 0;
}
}
Use case: Web3 Gaming
- Player authorizes session key for 24 hours
- Session key can only interact with game contract
- Max 50 gwei gas price
- Player doesn’t sign every action (better UX)
4. Batch Transactions
Problem: Multiple approvals required (approve token, then swap).
Solution: Batch into single UserOp.
// Create UserOp with batched calls
const userOp = {
sender: accountAddress,
nonce: await account.getNonce(),
callData: account.interface.encodeFunctionData('executeBatch', [
[
// Call 1: Approve USDC
{
to: USDC_ADDRESS,
value: 0,
data: usdc.interface.encodeFunctionData('approve', [
UNISWAP_ROUTER,
ethers.parseUnits('1000', 6)
])
},
// Call 2: Swap on Uniswap
{
to: UNISWAP_ROUTER,
value: 0,
data: router.interface.encodeFunctionData('swapExactTokensForTokens', [
ethers.parseUnits('1000', 6),
ethers.parseUnits('990', 6),
[USDC_ADDRESS, WETH_ADDRESS],
accountAddress,
deadline
])
}
]
]),
// Gas limits, paymaster, signature...
};
// Send to bundler
await bundler.sendUserOperation(userOp);
Result: Two transactions → One signature, one confirmation.
Production Adoption Numbers (LA Tech Week 2025)
Smart Account Deployments
Total deployed (all chains, Oct 2025): 12.4 million
By provider:
- Safe (formerly Gnosis Safe): 4.2M accounts
- Biconomy: 3.1M accounts
- Alchemy Account Kit: 2.8M accounts
- ZeroDev: 1.4M accounts
- Coinbase Smart Wallet: 0.9M accounts
Transaction Volume
Monthly UserOps (Sept 2025): 28 million
Gas sponsored by paymasters: $4.2M/month
Average UserOp cost: $0.15 (vs $0.50 for regular tx)
Chain Distribution
- Polygon: 35% of smart accounts (low gas costs)
- Optimism: 22% (OP Stack adoption)
- Base: 18% (Coinbase integration)
- Arbitrum: 15%
- Ethereum mainnet: 10% (high-value accounts only)
Developer Tools & SDKs
1. Alchemy Account Kit
import { AlchemyProvider } from '@alchemy/aa-alchemy';
import { LightSmartContractAccount } from '@alchemy/aa-accounts';
const provider = new AlchemyProvider({
apiKey: 'your-api-key',
chain: mainnet,
});
const account = new LightSmartContractAccount({
chain: mainnet,
owner: ownerAddress,
factoryAddress: '0x...',
rpcClient: provider,
});
// Send UserOp
const result = await provider.sendUserOperation({
target: '0x...',
data: '0x...',
value: 0n,
});
console.log(`UserOp hash: ${result.hash}`);
2. Biconomy SDK
import { BiconomySmartAccount } from '@biconomy/account';
import { Bundler, Paymaster } from '@biconomy/modules';
const bundler = new Bundler({
bundlerUrl: 'https://bundler.biconomy.io/api/v2/80001/...',
});
const paymaster = new Paymaster({
paymasterUrl: 'https://paymaster.biconomy.io/api/v1/80001/...',
});
const smartAccount = await BiconomySmartAccount.create({
signer: signer,
bundler: bundler,
paymaster: paymaster,
});
// Gasless transaction
const tx = await smartAccount.sendTransaction({
to: '0x...',
data: '0x...',
});
3. ZeroDev Kernel
import { createKernelAccount, createKernelAccountClient } from '@zerodev/sdk';
const account = await createKernelAccount(publicClient, {
signer: signer,
});
const client = createKernelAccountClient({
account,
chain: mainnet,
transport: http('https://rpc.url'),
sponsorUserOperation: async (userOp) => {
// Custom paymaster logic
},
});
// Send transaction
const hash = await client.sendTransaction({
to: '0x...',
value: parseEther('0.1'),
});
Security Considerations
1. Signature Validation Vulnerabilities
Risk: Weak signature validation allows unauthorized access.
Best practice:
function validateUserOp(...) external override returns (uint256) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
// ALWAYS use EIP-191 or EIP-712 signed message hash
// NEVER use raw userOpHash (vulnerable to replay)
address signer = hash.recover(userOp.signature);
require(signer == owner, "Invalid signature");
return 0;
}
2. Paymaster Abuse
Risk: Malicious users drain paymaster funds.
Mitigation:
- Rate limiting per address
- Whitelist allowed operations
- Max gas price caps
- Circuit breakers for unusual activity
3. Upgradeability Risks
Risk: Malicious upgrade steals funds.
Best practice:
- Use timelock for upgrades (24-48 hours)
- Require multisig approval
- Transparent proxy pattern
- Immutable critical functions (ownership transfer)
My Questions for the Community
-
Which provider are you using? Alchemy, Biconomy, ZeroDev, or custom?
-
Paymaster economics: How much should apps spend on sponsored gas? Is it worth it for retention?
-
Session keys: What’s the right permission model? Time-based, transaction count, or spend limit?
-
Recovery mechanisms: Social recovery (guardians) vs hardware 2FA vs both?
Account abstraction is finally here. The UX improvements are real and measurable.
David Kim
DAO Tooling Developer
Resources:
- ERC-4337 specification: ERC-4337: Account Abstraction Using Alt Mempool
- Alchemy Account Kit: https://accountkit.alchemy.com/
- Biconomy SDK: https://docs.biconomy.io/
- ZeroDev docs: https://docs.zerodev.app/
- Bundler list: ERC-4337 Documentation
- LA Tech Week 2025 (October 13-19, Los Angeles)