> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to land transactions on Solana

> Land Solana transactions reliably: size the compute budget, use a fresh blockhash, submit direct-to-leader through a staked SwQoS connection (Chainstack Warp), and retry correctly. Priority fees only order transactions within a path — the submission path is the real lever.

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

| Lever                   | What it does                                                                                                                        | Deep-dive                                                                                         |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Well-formed transaction | Sets a compute unit price and an accurate compute unit limit so the network can schedule and price the transaction correctly        | [Priority fees for faster transactions](/docs/solana-how-to-priority-fees-faster-transactions)    |
| Fresh blockhash         | Keeps the transaction inside its \~80–90 second validity window so it isn't dropped as expired                                      | [Handle the transaction expiry error](/docs/solana-how-to-handle-the-transaction-expiry-error)    |
| Submission path         | Routes `sendTransaction` to the current leader through a staked SwQoS connection instead of a shared public RPC — the biggest lever | [Solana Trader Nodes with Warp transactions](/docs/solana-trader-nodes)                           |
| Retry and confirm       | Re-broadcasts until confirmation or expiry, then confirms against `lastValidBlockHeight`                                            | [Enhance transfers with retry logic](/docs/enhancing-solana-spl-token-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`](https://github.com/anza-xyz/kit), the current Solana JavaScript SDK — `@solana/web3.js` v1 still works but is now the maintenance branch:

```typescript theme={"system"}
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:

* [Estimate priority fees with RPC methods](/docs/solana-estimate-priority-fees-getrecentprioritizationfees) — `getRecentPrioritizationFees` for a percentile-based estimate over your writable accounts.
* [Analyze adjacent transactions for priority fees](/docs/solana-analyzing-adjacent-transactions-for-priority-fees) — read what actually landed in recent blocks.

For complete runnable scripts, see [Priority fees for faster transactions](/docs/solana-how-to-priority-fees-faster-transactions) and [Priority fees for a Jupiter swap in Python](/docs/solana-priority-fees-for-a-jupiter-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](/docs/solana-how-to-handle-the-transaction-expiry-error). For transactions that must stay valid beyond that window — offline or multi-party signing — use a [durable nonce](/docs/solana-durable-nonces) 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](https://solana.com/developers/guides/advanced/stake-weighted-qos) is for, and it's the difference between "the leader saw my transaction" and "my transaction never arrived."

[Chainstack Warp transactions](/docs/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](/docs/solana-trader-nodes) and [Global Nodes](/docs/global-elastic-node), works over HTTP (not WebSocket), and needs no custom transaction crafting — you switch the endpoint and your existing setup lands through the staked relay.

<Note>
  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.
</Note>

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](/docs/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](https://compare.chainstack.com/).

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](#lever-1-build-a-well-formed-transaction). 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](/docs/enhancing-solana-spl-token-transfers-with-retry-logic) and [Use multiple RPC endpoints to optimize performance](/docs/solana-how-to-use-multiple-rpc-endpoints-optimize-dapp-performance). To confirm and index landed transactions by block and block time, see [Associate transactions with block time using Yellowstone gRPC](/docs/solana-transactions-block-time-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](/docs/solana-how-to-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](#lever-3-choose-the-right-submission-path-the-biggest-lever).

**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](/docs/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](#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](/docs/enhancing-solana-spl-token-transfers-with-retry-logic).

**Which Solana JavaScript library should I use?**
For new code, `@solana/kit` — the current Solana SDK, shown in [Lever 1](#lever-1-build-a-well-formed-transaction). `@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

<CardGroup cols={2}>
  <Card title="Priority fees for faster transactions" href="/docs/solana-how-to-priority-fees-faster-transactions" icon="angle-right" horizontal />

  <Card title="Estimate priority fees with RPC methods" href="/docs/solana-estimate-priority-fees-getrecentprioritizationfees" icon="angle-right" horizontal />

  <Card title="Analyze adjacent transactions for priority fees" href="/docs/solana-analyzing-adjacent-transactions-for-priority-fees" icon="angle-right" horizontal />

  <Card title="Priority fees for a Jupiter swap in Python" href="/docs/solana-priority-fees-for-a-jupiter-in-python" icon="angle-right" horizontal />

  <Card title="Handle the transaction expiry error" href="/docs/solana-how-to-handle-the-transaction-expiry-error" icon="angle-right" horizontal />

  <Card title="Enhance transfers with retry logic" href="/docs/enhancing-solana-spl-token-transfers-with-retry-logic" icon="angle-right" horizontal />

  <Card title="Solana Trader Nodes with Warp transactions" href="/docs/solana-trader-nodes" icon="angle-right" horizontal />

  <Card title="Solana MEV protection" href="/docs/solana-mev-protection" icon="angle-right" horizontal />
</CardGroup>

<CardGroup>
  <Card title="Ake">
    <img src="https://mintcdn.com/chainstack/UN3rP7zhB69idvnC/images/docs/profile_images/1719912994363326464/8_Bi4fdM_400x400.jpg?fit=max&auto=format&n=UN3rP7zhB69idvnC&q=85&s=792a24ab1b4682406fa589c0ecd88e5d" alt="Ake" style={{width: '80px', height: '80px', borderRadius: '50%', objectFit: 'cover', display: 'block', margin: '0 auto'}} noZoom width="400" height="400" data-path="images/docs/profile_images/1719912994363326464/8_Bi4fdM_400x400.jpg" />

    <Icon icon="code" iconType="solid" /> Director of Developer Experience @ Chainstack
    <br /><Icon icon="screwdriver-wrench" iconType="solid" /> Talk to me all things Web3
    <br />20 years in technology | 8+ years in Web3 full time years experience

    <div style={{display: "flex", justifyContent: "center", gap: "12px"}}>
      <a href="https://github.com/akegaviar/" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="github" iconType="brands" />
      </a>

      <a href="https://twitter.com/akegaviar" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="twitter" iconType="brands" />
      </a>

      <a href="https://www.linkedin.com/in/ake/" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="linkedin" iconType="brands" />
      </a>
    </div>
  </Card>
</CardGroup>
