BotTx|Documentation

Advanced

Transaction Building Best Practices

BoltTx delivers your transaction as fast as physically possible — but landing fast still requires a well-built transaction. This page covers the mistakes that most often cause transactions to fail, stall, or get dropped, regardless of how good your delivery path is.

Compute Unit Budget

Every Solana transaction has an implicit compute unit (CU) budget. Unless you call ComputeBudgetProgram.setComputeUnitLimit, the budget defaults to 200,000 CU per instruction (up to a hard cap of 1,400,000 CU for the whole transaction). Swaps, multi-hop DEX routes, and complex DeFi interactions routinely need explicit CU limits — relying on defaults means your transaction fails on-chain even though it was delivered successfully.

Simulate first, then set

Use simulateTransaction against a recent blockhash to measure actual CU consumption. Set the compute unit limit to 1.2× that measurement — enough headroom to absorb price-feed or liquidity-state changes between simulation and landing.

Never request the max by default

Requesting 1.4M CU "just in case" makes your transaction larger and slower for schedulers to prioritize. Only request what you need.

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

// 1. Simulate first to measure real usage
const sim = await connection.simulateTransaction(transaction);
const actualCU = sim.value.unitsConsumed ?? 200_000;

// 2. Set limit to 1.2x measured
transaction.add(
  ComputeBudgetProgram.setComputeUnitLimit({
    units: Math.ceil(actualCU * 1.2),
  })
);

Priority Fee vs. Tip

These are two separate things — you can use both, neither, or either. Understanding the difference matters for optimizing cost.

BoltTx tip

A required SOL transfer to a BoltTx tip address — pays for delivery and unlocks your plan's tier. This is not a Solana-level fee; it's how BoltTx meters usage.

Solana priority fee

An optional compute-unit price set via setComputeUnitPrice. This tells Solana validators to prioritize your transaction during congested slots. BoltTx does not require it, but it helps land in busy periods.

Recommendation: Always include the BoltTx tip (it's required). Add a priority fee only when the network is congested or when you're competing for a specific slot — otherwise it's wasted money.

Address Lookup Tables (ALTs)

Solana transactions have a hard 1232-byte size limit. Complex transactions (multi-hop swaps, bundled operations) hit this limit quickly because every account referenced consumes 32 bytes. Address Lookup Tables let you reference accounts by a 1-byte index instead — a single ALT can shrink a 1200-byte transaction to 400 bytes.

Use an ALT when your transaction references more than ~20 accounts, or when you see "Transaction too large" errors.

Using versioned transactions with ALTs
import {
  PublicKey,
  TransactionMessage,
  VersionedTransaction,
  AddressLookupTableAccount,
} from "@solana/web3.js";

// Fetch your ALT(s) — each can hold up to 256 addresses
const alt = await connection
  .getAddressLookupTable(new PublicKey("YOUR_ALT_ADDRESS"))
  .then(r => r.value!);

// Build a v0 message that references the ALT
const message = new TransactionMessage({
  payerKey: payer.publicKey,
  recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
  instructions: [ /* your instructions */ ],
}).compileToV0Message([alt]);

const tx = new VersionedTransaction(message);
tx.sign([payer]);

// tx.serialize() now produces a much smaller payload

Simulation Passes But Landing Fails

A common frustration: simulateTransaction returns success, but when you submit, the transaction fails on-chain. This almost always comes down to state changes between simulation and execution.

  • Pool prices moved — your slippage check now fails.
  • Another transaction consumed the liquidity you were targeting.
  • A signer's balance changed (e.g., a parallel transaction drained their SOL below the rent-exempt threshold).
  • Your blockhash expired between simulation and landing.

Fix: Simulate against the most recent blockhash, submit immediately after, and always include a generous slippage tolerance on swaps. On BoltTx, landing is sub-second — so the gap between simulation and execution is typically one or two slots.

Transaction Size Debugging

If your transaction is rejected as too large, check in this order:

  1. Count the unique accounts referenced. Each one is 32 bytes.
  2. Count your signatures. Each one is 64 bytes plus a 1-byte index.
  3. Sum instruction data sizes — some programs serialize large payloads (complex swap routes often do).
  4. If the sum exceeds 1232 bytes, move the heaviest account list into an ALT and resubmit as a v0 transaction.

Blockhash Expiration

Regular transactions reference a recent blockhash and must land within 150 slots (~60 seconds at 400ms per slot). When preflight is enabled, an expired blockhash is surfaced as a BlockhashNotFound RPC error; with skip_preflight=true, the transaction is quietly dropped on-chain. This is especially common when:

  • You sign far in advance (e.g., signing on a mobile device, then submitting from a backend).
  • Your delivery path has high latency — the transaction arrives on-chain too late.
  • You retry a failed transaction without refreshing the blockhash.

Fix: Refetch the blockhash immediately before signing. For scenarios that need long-lived validity, switch to Durable Nonce.

Pre-submission Checklist

  • Compute unit limit is set from simulation × 1.2, not the default 200k.
  • BoltTx tip instruction is included and meets your plan's minimum.
  • Blockhash was fetched less than 30 seconds ago.
  • Transaction size is under 1232 bytes — or you are using a versioned transaction with ALTs.
  • All required signers have signed.
  • Simulation passed against the same blockhash you plan to submit with.

Further reading