Solana skipPreflight — When to Use It, When Not, and Why It Matters

What skipPreflight does in Solana sendTransaction. When enabling it is the right call, when it costs you, and the production patterns that distinguish working setups from broken ones.

BoltTx Team··7 min read
solanaskippreflightsendtransactionrpctransaction

skipPreflight is one of those Solana parameters that tutorials gloss over and production code gets wrong. The default is false, the docs are brief, and the implications aren't obvious until you've debugged enough transactions to understand what preflight actually does. For most production code, you want this set to true — but it's worth knowing when and why.

This piece covers what preflight does, the cases where you should skip it, the cases where you shouldn't, and the production patterns that get this right.

What Preflight Actually Is

When you call sendTransaction, the RPC has the option to simulate the transaction before forwarding it to validators. This simulation is "preflight." It runs the transaction against the current chain state, sees whether it would succeed, and only forwards to validators if it would.

// Default: preflight enabled
connection.sendTransaction(tx, signers);
// = sendTransaction(tx, signers, { skipPreflight: false })

// Skip preflight
connection.sendTransaction(tx, signers, { skipPreflight: true });

When preflight is enabled:

When preflight is disabled:

When to Set skipPreflight: true

In production transaction-sending code, almost always. The reasons:

You've already validated client-side. If your code knows the transaction is well-formed, knows the accounts exist, knows the math works, you don't need the RPC to re-simulate.

Latency matters. Removing the simulation step shaves milliseconds off the submit path. For latency-sensitive use cases (arbitrage bots, sniper bots), this matters.

You can simulate yourself when needed. During development you can call simulateTransaction explicitly to test. In production, skip the auto-simulate.

Preflight isn't always reliable. In a fast-moving network, the state at simulation time differs from the state at execution time. A simulation pass doesn't guarantee execution success.

// Production-typical sendTransaction call
const signature = await connection.sendTransaction(tx, signers, {
  skipPreflight: true,
  maxRetries: 0,
  preflightCommitment: "processed",  // ignored when skipPreflight: true
});

When to Keep Preflight Enabled

Cases where preflight earns its latency cost:

You're sending user-submitted transactions you haven't validated. A wallet UI accepts a transaction the user constructed; you don't fully trust it; preflight catches obvious failures.

Development and debugging. During development, the failure feedback from preflight is useful. The latency doesn't matter when you're iterating.

Cost-sensitive infrequent transactions. If you're submitting one transaction at a time and a failed transaction would cost you meaningful fees, preflight saves money on the cases where it would have failed anyway.

Programs you don't fully understand. If you're calling a third-party program and aren't sure your accounts are right, preflight helps.

For the common case of a bot or app sending many transactions you control, none of these apply, and you should skip preflight.

What Happens When Preflight Catches an Error

A preflight failure looks like:

Error: failed to send transaction: Transaction simulation failed:
Error processing Instruction 0: custom program error: 0x1

The transaction wasn't forwarded. You don't pay fees. You can fix and resubmit.

Compared to the failure case without preflight:

// You called sendTransaction successfully
// Signature: 5Pp3...
// You then check the transaction status:
{
  err: { InstructionError: [0, { Custom: 1 }] }
}

The transaction was forwarded, included in a block, and failed during execution. You paid fees for the failed transaction. The signature exists but the transaction errored.

If you never check the result, you'll think the transaction succeeded. This is one of the failure modes that bites people who set skipPreflight: true without proper post-submission confirmation logic.

The Confirmation Pattern That Pairs With skipPreflight: true

If you skip preflight, you must confirm the transaction's actual outcome. The pattern:

const signature = await connection.sendTransaction(tx, signers, {
  skipPreflight: true,
  maxRetries: 0,
});

// Wait for confirmation
const result = await connection.confirmTransaction(
  { signature, ...latestBlockhash },
  "confirmed"
);

if (result.value.err) {
  // Transaction landed but failed during execution
  console.error("Transaction failed:", result.value.err);
  // Decide whether to retry, alert, etc.
} else {
  // Transaction succeeded
  console.log("Confirmed:", signature);
}

The confirmation step closes the loop. Without it, you're flying blind on whether your transactions are actually working.

Common Mistakes

Things developers do wrong:

Setting skipPreflight: true and not confirming. Transactions silently fail; bot operator doesn't notice for days.

Setting skipPreflight: false in production with high-frequency sends. Adds latency to every submit. Wastes RPC capacity.

Using preflight as a substitute for client-side validation. Validate before constructing the transaction. Preflight isn't a safety net for sloppy code.

Trusting preflight success as a guarantee. Network state changes. A transaction that passed preflight 200ms ago might fail when actually executed.

Setting skipPreflight: true but using preflightCommitment. preflightCommitment is ignored when skipPreflight: true. Sets a misleading default but isn't broken.

When skipPreflight Saves Real Money

The economic argument for skipping preflight in latency-sensitive contexts:

In a competitive arbitrage scenario, a 50ms difference in submission latency can mean the difference between landing in slot N and slot N+1. The slot N+1 fill is meaningfully worse. Over many trades, this adds up to real P&L.

Preflight adds round-trip latency (your client → RPC → simulate → RPC → forward). For high-frequency operators, this round-trip is the difference between profitable and unprofitable.

The trade-off: you pay fees on transactions that would have failed (which preflight would have caught). For most bots, this is a small percentage of total fees, easily recovered by the latency improvement.

Other Send Options That Pair With skipPreflight

When you skip preflight, also consider:

maxRetries: 0. Don't let the RPC silently retry. Manage retries yourself with fresh blockhashes.

Use sendRawTransaction directly. Pre-serialise the transaction; saves a marginal amount of work in the RPC.

Set explicit compute unit budget and price. Preflight catches under-budgeted CU; without preflight, you discover this only when the transaction fails on-chain.

Add telemetry. Per-signature delivery records become more important when you're not getting preflight feedback.

const signature = await connection.sendRawTransaction(
  tx.serialize(),
  {
    skipPreflight: true,
    maxRetries: 0,
  }
);

What to Do This Week

If you have production code:

  1. Audit your sendTransaction calls. Are you passing skipPreflight: true in production? If not, why not?
  2. Verify you're confirming after sending. Without confirmation, silent failures will accumulate.
  3. Set maxRetries: 0 alongside. Manage retries yourself.
  4. Set explicit CU budget and priority fee. Don't let defaults bite you when preflight isn't catching them.
  5. Check that simulated transactions match actual transactions. During development, run simulate then send and compare. Catches preflight-vs-execution divergence.
  6. Add per-signature telemetry. Track outcome for every submitted transaction.

What BoltTx Provides

BoltTx is built for production submission patterns:

import { Connection } from "@solana/web3.js";

const connection = new Connection(
  "https://bolttx.io/?api-key=YOUR_API_KEY",
  "processed"
);

const signature = await connection.sendTransaction(tx, signers, {
  skipPreflight: true,
  maxRetries: 0,
});

Free tier signup. Run real workload for a week with skipPreflight: true and proper confirmation; compare landing rate.

FAQ

Should I always set skipPreflight: true? For production high-frequency sending, yes. For one-off user transactions or development, false is fine.

Will I waste fees on failed transactions? You may, if your transactions can fail. Validate client-side to minimise this.

Does skipPreflight: true affect the on-chain outcome? No. It only affects what the RPC does before forwarding. Once forwarded, the transaction executes the same way either way.

Why is preflight enabled by default if it's worse for production? The default is conservative — assumes you might submit something that fails. Production code typically knows better.

Can I dynamically choose skipPreflight per transaction? Yes. The parameter is per-call. You can keep it false for user-submitted transactions and true for system-generated ones.

Further Reading

Back to all posts