Solana Compute Unit Pricing: Limit x Price

How setComputeUnitLimit and setComputeUnitPrice combine into your actual fee, why the default limit wastes money, and how to measure real consumption.

BoltTx Team··9 min read
solanacompute-unitpriority-feefeestransaction-landingoptimization

Two instructions control what a Solana transaction costs beyond its base fee, and they multiply:

priority fee = computeUnitLimit × computeUnitPrice

Most people set the price and leave the limit at its default. That combination is the most common way to overpay on Solana, and it costs the most during exactly the moments fees matter.

What Each One Does

setComputeUnitLimit declares how much compute your transaction may use. It is a ceiling, not a reservation — but ★you are charged against the ceiling, not against what you actually consume.★

setComputeUnitPrice sets how many micro-lamports you pay per compute unit. This is the knob that competes for block space.

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

instructions.unshift(
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
  ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }),
);

Why the Default Costs You

Without an explicit limit, Solana applies a default per instruction that is generous — far above what most transactions actually use.

The arithmetic that follows is unforgiving. Say your transaction genuinely consumes 40,000 CU but runs against a default of 200,000:

Real usage:     40,000 CU
Charged at:    200,000 CU
★Overpayment:   5×★

At a low unit price this is a rounding error. ★During congestion, when the price you must pay to land rises sharply, you are paying five times more than necessary at the worst possible moment.★

Setting the limit correctly does not make you faster. It makes the same speed cost less, which means you can afford a higher price per unit for the same total budget — and price is what actually competes.

If fees are tuned and transactions still miss, a free BoltTx key is one line to test the routing side against.

Measuring Real Consumption

Do not guess. Simulate.

const sim = await connection.simulateTransaction(tx, {
  sigVerify: false,
  replaceRecentBlockhash: true,
});

console.log("consumed:", sim.value.unitsConsumed);

unitsConsumed is the actual number. Run it against a realistic case — a real pool, a real account state — not an empty devnet fixture.

Then set the limit above it with margin:

const measured = sim.value.unitsConsumed ?? 200_000;

// Margin covers account state that grows, extra CPI depth, and
// paths your simulation did not hit. Too tight fails transactions
// that would otherwise have landed.
const limit = Math.ceil(measured * 1.2);

★The margin is not optional.★ Compute usage varies with account state — a swap through a pool with more tick data costs more than the same swap through a shallow one. A limit set to the exact measured value fails the first time reality differs from your simulation.

Rough Consumption by Operation

Useful for sanity-checking a simulation result rather than as values to hardcode:

Operation Order of magnitude
SOL transfer very low
SPL token transfer low
Simple AMM swap moderate
Routed multi-hop swap ★high★
Liquidation with several CPIs ★high★
Account creation moderate

★Always measure your own.★ These vary by program version, account state, and how deep the CPI chain goes.

Fees Are Contested Per Account

The part most fee logic gets wrong: priority fees are not competed for globally. They are competed for per writable account.

// Wrong: a global figure that has nothing to do with your transaction.
// const recent = await connection.getRecentPrioritizationFees();

// Right: pass the accounts your transaction actually writes to.
const recent = await connection.getRecentPrioritizationFees({
  lockedWritableAccounts: [poolAccount, yourTokenAccount],
});

const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
const median = fees[Math.floor(fees.length / 2)] ?? 0;

★A swap against a hot pool during a launch and a plain transfer face completely different competition.★ Querying without the account list gives you a number describing neither.

Putting It Together

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

async function withComputeBudget(
  connection: Connection,
  instructions: TransactionInstruction[],
  writableAccounts: PublicKey[],
  measuredUnits: number,          // ★from simulation, not a guess★
  contentionMultiplier = 2,       // higher for launches and liquidations
) {
  const recent = await connection.getRecentPrioritizationFees({
    lockedWritableAccounts: writableAccounts,
  });
  const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
  const median = fees[Math.floor(fees.length / 2)] ?? 0;

  return [
    ComputeBudgetProgram.setComputeUnitLimit({
      units: Math.ceil(measuredUnits * 1.2),
    }),
    ComputeBudgetProgram.setComputeUnitPrice({
      microLamports: Math.max(median * contentionMultiplier, 1_000),
    }),
    ...instructions,
  ];
}

Two notes on the shape. The compute budget instructions go first, before your actual instructions. And contentionMultiplier is where you express how contested the moment is — a routine transfer and a launch snipe should not use the same number.

Failure Modes

Limit too low. The transaction fails with an exceeded-budget error after consuming compute and paying the base fee. ★This is the worst outcome: you paid and got nothing.★

Limit left at default. You overpay proportionally, worst during congestion.

Price hardcoded. Wasteful when the network is quiet, insufficient when it is busy. It is wrong in both directions.

Querying fees without accounts. You get a network-wide figure that does not describe your competition.

Budget instructions not first. They must precede the instructions they apply to.

What This Does Not Fix

Worth being clear, because compute budget tuning gets recommended for problems it does not touch.

★Correct CU settings do not make a transaction land.★ They control what you pay and prevent one specific failure mode. If your transactions are not landing, the causes are blockhash expiry, a price too low for current contention, no retry loop, or a submission path that gets deprioritised under load.

Fee tuning makes each attempt cheaper and more likely to be scheduled once accepted. It does nothing about whether the transaction reaches a block producer in time.

What Landing Looks Like

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

If your fees are derived from live conditions, your CU limit is measured, and transactions still arrive late under load, what remains is routing.

Where BoltTx Fits

We handle the routing half. Not fee estimation, not indexing.

Submissions go through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands. Your compute budget instructions are part of the transaction you sign — we never modify contents.

You sign locally. We never hold funds and never sign. The tip travels inside the transaction, paid on chain from your own wallet, and reverts with the transaction if it fails, 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:

const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

FAQ

What is a compute unit on Solana? The unit of computational work a transaction consumes. Each transaction declares a limit, and the priority fee is that limit multiplied by the price you set per unit.

What happens if I do not set a compute unit limit? Solana applies a generous default, and you are charged against that default rather than your real usage. If your transaction uses a fraction of it, you overpay proportionally — worst during congestion.

How do I find my transaction's real compute usage? Call simulateTransaction and read unitsConsumed. Simulate against realistic account state rather than an empty fixture, since usage varies with what the accounts contain.

How much margin should I add to the measured limit? Around 20% is a reasonable starting point. Compute usage varies with account state and CPI depth, and a limit set to the exact measured value fails the first time reality differs from your simulation.

What happens if the compute unit limit is too low? The transaction fails with an exceeded-budget error after consuming compute and paying the base fee. It is the worst outcome, since you pay without landing anything useful.

How do I set the compute unit price correctly? Query getRecentPrioritizationFees with the writable accounts your transaction touches, then scale the median by how contested the moment is. A hardcoded value is wrong when quiet and wrong when busy.

Why do I need to pass accounts to getRecentPrioritizationFees? Because priority fees are competed for per writable account, not globally. A swap against a hot pool and a plain transfer face completely different competition, and a global query describes neither.

Does setting a lower compute limit make my transaction faster? No. It makes the same transaction cheaper, which lets you afford a higher price per unit within the same budget — and price is what competes for scheduling.

What is the difference between compute unit limit and price? Limit is how much compute you declare and are charged for. Price is how many micro-lamports you pay per unit. Your priority fee is the two multiplied together.

Where do compute budget instructions go in a transaction? First, before the instructions they apply to. Placing them later means they do not govern what precedes them.

How many compute units does a swap use? It varies by program and account state — a routed multi-hop swap costs substantially more than a simple one. Simulate your own rather than relying on a published figure.

Do compute unit settings affect whether my transaction lands? Only indirectly. They control cost and prevent budget-exceeded failures. Landing depends on blockhash freshness, price relative to contention, retry behaviour, and submission routing.

Should the compute unit price change between transactions? Yes, if the contention differs. A routine transfer and a launch snipe compete against completely different conditions, and using one number for both means overpaying in one case and failing in the other.

Can I simulate to get the fee as well as the units? Simulation gives you unitsConsumed. The price side comes from getRecentPrioritizationFees on your writable accounts. You combine them yourself.

Why did my transaction consume more compute than my simulation? Account state changed between simulating and executing — a pool with more tick data, a longer CPI chain, an account that grew. This is exactly why the limit needs margin above the measured value.

Back to all posts