Step 1: find out what happened
Ask the cluster about the signature.searchTransactionHistory makes the lookup work for transactions of any age.
If you got an error back from
sendTransaction itself, there is no signature to look up. The transaction was rejected before it was sent. Go to Rejected before sending.
To check signatures from code, use the wait for confirmation recipe.
Rejected before sending
By defaultsendTransaction simulates the transaction first and refuses to send it if the simulation fails. You get error -32002 with the reason and the program logs:
data.logsusually says what went wrong in plain words. Here, the payer has 0.06 SOL and tried to send 1,000.data.errsays which instruction failed.InstructionError: [0, ...]is the first instruction, counting from zero.- The custom error code belongs to the program that failed, not to Solana.
Custom: 1from the System Program is “insufficient funds”, butCustom: 1from another program means something else. Look the number up in that program’s error list (its IDL, or its source). The message shows it in hex, so0x1770is6000, the first custom error in an Anchor program.
Common rejections
Debug with simulateTransaction
When you want the logs without sending anything, simulate. Two options make it far more useful while debugging:replaceRecentBlockhash: trueswaps in a current blockhash, so you can keep simulating the same transaction after its blockhash expires. It cannot be combined withsigVerify: true.sigVerify: falseskips signature checks, so you can simulate an unsigned or partially signed transaction.
err, logs and unitsConsumed even when the simulation succeeds. Use unitsConsumed to set an accurate compute unit limit: a tight limit makes the priority fee you pay per unit go further.
Landed but failed
The transaction is on chain, the fee is gone, and none of its changes took effect. Get the full record:meta.err has the same shape as a simulation error and meta.logMessages has the program logs. Read them as described in Rejected before sending.
A transaction that simulated fine can still fail on chain because state changed in between. The usual culprits are slippage on a swap, an account someone else closed or modified, or a nonce that was already used. Re-simulate against current state to confirm, then build in tolerance: wider slippage, a fresh quote, or a retry that rebuilds the transaction.
Never landed
getSignatureStatuses returns null: the transaction was accepted by the RPC node but no leader included it. Nothing was charged.
The blockhash expired
A transaction is only valid for 150 blocks after its blockhash. With slots taking about 215 ms, that is roughly 30 to 35 seconds. Once it passes, the transaction can never land, so stop waiting and rebuild it. Check whether a blockhash is still usable:lastValidBlockHeight that getLatestBlockhash returns alongside your blockhash. Compare it with getBlockHeight: while the block height is lower, keep resending the same signed transaction. Once it is higher, the transaction is dead.
The priority fee was too low
When blocks are busy, leaders fill them with the transactions that pay most per compute unit. Check what recently landed for the accounts you touch:SetComputeUnitPrice, and pair it with an accurate SetComputeUnitLimit from simulation.
It was dropped on the way
A sent transaction can be lost between the RPC node and the leader, especially during congestion. Resend the same signed bytes every couple of seconds until it confirms or the blockhash expires. Resending is safe: the same signature can only land once. For transactions where landing quickly matters, send through Apex. Apex sends each transaction to upcoming leaders over several paths at once and keeps retrying until it lands or expires. Its own errors are listed in Apex errors and rate limits.Still stuck
Collect the signature (or the base64 transaction if it never got one), the full error includingdata.logs, the region you sent through, and whether preflight was on. Then ask in Discord.