Solana Compute Unit Optimization — How to Set CU Budget Right

What compute units are on Solana, how to set the budget correctly, and why most bots either over-pay or under-budget. Practical guide with examples.

BoltTx Team··7 min read
solanacompute-unittransactionoptimizationrpc

If you're running production transactions on Solana, the compute unit (CU) budget is one of those parameters that's easy to set wrong and hard to notice you've set wrong. Too low and your transactions silently fail. Too high and you waste priority fees. Most bot operators we've talked to have one of these two problems and don't realise it.

This piece covers what compute units are, how to budget them right, and the production patterns that distinguish well-tuned bots from ones leaking money on this single parameter.

What Compute Units Are

A compute unit is Solana's unit of computational work. Different instructions consume different amounts of CU:

Two values control your transaction's CU behaviour:

CU limit. Maximum CU your transaction is allowed to consume. Default is 200,000. If your transaction tries to consume more than this, it fails.

CU price. The priority fee per CU, in microlamports. Default is 0 (no priority). Setting this higher means more validator income from your transaction = more likely to land.

Total priority fee per transaction: cu_limit * cu_price / 1_000_000 / 1_000_000_000 SOL.

Setting CU Limit Right

The default 200,000 is wrong for most non-trivial transactions. Set it explicitly:

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

const tx = new Transaction()
  .add(ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }))
  .add(yourActualInstruction);

The right value depends on what your transaction does. Two ways to decide:

Approach 1: Profile via simulation.

// Simulate without setting an explicit limit
const sim = await connection.simulateTransaction(tx);
console.log("Used CU:", sim.value.unitsConsumed);
// Set limit at 1.2-1.5x measured

This is reliable for transactions that don't depend much on chain state. For state-dependent transactions (where price affects path selection, etc.), simulate under representative conditions.

Approach 2: Use dynamic limit setters.

Some libraries (Jupiter's swap API, Anchor with certain configurations) calculate the budget for you. For Jupiter:

const swapResp = await fetch(SWAP_URL, {
  method: "POST",
  body: JSON.stringify({
    quoteResponse: quote,
    userPublicKey: wallet.publicKey.toString(),
    dynamicComputeUnitLimit: true,  // <-- this
  }),
});

dynamicComputeUnitLimit: true tells Jupiter to estimate the right limit for your specific route. Saves manual profiling.

Common CU Limit Mistakes

Leaving the default of 200,000. Many real transactions need more. Multi-hop swaps, complex DeFi compositions, programs that use CPI extensively — these break on the default.

Setting it too high "to be safe." A 1,000,000 CU limit on a transaction that uses 150,000 means you're paying priority fees on the wasted 850,000.

Not differentiating between transaction types. Different routes need different budgets. A flat "always 250,000" works for some transactions but breaks for complex ones.

Padding with bigger random buffer than needed. Profile at 1.2-1.5x measured, not 3x or 10x.

Forgetting to set the budget instruction first. The CU budget instruction must be one of the first instructions in the transaction. Setting it after other instructions doesn't always work.

Setting CU Price Right

Already covered in Solana Priority Fees Guide. Brief recap:

const tx = new Transaction()
  .add(ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }))
  .add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100_000 }))
  .add(yourActualInstruction);

CU Optimization for Specific Use Cases

Different workloads have different CU profiles. What to know:

Simple swaps (AMM v4). ~30,000-50,000 CU. Default budget is enough; you're wasting some.

Concentrated liquidity swaps (CLMM, Whirlpool). Per-tick-crossed CU varies. A swap that crosses 2-3 ticks needs ~80,000-150,000 CU. Cross more ticks (volatile pool, large size), need more. Profile your actual usage.

Multi-hop routes through Jupiter. Highly variable. Use dynamicComputeUnitLimit: true and don't try to predict.

Lending operations. Borrow/repay/liquidate often need 200,000-400,000 CU due to multiple CPIs.

NFT mints. Compressed NFT mints are cheap (~30,000 CU). Standard NFT mints with metadata are heavier (~100,000-200,000 CU).

Arbitrage transactions. Multi-instruction transactions (close ATA, swap, swap, close ATA) compound. Profile end-to-end.

How CU Failures Manifest

A CU exhaustion failure looks like:

{
  err: { InstructionError: [N, "ComputationalBudgetExceeded"] }
}

The transaction was included in a block but consumed all its CU partway through and was reverted. You paid for the CU consumed; you didn't get the result.

Symptoms in production:

If you see these patterns, audit your CU budget by simulating against real conditions.

How CU Over-Padding Manifests

Less obvious because nothing fails:

If your CU limit is 1,000,000 and you're using 150,000, you're paying priority fees on 850,000 wasted CU per transaction. At 100 microlamports/CU, that's 0.000085 SOL per transaction wasted. Times 1,000 transactions per day, ~0.085 SOL/day, ~31 SOL/year.

What to Do This Week

If you have production transactions:

  1. Audit your CU limits. Are you setting them explicitly? Are they at right level?
  2. Profile each transaction type. Use simulateTransaction to measure real consumption.
  3. Set limits at 1.2-1.5x measured. Tight enough not to waste; loose enough to handle worst case.
  4. Use dynamicComputeUnitLimit: true for Jupiter swaps. Don't guess.
  5. Compute your "effective CU price" per transaction. Total priority fees / actual CU consumed. Want this high (you're not over-paying).
  6. Re-profile after protocol changes. CU costs can shift when programs upgrade.

Common Compute Budget Patterns

Production patterns we've seen work:

// Simple AMM swap
const cuLimit = 100_000;

// CLMM swap (use dynamic if possible)
const cuLimit = await estimateClmmCu(pool, amount);

// Jupiter swap
// Set dynamicComputeUnitLimit: true in the swap API call

// Complex multi-instruction transaction
// Profile via simulation, set 1.3x measured

For bots that handle many transaction types, parameterise the CU limit by transaction type rather than using a single value.

Try BoltTx for Production CU Workloads

CU budget tuning matters more when you're sending many transactions, which is when BoltTx's properties are most valuable:

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

const connection = new Connection(
  "https://bolttx.io/?api-key=YOUR_API_KEY",
  "processed"
);

Free tier signup. Run real CU-heavy traffic and compare your effective fee paid per landed transaction.

FAQ

What's the maximum CU per transaction? 1,400,000 CU. You can't set a CU limit above this.

Can I set the CU price without setting the limit? Yes, but you'll often want to set both. Without an explicit limit, you're using the default 200,000 which may be wrong.

Does the CU limit affect transaction priority? No. Total priority fee = limit × price. Validators sort by total priority fee, not by limit alone.

What happens if I set the limit too low? Transaction fails partway through with ComputationalBudgetExceeded. You pay for the consumed CU.

Should I include the budget instructions in my transaction signature scheme? The budget instructions are part of the transaction; signing covers them. No separate handling needed.

Further Reading

Back to all posts