Creating a Basic Smart Wallet Using the ERC-4337 Standard
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
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.
Next, copy the RPC endpoint. We will need it soon.
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.
Next Trend Realty LLC./wwwHar.com/Chester-Swanson/agent_cbswan
1 年Thanks for Sharing.