Quickstart
A quick technical guide for getting started with userop.js
This guide provides a quick walkthrough for integrating userop.js and explanation of high level concepts.
Install
The userop.js package is hosted on npm and can be installed using one of the common package managers.
npm install userop
pnpm install userop
yarn add userop
Supported viem and ethers.js versions
Userop.js has been tested against the following versions of viem and ethers.js. Using a lower version in your application may result in type errors and other incompatibilities.
- viem:
^2.9.12
- ethers.js:
^6.11.1
Sending a UserOperation
Below is a full code snippet for sending ETH from your Smart Account to another address with the following configurations.
- Using
v0.6
of the ERC-4337 EntryPoint SimpleAccount
implementation- viem
PublicClient
or ethersJsonRpcProvider
- EOA owner using a viem
WalletClient
or ethersSigner
Looking to get your hands on some code samples?
Checkout the ERC-4337 examples repository for a collection of userop.js related use cases.
import { createPublicClient, createWalletClient } from "viem";
import { V06 } from "userop";
// 1. Initialize clients to eth node and wallet.
const ethClient = createPublicClient({
// See https://viem.sh/docs/clients/public
});
const walletClient = createWalletClient({
// See https://viem.sh/docs/clients/wallet
});
// 2. Initialize client to smart account.
const account = new V06.Account.Instance({
...V06.Account.Common.SimpleAccount.base(ethClient, walletClient),
})
// 3. Initiate an ETH transfer from your smart account.
const send = await account
.encodeCallData("execute", [TO_ADDRESS, VALUE, "0x"])
.sendUserOperation();
// 4. Wait for UserOperation to be included onchain.
const receipt = await send.wait();
import { JsonRpcProvider, BaseWallet } from "ethers";
import { V06 } from "userop";
// 1. Initialize clients to eth node and wallet.
const ethClient = new JsonRpcProvider(
// See https://docs.ethers.org/v6/api/providers/jsonrpc
);
const walletClient = BaseWallet(
// See https://docs.ethers.org/v6/api/wallet
);
// 2. Initialize client to smart account.
const account = new V06.Account.Instance({
...V06.Account.CommonConfigs.SimpleAccount.base(
ethClient,
walletClient,
),
})
// 3. Initiate an ETH transfer from your smart account.
const send = await acc
.encodeCallData("execute", [TO_ADDRESS, VALUE, "0x"])
.sendUserOperation();
// 4. Wait for UserOperation to be included onchain.
const receipt = await send.wait();
V06
namespace
V06
namespaceIn the code snippet above, all userop.js modules are organised under the root namespace V06
. This simply represents v0.6
of the ERC-4337 EntryPoint. Similarly, future protocol versions will have its own root namespace (e.g. V07
for v0.7
).
Account
instance
Account
instanceThe core component of userop.js is the Account
instance which represents an interface to a user's Smart Account. The Account
instance is compatible with both viem and ethers.js and used to interact with all the various ERC-4337 components.
At its core, an Account
can also be configured to support any arbitrary combination of node, bundler, account, and paymaster. In the above code snippet we've used a helper function in the Common Configs module, to create a minimum configuration for SimpleAccount
. Under the hood this function returns an object of the following type.
export interface AccountOpts<A extends Abi, F extends Abi> {
// Required global values
accountAbi: A;
factoryAbi: F;
factoryAddress: Address;
ethClient: PublicClient | JsonRpcProvider;
// Optional global values
bundlerClient?: PublicClient | JsonRpcProvider;
entryPointAddress?: Address;
salt?: bigint;
waitTimeoutMs?: number;
waitIntervalMs?: number;
// Required hook methods
setFactoryData: Hooks.SetFactoryDataFunc<F>;
requestSignature: Hooks.RequestSignatureFunc;
// Optional hook methods
requestGasPrice?: Hooks.RequestGasPriceFunc;
requestGasValues?: Hooks.RequestGasValuesFunc;
requestPaymaster?: Hooks.RequestPaymasterFunc;
onBuild?: Hooks.OnBuildFunc;
}
It's useful to understand how Account Options work if you intend on extending an account's configuration beyond what's available in Common Configs. We'll go into details in later sections, but the easiest example of this would be extending the base SimpleAccount
config to add a Stackup paymaster.
const account = new V06.Account.Instance({
...V06.Account.Common.SimpleAccount.base(
ethClient,
walletClient,
),
requestPaymaster: V06.Account.Hooks.RequestPaymaster.withCommon({
variant: "stackupV1",
parameters: { rpcUrl: STACKUP_V1_PM_RPC, type: "payg" },
}),
})
You can also create AccountOpts
for any ERC-4337 compliant implementation that isn't just SimpleAccount
. By providing the relevant ABIs, addresses, and hook functions, the Account
instance will automatically know how to build the correct User Operation while providing intelligent typing.
Account
hooks
Account
hooksRegardless of the account implementation used, most User Operations follow a similar build lifecycle.
- Generate the
sender
address - Set the
initCode
if applicable - Resolve
nonce
- Fetch the gas prices
- Estimate the gas values
- Request paymaster data if applicable
- Sign the final
userOpHash
- Send the User Operation
Throughout the build lifecycle, an Account
instance will call into various hook functions to fill in the required fields on a User Operation. For V06
, these hooks include:
setFactoryData
requestSignature
requestGasPrice
requestGasValues
requestPaymaster
onBuild
For details on each hook function see Account Options. As long as it follows the defined interface, these functions can be implemented in any arbitrary way to return values suited for a particular Smart Account implementation. For example, the requestSignature
for SimpleAccount
would return a signature of the userOpHash
that was signed by the account's EOA owner.
Updated 8 months ago