Skip to main content
Landing a Solana transaction reliably comes down to four things working together: a well-formed transaction, a fresh blockhash, the right submission path, and correct retry logic. The single biggest lever is the submission path — getting sendTransaction to the current leader through a staked stake-weighted quality of service (SwQoS) connection lands far more competitively than broadcasting through a shared public RPC and hoping. Priority fees help, but they only order your transaction within a path; they don’t get it to the leader. This page is the hub for landing transactions on Solana. Each lever below links to a focused deep-dive with runnable code.

The four levers that land a transaction

LeverWhat it doesDeep-dive
Well-formed transactionSets a compute unit price and an accurate compute unit limit so the network can schedule and price the transaction correctlyPriority fees for faster transactions
Fresh blockhashKeeps the transaction inside its ~80–90 second validity window so it isn’t dropped as expiredHandle the transaction expiry error
Submission pathRoutes sendTransaction to the current leader through a staked SwQoS connection instead of a shared public RPC — the biggest leverSolana Trader Nodes with Warp transactions
Retry and confirmRe-broadcasts until confirmation or expiry, then confirms against lastValidBlockHeightEnhance transfers with retry logic

Why transactions fail to land

A Solana transaction that never confirms almost always failed at one of four points:
  • It never reached the leader. Submitted through a congested shared RPC, the transaction competes with everyone else for forwarding to the current leader and can be dropped before it ever arrives.
  • Its blockhash expired. Every transaction references a blockhash valid for only 150 blocks (about 60–90 seconds). Miss that window and the network drops it with a TransactionExpiredBlockheightExceededError.
  • Its compute unit limit was wrong. An undersized limit makes the transaction fail at execution; an oversized one overpays and can deprioritize it. The limit must match what the transaction actually consumes.
  • It was submitted once and abandoned. Without re-broadcasting, a single dropped attempt is the end. RPC nodes rebroadcast generically, but application-controlled retries land more reliably under congestion.
The levers below address each failure point in order.

Lever 1: build a well-formed transaction

A landable transaction sets two Compute Budget Program instructions: a compute unit price (the priority fee, in micro-lamports per compute unit) and a compute unit limit sized to what the transaction actually consumes. The priority fee you pay is compute_unit_price × compute_unit_limit, so an accurate limit both improves landing and avoids overpaying. Size the limit from a live simulation rather than guessing. This example uses @solana/kit, the current Solana JavaScript SDK — @solana/web3.js v1 still works but is now the maintenance branch:
import {
  createSolanaRpc, createSolanaRpcSubscriptions, pipe,
  createTransactionMessage, setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions,
  estimateComputeUnitLimitFactory, signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory, getSignatureFromTransaction,
} from "@solana/kit";
import {
  getSetComputeUnitPriceInstruction, getSetComputeUnitLimitInstruction,
} from "@solana-program/compute-budget";

const rpc = createSolanaRpc("YOUR_CHAINSTACK_SOLANA_HTTPS_ENDPOINT");
const rpcSubscriptions = createSolanaRpcSubscriptions("YOUR_CHAINSTACK_SOLANA_WSS_ENDPOINT");

// signer: a TransactionSigner (a wallet, or from generateKeyPairSigner)
// instructions: your program instructions
// microLamportsPerCU: from the local fee market (see below)
async function landTransaction(signer, instructions, microLamportsPerCU) {
  // 1. Fresh blockhash at the confirmed commitment.
  const { value: blockhash } = await rpc.getLatestBlockhash({ commitment: "confirmed" }).send();

  // 2. Draft the message with the priority fee (compute unit price).
  const draft = pipe(
    createTransactionMessage({ version: 0 }),
    (m) => setTransactionMessageFeePayerSigner(signer, m),
    (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
    (m) => appendTransactionMessageInstructions(
      [getSetComputeUnitPriceInstruction({ microLamports: microLamportsPerCU }), ...instructions], m),
  );

  // 3. Size the compute unit limit from a live simulation, then set it (~10% margin).
  const units = await estimateComputeUnitLimitFactory({ rpc })(draft);
  const message = appendTransactionMessageInstructions(
    [getSetComputeUnitLimitInstruction({ units: Math.ceil(units * 1.1) })], draft);

  // 4. Sign and send. sendAndConfirmTransactionFactory confirms over websockets against
  //    the blockhash + lastValidBlockHeight — no deprecated confirmTransaction.
  const signedTx = await signTransactionMessageWithSigners(message);
  await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTx, { commitment: "confirmed" });
  return getSignatureFromTransaction(signedTx);
}
Choose the compute unit price from the live local fee market, not a fixed constant. Solana’s fee market is keyed to the writable accounts your transaction locks, so pass those accounts — not program IDs — to getRecentPrioritizationFees. Since Agave 2.0, priority fees go entirely to the block-producing validator. Two ways to estimate the price: For complete runnable scripts, see Priority fees for faster transactions and Priority fees for a Jupiter swap in Python (these currently use web3.js v1; the landing concepts are identical).

Lever 2: use a fresh blockhash and respect expiry

Every Solana transaction references a recent blockhash that is valid for 150 blocks — about 60–90 seconds. The transaction carries a lastValidBlockHeight; if the network reaches that height without confirming it, the transaction is dropped and you get a TransactionExpiredBlockheightExceededError. Fetch the blockhash at the confirmed commitment right before you sign, track its lastValidBlockHeight, and stop retrying once the current block height passes it — then rebuild with a new blockhash. The full diagnosis and handling strategy is in Handle the transaction expiry error. For transactions that must stay valid beyond that window — offline or multi-party signing — use a durable nonce instead of a recent blockhash.

Lever 3: choose the right submission path (the biggest lever)

Where you submit sendTransaction matters more than any fee you attach. There are two paths:
  • Shared public RPC. Your transaction reaches an RPC node that forwards it to the current leader over the regular path. Under congestion it competes with every other transaction for that forwarding, and a share of transactions are dropped before the leader ever sees them.
  • Staked SwQoS connection, direct to the leader. Transactions submitted through a staked validator connection get stake-weighted priority forwarding to the leader. This is what SwQoS is for, and it’s the difference between “the leader saw my transaction” and “my transaction never arrived.”
Chainstack Warp transactions route only the sendTransaction call to the current leader through bloXroute’s staked validator connections, while every other request goes through your standard Chainstack Solana node. Warp is available on both Solana Trader Nodes and Global Nodes, works over HTTP (not WebSocket), and needs no custom transaction crafting — you switch the endpoint and your existing setup lands through the staked relay.
Priority fees and the submission path solve different problems. The submission path decides whether your transaction reaches the leader; the priority fee decides its ordering once it’s there. A high priority fee on a transaction that never reaches the leader still doesn’t land.
For competitive or atomic flows — arbitrage, liquidations, high-contention mints — Jito bundles are a distinct channel: an all-or-nothing group of up to five transactions that a Jito validator executes in order, prioritized by a tip you attach rather than by the priority fee. With most Solana stake running the Jito client, tips are a primary path for latency-sensitive transactions. Bundles are submitted to Jito’s Block Engine (mainnet.block-engine.jito.wtf), not through a standard RPC node — so this path runs alongside your Chainstack endpoint rather than through it, and complements SwQoS rather than replacing it. See Solana MEV protection for the anti-sandwich dontfront pattern. For live, continuously-measured landing rates comparing Warp against other providers, see the Chainstack Compare dashboard. Under heavy, unpredictable load, the most reliable setups fan out — submit the same signed transaction through more than one path at once (for example a staked connection plus a public RPC, or add a Jito bundle). De-duplication by signature makes this safe: the network runs a given transaction at most once, no matter how many paths deliver it.

Lever 4: retry and confirm correctly

A single submission is not enough under congestion. Re-broadcast the same signed transaction until it either confirms or its blockhash expires — never resubmit with a new blockhash while the old one is still valid, or you risk landing the transaction twice.
  • For the standard path, sendAndConfirmTransactionFactory confirms over websockets against the blockhash and lastValidBlockHeight — see the Lever 1 example. Avoid the legacy confirmTransaction RPC method; it’s deprecated.
  • For an aggressive path under congestion, send with maxRetries: 0 and skipPreflight: true, then run your own loop: re-send the same signed transaction every few seconds and poll getSignatureStatuses, stopping when it confirms or the current block height passes lastValidBlockHeight. Skip preflight only after a successful simulation — skipPreflight: true also skips the check that catches a malformed transaction, so it hides errors if you haven’t already validated.
  • Submit through geographically close endpoints to cut propagation latency.
Full patterns are in Enhance SPL token transfers with retry logic and Use multiple RPC endpoints to optimize performance. To confirm and index landed transactions by block and block time, see Associate transactions with block time using Yellowstone gRPC.

Priority fees vs submission path: which matters more?

Both, in order. First get the transaction to the leader (submission path), then compete for ordering once it’s there (priority fee). A well-tuned priority fee on a shared RPC still loses to a direct-to-leader submission during congestion, because ordering only matters among the transactions the leader actually received. Tune the fee after you’ve fixed the path, not instead of it.

FAQ

Why does my transaction get TransactionExpiredBlockheightExceededError? Its blockhash aged past the 150-block (~60–90 second) validity window before the network confirmed it — usually because it was submitted through a congested path, never reached the leader, and wasn’t rebroadcast in time. Fix the submission path first, then add retry logic. See Handle the transaction expiry error. Do higher priority fees guarantee my transaction lands? No. Priority fees order transactions the leader has already received; they don’t get a transaction to the leader. If the transaction is dropped before the leader on a congested shared RPC, no fee saves it. See Lever 3: choose the right submission path. What is SwQoS and how do I use it? Stake-weighted quality of service (SwQoS) gives transactions submitted through a staked validator connection priority forwarding to the leader. On Chainstack you get it by enabling Warp transactions on a Trader or Global node — no custom code, just a different endpoint. How should I size the compute unit limit? Simulate the transaction, read unitsConsumed, and set the limit to that value plus a small margin (around 10%). A limit that matches actual consumption improves scheduling and avoids overpaying, since the fee is compute_unit_price × compute_unit_limit. See Lever 1: build a well-formed transaction. Is sendTransaction retrying enough on its own? The RPC node rebroadcasts generically, but application-controlled retries — re-sending the same signed transaction until it confirms or expires — land more reliably under congestion. See Enhance transfers with retry logic. Which Solana JavaScript library should I use? For new code, @solana/kit — the current Solana SDK, shown in Lever 1. @solana/web3.js v1 still works but is the maintenance branch. The deep-dive tutorials linked here currently use v1; the landing concepts are identical.

Deep-dives

Priority fees for faster transactions

Estimate priority fees with RPC methods

Analyze adjacent transactions for priority fees

Priority fees for a Jupiter swap in Python

Handle the transaction expiry error

Enhance transfers with retry logic

Solana Trader Nodes with Warp transactions

Solana MEV protection

Ake

Ake Director of Developer Experience @ Chainstack
Talk to me all things Web3
20 years in technology | 8+ years in Web3 full time years experience
Last modified on July 1, 2026