Building with Account Abstraction: ERC-4337 Tutorial
Learn to build with account abstraction using ERC-4337. This tutorial covers smart contract wallets, UserOp bundlers, paymasters, and the best account a...
Building with Account Abstraction: ERC-4337 and Beyond
TL;DR
Account abstraction (AA) replaces externally owned accounts (EOAs) with smart contract wallets, enabling gasless transactions, social recovery, session keys, and batched operations. ERC-4337 introduced this without protocol-level changes, and newer standards like ERC-7702 (Pectra, 2025) and EIP-7560 push the ecosystem further. This account abstraction tutorial walks you through building a smart contract wallet, deploying a paymaster, and integrating with a UserOp bundler using production-ready SDKs.
---
What Is Account Abstraction?
Account abstraction decouples transaction validation from Ethereum's rigid EOA model. Instead of requiring every transaction to originate from a private-key-signed EOA, AA allows smart contract wallets to define their own authentication logic, gas payment strategies, and execution flows.
Why It Matters in 2026
- User onboarding: Users can sign in with email, passkeys, or social login — no seed phrases.
- Gasless UX: Paymasters sponsor gas fees, so users never need to hold ETH.
- Programmable security: Multi-sig, time-locks, spending limits, and social recovery are native.
- Batched transactions: Approve + swap in a single UserOperation.
---
ERC-4337 Architecture Overview
ERC-4337 introduced a higher-level mempool for UserOperation objects. Here's the architecture:
User → UserOperation → Bundler → EntryPoint Contract → Smart Contract Wallet
↕
Paymaster (optional gas sponsorship)
Key Components
| Component | Role | |-----------|------| | UserOperation (UserOp) | A pseudo-transaction struct containing sender, calldata, gas limits, and signature | | Bundler | An off-chain service that batches UserOps and submits them to the EntryPoint | | EntryPoint | A singleton contract (v0.7+) that validates and executes UserOps | | Smart Contract Wallet | The user's on-chain account with custom validation logic | | Paymaster | An optional contract that sponsors gas fees on behalf of users | | Account Factory | Deploys new smart contract wallets via CREATE2 for deterministic addresses |
---
Step 1: Building a Minimal Smart Contract Wallet
Let's build a minimal ERC-4337-compatible wallet. The wallet must implement the IAccount interface from the EntryPoint:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
contract SimpleSmartWallet is IAccount {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
address public owner;
IEntryPoint private immutable _entryPoint;
constructor(IEntryPoint entryPoint_, address owner_) {
_entryPoint = entryPoint_;
owner = owner_;
}
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external override returns (uint256 validationData) {
require(msg.sender == address(_entryPoint), "only EntryPoint");
// Verify the signature
bytes32 ethSignedHash = userOpHash.toEthSignedMessageHash();
address recovered = ethSignedHash.recover(userOp.signature);
if (recovered != owner) {
return 1; // SIG_VALIDATION_FAILED
}
// Prefund the EntryPoint if needed
if (missingAccountFunds > 0) {
(bool success, ) = payable(msg.sender).call{
value: missingAccountFunds
}("");
require(success, "prefund failed");
}
return 0; // SIG_VALIDATION_SUCCESS
}
function execute(
address target,
uint256 value,
bytes calldata data
) external {
require(msg.sender == address(_entryPoint), "only EntryPoint");
(bool success, bytes memory result) = target.call{value: value}(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
receive() external payable {}
}
This is a production-starting-point for ERC-4337 development. The validateUserOp function is where all custom authentication logic lives — you could replace ECDSA with WebAuthn/passkey verification, multi-sig, or any scheme.
---
Step 2: Writing a Paymaster in Solidity
A paymaster Solidity contract sponsors gas for users. Here's a simple verifying paymaster:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@account-abstraction/contracts/core/BasePaymaster.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
contract VerifyingPaymaster is BasePaymaster {
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
address public verifyingSigner;
constructor(
IEntryPoint entryPoint_,
address signer_
) BasePaymaster(entryPoint_) {
verifyingSigner = signer_;
}
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) internal view override returns (bytes memory context, uint256 validationData) {
// Extract the paymaster signature from paymasterAndData
bytes calldata signature = userOp.paymasterAndData[20:];
bytes32 ethSignedHash = userOpHash.toEthSignedMessageHash();
address recovered = ethSignedHash.recover(signature);
if (recovered != verifyingSigner) {
return ("", 1); // Reject
}
return ("", 0); // Approved
}
}
The paymaster must be staked and funded on the EntryPoint. When a UserOp includes paymasterAndData, the EntryPoint calls the paymaster to validate and deducts gas costs from the paymaster's deposit instead of the user's wallet.
---
Step 3: Integrating a UserOp Bundler
The UserOp bundler is the off-chain infrastructure that collects UserOperations and submits them on-chain. Popular bundler options in 2026:
| Bundler | Language | Notes | |---------|----------|-------| | Rundler (Alchemy) | Rust | Production-grade, high throughput | | Stackup Bundler | Go | Open-source, easy to self-host | | Infinitism Bundler | TypeScript | Reference implementation | | Pimlico Alto | TypeScript | Feature-rich, widely adopted |
Sending a UserOp via an Account Abstraction SDK
Here's how to construct and send a UserOp using permissionless.js (by Pimlico), one of the leading account abstraction SDK options:
import { createSmartAccountClient } from "permissionless";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
// 1. Set up clients
const publicClient = createPublicClient({
chain: sepolia,
transport: http("https://rpc.sepolia.org"),
});
const pimlicoClient = createPimlicoClient({
transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"),
entryPoint: { address: "0x0000000071727De22E5E9d8BAf0edAc6f37da032", version: "0.7" },
});
// 2. Create smart account
const signer = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const smartAccount = await toSimpleSmartAccount({
client: publicClient,
owner: signer,
entryPoint: { address: "0x0000000071727De22E5E9d8BAf0edAc6f37da032", version: "0.7" },
});
// 3. Create smart account client with bundler + paymaster
const smartAccountClient = createSmartAccountClient({
account: smartAccount,
chain: sepolia,
bundlerTransport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"),
paymaster: pimlicoClient,
});
// 4. Send a gasless transaction
const txHash = await smartAccountClient.sendTransaction({
to: "0xRecipientAddress",
value: 0n,
data: "0x",
});
console.log("Transaction hash:", txHash);
This sends a gasless transaction — the paymaster covers the fees, and the bundler handles submission.
---
Beyond ERC-4337: What's New in 2026
ERC-7702 (Live Since Pectra, 2025)
ERC-7702 allows EOAs to temporarily delegate to smart contract code within a single transaction. This means existing MetaMask users can get AA features (batching, sponsorship) without migrating to a new address.
// ERC-7702 authorization tuple (signed by EOA)
// Allows the EOA to behave as a smart contract wallet for one tx
{
chainId: 1,
address: "0xSmartWalletImplementation",
nonce: 42,
yParity: 0, r: "0x...", s: "0x..."
}
EIP-7560: Native Account Abstraction
EIP-7560 proposes enshrining AA into the protocol itself, eliminating the need for a separate UserOp mempool. While still in development, it represents the long-term direction for Ethereum.
Key Differences
| Feature | ERC-4337 | ERC-7702 | EIP-7560 | |---------|----------|----------|----------| | Protocol change required | No | Yes (Pectra) | Yes (future) | | Works with existing EOAs | No | Yes | Yes | | Separate mempool | Yes | No | No | | Production ready | Yes | Yes | No |
---
People Also Ask
What is the difference between ERC-4337 and ERC-7702?
ERC-4337 uses a separate mempool and EntryPoint contract to enable smart contract wallets without protocol changes. ERC-7702 (shipped with Pectra in 2025) lets existing EOAs temporarily adopt smart contract logic, enabling AA features without changing your address. They're complementary — many wallets use both.
Do users need ETH to use account abstraction?
No. With a paymaster, gas fees can be sponsored by a dApp or paid in ERC-20 tokens (like USDC). This is one of the primary UX benefits of account abstraction.
Which account abstraction SDK should I use?
The most popular options in 2026 are permissionless.js (Pimlico), Alchemy's aa-sdk, ZeroDev SDK, and Biconomy SDK. Choose based on your bundler preference and feature needs (session keys, multi-chain support, passkey integration).
Is account abstraction safe for production?
Yes. The ERC-4337 EntryPoint v0.7 has been audited multiple times and secures billions in assets. However, custom wallet and paymaster implementations should always be independently audited.
---
Actionable Takeaways
- Start with an SDK: Don't build from scratch. Use permissionless.js or aa-sdk to handle UserOp construction, bundler communication, and paymaster integration.
- Use EntryPoint v0.7: It's the current standard. Avoid v0.6 for new projects.
- Consider ERC-7702 for EOA users: If your users already have MetaMask wallets, ERC-7702 lets them access AA features without migration.
- Always audit your paymaster: A misconfigured paymaster can be drained. Implement spending limits, whitelisting, and rate limiting.
- Test on Sepolia first: Use Pimlico or Alchemy's free testnet bundlers before deploying to mainnet.
- Plan for multi-chain: Account abstraction works across EVM chains. Use
CREATE2factories for deterministic addresses across networks.
---
Last updated: August 1, 2026. Built and tested against EntryPoint v0.7 and Solidity 0.8.24+.