ERC-4337 Smart Account Tutorial With Web3j

As part of my participation in the Web3j Libraries Full Development Lifecycle project under the LF Decentralized Trust Mentorship Program, I’ve developed an ERC-4337 Smart Account tutorial. It demonstrates how to create a minimal ERC-4337-compatible Smart Account, compile and deploy it with Web3j, and interact with it using specific Account Abstraction User Operations, including function calls and ETH transfers between Smart Accounts.
Introduction: What Is Account Abstraction?
Account Abstraction (AA) is an Ethereum improvement that redefines how user accounts work on-chain. Traditionally, Ethereum distinguishes between two types of accounts:
- Externally Owned Accounts (EOAs), controlled by private keys (e.g., MetaMask wallets).
- Contract Accounts, which are controlled by smart contract code.
Account Abstraction blurs this line by allowing smart contracts to act as user accounts. These smart accounts can initiate transactions, pay gas fees flexibly (e.g., via paymasters), and support custom authentication logic — such as biometrics, multisig, or social recovery — all defined in code.
The key concept behind AA is delegating transaction validation and execution logic to smart contracts via a standardized interface (IAccount) and relaying mechanism through an EntryPoint contract. Instead of sending transactions directly, users submit UserOperations, which bundlers relay to the EntryPoint. The EntryPoint then calls the smart account's validateUserOp() and execute() methods to authorize and perform actions.
To learn more about the fundamentals of account abstraction, you can check this blog post: https://blog.web3labs.com/account-abstraction-in-ethereum-part-1/
Part 1: Contract Creation
Begin by cloning the eth-infinitism account abstraction repo:
For a simple setup, we can just copy these two files:
contracts/interfaces/IAccount.sol
contracts/interfaces/PackedUserOperation.sol
Copying the files avoids the need to manage the entire repo. In this case, I put them in a folder namedSolidityFiles.
Then, we create a new Solidity file in the same directory called SmartAccount.sol by pasting the following minimal contract:
Let's take a look for what this contract does:
- validateUserOp() — Called by the EntryPoint during operation validation. It verifies that the operation is authorized and, if necessary, sends a prefund to the EntryPoint to cover gas costs. This prefund is later refunded after execution. (For this tutorial, note that our validateUserOp() always returns 0, bypassing the signature check. This should NOT be done in a production environment.)
- execute() — Allows the EntryPoint to instruct the smart account to send ETH or make contract calls to any destination. This is how the account actually performs actions on-chain once validated.
- receive() — Accepts ETH directly.
Part 2: Deploying the Smart Account with Web3j
With our contract wrapper ready, it's time to deploy our smart account.
Let’s start by creating a new Web3j project with the Web3j-CLI, using the command
This command will create a new project with the contract we just coded.
(To learn more about this, check out the Web3j documentation: https://docs.web3j.io/4.14.0/command_line_tools/#import-an-existing-project)
Then, in our Java project, the generated contract wrappers are in build/generated/sources/web3j/main/
We will deploy our account on the Sepolia testnet using Pimlico as our bundler:
We then import our generated contract wrapper and use the deploy() method to deploy it:
This deploys the Smart Account contract and logs its address. We can use Sepolia Etherscan to verify the deployed address.
Part 3: Sending ETH Between Smart Accounts
To test sending funds between two smart accounts, we first need to deploy another account.
Repeat the deployment step and store the second address. Again:
With our smart account deployed on-chain, we can now interact with it by sending UserOperation to our bundler.
Let’s try to send some ETH between the two accounts we have deployed. We are using the execute() function, so first we need to encode it:
Then we need to construct a UserOperation(EIP4337Transaction):
The reason we left the gas fields empty is because we can get an estimate using the ethEstimateUserOperationGas API from our bundler.
When a user operation is sent:
- The bundler receives it and simulates it locally to ensure it will succeed.
- If valid, the bundler sends it to the EntryPoint contract.
- The EntryPoint calls validateUserOp() on your smart account.
- This function checks authorization (e.g., via signature).
- It ensures the smart account has enough balance to cover gas. If not, it invokes the smart account to send the required prefund.
- The EntryPoint then calls the function on your smart account.
EIP-4337 introduces a new transaction type (UserOperation) that isn't paid for directly by the user’s EOA. Instead, the EntryPoint contract requires a prefund — an upfront deposit to cover the gas costs of the UserOperation. This deposit must come from the smart account’s balance at the EntryPoint contract. If the smart account lacks sufficient funds, it needs to prefund the missing amount in validateUserOp().
We can manually fund the deployed account with ETH to cover gas costs for its operations via this prefund deposit mechanism.
Now, we can get the gas estimates from our bundler (https://docs.pimlico.io/references/bundler/endpoints/eth_estimateUserOperationGas):
After setting the gas fields based on the returned result, we can submit the operation to our bundler:
After sending the userOp, the API endpoint should return the hash for our userOp, and we can track it on Sepolia Etherscan.
As you can see in the screenshot, we are able to see our call to execute():
Conclusion
This tutorial shows how easy it is to create, deploy, and interact with ERC-4337 Smart Accounts using Web3j. By supporting the creation of and interaction with UserOperation, Web3j enables developers to experiment with one of Ethereum’s most transformative upgrades.
Try building your own Smart Account today and experiment with it, share your feedback, suggest improvements, and help us shape the future of ERC-4337 support in Web3j. You’re also warmly invited to join the live Account Abstractions demo during the upcoming Web3j Meetup: Mentees’ Contributions to Web3j 5 on November 19, 2025.
📅 Register now to secure your spot
As one of the Web3j mentees under the LF Decentralized Trust program, I see firsthand how community contributions strengthen the Web3j ecosystem.
You can connect with us on the LF Decentralized Trust Discord or join our community call.