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.
Read the workflow
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.
- ELIGIBLE
- PREPARED
- SUBMITTED
- CONFIRMED
- EXPIRED
- FAILED
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.
- LEGACY
- V0 WITH ADDRESS LOOKUP TABLES
- 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. Unless a lesson explicitly says “read-only Devnet,” use paper or a local test and do not connect to a public network.
- Build a state diagram from wallet intent to finalized account state, including expired and failed branches.
- Use a mocked RPC to return delayed, null, duplicate, and out-of-order status responses.
- Bind every response to its original cluster, signature, blockhash lifetime, and request generation.
- Retry one logical purchase from two browser tabs and prove it creates only one business result.
- 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?
- Submit a new transaction immediately; a timeout proves the first did not land.
- Treat the outcome as unknown, reconcile the original signature and relevant state, and retry only under an explicit duplicate-safe policy.
- 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.
Scored checkpoints and level exams are online and require a free account. They are not included in this pack.
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 when you reconnect.