Skip to content

How to send a User Operation

This guide will show you how to send a User Operation with Account Kit by creating an Alchemy Smart Account Client, connecting it to a Light Account (a type of smart account implementation), and sending a User Operation from that client. By the end of this guide, you will have a basic understanding of how to use the SDK.

1. Create your Client

Using the SDK, we will create an Alchemy Smart Account Client. As it is, the clients gives you methods to query information related to user operations and smart accounts. To create a client, you will need an Alchemy API Key or RPC URL, which you can access from the Alchemy Dashboard.

See Alchemy Smart Account Client for more details.

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"),
});

2. Construct the call data

The best part of Account Kit is that it abstracts the differences between User Operation calldata and standard Transaction calldata, such that you can pass in typical calldata to sendUserOperation as if it was a transaction sent from your smart account, and we will wrap it as necessary to generate calldata as it would be as a User Operation.

The second best part of Account Kit is that it is build atop viem. This means we can leverage utility methods to easily generate calldata, with type safety, using a smart contract's ABI.

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

// this is an example ABI for a contract with a "mint" function
const AlchemyTokenAbi = [
  {
    inputs: [{ internalType: "address", name: "recipient", type: "address" }],
    name: "mint",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
];
const uoCallData = encodeFunctionData({
  abi: AlchemyTokenAbi,
  functionName: "mint",
  args: [smartAccountClient.getAddress()],
});

const uo = await smartAccountClient.sendUserOperation({
  uo: {
    target: "0xTARGET_ADDRESS",
    data: uoCallData,
  },
});
const txHash = await smartAccountClient.waitForUserOperationTransaction(uo);
console.log(txHash);
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"),
});

Some other helpful viem methods include: encodeFunctionData, decodeFunctionData, and decodeFunctionResult.

3. Send the User Operation

Now, we will use the connected client to send a user operation. We will use the sendUserOperation method on the client.

You can either send ETH to the smart account to pay for User Operation's gas, or you can connect your client to our Gas Manager using the withAlchemyGasManager method to sponsor the UO's gas. We will use the latter approach below. You can go to the Alchemy Dashboard to get a Gas Manager policy ID.

We will also want to wait for the transaction that contains the User Operation so that we know the User Operation is executed on-chain. We can use the waitForUserOperationTransaction method on the client, as seen below.

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

// this is an example ABI for a contract with a "mint" function
const AlchemyTokenAbi = [
  {
    inputs: [{ internalType: "address", name: "recipient", type: "address" }],
    name: "mint",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
];
const uoCallData = encodeFunctionData({
  abi: AlchemyTokenAbi,
  functionName: "mint",
  args: [smartAccountClient.getAddress()],
});


const uo = await smartAccountClient.sendUserOperation({
  uo: {
    target: "0xTARGET_ADDRESS",
    data: uoCallData,
  },
});
const txHash = await smartAccountClient.waitForUserOperationTransaction(uo);
console.log(txHash);
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"),
});

Try the full example!

And that's it! Let's put it all together. You can copy the following snippet to try it yourself!

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

const PRIVATE_KEY = "0xYourEOAPrivateKey";
const eoaSigner: SmartAccountSigner =
  LocalAccountSigner.privateKeyToAccountSigner(`0x${PRIVATE_KEY}`);

const client = await createModularAccountAlchemyClient({
  apiKey: "ALCHEMY_API_KEY", // replace with your Alchemy API Key
  chain: sepolia,
  signer: eoaSigner,
  gasManagerConfig: {
    policyId: "POLICY_ID", // replace with your policy id, get yours at https://dashboard.alchemy.com/
  },
});

// this is an example ABI for a contract with a "mint" function
const AlchemyTokenAbi = [
  {
    inputs: [{ internalType: "address", name: "recipient", type: "address" }],
    name: "mint",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
];

export const uoCallData = encodeFunctionData({
  abi: AlchemyTokenAbi,
  functionName: "mint",
  args: [client.getAddress()],
});

const uo = await client.sendUserOperation({
  uo: {
    target: "0xTargetAddress",
    data: uoCallData,
  },
});

const txHash = await client.waitForUserOperationTransaction(uo);

console.log(txHash);