A wallet approval is one step in a Solana transaction. The transaction still has to describe the work, name every account, carry a recent blockhash, fit within 1,232 bytes, pay its fees, execute, and reach the confirmation level your product promises.
Once those parts are visible, transaction bugs stop looking random. You will build that mental model and get a Transaction Readiness Checklist for reviewing a transaction before it reaches a wallet.
A transaction is an atomic envelope
A Solana transaction contains signatures and a message. The message holds a header, account addresses, a recent blockhash, and one or more compiled instructions.
transaction
├─ signatures
└─ message
├─ header
├─ account addresses
├─ recent blockhash
└─ compiled instructionsThe runtime processes the instructions in order. A failed instruction rolls back every state change in the transaction. The fee is still charged because validators already verified and processed the transaction.
Atomic execution is useful when two actions must succeed together. A token workflow can create a recipient token account and transfer tokens in one transaction. Either both changes land or neither does.
It is less useful when a workflow contains independent steps that do not need one shared outcome. Packing unrelated work together increases size, compute use, account locks, and the number of ways the whole transaction can fail.
Instructions describe one program call
One instruction targets exactly one program and contains three parts:
- program_id: the program that will execute.
- accounts: an ordered list of account metadata.
- data: bytes that identify the operation and carry its arguments.
The receiving program defines the format of the data. A System Program transfer interprets different bytes than a Token Program transfer or an Anchor instruction.
Account order also belongs to the program contract. Passing the right addresses in the wrong positions can fail validation even when every account exists.
Account metadata declares access up front
Every account entry tells the runtime whether the account must sign and whether the instruction may write to it.
type AccountMeta = {
address: Address;
isSigner: boolean;
isWritable: boolean;
};Account metadata helps Solana schedule transactions in parallel. Two transactions that only read the same account can run together. Transactions that write to the same account compete for the same lock.
Use the narrowest permissions the instruction needs. Marking extra accounts writable reduces concurrency. Missing a required signer or writable flag causes a deterministic failure.
Read the Solana account model guide for the underlying account roles.
Signatures and blockhashes bind approval to one message
Signers authorize the serialized transaction message. A signature does not approve a loose list of actions that the application can change afterward.
Once a signer approves the message, changing an account, blockhash, instruction, fee payer, or amount invalidates that signature. A wallet should display the transaction intent before approval, and applications must rebuild expired transactions before asking again.
The first signature usually belongs to the fee payer. Other instructions can require more signers, such as a newly generated account or a second authority.
Program Derived Addresses do not have private keys. A program can authorize a PDA during a cross-program invocation through the runtime, but a browser wallet cannot produce a normal Ed25519 signature for that PDA.
The fee payer funds execution
The fee payer is writable and must sign. Its SOL balance covers the base fee, optional priority fee, and any account-funding lamports included in the instructions.
Fee sponsorship changes who pays, not who authorizes protected actions. A sponsored token transfer still needs the user's authority signature when the token program requires it.
A recent blockhash gives the transaction an expiry
The message includes a recent blockhash. It prevents an old signed transaction from remaining valid forever and gives validators a recent ledger reference.
Solana currently accepts a recent blockhash for 150 slots. Wall-clock duration varies with slot production, so applications should track the returned lastValidBlockHeight rather than rely on a fixed timer.
A safe client flow is:
- Fetch a recent blockhash and last valid block height.
- Build the message with the intended instructions.
- Ask the required signers to approve that message.
- Submit the signed transaction.
- Track its signature until confirmation or expiry.
- On expiry, fetch a new blockhash and request a fresh signature.
Never replace the blockhash on an already signed message and reuse the old signature. The signature covers the original bytes.
Durable nonces support specialized offline and delayed-signing flows. They add state and operational responsibilities, so a normal interactive app should use recent blockhashes unless it has a concrete nonce requirement.
Size, compute, and fees shape transaction design
Transaction size is a design constraint
A serialized Solana transaction may not exceed 1,232 bytes. That budget includes every 64-byte signature, account address, blockhash, header field, and instruction payload.
The limit changes how you compose features. More signers consume space. Large instruction data consumes space. Repeating many account addresses consumes space.
Versioned transactions can use Address Lookup Tables to reference account addresses more compactly. They help account-heavy transactions, but they do not remove the total size and compute constraints or make an oversized instruction payload free.
Before introducing a lookup table, check whether the transaction is doing too much. Splitting independent work often produces a simpler recovery path than compressing one large operation.
Compute limits bound program execution
The runtime meters work in compute units, or CUs. A transaction can request up to the current maximum of 1.4 million CUs.
Without an explicit compute-unit limit, the runtime derives defaults from the instruction mix. The current documented defaults are 200,000 CUs for each non-builtin instruction and 3,000 CUs for each builtin instruction, capped by the transaction maximum.
Programs consume CUs for instruction processing, memory operations, cryptography, logging, and cross-program calls. Crossing the requested limit makes the transaction fail.
Simulate representative transactions before choosing a limit. Add a small measured margin, then monitor real execution. A permanently oversized limit is not harmless because the priority fee uses the requested limit rather than actual consumption.
Fees have a base and optional priority component
Every transaction pays a base fee. The current base fee is 5,000 lamports per signature, including applicable precompile signature verifications.
An optional priority fee can increase scheduling priority. Its documented calculation is:
priority fee = ceil(
compute unit price × requested compute unit limit / 1,000,000
)The compute unit price is measured in micro-lamports per CU. The requested limit matters, even when the program uses fewer CUs.
A request for 300,000 CUs at 10,000 micro-lamports per CU adds 3,000 lamports:
10,000 × 300,000 / 1,000,000 = 3,000 lamportsSet both values from current conditions and observed compute use. Copying a fixed priority fee from an old tutorial can either waste SOL or leave a transaction underpriced during demand.
Fees are deducted before execution and remain charged when the transaction fails. Simulation, validation, and clear wallet previews therefore support cost control.
Submission is not confirmation
An RPC can return a transaction signature after accepting the signed bytes for forwarding. The response does not prove that the transaction landed or succeeded.
Your product needs explicit states:
- signing: waiting for wallet approval.
- submitted: the RPC accepted the signed transaction.
- processed: a validator included it in its working fork.
- confirmed: the cluster voted on the block with sufficient confidence.
- finalized: the block reached the strongest standard commitment.
- failed: execution returned an error.
- expired: the blockhash validity window ended without a confirmed result.
Use the commitment level that matches the consequence. A low-risk interface update might react at confirmed. Settlement, withdrawal, or irreversible off-chain fulfillment may require finalized and additional business checks.
Always retain the signature. It is the identifier your client, backend, support team, and user can use to inspect the same operation.
Use the Solana transaction reliability runbook for retry logic and ambiguous outcomes.
Run the Transaction Readiness Checklist
Review these items before requesting a wallet signature:
- Intent: Does the interface state the action, asset, amount, recipient, and cluster?
- Programs: Is each program ID expected for this environment?
- Accounts: Are account order, ownership, and decoded types correct?
- Permissions: Are signer and writable flags no broader than required?
- Funds: Can the fee payer cover fees and any account creation?
- Blockhash: Is the blockhash fresh, with its last valid block height retained?
- Size: Does the serialized transaction fit within 1,232 bytes?
- Compute: Has the real instruction path been simulated with a measured limit?
- Fees: Does the priority fee reflect the requested CU limit and current conditions?
- Recovery: Can the app distinguish rejection, failure, expiry, and an unknown submitted state?
After signing, persist the signature and transaction intent before waiting for confirmation. A refresh should not erase the only link between the user action and its on-chain result.
Common transaction failures point to a specific layer
Signature verification failed
The signed message changed, a required signer is missing, or a signature does not match its account. Rebuild the exact message and collect every required signature again.
Blockhash not found
The blockhash expired or came from a different cluster context. Fetch a current blockhash, rebuild, and request a new signature.
Transaction too large
Measure serialized size. Remove unnecessary signers or data, split independent instructions, or evaluate a versioned transaction with a lookup table.
Computational budget exceeded
Simulate the complete instruction path, including cross-program calls. Fix unexpectedly expensive program work or set a measured compute-unit limit.
Account in use or slow landing
The transaction may contend for writable account locks or carry insufficient scheduling priority. Inspect the hot writable accounts before raising fees.
Simulation passed but execution failed
State can change between simulation and execution. Re-check balances, authorities, account versions, slippage, and other state-dependent assumptions.
FAQ
Can one transaction call several programs?
Yes. Each top-level instruction targets one program, and a transaction can contain multiple instructions. Programs can also call other programs through cross-program invocations.
Do all instructions succeed together?
Yes. Transaction state changes are atomic. A failed instruction rolls back the state changes from earlier instructions, while the transaction fee remains charged.
How long does a recent blockhash last?
The current processing-age limit is 150 slots. Track lastValidBlockHeight because slot timing is not a fixed wall-clock promise.
Does a higher priority fee guarantee inclusion?
No. It affects scheduling priority but cannot repair an invalid transaction, remove account contention, or guarantee inclusion before expiry.
Should every app wait for finalized commitment?
No. Choose commitment based on product risk. Show pending state honestly and reserve irreversible off-chain action for the confirmation policy that matches its consequence.
Primary references
- Solana transactions
- Transaction structure
- Solana instructions
- Instruction structure
- Solana fees
- Fee structure
Next step: Run the Transaction Readiness Checklist against one production flow. If intent, authority, or recovery remains unclear, bring the transaction map to the Builderz project fit form.



