When a Solana transaction fails, the error you get back is often unhelpful. "Custom: 0x1771" tells you a number, not what went wrong. For production debugging, knowing how to translate failure modes into root causes is one of the higher-leverage skills you can have.
This piece covers how to debug failed Solana transactions: the common error patterns, the tools, and the workflow that turns mystery failures into clear root causes.
The Common Failure Categories
Most Solana transaction failures fall into a few categories:
Compute exceeded. ComputationalBudgetExceeded. The transaction needed more CU than budgeted.
Slippage exceeded. Custom error from the AMM/program. The transaction's minimum-output condition wasn't met.
Insufficient balance. Self-explanatory but easy to overlook with priority fees and tips.
Account does not exist. ATA hasn't been created; the program needs an account that isn't there.
Account validation failure. Program-specific. The account passed in is wrong type or wrong state.
Blockhash expired. The transaction was submitted against a blockhash that's no longer recent.
Transaction too large. 1232-byte limit; complex transactions hit this.
Custom program errors. Each program defines its own error codes; the codes don't have universal meaning.
Each has different debugging approaches.
Reading the Error
A typical failure response:
{
"err": {
"InstructionError": [
0,
{ "Custom": 6000 }
]
}
}
Decoding:
InstructionErrormeans a specific instruction failed[0, ...]means it was the first instruction (0-indexed)Custommeans the program returned a custom error code6000is the error code
To know what 6000 means, you need the program's error definitions. For Anchor programs, the IDL has them. For non-Anchor programs, check the program's source.
Common Anchor Errors
Anchor programs have standard error code ranges:
2000-2999: Anchor framework errors3000+: Custom program errors (defined by the program)
A few common Anchor framework codes:
2000:InstructionMissing— instruction not found2003:ConstraintMut— account marked immutable but program tried to mutate2004:ConstraintHasOne—has_oneconstraint failed2006:ConstraintRaw— raw constraint failed (custom check)3008:AccountDidNotDeserialize— couldn't decode account3010:AccountDiscriminatorMismatch— wrong account type passed
When you see these, look at what your code is doing wrong (passing wrong account, missing signer, etc.).
Common Token Program Errors
SPL Token program error codes:
1: Insufficient funds4: Invalid owner5: Account is owned by a different program6: Mint mismatch
These are common in transfer and swap operations.
Debugging Workflow
A practical debugging workflow:
1. Get the signature. From your code's logs or per-signature telemetry.
2. Find the transaction in an explorer. Solscan or the official Solana Explorer. Look at the parsed view if available.
3. Read the error code. Decode using the relevant program's error definitions.
4. Read the logs. Solana programs can emit log messages. The logs often have human-readable hints about what failed.
5. Simulate the transaction. If you can reproduce, simulate against current state. The simulation may surface the same error with more context.
6. Check the accounts. Often the issue is that an account is in unexpected state — wrong owner, wrong size, wrong content.
7. Reproduce locally if possible. Fork mainnet state and run the transaction in a test environment.
Tools That Help
Solscan. General-purpose. Parses common transactions; shows logs.
Solana Explorer. Canonical reference; shows logs.
XRAY. Better parsing for some transaction types.
Anchor's IDL. For Anchor programs; gives you typed error definitions.
Program source code. When all else fails. Find the program on GitHub and read the error definitions.
Your own per-signature telemetry. Pre-failure context that explorers don't have.
Common Real-World Debugging Cases
A few patterns we've seen:
Failure: ConstraintRaw on a swap. Often slippage tolerance violated. Check whether the price moved between simulation and execution.
Failure: ComputationalBudgetExceeded on multi-hop swap. CU budget too low for the actual route. Increase to handle worst case.
Failure: AccountNotFound on first user interaction. ATA hasn't been created. Add an init-if-needed instruction.
Failure: BlockhashNotFound after retries. The blockhash expired. Refresh blockhash on retry, don't reuse.
Failure: Custom: 6000 on Jupiter swap. Often slippage. Increase tolerance or refresh quote.
Failure: Transaction simulation succeeded but execution failed. State changed between simulate and execute. Don't trust simulation as a guarantee.
Failure: Successful sendTransaction but transaction not on-chain. The blockhash expired and the transaction was silently dropped. Verify by polling the signature.
Production Debugging Patterns
For production code:
Capture signature for every send. Without it, you can't look up failures later.
Capture full instruction context. What you submitted, what state you assumed.
Capture the simulation result if you simulated. Sometimes simulation passes and execution fails; the gap is informative.
Sample logs systematically. A small percentage of all transactions, full context. Useful for analysis.
Alert on anomalies. Sudden spike in failures of a specific type signals an environmental change.
What to Do This Week
If you're debugging a Solana production issue:
- Get the signature. Without it, you're guessing.
- Look at it in Solscan first. The parsed view often has the answer.
- Read the program's error definitions. Don't guess at custom error codes.
- Read the logs. Programs often log the cause.
- Reproduce locally if you can. Fork mainnet state; run the transaction.
- For systematic issues, look at multiple failures. Patterns are more informative than individual cases.
Try BoltTx for Production Debugging
BoltTx provides per-signature delivery telemetry — for any transaction you submitted, see when it was received, when it was relayed, when it landed (or why it didn't):
import { Connection } from "@solana/web3.js";
const connection = new Connection(
"https://bolttx.io/?api-key=YOUR_API_KEY",
"processed"
);
The telemetry helps distinguish "RPC issue" from "on-chain failure" from "blockhash expired" — each has different fixes. Free tier signup.
FAQ
My transaction signature exists but the transaction didn't land. What happened? Most likely the blockhash expired before inclusion. Refresh blockhash and resubmit.
What does "Custom: 6000" mean? Depends on the program. Check the program's error definitions (Anchor IDL or source code).
Why does simulation succeed but execution fail? State changed between simulation and execution. Don't trust simulation as a guarantee — always confirm execution.
How do I find the program's error codes? For Anchor programs, the IDL. For raw programs, the source code's error enum.
What if the explorer shows the transaction landed but my code thinks it failed? Check your confirmation logic. You may be checking the wrong commitment level or missing the success signal.