Solana Transaction Landing: The Complete Guide

Your transaction returned a signature but never reached a block. What landing means on Solana, the four reasons transactions vanish, and how to measure yours.

BoltTx Team··14 min read
solanatransaction-landingsendtransactionswqostrading-botrpc

You called sendTransaction. You got a signature back. You logged it as a success. Twenty seconds later someone asks why their swap never happened, and that signature returns null from every explorer you try.

Nothing failed. That is what makes it confusing. The transaction was never rejected. It just never arrived.

This is the most misunderstood part of building on Solana, and it costs real money. A bot that believes it has a 98% success rate because sendTransaction returned without an error is measuring the wrong thing.

What Landing Actually Means

On most chains you submit a transaction to a mempool, it waits there, and eventually a block producer picks it up. If it takes a while, it takes a while. It is queued somewhere.

Solana has no public mempool. There is no queue. When you submit, an RPC node forwards your transaction toward the validator scheduled to produce upcoming blocks. If that validator does not include it, nothing retries it for you. It is dropped, and nothing records that it ever existed.

So there are three distinct states, and collapsing them is where teams get hurt:

Submitted. Your RPC accepted the transaction and returned a signature. That signature is computed locally from the transaction bytes. You can generate it before anything touches the network. A signature is not a receipt.

Landed. The transaction was included in a block. It exists on chain and has a slot number.

Succeeded. It landed and the instructions executed without error.

A transaction can be submitted and never land. It can land and still fail, from exceeded slippage, an insufficient balance, a program error. Different problems, different fixes.

If your monitoring turns these three into one boolean, you cannot debug anything.

Already know why and just want a different path to test? A free BoltTx key is one line. The rest of this is the diagnosis.

The Four Reasons Transactions Do Not Land

1. Blockhash expiry

Every Solana transaction references a recent blockhash, valid for roughly 150 blocks — about 60 seconds under normal conditions, less under load. Miss the window and the transaction is permanently invalid. It will not land later. It is dead.

This bites hardest in two places. Bots that fetch one blockhash and reuse it across a batch watch the tail of the batch expire. And any retry loop that resubmits the same signed bytes is resubmitting something that expired several attempts ago.

Fetch the blockhash close to signing. If you are retrying past the window, you need to re-sign, not resend.

2. Congestion and priority

When the network is busy, block space is contested. Transactions carrying higher priority fees get scheduled first. Yours, with a default fee, does not get scheduled at all.

The frustrating part is that congestion correlates with exactly the moments you care about. A token launches, a liquidation cascades, an arbitrage window opens. Everyone submits at once, and a transaction with no priority fee has close to no chance.

3. Your submission path

This one is invisible from your code, which is why it usually gets diagnosed last.

The route from your process to a block producer has several hops: your machine, your RPC provider, the validator. Each hop adds delay. Under normal conditions the difference does not matter. Under congestion, when the landing window is a slot or two wide, it decides everything.

Stake-weighted quality of service matters here. Validators accept forwarded transactions in proportion to the forwarding node's stake. A provider without meaningful stake gets deprioritised precisely when block space is scarce.

4. You never retried

A single submission attempt is a coin flip during congestion. Production senders resubmit continuously until the transaction lands or the blockhash expires. That is a different pattern from calling it once and hoping.

Where Transaction Relays Fit

A transaction relay sits between your backend and the validator. Instead of your process talking to a general-purpose RPC that happens to also forward transactions, a relay does forwarding as its only job.

The distinction matters because the two are optimised for different things. A general-purpose RPC has to serve account reads, transaction history, program subscriptions, and sends — all from the same infrastructure. A relay only carries signed transactions toward block producers, so its stake weight and its routing exist for exactly one purpose.

For a swap bot, an arbitrage strategy, or an NFT mint with a hard start time, this is the difference between an endpoint that degrades under load and one that does not. For an indexer or a wallet balance view, it makes no difference at all — use whatever you already have.

If your transactions time out when the network gets busy, but the same code works fine at 3am, you are looking at a routing problem rather than a code problem.

Measuring Your Actual Landing Rate

Most teams cannot answer "what percentage of my transactions land?" Which means they cannot tell whether a change helped.

The measurement is not complicated:

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

const connection = new Connection(RPC_URL, "confirmed");

async function didItLand(signature: string): Promise<boolean> {
  // searchTransactionHistory looks past the recent-status cache,
  // so this still works for older signatures.
  const { value } = await connection.getSignatureStatuses([signature], {
    searchTransactionHistory: true,
  });

  const status = value[0];
  if (!status) return false;    // never landed
  if (status.err) return false; // landed, but the instructions failed
  return true;                  // landed and succeeded
}

Note the two different reasons for false. Track them as separate counters:

type Outcome = "landed_ok" | "landed_failed" | "never_landed";

// never_landed  -> submission problem: fee, path, retry, or expiry
// landed_failed -> program problem: slippage, balance, account state

These two numbers have nothing to do with each other, and fixing one does nothing for the other. We have seen a team spend two weeks tuning slippage tolerance for a problem that was entirely blockhash expiry. Their transactions were not failing on chain. They were not reaching it.

A Sender That Actually Retries

The naive version calls sendTransaction once. The version that works keeps resubmitting until the blockhash window closes:

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

async function sendUntilLanded(
  connection: Connection,
  tx: VersionedTransaction,
  lastValidBlockHeight: number,
): Promise<string | null> {
  const raw = tx.serialize();

  // skipPreflight: we already know this transaction is well-formed.
  // Preflight costs a round trip and simulates against a slot we may
  // not land in, so it can pass and still tell us nothing useful.
  const signature = await connection.sendRawTransaction(raw, {
    skipPreflight: true,
    maxRetries: 0, // we drive the retries ourselves
  });

  while (true) {
    const height = await connection.getBlockHeight("confirmed");
    if (height > lastValidBlockHeight) {
      return null; // expired: re-sign with a fresh blockhash, do not resend
    }

    const { value } = await connection.getSignatureStatuses([signature]);
    if (value[0] && !value[0].err) return signature;
    if (value[0]?.err) throw new Error(`Landed but failed: ${value[0].err}`);

    // Resending identical bytes is safe. Same signature, so the
    // network can include it at most once.
    await connection.sendRawTransaction(raw, {
      skipPreflight: true,
      maxRetries: 0,
    });
    await new Promise((r) => setTimeout(r, 400)); // roughly one slot
  }
}

Three things people get wrong here:

maxRetries: 0 is deliberate. The RPC's built-in retry runs on its own schedule. If you are driving retries yourself and the RPC is also retrying underneath you, timing becomes unpredictable.

Resending identical bytes is safe. Same signature means it can be included exactly once. There is no double-spend risk.

Expiry means re-sign, not resend. Once lastValidBlockHeight passes, that transaction is permanently dead. Build a new one.

What Landing Looks Like When It Works

Real transactions through our delivery nodes: median confirmation 336ms — under one slot.

Most transactions are in a block within two slots of submission. The practical takeaway for strategy design: budget for two slots rather than assuming the very next block.

Fixing a Low Landing Rate, In Order

Work through these in sequence. Each is cheap to test and rules out a whole class of problem.

Add a priority fee. If you are sending with none, this is your problem. Derive it from recent network conditions rather than hardcoding a constant.

Check your blockhash timing. Log the gap between fetching the blockhash and submitting. More than a few seconds is worth tightening.

Turn on skipPreflight and drive retries yourself. You get the round trip back and stop simulating against the wrong slot.

Then look at your submission path. If the first three are clean and you still lose transactions during congestion, the route to the validator is what is left.

That last one cannot be fixed in application code, which is exactly why it should be checked last, after the cheap fixes are ruled out.

Where BoltTx Fits

We do one thing: get signed transactions into blocks. Not indexing, not parsed history, not NFT metadata. Delivery.

Submissions route through our own nodes in four regions, with stake-weighted routing and no public mempool exposure, so transactions are not observable before they land. You keep your keys. We never hold funds, never sign, and never modify transaction contents.

Pricing follows the same logic. You include a tip in the transaction itself, paid on chain from your own wallet. If the transaction reverts, the tip reverts with it, because that is how Solana handles atomic transactions. You pay only on transactions that reach the chain.

Get a free API key. No monthly fee, no subscription. Switching is a one-line change:

// Before
// const connection = new Connection("https://your-current-rpc.example.com");

// After: pick the region closest to where your bot runs
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

FAQ

What does it mean when a Solana transaction does not land? It was never included in a block. Not rejected, not reverted. It simply never made it on chain, so querying the signature returns null because no such transaction exists on the ledger. The four usual causes are blockhash expiry, insufficient priority fee during congestion, a slow submission path, and not retrying.

Why did sendTransaction return a signature if the transaction never landed? The signature is derived from the transaction bytes and your keypair, computed locally before anything reaches the network. A returned signature means the transaction is well-formed and was accepted for forwarding. It does not mean the transaction is on chain. Always confirm with getSignatureStatuses.

How long is a Solana blockhash valid? Roughly 150 blocks, about 60 seconds under normal conditions and less under load. After that the transaction is permanently invalid and has to be rebuilt with a fresh blockhash. Resending the same bytes will never work.

What is a good Solana transaction landing rate? There is no universal number, because it depends on what you send and when you send it. What matters is measuring your own rate consistently and watching whether changes move it. Split the metric into never-landed and landed-but-failed, because they have completely different causes.

Does a higher priority fee guarantee landing? No. It improves your position in scheduling but does not guarantee inclusion. If your submission path is slow or your blockhash is near expiry, a high fee will not save the transaction. Priority fee is one of four factors, not a master switch.

What is SWQoS on Solana? Stake-weighted quality of service. Validators accept forwarded transactions in proportion to the forwarding node's stake weight. During congestion, transactions arriving through a low-stake path get deprioritised. This is why identical code can produce very different landing rates depending on the provider underneath it.

Should I use skipPreflight when sending transactions? For production senders, usually yes. Preflight simulates against the current slot, which may not be the slot you land in, and it costs a network round trip. Skip it once you are confident the transaction is well-formed, and use getSignatureStatuses to learn the outcome.

Is it safe to resubmit the same transaction repeatedly? Yes. Identical signed bytes produce an identical signature, and Solana will include it at most once. Continuous resubmission until the blockhash expires is the standard production pattern, not an edge case.

How do I tell the difference between did not land and landed but failed? Call getSignatureStatuses with searchTransactionHistory: true. A null result means it never landed. A result with a non-null err means it landed and the instructions failed. Track these separately, because conflating them hides which problem you actually have.

Why do my transactions fail more during token launches? Launches create congestion, and congestion makes all four failure modes compound at once. Block space is contested, priority fees spike, and the landing window narrows to a slot or two. This is exactly when submission path quality separates providers.

What does maxRetries do in sendTransaction? It tells the RPC how many times to resubmit on your behalf before giving up. The default is non-zero, which means the RPC is retrying on a schedule you do not control. If you are running your own retry loop, set it to 0 so there is exactly one component deciding when to resend.

My transactions time out when the network is busy. What should I look at? Work through the four causes in order: priority fee first, then blockhash timing, then whether you are retrying at all, then the submission path. The fact that it only happens under load points at fee and routing rather than at your transaction construction, which would fail consistently rather than intermittently.

What is a Solana transaction relay? A service whose only job is forwarding signed transactions toward block producers. Unlike a general-purpose RPC that also serves reads, history, and subscriptions from the same infrastructure, a relay carries transactions and nothing else. That focus is what lets it maintain routing quality during congestion.

Do I need a separate provider for sending and reading? Many production setups do exactly that: a general-purpose RPC for account reads and history, a delivery-focused endpoint for sends. The two integration points are independent, so you can change one without touching the other.

How do I set up monitoring that catches landing failures early? Track three counters separately — landed and succeeded, landed but failed, never landed — and alert on the ratio rather than the absolute count. A drift in never-landed points at fee or routing. A drift in landed-but-failed points at your program or market conditions. A single success-rate number hides both.

How do I check if a Solana transaction landed? Call getSignatureStatuses with searchTransactionHistory: true. A null result means it never reached a block. A result with a non-null err means it landed and the instructions failed. Only a result with err: null is a success.

What does it mean when a signature returns null on an explorer? The transaction was never included in any block, so no such record exists on the ledger. This is a submission problem, not an execution problem, and the fix is fee, blockhash timing, retry behaviour, or routing.

Can a Solana transaction land after several minutes? No. Once the blockhash passes lastValidBlockHeight, roughly 150 blocks after it was issued, the transaction is permanently invalid. There is no delayed inclusion on Solana.

Does a higher tip make my transaction land? Keep the two apart. A priority fee is an on-chain bid that affects how a validator schedules your transaction once it already has it. A tip pays for delivery — it buys the routing that gets the transaction there, not a place in the block. Neither rescues a transaction that arrives after the window closed.

Why do transactions land in testing but not in production? Testing usually happens when the network is calm, where every submission path performs the same. Production traffic tends to cluster around volatile moments, which is exactly when fee, retry behaviour, and routing start to matter.

Going deeper on each part of this:

Related mechanics:

Back to all posts