← Solana Architect

Expert · LESSON 03 OF 07

3. Transaction reliability and recovery

By the end, you can reconcile solana transactions across confirmation, expiration, retries, and interrupted clients.

Look up Solana terms in the glossary

In this lesson9 field notes · practice · sources · checkpoint
A transaction moves through eligibility, preparation, submission, and confirmation, with explicit branches for expiry and failure recovery.
A transaction moves through eligibility, preparation, submission, and confirmation, with explicit branches for expiry and failure recovery.Open full-size visual ↗
Read a text version of this diagram

An eligible operation is prepared and submitted with a bounded blockhash lifetime, then reconciled against chain state. Confirmation is one terminal path; expiry or execution failure requires a distinct recovery path. Retried requests must reuse stable identifiers and must inspect prior outcomes so a timeout cannot create duplicate or unintended actions.

  1. ELIGIBLE
  2. PREPARED
  3. SUBMITTED
  4. CONFIRMED
  5. EXPIRED
  6. FAILED
Three Solana formats compare size, address lookup, and resource limits. V1 is active on all clusters; reading it requires opt-in, while sending it remains optional.
Solana transaction formats: legacy, v0, and v1. Three Solana formats compare size, address lookup, and resource limits. V1 is active on all clusters; reading it requires opt-in, while sending it remains optional.Open full-size visual ↗
Read a text version: Solana transaction formats: legacy, v0, and v1

Legacy and v0 transactions are limited to 1,232 bytes and request resource limits with ComputeBudget instructions. V0 adds address lookup tables to reference non-signer accounts compactly; required signers remain inline. V1 raises the size limit to 4,096 bytes, keeps account addresses inline without lookup tables, and stores resource limits in the signed message configuration. Its priority fee is a total lamport amount. V1 is active on Mainnet, Devnet, and Testnet. Applications may keep sending legacy or v0, but a history reader must opt in with maxSupportedTransactionVersion set to 1 to read v1 transactions.

  1. LEGACY
  2. V0 WITH ADDRESS LOOKUP TABLES
  3. V1 WITH MESSAGE CONFIG
For an uncertain submission, retain the original signature and last valid block height. While the blockhash remains valid, rebroadcast only the same signed bytes and keep checking that signature. After expiry, reconcile history and application receipt state before creating a new signed message. A transaction failure may still charge its fee; a fresh signature for the same business action needs durable idempotency protection.
Retry delivery with the same signed transaction; create a new message only after expiry and business-state reconciliation.Open full-size visual ↗

FREE INTERACTIVE CASEBOOK · ONLINE OR OFFLINE

Same signature or new authorization?

Work through six fictional wallet, RPC, expiration, execution, and duplicate-intent incidents. Choose the next action from the evidence available; feedback distinguishes delivery retries from a fresh business action.

Open the Transaction Recovery Tabletop Download the offline tabletop

The practice uses no wallet, RPC, blockchain, network request, or saved answers.

FIELD NOTE 01

Separate user intent from network evidence

A button click expresses intent. A wallet prompt shows a message awaiting approval. A returned signature identifies a signed transaction, and an RPC acknowledgement only says a provider accepted a submission request. These are different states. Track them separately so the interface never reports “complete” from a click, signature string, or optimistic balance change. Record the cluster and transaction signature alongside the user action; never treat an explorer page without its cluster context as sufficient evidence.

FIELD NOTE 02

Freeze the message before collecting signatures

A transaction message contains its fee payer, recent blockhash or durable nonce, account addresses, signer and writable permissions, instruction order, and instruction data. Every signer approves those bytes. Changing the fee payer, account list, instruction, or blockhash creates a different message and invalidates prior signatures. A coordinator should check that returned signatures match the required public keys and that the user-reviewed message did not change before submission. Keep the original serialized message or a digest when several parties sign asynchronously.

FIELD NOTE 03

Treat blockhash lifetime as transaction state

A recent blockhash gives a transaction a limited processing window. Fetch and retain `lastValidBlockHeight` with it; after the block height passes that limit, the old transaction cannot be made live again by retrying the same bytes. Build a fresh message and collect signatures again. Durable nonce transactions support a different lifetime model, but the nonce must be valid and advanced correctly. Before rebuilding, query the original signature because a timeout does not prove the transaction was never processed.

FIELD NOTE 04

Understand execution, fees, and commitment

Solana transactions execute their instructions atomically: state changes are committed together or rolled back if execution fails. The fee payer can still be charged when execution fails. An RPC response can report processed, confirmed, or finalized observations according to commitment and cluster migration state. On TowerBFT clusters, confirmed and finalized are distinct; after Alpenglow migration, Solana documents them as converging and plans to remove confirmed. Check the same cluster with a compatible getAgGenesisCert RPC, and use finalized in new code. Store the returned slot and status, and wait for the commitment required by the product before updating durable business records. Do not describe a submitted transaction as finalized without the required chain evidence.

FIELD NOTE 05

Make application retries idempotent

The runtime rejects a transaction message that has already been processed, but a client can build a new message for the same logical action. Prevent duplicate deposits, orders, claims, or withdrawals at the application and program layers. Use a business idempotency key or canonical receipt account, reserve the action atomically, and make repeated requests return the existing result. The uniqueness rule must cover the actual protocol intent, not only a wallet button or a particular transaction signature.

FIELD NOTE 06

Protect clients from stale and reordered results

Two requests can finish out of order: a slow balance read for wallet A may arrive after the user has switched to wallet B. Tag asynchronous work with the original cluster, address, and request generation. Apply a response only if those values still match current state. Treat provider timeouts and rate limits as retryable observations, not proof of failure. Use bounded backoff and avoid sending a new value-moving transaction automatically when the earlier attempt is uncertain.

FIELD NOTE 07

Reconcile instead of guessing

Define a state machine such as draft, awaiting signature, signed, submitted, confirmed, finalized, expired, and failed. Each transition needs evidence: wallet response, signature, status query, transaction metadata, block height, or account state. On restart, look up the same signature and inspect the intended account changes before creating another action. If providers disagree, record the uncertainty and query an independent endpoint. A durable receipt should let support explain exactly what happened without asking for a recovery phrase or private key.

FIELD NOTE 08

Treat transaction version as part of recovery evidence

Transaction v1 is live on Mainnet, Devnet, and Testnet. The Solana Foundation records Mainnet activation at the start of epoch 1035 on September 15, 2026, at approximately 01:00 UTC. Keep transaction writing and reading separate: applications may continue to send legacy or v0 transactions, but history readers that encounter v1 must request the integer `maxSupportedTransactionVersion: 1`. A `getTransaction` request without that support can fail even when the transaction landed; a `getBlock` request can fail as a whole when its block contains a v1 transaction. `blockSubscribe` also takes the version option; without support, its stream can stop advancing at a v1 slot and report `block: null` with an error. Treat that as a failed read, not an empty block. SDK support is directional: the current migration guide says `@solana/web3.js` v1.99.0 and later can read and deserialize v1 with that opt-in, but the web3.js v1 line cannot build, sign, or send v1 transactions; earlier v1 releases are limited to legacy and v0. Use a current, pinned `@solana/kit` 8.x client for v1 construction and sending, and verify the exact wallet path too. For v1 construction, set the compute-unit limit and loaded-account-data-size limit explicitly; unlike older formats, v1 carries resource configuration in the message and compute-budget instructions are no-ops. This means a client that copies a legacy/v0 fee calculation can report the wrong priority fee: older formats derive it from requested compute units times the micro-lamport price per unit, while v1 records a total priority fee in lamports. Before retrying an uncertain submission, preserve the original signature and blockhash lifetime, query with a decoder that supports the transaction format, and reconcile state. If the lookup fails because the reader is too old, that is a compatibility failure, not evidence that the transaction never landed. Test the migration against pinned SDK versions and the wallet path used by the application.

FIELD NOTE 09

Retry the same signed message before rebuilding

A successful `sendTransaction` response returns the first transaction signature; it does not confirm execution. Keep that signature, the exact serialized signed bytes, the cluster, and the `lastValidBlockHeight` returned with the blockhash. If the client loses the acknowledgement or times out while the recent blockhash is still valid, it may rebroadcast the same fully signed bytes: the transaction message and signature stay identical, so the runtime duplicate check prevents a second execution. Continue checking that same signature and report the result as pending until the required commitment is observed. Do not ask the wallet to sign a new message merely to repeat network delivery. After the recorded block-height limit passes, the old message cannot be refreshed without invalidating its signatures. Search transaction history when the signature may have left the recent cache, query a compatible transaction reader at the needed commitment, and reconcile the relevant account or receipt state. A `null` from one provider is not proof of absence. If evidence supports a genuinely unprocessed action, check the business idempotency key or on-chain receipt before building a new message and collecting fresh signatures. A processed instruction error can still charge the fee payer even though instruction state rolls back; distinguish that chain failure from a wallet cancellation or an RPC timeout.

MAKE THE CONNECTION

A detail worth keeping.

For a checkout that transfers a token and records an order, inject a dropped response after submission and again after finalization but before the database write. Reconcile the same signature, confirm the order exists once, and show that a retry cannot charge the buyer twice.

Work through an example

For a multi-party signing flow, let one signer respond after the recent blockhash expires. The old signatures cannot authorize a rebuilt message. Explain why the client must fetch a fresh blockhash, freeze the new message, and collect all required signatures again.

Create a transaction-recovery table for an escrow purchase. Include user cancellation, RPC timeout, expired blockhash, failed execution with fees, delayed finalization, duplicate browser requests, and a lost database acknowledgement.

GUIDED PRACTICE · EXPERT

Do the work, one step at a time.

Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.

  1. Build a state diagram from wallet intent to finalized account state, including expired and failed branches.
  2. Use a mocked RPC to return delayed, null, duplicate, and out-of-order status responses.
  3. Bind every response to its original cluster, signature, blockhash lifetime, and request generation.
  4. Retry one logical purchase from two browser tabs and prove it creates only one business result.
  5. Write the recovery procedure for an uncertain submission and identify which observations remain non-final.
  6. Rebroadcast identical signed bytes while the recorded blockhash remains valid, then simulate expiry and a history miss; keep the business action unresolved until signature history and receipt state justify the next message.

Before you move on, check your reasoning

  • Draw the transaction and business-state lifecycles side by side.
  • Inject interruptions at signing, submission, confirmation, and persistence boundaries.
  • For every retry, name the evidence that prevents duplicate execution.

OPEN SELF-CHECK · UNGRADED

Pause and test your reasoning

Practice 1 of 3

The RPC request timed out immediately after transaction submission. What is the safest next step?

  1. Submit a new transaction immediately; a timeout proves the first did not land.
  2. Treat the outcome as unknown, reconcile the original signature and relevant state, and retry only under an explicit duplicate-safe policy.
  3. Tell the user it succeeded because the wallet signed it.
Reveal the answer and explanation

One best answer: Treat the outcome as unknown, reconcile the original signature and relevant state, and retry only under an explicit duplicate-safe policy.

A client timeout does not reveal whether an RPC received, forwarded, or confirmed the signed transaction. Query the original signature and application state on the correct cluster; only retry after expiry and duplicate behavior are understood.

Sources: Transaction Confirmation and Expiration · getSignatureStatuses

Practice 2 of 3

A recent-status query is empty after a blockhash expires. What must happen before retrying the user’s action?

  1. An expired blockhash proves that the original transaction never executed and cannot have succeeded.
  2. An empty recent-status result guarantees that the original business action never occurred.
  3. Reconcile history and state before retrying.
Reveal the answer and explanation

One best answer: Reconcile history and state before retrying.

Blockhash expiry limits transaction validity, but a timeout or empty recent cache is not complete history. Reconcile the original signature and durable business state before a new authorization.

Sources: getSignatureStatuses · getTransaction · Transaction Confirmation and Expiration

Practice 3 of 3

Two workers receive the same confirmed claim request. Which control prevents two business actions?

  1. Enforce a durable business idempotency key with an atomic reservation; reconcile signature and receipt evidence before retrying.
  2. Use the transaction signature as the only duplicate key; when a retry rebuilds the transaction, the changed message generates a different signature and permits another claim.
  3. Treat an expired blockhash or empty recent-status query as proof that the claim never executed.
Reveal the answer and explanation

One best answer: Enforce a durable business idempotency key with an atomic reservation; reconcile signature and receipt evidence before retrying.

Different signed transactions can represent the same business intent. A durable application-level idempotency key and atomic state change prevent duplicate actions; reconcile persisted receipts and signature history when workers race or recover.

Sources: Transactions · getSignatureStatuses

This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.

Never enter a recovery phrase or private key into a learning site or lesson exercise.

READ THE SOURCE

Go deeper in the official docs

Reviewed on 2026-10-02. Protocol documentation can change; check the linked page’s current version as you use this material.

  1. Transactions ↗
  2. Transaction Pipeline ↗
  3. Transaction Confirmation and Expiration ↗
  4. getSignatureStatuses ↗
  5. getTransaction ↗
  6. Larger Transaction Sizes ↗
  7. Versioned Transactions ↗
  8. Fee Structure ↗
  9. Kit Client ↗
  10. Migrating to Kit ↗
  11. Partial Signing ↗
  12. Retrying Transactions ↗
  13. getLatestBlockhash ↗
  14. Production Readiness ↗

Report an issue with this lesson

YOUR PROGRESS, SAVED

Ready for the checkpoint?

Make a free account to save your progress and unlock the quizzes.

Create free account