Failed Solana transactions leave clues in simulation logs, RPC error codes, and explorer program output. A disciplined decode workflow separates user errors, client bugs, RPC issues, and on-chain program defects before you retry or page on-call.
function parseCustomError(logs: string[]): number | null { const line = logs.find((l) => l.includes("custom program error:")); if (!line) return null; const m = line.match(/custom program error: (0x[0-9a-f]+|\d+)/i); if (!m) return null; return m[1].startsWith("0x") ? parseInt(m[1], 16) : Number(m[1]);}
Retrying without reading logs - Users hammer resubmit; you burn priority fees on the same deterministic failure. Fix: Simulate once, classify error, fix root cause.
Wrong cluster RPC - Devnet tx simulated against mainnet RPC returns AccountNotFound. Fix: Pin RPC_URL, wallet network, and explorer ?cluster= together.
Assuming preflight is always on - Some server paths disable preflight for latency. Fix: Explicit simulateTransaction in support tooling.
Decimal vs lamport amounts - Client sends 1.5 SOL as float; program expects integer lamports. Fix:bigint end-to-end; convert at UI boundary only.
Stale IDL error map - Deployed program v2, client still decodes v1 codes. Fix: Version IDL in CI; regenerate clients on every program release.
Ignoring unitsConsumed - Near-limit programs fail only under load. Fix: Track CU in metrics; alert when p95 exceeds 80% of budget.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.