kriptonio logo
| Blog

Updated on 21 Jun 2023

How to create simple Smart Wallet based on ERC-4337 standard

#web3#erc4337#smart-wallet

Account abstraction based on the ERC-4337 standard is a new way to represent user wallets on Ethereum, offering multiple benefits for users. Wallets will be represented as smart contracts, allowing for the abstraction and programmability of various aspects of current wallets. These include the ability to change the signer/owner of the wallet, the possibility of social recovery, paying gas fees in tokens, sponsoring transaction gas fees, and more.

This article is intended for readers who are already familiar with the basic concepts and components of the ERC-4337 standard and want to learn how to create a simple smart wallet based on the ERC-4337 standard.

Key ingredients

To create a smart wallet, we will need the following:

  • Bundler RPC
  • Solidity smart contracts for the smart wallet
  • Web3 libraries

Bundler RPC

For the bundler RPC, you can get one for free from Kriptonio. Simply register on Kriptonio and create a new bundler RPC endpoint.

In this example, we will use the Polygon Mumbai blockchain network.

New Bundler

Next, copy the RPC endpoint. We will need it soon.

Copy RPC

Solidity Smart Contracts for Smart Wallet

We will use contracts provided by eth-infinitism, specifically SimpleAccount.sol for our smart wallet and SimpleAccountFactory.sol. The latter one is used to create new smart wallets on the chain.

Web3 libraries

For sending RPC requests to the blockchain network, we will use the ethers v5 library, and as a helper library for creating and signing UserOperation structs, we will use userop library.

Project setup & installing dependencies

Initialize a new npm project.

npm init -y

Install the required smart wallet dependencies.

yarn add ethers@5 userop

We will also use TypeScript in the project, so let's install TypeScript dependencies as well.

yarn add -D typescript ts-node
yarn run tsc --init

Let's code

The first thing we need to do is to create a new classic wallet (EOA) that will be the owner of our new smart wallet. The owner is allowed to send transactions from the smart wallet.

const privateKey = ethers.Wallet.createRandom().privateKey;
const owner = new ethers.Wallet(privateKey);

To create a new Smart Wallet, we need two smart contracts which should be deployed before deploying our smart wallet. The first contract is the EntryPoint contract. It acts as a mediator between bundlers and the actual smart contract wallet.

The second one is the contract used as a factory for deploying a new instance of our smart wallet.

The former one is deployed by the core team, and the latter one has to be deployed by the creator of the smart wallet contract. In this case, it's eth-infinitism.

In both cases, we don't need to worry about deployment, so let's just specify the addresses of these contracts.

const entryPointAddress = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
const factoryAddress = '0x9406Cc6185a346906296840746125a0E44976454';

We are now ready to use the userop library to prepare our smart wallet contract object.

On top of your file, add the following import:

import { Client, Presets } from "userop";

Now you can create a smart account builder instance:

const smartAccount = await Presets.Builder.SimpleAccount.init(
  owner,
  bundlerRpcUrl,
  {
    entryPoint: entryPointAddress,
    factory: factoryAddress,
  }
);

This will create a builder instance which knows how to create user operations based on SimpleAccount.sol smart contract.

It also knows how to deploy the smart contract upon sending first transaction.

You can already see the future address of our smart wallet by calling getSender method.

console.log('smart wallet address', smartAccount.getSender());

IMPORTANT: In order to successfully send transactions later, the smart wallet needs to have enough funds to pay for gas fees. In this example using the Polygon Mumbai network, we will use the Polygon Mumbai Faucet to get some free test tokens. Simply go to the faucet and paste your wallet address. Wait a few seconds and you should see some tokens in your wallet.

To send transactions from the smart wallet, we need to construct a UserOperation struct, fill it with gas estimates, and sign it. User Operation contains following fields:

{
  sender,
  nonce,
  initCode,
  callData,
  callGasLimit,
  verificationGasLimit,
  preVerificationGas,
  maxFeePerGas,
  maxPriorityFeePerGas,
  paymasterAndData,
  signature,
}

We won't go into details for each field. Each of these fields is exampled in ERC-4337 spec.

To speed up the process we will be using userop library to create and sign UserOperation struct.

const client = await Client.init(bundlerRpcUrl, {
  entryPoint: entryPointAddress,
});

const result = await client.sendUserOperation(
  smartAccount.execute(smartAccount.getSender(), 0, "0x"),
);

This user operation will send 0 MATIC back to the owner. This transaction doesn't make much sense in a real-life scenario, but it's enough to demonstrate account deployment and transaction sending in our scenario.

Let's now wait for the transaction to be mined.

const event = await result.wait();
console.log(`Transaction hash: ${event?.transactionHash}`);

After transaction is mined you can extract transaction hash from the event object.

As this is the first transaction from the smart wallet, the smart wallet itself will be deployed.

Congratulations 🎉! You have successfully deployed your smart wallet and sent the first user operation via it.

Source Code

You can find the complete source code used in this article here.

Questions

Hopefully, this article was helpful. If you have any questions, feel free to ask them in our Discord server.

You may also like to read

Introducing Kriptonio: The All-in-One Platform for Building and Operating Web3 Applications

As the world increasingly embraces blockchain technology, the demand for Web3 applications that can take advantage of its unique features…

featured image

Published on 2 May 2023