The formula that determines what you pay

Solana transaction fees have two parts:

  1. A base fee, currently 5,000 lamports per signature.
  2. An optional priority fee, set through compute-budget instructions.

The priority-fee formula is:

priority-fee-formula.txt
priority fee (lamports)
= ceil(compute-unit price (micro-lamports) × compute-unit limit / 1,000,000)

If a transaction requests a 220,000 compute-unit limit and bids 25,000 micro-lamports per compute unit:

worked-example.txt
ceil(25,000 × 220,000 / 1,000,000) = 5,500 lamports
 
one-signature total fee = 5,000 base + 5,500 priority = 10,500 lamports

The important detail is easy to miss: Solana calculates the priority fee from the requested limit, not the compute units the transaction actually consumes. If the same transaction only uses 140,000 units, it still pays against the 220,000-unit request. It also pays the fee when execution fails.

The official Solana fee documentation defines both the formula and the current base fee. Treat those protocol docs as the source of truth because fee mechanics can change.

The expensive mistake is not only bidding too many micro-lamports. It is multiplying that bid by a compute-unit limit you never needed.

Compute-unit limit and compute-unit price do different jobs

The two compute-budget instructions are independent:

ControlWhat it changesCommon failure
Compute-unit limitMaximum compute the transaction may consumeToo low causes compute exhaustion; too high pays for unused headroom
Compute-unit priceBid in micro-lamports for each requested compute unitToo low loses scheduling priority; too high burns margin

The default limit is 200,000 compute units for each non-builtin instruction, up to a transaction maximum of 1.4 million. Defaults are broad safety rails, not a fee policy. Solana's compute-budget guide recommends simulating the transaction and adding a 10% safety margin to the consumed units.

This is the right sequence:

fee-policy.txt
build → simulate → set CU limit → sample relevant fees → set CU price → sign → send → observe

Do not add multiple instructions of the same compute-budget type. Duplicate limit or price instructions can cause a DuplicateInstruction error.

A production-safe TypeScript pattern

The following example uses @solana/web3.js. It simulates with a generous temporary limit, applies a 10% margin, and then rebuilds the final transaction with the selected fee.

build-priority-transaction.ts
import {
  ComputeBudgetProgram,
  Connection,
  PublicKey,
  TransactionInstruction,
  TransactionMessage,
  VersionedTransaction,
} from "@solana/web3.js";
 
const MAX_COMPUTE_UNITS = 1_400_000;
 
type BuildArgs = {
  connection: Connection;
  payer: PublicKey;
  instructions: TransactionInstruction[];
  microLamports: number;
};
 
export async function buildPriorityTransaction({
  connection,
  payer,
  instructions,
  microLamports,
}: BuildArgs) {
  const { blockhash } = await connection.getLatestBlockhash("confirmed");
 
  const simulationMessage = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units: MAX_COMPUTE_UNITS }),
      ...instructions,
    ],
  }).compileToV0Message();
 
  const simulation = await connection.simulateTransaction(
    new VersionedTransaction(simulationMessage),
    { sigVerify: false },
  );
 
  if (simulation.value.err) {
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
  }
 
  const consumed = simulation.value.unitsConsumed ?? 200_000;
  const units = Math.min(MAX_COMPUTE_UNITS, Math.ceil(consumed * 1.1));
 
  const finalMessage = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units }),
      ComputeBudgetProgram.setComputeUnitPrice({ microLamports }),
      ...instructions,
    ],
  }).compileToV0Message();
 
  return {
    transaction: new VersionedTransaction(finalMessage),
    units,
    estimatedPriorityFeeLamports: Math.ceil((microLamports * units) / 1_000_000),
  };
}

The returned transaction is unsigned. Sign only after the final message is built. Rebuilding after signing changes the message and invalidates the signature.

The 10% margin is a starting point, not a law. Programs with input-dependent compute may need a wider buffer. Measure unitsConsumed across real instruction shapes, not one happy-path fixture.

Solana has local fee markets

A cluster-wide median is often the wrong input. Transactions compete with other transactions that lock the same writable accounts. A quiet transfer and a crowded DEX route can face different prices in the same slot.

The native getRecentPrioritizationFees method accepts up to 128 account addresses and returns recent fees for transactions that would lock all of those accounts as writable. Validators cache up to 150 blocks of observations.

That gives you a better estimator loop:

  1. Extract the transaction's writable accounts.
  2. Request recent prioritization fees for that account set.
  3. Remove stale or unusable observations.
  4. Choose a percentile based on urgency.
  5. Apply a maximum fee cap.
  6. Reprice from fresh data when the blockhash or opportunity changes.

You can inspect current percentiles with the rpc edge Priority Fee Estimator before implementing the same policy in your sender.

Choose a percentile from the action, not one global preset

One fee preset cannot serve every transaction. The loss from a missed liquidation is not the same as the loss from a delayed housekeeping instruction.

ActionSensible starting policyWhy
Background maintenanceLower percentile, conservative capDelay is cheaper than overpayment
User swapMid percentile, bounded retriesBalance confirmation time and user cost
Time-sensitive rebalanceUpper percentile, strict expiryThe action loses value quickly
Liquidation or arbitrageOpportunity-priced capFee ceiling should follow expected profit and failure cost

For a trading strategy, define the cap from expected value:

expected-value-cap.txt
maximum all-in landing cost
< expected gross edge - slippage budget - risk reserve

That converts fee policy from an arbitrary number into a risk rule. If the required bid crosses the cap, skip the trade. Paying to land a negative-expectancy action is not better execution.

Priority fee, Jito tip, SWQoS, and delivery are separate levers

These mechanisms act at different stages:

LeverWhat it buysWhat it cannot fix
Priority feeBetter native scheduling priorityA packet that never reaches the leader
Jito tipCompetitiveness in the Block Engine auctionNative scheduling on a non-Jito path
SWQoSStake-weighted access to leader ingestLow priority after admission
Direct leader deliveryA shorter path to current and upcoming leadersInvalid execution or an uneconomic bid

A Jito tip is not a substitute for a priority fee. Jito's low-latency transaction documentation describes the Block Engine path and its auction. Use a tip when that path or bundle behavior is part of your execution. Use the native priority fee to compete in Solana's scheduler.

Likewise, a high priority fee does nothing if the packet is dropped before scheduling. That is why stake-weighted QoS and direct leader paths matter. The complete landing path is delivery, admission, scheduling, and execution.

Price the fee, then shorten the path.

Estimate the current priority fee for your workload, then send through rpc edge's stake-weighted, leader-aware transaction path.

Open the fee estimator →

Retries need a budget and an expiry condition

Retries should not mean sending forever. Reuse the same signed transaction only while its blockhash is valid and the underlying action still makes sense. Stop when any of these conditions becomes true:

  • The signature reaches the required commitment.
  • The blockhash expires.
  • The opportunity deadline passes.
  • The fee cap or retry budget is exhausted.
  • The action is no longer profitable or safe.

If you rebuild with a fresh blockhash, you create a different transaction and signature. Before doing that, check whether the prior transaction landed. Otherwise two valid versions of the same intent may execute.

Our guide to why Solana transactions fail covers stale blockhashes, preflight errors, account contention, and delivery failures beyond fee selection.

Measure fee policy by landing outcome

An estimator is only a hypothesis until you compare it with results. Record at least:

landing-telemetry.txt
signature
strategy and transaction class
submission timestamp and slot
target leader and delivery path
writable-account set or route identifier
requested CU limit
simulated and actual CU consumption
CU price and estimated priority fee
Jito tip, if any
first observed slot
confirmed or finalized slot
error, expiry, and retry count

Then group results by transaction class, region, delivery path, and fee percentile. The useful charts are landing rate and time-to-land against fee paid. Average fee alone tells you nothing about the fills you missed.

Watch the tail. A policy that looks efficient at p50 can still collapse during the congested slots that contain the highest-value opportunities.

Production checklist

Before shipping a priority-fee policy:

  • Simulate the final instruction shape.
  • Set one compute-unit limit with a measured safety margin.
  • Set one compute-unit price from recent, relevant account data.
  • Cap fees in lamports and in expected-value terms.
  • Keep Jito tips separate in configuration and telemetry.
  • Use a fresh blockhash and an explicit expiry condition.
  • Send to the leaders that can include the transaction.
  • Measure landing rate, not accepted RPC responses.
  • Recalibrate by transaction class and congestion regime.

The takeaway

Priority fees are not a magic speed field. They are a bid applied to the compute budget you request. Set the limit from simulation, set the price from the local writable-account market, and cap both against the value of the action.

Then engineer the rest of the path. The fee determines ordering only after a transaction arrives and is admitted. A leader-aware transaction sender, stake-weighted ingest, fresh blockhashes, and bounded retries turn that fee into a landing policy you can measure.

Frequently asked questions

How is a Solana priority fee calculated?
The priority fee in lamports is the requested compute-unit limit multiplied by the compute-unit price in micro-lamports, divided by one million and rounded up. The fee uses the requested limit, not the compute units the transaction consumes.
What priority fee should I use on Solana?
There is no universal number. Sample recent fees for the writable accounts your transaction will lock, choose a percentile that matches the urgency of the action, apply a hard cap, and adjust from observed landing results.
Is a Jito tip the same as a Solana priority fee?
No. A Solana priority fee affects native transaction scheduling. A Jito tip competes in the Jito Block Engine auction. A transaction or bundle may need both, depending on its delivery path and execution requirements.
Does a higher priority fee guarantee that a transaction lands?
No. A transaction can still arrive late, be dropped before scheduling, fail simulation, use an expired blockhash, or lose a Jito auction. Fees solve ordering after the transaction reaches the relevant scheduler.
Why should I simulate before setting the compute-unit limit?
The priority fee is charged against the requested compute-unit limit. Simulation gives you an estimate of actual consumption, so you can add a modest safety margin without paying for a needlessly large limit.