Skip to content

How to simulate a User Operation

This guide will show you how to simulate a UserOperation (UO) with Account Kit by adding support for UO simulation on an AlchemySmartAccountClient and sending a User Operation from that client only if simulation passes. By the end of this guide, you will have a basic understanding of how to safely send UOs with the aa-sdk.

NOTE

Please note that the UO simulation results are based on the blockchain's state at the time of simulation. Changes in the blockchain state, such as updates to contract variables or balances, can occur between the time of simulation and when your UO actually gets executed.

This could lead to different outcomes than predicted. For instance, if a UO's effect is conditional on the current state of a contract, and this state is altered before the UO is executed, the final result may not match the simulation.

Please be aware of this potential variance and consider it while using UO simulations.

1. Using alchemyUserOperationSimulator middleware

To simulate User Operations, we must create an Alchemy Client and pass in the useSimulation flag to true.

Then, whenever you call a method on the client which generates the UO to send (e.g. sendUserOperation, sendTransaction, sendTransactions, buildUserOperation, or buildUserOperationFromTx), the client will also simulate which assets change as a result of the UO, and if simulation fails, the client will not send the UO unnecessarily!

ts
import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";
import { LocalAccountSigner, sepolia } from "@alchemy/aa-core";

export const smartAccountClient = await createModularAccountAlchemyClient({
  apiKey: "YOUR_API_KEY",
  chain: sepolia,
  signer: LocalAccountSigner.mnemonicToAccountSigner("OWNER_MNEMONIC"),
  useSimulation: true,
});

const uo = await smartAccountClient.sendUserOperation({
  uo: {
    target: "0xTARGET_ADDRESS",
    data: "0xDATA",
    value: 1n,
  },
});

const txHash = await smartAccountClient.waitForUserOperationTransaction(uo);

console.log(txHash);

2. Using simulateUserOperation

You can also selectively simulate UOs by calling the simulateUserOperation method before sending a UO. You would be responsible for catching any errors like how it is done below, but this is a nice alternative to always running simulation.

ts
import { createModularAccountAlchemyClient } from "@alchemy/aa-alchemy";
import {
  LocalAccountSigner,
  UserOperationCallData,
  sepolia,
} from "@alchemy/aa-core";

export const smartAccountClient = await createModularAccountAlchemyClient({
  apiKey: "YOUR_API_KEY",
  chain: sepolia,
  signer: LocalAccountSigner.mnemonicToAccountSigner("OWNER_MNEMONIC"),
});

const uoStruct: UserOperationCallData = {
  target: "0xTARGET_ADDRESS",
  data: "0xDATA",
  value: 1n,
};

const uoSimResult = await smartAccountClient.simulateUserOperation({
  uo: uoStruct,
});

if (uoSimResult.error) {
  console.error(uoSimResult.error.message);
}

const uo = await smartAccountClient.sendUserOperation({ uo: uoStruct });

const txHash = await smartAccountClient.waitForUserOperationTransaction(uo);

console.log(txHash);