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.

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!

sim-middleware.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.

sim-method.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);