Guide

Solana transaction simulation explained

Every Solana transaction costs a base fee even when it fails on-chain. Before you sign, wallets and well-built dApps run a simulation — a dry-run that executes your transaction against the current ledger state without broadcasting it. Simulation catches insufficient balance, wrong accounts, program logic errors, and compute exhaustion before you spend SOL. Understanding how it works saves money, speeds debugging, and explains why Phantom sometimes blocks a transaction with a cryptic preflight error.

What simulation actually does

Solana validators process transactions in a sandboxed runtime. Simulation replays that same pipeline locally on an RPC node: load accounts, run each instruction in order, update balances, and return success or failure — plus detailed logs. Nothing is written to the real chain; no signature is required for a read-only simulation (though signed transactions can be simulated too).

The RPC method is simulateTransaction. Under the hood the node:

Wallets call this automatically when you click Approve. Backend services call it before sendTransaction to avoid broadcasting doomed transactions during high traffic. If you have ever seen "Transaction simulation failed" in Phantom, that is preflight — simulation returned an error and the wallet refused to sign.

Preflight in wallets vs manual simulation

Wallet preflight is simulation with guardrails. Phantom, Solflare, and Backpack simulate by default, then show a human-readable summary: SOL moving, token transfers, program interactions. If simulation fails, the wallet blocks signing rather than letting you pay a fee for a transaction that will revert.

Developers can simulate manually for finer control:

Manual simulation is essential when building multi-instruction transactions (swap + stake + close account) where the wallet summary is too coarse. You inspect simulation.value.logs line by line to see which instruction failed.

Reading simulation output

A successful simulation returns err: null and an array of log strings. A failed simulation still returns logs — often the most useful part. Typical patterns:

Cross-check failed simulations against our transaction failed guide for user-facing fixes (wrong network, stale blockhash, insufficient SOL for fees). For on-chain receipts after a real send, use reading transactions on Solscan.

Common simulation failure causes

Stale or missing blockhash

Transactions embed a recent blockhash as a TTL. If your builder cached a blockhash for too long, simulation may fail with Blockhash not found. Fetch a fresh blockhash with getLatestBlockhash and rebuild. Wallets usually refresh automatically; backend cron jobs do not.

Account state drift

Simulation uses the RPC node's view of account data right now. Between building and signing, someone else may close an account, drain a pool, or change a config. A simulation that passed five seconds ago can fail at send time — and vice versa. High- contention DeFi pools are the classic case. Retry with fresh account fetches.

Missing signers or wrong authority

Simulation with sigVerify: false skips signature checks, so you can test unsigned txs. But program logic still enforces that the right pubkey signed. If your transaction is missing a required signer, simulation may pass (unsigned) yet fail on send. Always simulate the final signed bytes before mainnet broadcasts when automating.

Compute and priority fees

Heavy programs consume compute units (CU). Default budget is 200,000 CU per instruction unless you add a SetComputeUnitLimit instruction. Simulation reports unitsConsumed — use it to set limits without overpaying. During congestion, low priority fees cause transactions to drop without ever landing; simulation cannot predict queue position, only execution correctness. See priority fees explained for tuning tips.

RPC quality

Simulation is only as accurate as the node's ledger snapshot. Lagging or rate-limited RPC endpoints return stale account data or time out mid-simulate. If preflight fails intermittently, switch providers — our RPC endpoints guide covers public vs dedicated nodes and 429 fallbacks.

When simulation lies (edge cases)

Simulation is a best-effort preview, not a guarantee:

Production backends should simulate, then send with preflight enabled unless you have a measured reason not to. Never disable preflight in user-facing flows to hide bugs.

Developer workflow: simulate before send

A minimal TypeScript pattern (conceptual — adapt to your stack):

const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash('confirmed');
tx.recentBlockhash = blockhash;
tx.feePayer = payer.publicKey;

const sim = await connection.simulateTransaction(tx, {
  sigVerify: false,
  commitment: 'confirmed',
});

if (sim.value.err) {
  console.error('Simulation failed:', sim.value.err);
  console.error('Logs:', sim.value.logs?.join('\n'));
  throw new Error('Aborting send — fix simulation first');
}

console.log('CU used:', sim.value.unitsConsumed);
// sign, then sendTransaction with skipPreflight: false

Log unitsConsumed during development to right-size compute budgets. On mainnet, add a priority fee instruction when simulation shows high CU usage and mempools are busy. Test the same flow on devnet first — simulation semantics match mainnet, but SOL is free.

Merchants verifying inbound payments simulate less often; they read confirmed transactions instead. Our payment verification guide covers the receive side. Simulation is for the send path.

Security note: simulation is not approval

A passing simulation means this transaction would execute successfully given current state — not that it is safe to sign. Malicious dApps can simulate harmless-looking transfers while the signed bytes drain your wallet via a different instruction layout. Always read the wallet's human summary, verify the dApp domain, and follow wallet security practices. Simulation catches program errors; it does not catch phishing.

Related guides