Solana Transaction Retry Logic and Compute Unit Fee Strategy
Dropped transactions cost opportunity on Solana. Learn how to implement exponential back-off retries, dynamic compute-unit-limit estimation, and priority-fee escalation to keep your bot competitive in congested slots.
Solana's throughput is a feature until your transaction lands in slot N+3 instead of slot N, or never lands at all. During periods of network congestion — Jito bundle wars, meme coin launches, liquidation cascades — the validator leader queue fills fast and plain RPC submissions get silently dropped. The retry logic and fee strategy you ship at launch will determine whether your trading bot is competitive or consistently late.
Why Transactions Get Dropped
Solana does not have a traditional mempool. Transactions are forwarded from your RPC node to the current leader and the next two leaders in the schedule. If none of those leaders include the transaction before it expires (default: 150 blocks, roughly 60 seconds), it is gone. The RPC endpoint does not retry aggressively by default — skipPreflight: true gets you past simulation gating but does nothing to keep the transaction alive under leader churn.
Two failure modes dominate in production:
- Leader queue saturation: The leader's ingress queue is at capacity and drops packets. Common during burst activity.
- Stale blockhash: Your transaction references a recent blockhash that has expired by the time a leader tries to include it. This kills transactions that sit in application queues too long.
Exponential Back-off Retry Loop
The foundation is a retry loop that resubmits the same signed transaction — not a re-signed one — at increasing intervals. Resubmitting the same bytes is safe because the transaction is idempotent by design (same blockhash, same signatures). Re-signing on every retry wastes time and introduces a new expiry window each iteration.
A practical pattern in TypeScript:
async function sendWithRetry(
connection: Connection,
rawTx: Uint8Array,
maxRetries = 6,
baseDelayMs = 400
): Promise<string> {
let attempt = 0;
while (attempt < maxRetries) {
const sig = await connection.sendRawTransaction(rawTx, {
skipPreflight: true,
maxRetries: 0, // disable RPC-level retries, own the loop
});
const confirmed = await pollForConfirmation(connection, sig, 4000);
if (confirmed) return sig;
await sleep(baseDelayMs * 2 ** attempt);
attempt++;
}
throw new Error("Transaction failed after max retries");
}
Key decisions here: maxRetries: 0 tells the RPC not to do its own retry loop so you control timing precisely. The 400 ms base with exponential back-off reaches ~12 seconds of total wait at attempt 5, which fits comfortably within the 60-second blockhash window. For latency-critical paths (arbitrage, liquidations) reduce the base to 150–200 ms and cap at 4 retries.
Dynamic Compute Unit Limit Estimation
Overpaying for compute units wastes priority-fee budget. Underpaying causes ComputeBudgetExceeded failures mid-transaction. Simulating first is the correct approach:
const sim = await connection.simulateTransaction(tx, { sigVerify: false });
const unitsConsumed = sim.value.unitsConsumed ?? 200_000;
const cuLimit = Math.ceil(unitsConsumed * 1.15); // 15% headroom
Set this limit via ComputeBudgetProgram.setComputeUnitLimit({ units: cuLimit }) as the first instruction in your transaction. The 15% buffer handles minor path variance (different token account states, minor price moves). For complex DeFi interactions involving multiple AMMs, push that buffer to 20–25%. Simulation is cheap — it runs locally against the RPC's snapshot — and the few milliseconds it adds are almost always worth it.
Priority Fee Escalation Strategy
The setComputeUnitPrice instruction sets your priority fee in micro-lamports per compute unit. The total priority fee paid is price × limit / 1,000,000 lamports. Getting the price right is the whole game.
Static fees are a crutch. A flat 50,000 micro-lamports may be wildly overpaying during quiet slots and completely uncompetitive during a liquidation wave. The correct approach queries recent fee distributions:
const recentFees = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: relevantAccounts,
});
const p75 = percentile(recentFees.map(f => f.prioritizationFee), 75);
const targetFee = Math.max(p75 * 1.2, MIN_FEE_FLOOR);
Filtering by lockedWritableAccounts returns fees specific to transactions that touched the same accounts your bot is competing over — a much tighter signal than the global fee average. The p75 with a 20% premium puts you above the median competitor without catastrophically overpaying. During escalating retry attempts, you can increment this multiplier: 1.2, 1.5, 2.0 across attempts.
Hard cap your fee. Define a maximum acceptable fee per trade type. A $0.01 priority fee cap per arb trade makes sense at certain spread thresholds; for liquidation bots the acceptable fee ceiling is a function of expected profit, not a fixed number.
Jito Bundles for Guaranteed Inclusion
When standard retry logic is not enough — particularly for sandwich-sensitive operations or when you need strict ordering — Jito bundles are the tool. Bundles land atomically in a single block and bypass the standard leader queue. The trade-off is that you pay a bundle tip (floor ~10,000 lamports, competitive tips often 50,000–500,000 lamports depending on activity) and require Jito-connected validators, which cover roughly 50–60% of stake weight on mainnet-beta.
Bundle tip goes to a Jito tip account, not into the fee account, so it does not count toward your compute unit fee calculation. Budget for it separately. For time-critical strategies where landing in a specific block matters more than fee minimization, bundles are worth the overhead.
Monitoring What Actually Matters
Retry counts and confirmation latency are your primary operational signals. Track:
- Retry depth distribution: if p95 retry depth is 3 or higher, your base fee or base delay is wrong for current network conditions
- Blockhash age at submission: log
currentSlot - txBlockhashSlot— if this regularly exceeds 100, your signing pipeline has a latency problem - Fee paid vs. slot fee percentile: confirms you are not consistently overpaying or underpaying
Alert on any 30-second window where your confirmation rate drops below 85%. At that threshold you have a fee or retry configuration problem, not just normal network variance.
If you want these systems built and monitored rather than built and forgotten, talk to the team at TierZero — we run production bots where these numbers are measured daily, not estimated.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article