# Send parallel transactions

> Send multiple concurrent transactions from the same Smart Wallet account using nonce key overrides.

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

This guide explains how to send multiple parallel transactions. Note that you don't need to send parallel transactions for batching calls. This is for sending new transactions when, for example, there's already a transaction in the mempool for a certain account.

## Prerequisites

* An [Alchemy API key](https://dashboard.alchemy.com/apps)
* A [Gas Manager](https://dashboard.alchemy.com/gas-manager/policy/create) policy
* A signer to own the account and sign messages

<Info>The `nonceKey` override must fit into a `uint152`!</Info>

<Tabs>
  <Tab title="JavaScript" language="typescript">
    <Info>`@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](mailto:support@alchemy.com).</Info>

Use the `nonceOverride` capability on the smart wallet client's [`sendCalls`](/docs/wallets/reference/wallet-apis/functions/sendCalls) or [`prepareCalls`](/docs/wallets/reference/wallet-apis/functions/prepareCalls) action.

You'll need the following env variables:

* `ALCHEMY_API_KEY`: An [Alchemy API key](https://dashboard.alchemy.com/apps)
* `ALCHEMY_POLICY_ID`: A [Gas Manager](https://dashboard.alchemy.com/gas-manager/policy/create) policy ID
* `PRIVATE_KEY`: A private key for a signer

<CodeBlocks>
  ```ts title="sendParallelCalls.ts"
  import { client, config } from "./client.ts";

  const [
    { id: idOne },
    { id: idTwo },
  ] = await Promise.all([
    client.sendCalls({
      capabilities: {
        paymaster: {
          policyId: config.policyId,
        },
        nonceOverride: {
          nonceKey: "0x01",
        },
      },
      calls: [
        {
          to: "0x0000000000000000000000000000000000000000",
          value: BigInt(0),
          data: "0x",
        },
      ],
    }),
    client.sendCalls({
      capabilities: {
        paymaster: {
          policyId: config.policyId,
        },
        nonceOverride: {
          nonceKey: "0x02",
        },
      },
      calls: [
        {
          to: "0x0000000000000000000000000000000000000000",
          value: BigInt(0),
          data: "0x",
        },
      ],
    }),
  ]);

  console.log("sendCalls result:", { idOne, idTwo });

  const callStatusResults = await Promise.all([
    client.waitForCallsStatus({ id: idOne }),
    client.waitForCallsStatus({ id: idTwo }),
  ]);

  console.log("Calls status results:", callStatusResults);
  ```

  ```ts title="client.ts"
  import type { Hex } from "viem";
  import { sepolia } from "viem/chains";
  import { privateKeyToAccount } from "viem/accounts";
  import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";

  export const config = {
    policyId: "YOUR_POLICY_ID",
  };

  export const client = createSmartWalletClient({
    transport: alchemyWalletTransport({
      apiKey: "YOUR_API_KEY",
    }),
    chain: sepolia,
    signer: privateKeyToAccount("0xYOUR_PRIVATE_KEY" as const),
  });
  ```
</CodeBlocks>

  </Tab>

  <Tab title="API" language="bash">
    You will need to fill in values wrapped in curly braces like `{SIGNER_ADDRESS}`.

<Steps>
  <Step title="Prepare parallel calls">
    Use your signer address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default. To send parallel calls, use the `nonceOverride` capability. This allows us to specify a `nonceKey` parameter, a hex value that fits inside a `uint152` and differentiates transactions.

    In production, as long as the nonce key override is nonzero (zero is the default), and different between `prepareCalls` requests, the transactions will be processed in parallel.

    In this example, you'll send the first request with the `0x01` nonce key override and the second with the `0x02` nonce key override.

    ```bash
    curl --request POST \
      --url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
      --header 'accept: application/json' \
      --data '
    {
      "id": 1,
      "jsonrpc": "2.0",
      "method": "wallet_prepareCalls",
      "params": [
        {
          "calls": [
            {
              "to": "0x0000000000000000000000000000000000000000",
              "data": "0x"
            }
          ],
          "from": "{SIGNER_ADDRESS}",
          "chainId": "{CHAIN_ID}",
          "capabilities": {
            "paymasterService": {
              "policyId": "{PAYMASTER_POLICY_ID}"
            },
            "nonceOverride": {
              "nonceKey": "0x01"
            }
          }
        }
      ]
    }'
    ```

    ```bash
    curl --request POST \
      --url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
      --header 'accept: application/json' \
      --data '
    {
      "id": 1,
      "jsonrpc": "2.0",
      "method": "wallet_prepareCalls",
      "params": [
        {
          "calls": [
            {
              "to": "0x0000000000000000000000000000000000000000",
              "data": "0x"
            }
          ],
          "from": "{SIGNER_ADDRESS}",
          "chainId": "{CHAIN_ID}",
          "capabilities": {
            "paymasterService": {
              "policyId": "{PAYMASTER_POLICY_ID}"
            },
            "nonceOverride": {
              "nonceKey": "0x02"
            }
          }
        }
      ]
    }'
    ```

    Each of these requests return a result like so:

    ```bash
    {
        "type": "user-operation-v070",
        "data": {...useropRequest},
        "chainId": "CHAIN_ID",
        "signatureRequest": {
            "type": "personal_sign",
            "data": {
                "raw": "HASH_TO_SIGN",
            },
            "rawPayload": "RAW_PAYLOAD_TO_SIGN"
        }
    }
    ```

    Note the two `signatureRequest` objects that were returned for each request! Also keep the returned `data` object from each request -- you need them when sending the parallel transactions.

    If the account hasn't been delegated yet, the response type will be `array` containing both an EIP-7702 authorization and a transaction. See [Send Transactions](/docs/wallets/transactions/send-transactions) for handling authorization signing.
  </Step>

  <Step title="Sign the signature requests">
    To sign the signature requests, you should sign the `raw` fields (note, this is not a string! You need to pass it to your signer as raw bytes, generally like so: `{ raw: "0x..." }`) with your signer of choice.

    This should use the `personal_sign` RPC method, as noted by the `type` in the `signatureRequest`.

    Alternatively, you can sign the raw payloads with a simple `eth_sign` but this RPC method is not favored due to security concerns.
  </Step>

  <Step title="Send the prepared calls">
    ```bash
    curl -X POST https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "method": "wallet_sendPreparedCalls",
        "params": [
          {
            "type": "array",
            "data": [
              {
                "type": "user-operation-v070",
                "data": {DATA_ONE_FROM_STEP_1},
                "chainId": "{CHAIN_ID}",
                "signature": {
                  "type": "secp256k1",
                  "data": "{SIGNATURE_ONE_FROM_STEP_2}"
                }
              },
              {
                "type": "user-operation-v070",
                "data": {DATA_TWO_FROM_STEP_1},
                "chainId": "{CHAIN_ID}",
                "signature": {
                  "type": "secp256k1",
                  "data": "{SIGNATURE_TWO_FROM_STEP_2}"
                }
              }
            ]
          }
        ],
        "id": 1
      }'
    ```

    This returns:

    ```json
    {
      "jsonrpc": "2.0",
      "id": "1",
      "result": {
        "id": "CALL_ID"
      }
    }
    ```

    Note the `CALL_ID`! Each `wallet_sendPreparedCalls` response returns an `id` that you'll use to track call status in the next step.

    For other potential responses, [check out the API reference!](https://www.alchemy.com/docs/wallets/api-reference/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-send-prepared-calls)
  </Step>

  <Step title="Track the prepared calls">
    You can use the `wallet_getCallsStatus` endpoint to check up on each transaction's status. This should be done once for each returned call id.

    ```bash
    curl -X POST https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "method": "wallet_getCallsStatus",
        "params": [
          [
            "{CALL_ID_ONE_OR_TWO_FROM_STEP_3}",
          ]
        ],
        "id": 1
      }'
    ```

    This returns:

    ```json
    {
      "id": "1",
      "jsonrpc": "2.0",
      "result": {
        "id": "CALL_ID",
        "chainId": "CHAIN_ID",
        "atomic": true,
        "status": 200,
        "receipts": [...]
      }
    }
    ```

    Note that the status codes match the following:
    | Code | Title |
    |------|--------------------------|
    | 100 | Pending |
    | 200 | Confirmed |
    | 400 | Offchain Failure |
    | 500 | Onchain Failure |
    | 600 | Partial Onchain Failure |

    To get your transaction's mined transaction hash, you can access `result.receipts[0].transactionHash`.

    For more details, check out [the API reference!](https://www.alchemy.com/docs/wallets/api-reference/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-get-calls-status)
  </Step>
</Steps>

<Accordion title="Full script example">
  This is a full working bash script which sends parallel calls! You'll need the following env variables:

  * ALCHEMY\_API\_KEY
  * PRIVATE\_KEY
  * SIGNER\_ADDRESS
  * CHAIN\_ID
  * POLICY\_ID

  <Info>
    The examples provided use [foundry](https://getfoundry.sh/) and
    [jq](https://jqlang.org/) to prepare and parse
  </Info>

  ```bash
  #!/bin/bash

  # Check required environment variables
  if [[ -z "$ALCHEMY_API_KEY" || -z "$PRIVATE_KEY" || -z "$SIGNER_ADDRESS" || -z "$CHAIN_ID" || -z "$POLICY_ID" ]]; then
      echo "Error: Please set ALCHEMY_API_KEY, PRIVATE_KEY, SIGNER_ADDRESS, CHAIN_ID, and POLICY_ID environment variables"
      exit 1
  fi

  # Configuration
  API_URL="https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY"

  echo "Starting parallel user operations flow..."
  echo "Using signer address: $SIGNER_ADDRESS"

  # Step 1: Prepare first call (nonce key 0x01)
  echo "Preparing first call (nonce key 0x01)..."
  PREPARE_RESPONSE_1=$(curl -s --request POST \
    --url "$API_URL" \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --data '{
      "id": 1,
      "jsonrpc": "2.0",
      "method": "wallet_prepareCalls",
      "params": [
        {
          "calls": [
            {
              "to": "0x0000000000000000000000000000000000000000",
              "data": "0x"
            }
          ],
          "from": "'"$SIGNER_ADDRESS"'",
          "chainId": "'"$CHAIN_ID"'",
          "capabilities": {
            "paymasterService": {
              "policyId": "'"$POLICY_ID"'"
            },
            "nonceOverride": {
              "nonceKey": "0x01"
            }
          }
        }
      ]
    }')

  # Step 2: Prepare second call (nonce key 0x02)
  echo "Preparing second call (nonce key 0x02)..."
  PREPARE_RESPONSE_2=$(curl -s --request POST \
    --url "$API_URL" \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --data '{
      "id": 1,
      "jsonrpc": "2.0",
      "method": "wallet_prepareCalls",
      "params": [
        {
          "calls": [
            {
              "to": "0x0000000000000000000000000000000000000000",
              "data": "0x"
            }
          ],
          "from": "'"$SIGNER_ADDRESS"'",
          "chainId": "'"$CHAIN_ID"'",
          "capabilities": {
            "paymasterService": {
              "policyId": "'"$POLICY_ID"'"
            },
            "nonceOverride": {
              "nonceKey": "0x02"
            }
          }
        }
      ]
    }')

  # Check for errors in responses
  if [[ $(echo "$PREPARE_RESPONSE_1" | jq -r '.error') != "null" ]]; then
      echo "❌ Error in first prepareCalls: $(echo "$PREPARE_RESPONSE_1" | jq -r '.error.message')"
      exit 1
  fi

  if [[ $(echo "$PREPARE_RESPONSE_2" | jq -r '.error') != "null" ]]; then
      echo "❌ Error in second prepareCalls: $(echo "$PREPARE_RESPONSE_2" | jq -r '.error.message')"
      exit 1
  fi

  # Step 3: Extract raw hashes to sign
  echo "Extracting signature hashes..."
  RAW_HASH_1=$(echo "$PREPARE_RESPONSE_1" | jq -r '.result.signatureRequest.data.raw')
  RAW_HASH_2=$(echo "$PREPARE_RESPONSE_2" | jq -r '.result.signatureRequest.data.raw')

  echo "Raw hash 1: $RAW_HASH_1"
  echo "Raw hash 2: $RAW_HASH_2"

  # Step 4: Sign the hashes using cast
  echo "Signing hashes with cast..."
  SIGNATURE_1=$(cast wallet sign --private-key "$PRIVATE_KEY" "$RAW_HASH_1")
  SIGNATURE_2=$(cast wallet sign --private-key "$PRIVATE_KEY" "$RAW_HASH_2")

  echo "Signature 1: $SIGNATURE_1"
  echo "Signature 2: $SIGNATURE_2"

  # Step 5: Extract user operation data for sendPreparedCalls
  echo "Extracting user operation data..."
  USEROP_DATA_1=$(echo "$PREPARE_RESPONSE_1" | jq -c '.result.data')
  USEROP_DATA_2=$(echo "$PREPARE_RESPONSE_2" | jq -c '.result.data')

  # Step 6: Send prepared calls
  echo "Sending prepared calls..."
  SEND_RESPONSE=$(curl -s --request POST \
    --url "$API_URL" \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --data '{
      "jsonrpc": "2.0",
      "method": "wallet_sendPreparedCalls",
      "params": [
        {
          "type": "array",
          "data": [
            {
              "type": "user-operation-v070",
              "data": '"$USEROP_DATA_1"',
              "chainId": "'"$CHAIN_ID"'",
              "signature": {
                "type": "secp256k1",
                "data": "'"$SIGNATURE_1"'"
              }
            },
            {
              "type": "user-operation-v070",
              "data": '"$USEROP_DATA_2"',
              "chainId": "'"$CHAIN_ID"'",
              "signature": {
                "type": "secp256k1",
                "data": "'"$SIGNATURE_2"'"
              }
            }
          ]
        }
      ],
      "id": 1
    }')

  # Step 7: Display results
  echo "Results:"
  echo "Send response:"
  echo "$SEND_RESPONSE" | jq '.'

  # Check for success
  if [[ $(echo "$SEND_RESPONSE" | jq -r '.error') == "null" ]]; then
      echo "Success! Parallel user operations submitted."
      # Extract transaction hashes if available
      if [[ $(echo "$SEND_RESPONSE" | jq -r '.result') != "null" ]]; then
          echo "Result: $(echo "$SEND_RESPONSE" | jq -r '.result')"
      fi
  else
      echo "Error in sendPreparedCalls: $(echo "$SEND_RESPONSE" | jq -r '.error.message')"
      exit 1
  fi

  ```
</Accordion>

  </Tab>

</Tabs>

<Accordion title="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:

```ts title="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 owner's address as the account address via [EIP-7702](/docs/wallets/transactions/using-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.
* **Owners** use `LocalAccountSigner` / `WalletClientSigner` from `@aa-sdk/core`. In v5.x.x, a viem `LocalAccount` or `WalletClient` is used directly.

See the [full migration guide](/docs/wallets/resources/migration-v5) for a complete cheat sheet.
</Accordion>