# Retry Transactions

> Replace stuck or slow Smart Wallet transactions by re-preparing and resending calls with updated gas fees.

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

Replace stuck transactions by preparing and sending calls again.

Key use cases:

* Replace transactions stuck due to low gas prices
* Speed up pending transactions by increasing gas fees
* Override transactions that have been pending for too long
* Cancel stuck transactions by replacing with a no-op

## The retry flow

1. Send the initial transaction
2. Monitor the transaction status
3. If the transaction is stuck/pending too long, re-prepare the same call
4. Send the transaction with higher gas to replace the stuck transaction
5. The original transaction gets dropped from mempool

## 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>
  If the original transaction is already being mined, the replacement
  transaction may be dropped. In this case, you won't be able to retrieve data
  using the replacement's call ID, and the original transaction will be
  included!
</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>

When re-sending calls without a `nonceOverride`, the client automatically uses the same nonce as the pending transaction, replacing it. See the [`sendCalls` SDK reference](/docs/wallets/reference/wallet-apis/functions/sendCalls) for full parameter descriptions.

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="retryTransaction.ts"
  import { client, config } from "./client.ts";

  // Send initial transaction
  const { id } = await client.sendCalls({
    capabilities: {
      paymaster: {
        policyId: config.policyId,
      },
    },
    calls: [
      {
        to: "0x0000000000000000000000000000000000000000",
        value: BigInt(0),
        data: "0x",
      },
    ],
  });

  console.log("Initial transaction:", id);

  // Check status
  const status = await client.getCallsStatus(id);

  console.log("Transaction status:", status.status);

  // If still pending (status 100), send replacement
  if (status.status === 100) {
    console.log("Transaction pending, sending replacement...");

    // Re-send without nonceOverride to replace stuck transaction
    const { id: replacementId } = await client.sendCalls({
      capabilities: {
        paymaster: {
          policyId: config.policyId,
        },
      },
      calls: [
        {
          to: "0x0000000000000000000000000000000000000000",
          value: BigInt(0),
          data: "0x",
        },
      ],
    });

    console.log("Replacement transaction:", replacementId);

    // Wait for replacement to confirm
    const result = await client.waitForCallsStatus({ id: replacementId });

    console.log("Replacement confirmed:", result);
  }
  ```

  ```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="Send the initial transaction">

Use your signer address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default.

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "wallet_prepareCalls",
    "params": [
      {
        "calls": [
          {
            "to": "{TO_ADDRESS}",
            "value": "{VALUE}",
            "data": "{DATA}"
          }
        ],
        "from": "{SIGNER_ADDRESS}",
        "chainId": "{CHAIN_ID}",
        "capabilities": {
          "paymasterService": {
            "policyId": "{PAYMASTER_POLICY_ID}"
          }
        }
      }
    ]
  }'
```

This returns:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "type": "user-operation-v070",
    "data": "USER_OPERATION_DATA",
    "chainId": "CHAIN_ID",
    "signatureRequest": {
      "type": "personal_sign",
      "data": {
        "raw": "HASH_TO_SIGN"
      },
      "rawPayload": "RAW_PAYLOAD"
    }
  }
}
```

Sign the `raw` field and send the transaction as usual. If the account hasn't been delegated yet, the response type will be `array` containing both an EIP-7702 authorization and a transaction. See the [sending transactions documentation](/docs/wallets/transactions/send-transactions) for more details.

</Step>

<Step title="Check the transaction status">

After sending the transaction and getting a `CALL_ID`, monitor its status:

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

This returns:

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

If `status` remains 100 (Pending) for too long, proceed to retry.

</Step>

<Step title="Re-prepare the same call">

To replace the stuck transaction, re-prepare the same call. This will automatically use the same nonce as the pending transaction as long as you don't override the nonce!

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "wallet_prepareCalls",
    "params": [
      {
        "calls": [
          {
            "to": "{SAME_TO_ADDRESS}",
            "value": "{SAME_VALUE}",
            "data": "{SAME_DATA}"
          }
        ],
        "from": "{SIGNER_ADDRESS}",
        "chainId": "{CHAIN_ID}",
        "capabilities": {
          "paymasterService": {
            "policyId": "{PAYMASTER_POLICY_ID}"
          }
        }
      }
    ]
  }'
```

This will return a new signature request with updated gas prices:

```bash
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "type": "user-operation-v070",
    "data": "NEW_USER_OPERATION_DATA",
    "chainId": "CHAIN_ID",
    "signatureRequest": {
      "type": "personal_sign",
      "data": {
        "raw": "NEW_HASH_TO_SIGN"
      },
      "rawPayload": "NEW_RAW_PAYLOAD"
    }
  }
}
```

</Step>

<Step title="Sign and send the replacement transaction">

Sign the new signature request and send it:

```bash
curl -X POST https://api.g.alchemy.com/v2/{API_KEY} \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "wallet_sendPreparedCalls",
    "params": [
      {
        "type": "user-operation-v070",
        "data": "{NEW_USER_OPERATION_DATA}",
        "chainId": "{CHAIN_ID}",
        "signature": {
          "type": "secp256k1",
          "data": "{NEW_SIGNATURE}"
        }
      }
    ],
    "id": 1
  }'
```

This returns:

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

The replacement transaction will use the same nonce as the stuck transaction, causing the original to be dropped from the mempool.

</Step>

<Step title="Monitor the replacement transaction">

Check the status of your replacement transaction:

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

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

</Step>

</Steps>

<Tip>
  To cancel a stuck transaction entirely, send a no-op replacement (same `from`
  and `to` address with empty data).
</Tip>

  </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>