Account Options

A reference guide to AccountOpts for configuring your own instance of a userop.js Account.

AccountOpts

AccountOpts has a flexible interface for allowing an Account to be configured with any compliant ERC-4337 component.

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;
}

Required global values

accountAbi

This is a JSON ABI of the Smart Account's implementation contract. Ensure your ABI uses TypeScript's const assertion.

const ACCOUNT_ABI = [
  // ABI here...
] as const

This ABI will allow your Account instance to provide intelligent typing on encodeCallData. For example, if your smart account has the following function:

function execute(address dest, uint256 value, bytes calldata func) external

Then, Account will be able to enforce proper types in your application:

const build = await acc
  .encodeCallData("execute", [TO_ADDRESS, VALUE, "0x"])
  .buildUserOperation();

Providing an unknown function name or invalid parameters will cause a build time error.

factoryAbi

This is a JSON ABI of the Smart Account's factory contract. Ensure your ABI uses TypeScript's const assertion.

const FACTORY_ABI = [
  // ABI here...
] as const

This ABI will allow your Account instance to provide intelligent typing on for the setFactoryData hook.

factoryAddress

The address for your Smart Account's factory contract in the type 0x${string}. This allows the Account instance to properly set the address portion of a User Operation's initCode.

ethClient

An instance of either a viem PublicClient or ethers JsonRpcProvider. Used to call RPC methods on both the node and bundler.

Optional global values

bundlerClient

Defaults to ethClient.

An instance of either a viem PublicClient or ethers JsonRpcProvider. Used to call RPC methods for the bundler only. This is useful if you seperate providers for node and bundler.

entryPointAddress

Defaults to the canonical EntryPoint address (see 🗺️ Entity Addresses).

In certain cases where the EntryPoint address might be different (e.g. app chains using different deployers), this can be used to override the value. Note that, the interface will still assume v0.6 of the canonical EntryPoint.

salt

Defaults to 0n.

A big int value passed to the setFactoryData hook. This allows use cases for a single verification method to control multiple accounts.

waitTimeoutMs

Defaults to 60,000 (1 minute).

A timeout value in milliseconds used by the wait function after sending a User Operation and waiting for it to be included.

waitIntervalMs

Defaults to 3,000 (3 seconds).

An interval value in milliseconds used by the wait function after sending a User Operation to determine how ofter to poll for a User Operation receipt.

Required hook functions

setFactoryData

The setFactoryData hook is any arbitrary function with the following type that builds from the abitype package:

import {
  ExtractAbiFunctionNames,
  ExtractAbiFunction,
  AbiParametersToPrimitiveTypes,
} from "abitype";

type SetFactoryDataFunc<F extends Abi> = (
  salt: bigint,
  encoder: <M extends ExtractAbiFunctionNames<F>>(
    method: M,
    inputs: AbiParametersToPrimitiveTypes<ExtractAbiFunction<F, M>["inputs"]>,
  ) => Hex,
) => Promise<Hex>;

To simplify, the hook is a function that receives a big int salt and an encoder function as input and asynchronously outputs a hex string that is the callData to the factory contract for deploying a new Smart Account.

The encoder is a helper function that assists us in encoding the correct callData to send to the factory contract. It enforces strong types based on the factory ABI (F in the generic type).

Here is a concrete example using SimpleAccount factory and the type definition V06.Account.Hooks.SetFactoryDataFunc:

import { V06 } from "userop"  

const setFactoryData: V06.Account.Hooks.SetFactoryDataFunc<
    typeof FactoryAbi
  > = async (salt, encoder) => {
    return encoder("createAccount", [OWNER_ADDRESS, salt]);
  };

Providing an unknown function name or invalid parameters will cause a build time error.

requestSignature

The requestSignature hook is any arbitrary function with the type:

type RequestSignatureFunc = (
  type: "dummy" | "final",
  message: Hex,
) => Promise<Hex>;

The function is used by the Account instance to generate a User Operation signature given a type and message. A message will usually be a hex string of a userOpHash.

The type input can have a value of dummy or final. A dummy type indicates the signature needs to be structurally correct but does not need to be valid. These signatures are used for simulation such as estimating gas limits. A final type is required to be structurally correct and valid. It is used when sending a complete User Operation on-chain.

Here is a concrete example using SimpleAccount, an ethers Signer, and the type definition V06.Account.Hooks.RequestSignatureFunc:

const withEthersSigner = (
  signer: ethers.Signer,
): V06.Account.Hooks.RequestSignatureFunc => {
  // Dummy signatures can be signed by any key.
  // It only needs to be structurally correct for simulation.
  const dummy = ethers.Wallet.createRandom();

  return async (type, message) => {
    if (type === "dummy") {
      return dummy.signMessage(ethers.getBytes(message));
    }
    return signer.signMessage(ethers.getBytes(message);
  };
};

Optional hook functions

requestGasPrice

Defaults to V06.Hooks.RequestGasPrice.withEthClient(ethClient)

The requestGasPrice hook is any arbitrary function with the type:

type RequestGasPriceFunc = () => Promise<
  Pick<EntryPoint.UserOperation, "maxFeePerGas" | "maxPriorityFeePerGas">
>;

The function asynchronously returns an object with big int values for maxFeePerGas and maxPriorityFeePerGas which are appended to the User Operation. If omitted the default behaviour is to apply EIP-1559 gas fee logic and fallback to legacy gas price for non-supporting networks. It will use the given ethClient to make the necessary RPC calls.

requestGasValues

Defaults to V06.Hooks.RequestGasValues.withEthClient(ethClient)

The requestGasValues hook is any arbitrary function with the type:

type RequestGasValuesFunc = (
  userop: EntryPoint.UserOperation,
  entryPoint: Address,
  stateOverrideSet?: RpcStateOverride,
) => Promise<
  Pick<
    EntryPoint.UserOperation,
    "preVerificationGas" | "verificationGasLimit" | "callGasLimit"
  >
>;

The function receives a User Operation, EntryPoint address, and optional state override set as input and asynchronously returns an object with big int values for preVerificationGas, verificationGasLimit, and callGasLimit. If omitted, the default behaviour is to use either the given ethClient or bundlerClient to call eth_estimateUserOperationGas.

requestPaymaster

Defualts to nil

The requestPaymaster hook is any arbitrary function with the type:

type RequestPaymasterFunc = (
  userop: EntryPoint.UserOperation,
  entryPoint: Address,
) => Promise<
  Pick<EntryPoint.UserOperation, "paymasterAndData"> &
    Partial<EntryPoint.UserOperation>
>;

The function receives a User Operation and EntryPoint address as input and asynchronously returns an object with the paymasterAndData hex string along with any optional User Operation field required by the Paymaster to be used. If omitted, the default behaviour is for the Account to not use a Paymaster.

onBuild

Defualts to nil

The onbuild hook is any arbitrary function with the type:

type OnBuildFunc = (userop: EntryPoint.UserOperation) => void;

It is called once the final User Operation has been built. It is useful for initiating any action before sending such as logging it for debugging purposes. If omitted, it will result in a no op.