in-solana by Supanode

Retrying and Confirming Transactions Correctly

Sending a Solana transaction is fire-and-forget: sendTransaction returns a signature, but does not guarantee inclusion. You must poll for confirmation and retry intelligently when transactions fail to land.

The confirmation loop

import { createSolanaRpc } from "@solana/kit";

const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");

async function sendAndConfirm(signedTx: string) {
  const { value: { blockhash, lastValidBlockHeight } } =
    await rpc.getLatestBlockhash({ commitment: "confirmed" }).send();

  const signature = await rpc.sendTransaction(signedTx, {
    encoding: "base64",
    skipPreflight: true,
  }).send();

  // Poll until confirmed or blockhash expires
  while (true) {
    const [status, height] = await Promise.all([
      rpc.getSignatureStatuses([signature]).send(),
      rpc.getBlockHeight({ commitment: "confirmed" }).send(),
    ]);

    if (status.value[0]?.confirmationStatus === "confirmed") {
      return signature; // success
    }

    if (height > lastValidBlockHeight) {
      return null; // expired — caller should rebuild and retry
    }

    await new Promise((r) => setTimeout(r, 2000));
  }
}

Key principles

1. Track blockhash expiry, not time

Do not use a fixed timeout. Instead, compare the current block height against lastValidBlockHeight from getLatestBlockhash. This is the network’s own expiry signal.

2. Resend the same transaction while the blockhash is valid

While the blockhash has not expired, resending the same signed transaction is safe. It has the same signature, so Solana processes it at most once. Resending every 2-3 seconds increases the chance a leader receives it.

3. Rebuild after blockhash expires

Once the block height exceeds lastValidBlockHeight, the transaction can never land. Fetch a new blockhash, rebuild the transaction, re-sign it, and send again.

4. Check before retrying

Always check getSignatureStatuses before concluding a transaction failed. It may have confirmed while you were waiting.

Common mistakes

MistakeProblemFix
Using SDK timeout as the only checkTransaction may confirm after timeoutPoll getSignatureStatuses independently
Retrying with a new blockhash without checkingMay send duplicate transactionsCheck if original signature confirmed first
Not resending while blockhash is validOnly one chance to reach a leaderResend every 2-3 seconds
Using processed commitmentCan report false positivesUse confirmed for reliable confirmation

FAQ

Can a transaction execute twice if I resend it? No. Each transaction has a unique signature. The network deduplicates by signature. The same signed transaction can only execute once.

What commitment level should I check? Use confirmed for most applications. finalized provides the strongest guarantee (31+ confirmations) but takes ~12 seconds longer.

How long should I wait between polls? 2-3 seconds is typical. Polling more frequently wastes RPC requests without benefit — slot time is ~400ms, but confirmation requires multiple validators.

Should I use durable nonces? Only for special cases: offline signing, multisig approvals, or transactions that must survive long delays. Durable nonces add complexity (an extra instruction, a nonce account) and are not needed for standard real-time transactions.

Updated: