> ## Documentation Index
> Fetch the complete documentation index at: https://docs.base.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Batch Transactions

With Base Account, you can send multiple onchain calls in a single transaction. Doing so improves the UX of multi-step interactions by reducing them to a single click. A common example of where you might want to leverage batch transactions is an ERC-20 `approve` followed by a swap.

You can submit batch transactions by using the `wallet_sendCalls` RPC method, defined in [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792).

<Tip>
  **Do you prefer video content?**

  There is a video guide that covers the implementation in detail in the [last section of this page](#video-guide).
</Tip>

## Installation

Install the Base Account SDK:

<CodeGroup>
  ```bash npm theme={null}
  npm install @base-org/account
  ```

  ```bash pnpm theme={null}
  pnpm add @base-org/account
  ```

  ```bash yarn theme={null}
  yarn add @base-org/account
  ```

  ```bash bun theme={null}
  bun add @base-org/account
  ```
</CodeGroup>

## Setup

### Initialize the SDK

Import and create the Base Account SDK instance:

```tsx batchTransactions.tsx theme={null}
import { createBaseAccountSDK } from "@base-org/account";

const sdk = createBaseAccountSDK({
  appName: "Base Account SDK Demo",
  appLogoUrl: "https://base.org/logo.png",
});

const provider = sdk.getProvider();
```

## Basic Batch Transaction

### Simple Multiple Transfers

Send multiple ETH transfers in a single transaction:

```tsx batchTransactions.tsx expandable theme={null}
import { createBaseAccountSDK, getCryptoKeyAccount } from "@base-org/account";
import { numberToHex, parseEther } from "viem";

const sdk = createBaseAccountSDK({
  appName: "Batch Transaction Demo",
  appLogoUrl: "https://base.org/logo.png",
});

const provider = sdk.getProvider();

async function sendBatchTransfers() {
  try {
    // Get crypto account
    const cryptoAccount = await getCryptoKeyAccount();
    const fromAddress = cryptoAccount?.account?.address;

    // Prepare batch calls
    const calls = [
      {
        to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
        value: numberToHex(parseEther("0.001")), // 0.001 ETH
        data: "0x", // Empty data for simple transfer
      },
      {
        to: "0x742d35Cc6634C0532925a3b844Bc9e7595f6E456",
        value: numberToHex(parseEther("0.001")), // 0.001 ETH
        data: "0x", // Empty data for simple transfer
      },
    ];

    // Send batch transaction
    const result = await provider.request({
      method: "wallet_sendCalls",
      params: [
        {
          version: "2.0.0",
          from: fromAddress,
          chainId: numberToHex(base.constants.CHAIN_IDS.base),
          atomicRequired: true, // All calls must succeed or all fail
          calls: calls,
        },
      ],
    });

    console.log("Batch transaction sent:", result);
    return result;
  } catch (error) {
    console.error("Batch transaction failed:", error);
    throw error;
  }
}
```

## Contract Interactions

### ERC-20 Approve and Mint an NFT (ERC-721)

A common pattern is to approve the NFT contract to move your ERC-20 and then mint an NFT (ERC-721):

```tsx batchTransactions.tsx expandable theme={null}
import {
  createBaseAccountSDK,
  getCryptoKeyAccount,
  base,
} from "@base-org/account";
import { numberToHex, parseUnits, encodeFunctionData } from "viem";

// ERC-20 ABI for approve
const erc20Abi = [
  {
    inputs: [
      { name: "spender", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    name: "approve",
    outputs: [{ name: "", type: "bool" }],
    stateMutability: "nonpayable",
    type: "function",
  },
] as const;

// ERC721 ABI for the mint function
const erc721Abi = [
  {
    inputs: [
      { name: "to", type: "address" },
      { name: "tokenId", type: "uint256" },
    ],
    name: "mint",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
] as const;

// USDC contract address on Base Sepolia
const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";

// NFT contract address on Base Sepolia
const NFT_CONTRACT_ADDRESS = "0x82039e7C37D7aAc98D0F4d0A762F4E0d8c8DC273";

async function approveAndTransfer() {
  const sdk = createBaseAccountSDK({
    appName: "ERC-20 Batch Demo",
    appLogoUrl: "https://base.org/logo.png",
  });

  const provider = sdk.getProvider();
  const cryptoAccount = await getCryptoKeyAccount();
  const fromAddress = cryptoAccount?.account?.address;

  // Encode the first approve call - approve USDC to NFT contract
  const call1Data = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [
      NFT_CONTRACT_ADDRESS,
      parseUnits("1000", 6), // USDC has 6 decimals
    ],
  });

  // Encode the second call - mint NFT to the user's address
  const call2Data = encodeFunctionData({
    abi: erc721Abi,
    functionName: "mint",
    args: [fromAddress as `0x${string}`, BigInt("1")],
  });

  const result = await provider.request({
    method: "wallet_sendCalls",
    params: [
      {
        version: "2.0.0",
        from: fromAddress,
        chainId: numberToHex(base.constants.CHAIN_IDS.baseSepolia),
        atomicRequired: true,
        calls: [
          {
            to: USDC_ADDRESS,
            data: call1Data,
          },
          {
            to: NFT_CONTRACT_ADDRESS,
            data: call2Data,
          },
        ],
      },
    ],
  });

  return result;
}
```

## Advanced Features

### Checking Wallet Capabilities

Before sending batch transactions, you can check if the wallet supports atomic batching:

```tsx batchTransactions.tsx expandable theme={null}
async function checkCapabilities() {
  const provider = sdk.getProvider();

  try {
    const cryptoAccount = await getCryptoKeyAccount();
    const address = cryptoAccount?.account?.address;

    const capabilities = await provider.request({
      method: "wallet_getCapabilities",
      params: [address],
    });

    const baseCapabilities = capabilities[base.constants.CHAIN_IDS.base];

    if (baseCapabilities?.atomicBatch?.supported) {
      console.log("Atomic batching is supported");
      return true;
    } else {
      console.log("Atomic batching is not supported");
      return false;
    }
  } catch (error) {
    console.error("Failed to check capabilities:", error);
    return false;
  }
}
```

### Non-Atomic Batching

Sometimes you want calls to execute sequentially, even if some fail:

```tsx batchTransactions.tsx expandable theme={null}
const result = await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0.0",
      from: fromAddress,
      chainId: numberToHex(base.constants.CHAIN_IDS.base),
      atomicRequired: false, // Allow partial execution
      calls: calls,
    },
  ],
});
```

## Getting the Batch Transaction Result

`wallet_getCallsStatus` returns the execution status for a batch you previously submitted with `wallet_sendCalls`. Capture the `callsId` returned by `wallet_sendCalls`, then poll for the batch status until it is confirmed or fails.

```tsx batchTransactions.tsx lines expandable theme={null}
async function trackBatchTransaction(
  calls: Array<{
    to: `0x${string}`;
    data: `0x${string}`;
    value?: `0x${string}`;
  }>
) {
  const cryptoAccount = await getCryptoKeyAccount();
  const fromAddress = cryptoAccount?.account?.address;

  const callsId = await provider.request({
    method: "wallet_sendCalls",
    params: [
      {
        version: "2.0.0",
        from: fromAddress,
        chainId: numberToHex(base.constants.CHAIN_IDS.base),
        atomicRequired: true,
        calls,
      },
    ],
  });

  try {
    const status = await provider.request({
      method: "wallet_getCallsStatus",
      params: [callsId],
    });

    if (status.status === 200) {
      console.log("Batch completed successfully", status.receipts);
    } else if (status.status === 100) {
      console.log("Batch still pending", status.id);
    } else {
      console.error("Batch failed", status.status);
    }

    return status;
  } catch (error: any) {
    if (error.code === 4200) {
      throw new Error("No batch found for the provided callsId.");
    }

    if (error.code === 4100) {
      throw new Error(
        "The connected wallet does not support wallet_getCallsStatus."
      );
    }

    if (error.code === -32602) {
      throw new Error("The callsId parameter is invalid.");
    }

    throw error;
  }
}
```

You can learn more about `wallet_getCallsStatus` in the [reference documentation](/base-account/reference/core/provider-rpc-methods/wallet_getCallsStatus).

<Tip>
  **Need more control over gas?**

  You can override the gas limit for individual calls in a batch using the [`gasLimitOverride`](/base-account/reference/core/capabilities/gasLimitOverride) capability. This is useful for calls with nondeterministic gas consumption, such as swaps. See the [capabilities overview](/base-account/reference/core/capabilities/overview) for the full list of supported capabilities.
</Tip>

## Video Guide

<iframe width="560" height="315" src="https://www.youtube.com/embed/UGB0j_eHsNU?si=vfxwKY63Y9vhQpgS" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />
