← Solana Architect

Expert · LESSON 03 OF 06

3. Transaction reliability and recovery

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

In this lesson8 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 transaction size, account lookup, and resource limits; v1 is active on all clusters and requires reader opt-in.
Solana transaction formats: legacy, v0, and v1. Three Solana formats compare transaction size, account lookup, and resource limits; v1 is active on all clusters and requires reader opt-in.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, so transaction readers must opt in with maxSupportedTransactionVersion set to 1.

  1. LEGACY
  2. V0 WITH ADDRESS LOOKUP TABLES
  3. V1 WITH MESSAGE CONFIG
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 health. 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 or merely confirmed transaction as finalized when the verifier requires stronger commitment.

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

Solana’s current transaction documentation marks v1 active on mainnet, devnet, and testnet. A v1 reader must explicitly set `maxSupportedTransactionVersion: 1` when requesting transaction details; a reader that omits this support can fail on a transaction that did land. 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. The official upgrade notes describe the current rules; test them against pinned SDK versions and the wallet path used by the application.

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.

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

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 one best answer and its 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

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-09-28. 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 ↗

Report an issue with this lesson

OPEN LEARNING

Keep learning at your own pace.

This lesson, its guided practice, and all six course lessons are open to everyone. Account-based checkpoints, exams, and saved progress will open when free accounts are ready.

Continue to the next lesson ↗