Building Account Abstraction ERC-4337 (Part-1) Create Wallet Contract (initCode)

Shishir Singh
cumberlandlabs
Published in
4 min readJul 23, 2023
Dehradun Uttarakhand

In this series, We will build a complete end-to-end Account Abstraction use case in NodeJS.

Prerequisites

We require a basic to intermediate level of knowledge of the following topics:

  1. ERC-4337 Account Abstraction
  2. Web3
  3. expand.network
  4. Ganche and Truffle framework

With these prerequisites in place, you should be able to understand and follow the rest of this series fully. Let’s dive in:

Environment

Truffle v5.8.1 (core: 5.8.1)

Ganache v7.7.7

Solidity — 0.8.17 (solc-js)

Node v18.14.0

Web3.js v1.8.2

Primarily building and understanding the basis account abstraction (AA) consists of 3 major parts:

  1. On-Chain Wallet Creation
  2. Composing and bundling multiple user transactions and executing this bundle transaction on-chain
  3. Making the paymaster contract to pay for the gas fee on behalf of the user

On-Chain Wallet Creation

This step consists of the creation of a user on-chain wallet contract. I will demonstrate this step on the local ganache main net fork and deploy the wallet contract.

First, spin the local dev environment using ganache:

ganache-cli --fork <your node url> \
--wallet.accounts="0xb239d595137590b5c6331a8ec9798e56bc8cd5e5028a2c3e6a88bc5a16ad5a70,1000000000000000000000000" \
--database.dbPath <local db folder path> --chain.chainId 1

Generate a few random public/private key combinations and keep them in your test data JSON file. Sample here.

And that’s it. We are all set for the creation of our first on-chain wallet. Refer to the code here and make sure we have executed the executeHandleOps with only initCode. Later we can also execute the same operation along with the paymaster, which is covered in the later part of the series. For now, this operation is only about on-chain wallet creation.

async function init() {

await initAddresses() ;

await composeInitCode();

await fundContractsAndAddresses() ;

// await composeWETHTransferCallData();

await executeHandleOps(initCode,'0x', false) ;

// await executeHandleOps(initCode,'0x', true) ;

// await executeHandleOps('0x',callData, false) ;

// await composeWETHTransferCallData();

// await executeHandleOps('0x',callData, true) ;

await getBalance() ;

}
Transaction Success 🎉: 0x8243543959a1590d1dc074e13e033990d27ec2cc22a931a1279e32c8e34df681 
Transaction Success 🎉: 0x3626b22e33e57ba0b42fb61c6782b7290f5bda5ca7ed877f7ad5b77bc4f4360b
Transaction Success 🎉: 0xb4e4b995c9573337d40889de92ff2055f71f1541209ea5865f7e22046621820d
Transaction Success 🎉: 0x9c26a398c20554063476ff779237548fde9d66327a0a189f05f0dc67d9aee6c5
Transaction Success 🎉: 0xc5e558acd2f9b0c5386d624c2937e08767abcc964a2e8a85b74e1cc7dc8c33fa
Handleops Success --> 0xb91a30d6037b2af904c95b6c46e9ba3a617da44e56dff689e1df0972cc7c07bc
Alice ETH Balance 3795.498538896158417996
Alice sender wallet 0x94844034F46805DF14fCBf3c2aFDA868dD009aED ETH Balance 1.9797249176949305
Alice WETH Balance 0.25
Alice sender wallet 0x94844034F46805DF14fCBf3c2aFDA868dD009aED WETH Balance 0.25
Bob WETH Balance 0

With the successful execution, most likely, you will get the on-chain wallet contract address as ‘0x94844034F46805DF14fCBf3c2aFDA868dD009aED’ if you are passing the salt as 0. And the wallet's owner is Alice's public key (0x93….3030).

Below is the sequence diagram of what we have just built above in the code:

Account abstraction on-chain wallet creation

Once our wallet is successfully deployed, let’s define the major use cases for this wallet contract.

Social Recovery

This feature is a major win for the on-chain wallet vs. the off-chain wallets category. If the user forgets his/her mnemonic key, they can reset the wallet's ownership to the new public key. This wallet's key function is to provide social recovery via guardian consensus. At a very high level, we can design our wallet contract to perform below functions:

  1. Execute(): In this method, the on-chain wallet will execute the desired call data operation, which we will see in the next part of this series. We have already built this method in our code above.
  2. AddGuardian(): This method will add the desired guardian to the wallet contract. And establish on-chain consensus rules. Only the wallet owner will decide to invoke this method.
  3. RemoveGuardian(): This method will remove the desired guardian from the wallet contract. Only the wallet owner will decide to invoke this method.
  4. ChangeOwner(): One of the major use cases of wallet contracts is to change the ownership of it. In our case above, if Alice loses her private key, she can change the wallet's owner to another public address. This can be achieved once all the Guardian currently on the contract reaches an off-chain consensus.

Future Business Opportunities

  • Custodian-as-a-service: Our current wallet provider entities can potentially fit into this category where they can provide 3 major services.

Creation of an on-chain wallet via its UI/UX

Give choices to choose from well-established and high TVL Guardian to choose from

Change the on-chain wallet owner in case of user loses the private key of the wallet contract owner.

  • Guardian-as-a-service: Entities can establish themself as trusted guardians of the user’s on-chain wallet. They can market themselves by showing their cumulative wallet TVL as their credentials.

Now that we have on chain wallet successfully created, in the next part of this series, we will build the call data to execute via this wallet contract.

--

--