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
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
import { smartAccountClient } from "./smartAccountClient";
const { preVerificationGas, verificationGasLimit, callGasLimit } =
await smartAccountClient.estimateUserOperationGas({
uo: [
{
target: "0x...",
data: "0xcallDataTransacation1",
},
{
target: "0x...",
data: "0xcallDataTransacation2",
},
],
});
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"),
});