Skip to content
Alchemy Logo

Send batch transactions

Using Smart Wallets, you can batch multiple actions (such as token transfers, approvals, or swaps) into a single transaction. Users will no longer need multiple confirmations or pop-ups to handle sequential actions. This helps you speed up how users interact with your app and improve the user experience.

Smart wallets support running a batch of actions, called "calls", in a single transaction. These actions are atomic, which means if any of them fail (revert), the entire batched transaction will revert. This protects users from accidentally having part of a batch apply, but not all.

To use this feature, you just need to specify multiple calls to make. The parameter type accepts an array of calls, and each element should contain the action you want the wallet to take.

@alchemy/wallet-apis (v5.x.x) is currently in beta but is the recommended replacement for @account-kit/wallet-client (v4.x.x). If you run into any issues, please reach out.

You can batch transactions using either the smart wallet client sendCalls or prepareCalls actions.

sendCalls.ts
import { type Address, erc20Abi, encodeFunctionData, parseUnits } from "viem";
import { client } from "./client";
import { swapAbi } from "./swapAbi";
 
const DEMO_USDC_ADDRESS: Address = "0xCFf7C6dA719408113DFcb5e36182c6d5aa491443";
 
const DEMO_SWAP_ADDRESS: Address = "0xB0AEC4c25E8332256A91bBaf169E3C32dfC3C33C";
 
// Send a batch of calls — approve + swap in a single transaction
const { id } = await client.sendCalls({
  calls: [
    // To batch, simply specify multiple calls in the calls array
    {
      // approve 5000 USDC to the swap contract
      to: DEMO_USDC_ADDRESS,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: [DEMO_SWAP_ADDRESS, parseUnits("5000", 6)],
      }),
    },
    {
      // swap 5000 USDC to 1 WETH
      to: DEMO_SWAP_ADDRESS,
      data: encodeFunctionData({
        abi: swapAbi,
        functionName: "swapUSDCtoWETH",
        args: [parseUnits("5000", 6), parseUnits("1", 18)],
      }),
    },
  ],
});
 
// Wait for the transaction to be confirmed
const result = await client.waitForCallsStatus({ id });
 
console.log(result);
Using @account-kit/wallet-client (v4.x.x)?

The examples on this page use @alchemy/wallet-apis (v5.x.x). If you're using @account-kit/wallet-client (v4.x.x), the client setup looks like this:

client.ts (v4.x.x)
import { LocalAccountSigner } from "@aa-sdk/core";
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { alchemy, sepolia } from "@account-kit/infra";
 
const signer = LocalAccountSigner.privateKeyToAccountSigner("0xYOUR_PRIVATE_KEY" as const);
 
export const client = createSmartWalletClient({
  transport: alchemy({ apiKey: "YOUR_API_KEY" }),
  chain: sepolia,
  signer,
  account: signer.address, // can also be passed per action as `from` or `account`
  // Optional: sponsor gas for your users (see "Sponsor gas" guide)
  policyId: "YOUR_POLICY_ID", 
});

Key v4.x.x differences:

  • Account address must be specified on the client or per action (from or account). In v5.x.x, the client automatically uses the signer's address as the account address via EIP-7702.
  • Chain imports come directly from @account-kit/infra instead of viem/chains.
  • Numeric values use hex strings: value: "0x0" instead of value: BigInt(0).
  • In v4.x.x, the paymaster capability on prepareCalls or sendCalls is called paymasterService instead of paymaster, or you can set the policyId directly on the client.
  • Signers use LocalAccountSigner / WalletClientSigner from @aa-sdk/core. In v5.x.x, a viem LocalAccount or WalletClient is used directly.

See the full migration guide for a complete cheat sheet.

Build more:

Was this page helpful?