Skip to content

How to estimate gas for a user operation

Providing accurate user operation gas estimations is important to the user experience of ERC-4337. If a gas estimate is too low, a user operation may revert during simulation, or worse, revert onchain during the execution phase, leaving the user to pay for gas of a reverted operation. If gas estimation is too high, a user may be dissuaded from, or unable to, send their operation due to costs.

User operation gas estimation using SmartAccountClient

eth_estimateUserOperationGas is an RPC method that bundlers support as per the ERC-4337 specification. It estimate the gas values for a UserOperation. Given a UserOperation, optionally without gas limit or price fields, this method returns the needed gas limits.

SmartAccountClient of aa-sdk provides a default gasEstimator that internally includes calling eth_estimateUserOperationGas to the bundler in addition to applying user operation overrides or fee options for populating the user operation gas fields in a most desired manner. If you are looking to estimate gas of a user operation without building the entire user operation through the middleware pipeline, you can call the estimateUserOperationGas action on the SmartAccountClient to directly fetch network user operation gas estimates from the bundler. The action returns UserOperationEstimateGasResponse containing the estimated gas values.

Note 1

The actual gas estimation or fee estimation is performed by the bundler connected to the SmartAccountClient, so depending on the bundler you are using, the gas estimate value might be different.

Note 2

Note that the estimateUserOperationGas action returns the bare result of gas estimates returned directly from the connected bundler without applying user operation gas overrides or client fee options that are applied from the default gasEstimator used when constructing the actual user operation request to send to the network.

UserOperationEstimateGasResponse

ts
export interface UserOperationEstimateGasResponse<
  TEntryPointVersion extends EntryPointVersion
> {
  /* Gas overhead of this UserOperation */
  preVerificationGas: BigNumberish;
  /* Actual gas used by the validation of this UserOperation */
  verificationGasLimit: BigNumberish;
  /* Value used by inner account execution */
  callGasLimit: BigNumberish;
  /*
   * EntryPoint v0.7.0 operations only.
   * The amount of gas to allocate for the paymaster validation code.
   * Note: `eth_estimateUserOperationGas` does not return paymasterPostOpGasLimit.
   */
  paymasterVerificationGasLimit: TEntryPointVersion extends "0.7.0"
    ? BigNumberish
    : never;
}

Example

ts
import { smartAccountClient } from "./smartAccountClient";


const { preVerificationGas, verificationGasLimit, callGasLimit } =
  await smartAccountClient.estimateUserOperationGas({
    uo: [
      {
        target: "0x...",
        data: "0xcallDataTransacation1",
      },
      {
        target: "0x...",
        data: "0xcallDataTransacation2",
      },
    ],
  });
ts
import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";
import { LocalAccountSigner, sepolia } from "@alchemy/aa-core";

export const chain = sepolia;

export const smartAccountClient = await createModularAccountAlchemyClient({
  apiKey: "YOUR_API_KEY",
  chain,
  // you can swap this out for any SmartAccountSigner
  signer: LocalAccountSigner.mnemonicToAccountSigner("OWNER_MNEMONIC"),
});