Skip to content
Alchemy Logo

Retry Transactions

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

  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 replacement transaction. When you re-prepare a call, the Wallet APIs automatically bump the gas fees for you — you don't need to calculate new values yourself
  5. The original transaction gets dropped from mempool

When you re-prepare a stuck call, the Wallet APIs automatically increase the gas fees by 10% over the pending transaction (or to the current network minimum, whichever is higher). This ensures the replacement is priced high enough to evict the original from the mempool, so you don't need to set fee values manually.

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!

When re-sending calls without a nonceOverride, the client automatically uses the same nonce as the pending transaction, replacing it. The gas fees are also bumped automatically by Wallet APIs — by 10% over the pending transaction, or to the current network minimum, whichever is higher — so you don't need to calculate replacement fees yourself. See the sendCalls SDK reference for full parameter descriptions.

You'll need the following env variables:

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);
}
Using @account-kit/wallet-client (v4)?

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

client.ts (v4)
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 differences:

  • Account address must be specified on the client or per action (from or account). In v5, the client automatically uses the owner'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, 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, a viem LocalAccount or WalletClient is used directly.

See the full migration guide for a complete cheat sheet.

Was this page helpful?