Detecting a Solana Arbitrage Opportunity Is the Easy Half

Spotting a spread takes milliseconds. Whether it survives impact, fees, and the slots between detection and landing is the question that decides profitability.

BoltTx Team··9 min read
solanaarbitragedetectionprice-impactatomicitytrading-bot

Two pools quote different prices for the same pair. That is the whole detection problem, and it is solvable with arithmetic you already have.

★The reason most arbitrage bots lose money is not that they fail to detect. It is that they detect opportunities that were never profitable once execution was priced in.★

The Spread Is Not the Profit

// ★What you detect.★
const priceA = Number(reserveB_poolA) / Number(reserveA_poolA);
const priceB = Number(reserveB_poolB) / Number(reserveA_poolB);
const spread = Math.abs(priceA - priceB) / Math.min(priceA, priceB);

That figure describes an infinitesimally small trade. ★Your actual trade pays impact on both legs, in pools your own trade is making worse as it executes.★

// ★What you actually get.★
const out1 = amountOut(size, poolA.reserveIn, poolA.reserveOut, poolA.feeBps);
const out2 = amountOut(out1, poolB.reserveOut, poolB.reserveIn, poolB.feeBps);

const gross  = out2 - size;
const net    = gross - baseFee - priorityFee;      // ★lamports, not percent★

Compute the second one and compare it against zero. The spread percentage is a screening heuristic; the net lamport figure is the decision.

★A 2% spread on a pool thin enough that your size costs 1.5% each way is a losing trade that looks like a winner.★

Size Is the Variable, Not a Constant

The profit curve has a maximum, and it is not at the largest size you can afford:

small size   → tiny impact, tiny absolute profit
optimal size → ★maximum net★
large size   → impact on both legs exceeds the spread
function optimalSize(poolA, poolB) {
  let best = { size: 0n, net: 0n };
  for (const size of candidateSizes) {
    const net = simulateRoundTrip(size, poolA, poolB);
    if (net > best.net) best = { size, net };
  }
  return best;
}

★Fixing your trade size and only varying which opportunities you take is leaving money on both ends★ — too small on deep pools, unprofitable on thin ones.

If your detection is right and transactions still miss, a free BoltTx key is one line to test the submission path.

Atomicity Is Not Optional

Both legs must be in one transaction. This is not a preference — ★a two-transaction arbitrage is not arbitrage, it is two directional bets.★

const tx = new Transaction().add(
  buyOnPoolAIx,
  sellOnPoolBIx,        // ★same transaction — both or neither★
);

Solana gives you atomicity for free within a transaction, so if the second leg would fail, the whole thing reverts and you keep your capital minus the base fee.

This is what makes the 1232-byte limit and the four-level CPI nesting ceiling into arbitrage-specific constraints. Two pool legs plus token program calls consume both budgets quickly, which is why:

★Address lookup tables matter more here than almost anywhere else★ — your account set is stable across trades, so the setup cost amortises immediately.

Direct routes beat split routes. Fewer accounts, less compute, fewer levels. A route that lands beats a marginally better price that exceeds a limit.

The Slots Between Detection and Landing

★Every slot between reading reserves and executing is a slot for someone else to take the same trade.★

log({
  detectSlot,
  submitSlot,
  landSlot,
  staleness: landSlot - detectSlot,     // ★slots your edge was exposed★
});

Two things follow, and they are the whole competitive picture:

The opportunity you detected at slot N may be gone at slot N+3, taken by someone whose submission path was faster.

★Your minimum-output check is what turns "someone took it first" from a loss into a revert.★ Without it, you execute into a spread that no longer exists and pay for the privilege.

Set the minimum from your computed net, not from a percentage. An arbitrage with a minimum output equal to your input plus fees either profits or reverts — which is exactly the behaviour you want.

What Failed Attempts Cost

Arbitrage bots submit far more than they land, and the accounting has to reflect that:

metrics.record({ outcome, feeSpent: baseFee + priorityFee, netIfLanded });

★A reverted arbitrage costs the base fee and the priority fee for a transaction that changed nothing.★ At high frequency this is a real operating expense, not a rounding error.

The number that matters is net profit per attempt, not per success. A strategy winning 20% of races at a good margin can be less profitable than one winning 60% at a smaller margin, and only per-attempt accounting shows that.

Where Most Detection Logic Goes Wrong

Stale reserves. Prices from different slots produce a spread that never existed. ★Read both pools in one getMultipleAccountsInfo call.★

Ignoring the fee tier. Pools have different fee rates; comparing raw reserve ratios across them overstates the spread by the fee difference.

Forgetting decimals. Two tokens with different decimal counts produce a ratio off by orders of magnitude if not normalised — read them from getMint rather than assuming nine.

Assuming constant product. Concentrated liquidity pools do not follow the formula, so a spread computed that way is fiction.

Percentage thinking. ★A large percentage on a tiny pool is a smaller absolute profit than a small percentage on a deep one★, and only the lamport figure ranks opportunities correctly.

What Landing Looks Like

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

★For arbitrage that number is the strategy's viability, not a performance metric.★ An edge that survives half a slot but disappears by two is entirely determined by how fast your submissions actually land.

Where BoltTx Fits

We handle submission. Detection, sizing, and route construction stay in your code.

Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure — which matters more for arbitrage than for most strategies, since an observable transaction is an opportunity someone else can take before it lands.

You sign locally. We never hold funds, never sign, and never modify transaction contents, including your minimum output. 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

How do I detect arbitrage opportunities on Solana? Compare prices derived from pool reserves across venues. Detection is simple arithmetic; the real work is determining whether the spread survives impact, fees, and execution delay.

Why is my detected spread not profitable? Because the spread describes an infinitesimally small trade. Your size pays price impact on both legs, in pools your own trade is worsening as it executes.

How do I calculate real arbitrage profit? Simulate the full round trip through both pools at your intended size, subtract base and priority fees, and compare the result to zero in lamports rather than percent.

What trade size maximises arbitrage profit? Neither the smallest nor the largest. Profit peaks where the marginal gain from size stops exceeding the marginal impact, so search across candidate sizes rather than fixing one.

Do both legs need to be in one transaction? Yes. Without atomicity you have two directional bets rather than an arbitrage, and a failure on the second leg leaves you holding an unwanted position.

Why do arbitrage transactions hit the size limit? Two pool legs reference many accounts at 32 bytes each. Address lookup tables help substantially here because your account set is stable and reused across trades.

How does CPI depth affect arbitrage? Two legs through an aggregator can approach the nesting ceiling. Direct routes use fewer levels and fewer accounts, which is why they often beat a marginally better split route.

How do I stop executing on a stale opportunity? Set the minimum output from your computed net profit. If the spread has closed by execution, the transaction reverts instead of filling at a loss.

How much does a failed arbitrage cost? The base fee plus any priority fee, for a transaction that changed nothing. At high frequency this is a real operating expense that belongs in per-attempt accounting.

Should I measure profit per success or per attempt? Per attempt. A strategy with a high win rate and small margin can beat one with a large margin and low win rate, and only per-attempt figures reveal which you have.

Why must both pool reads come from the same slot? Because prices from different slots produce a spread that never existed. Use a single getMultipleAccountsInfo call so the reserves are consistent.

Do different pool fee tiers affect detection? Yes. Comparing raw reserve ratios across pools with different fees overstates the spread by the fee difference, which can turn a real edge into an imaginary one.

Does concentrated liquidity break arbitrage detection? It breaks the constant-product formula. Depth depends on where price sits relative to liquidity ranges, so applying the standard arithmetic produces a confident but wrong spread.

How do decimals affect the calculation? Two tokens with different decimal counts produce a ratio off by orders of magnitude unless normalised. It is a silent error because the number still looks like a price.

Does landing speed change arbitrage profitability? Directly. Every slot between detection and execution is a slot for a competitor to take the same trade, so the landing distribution determines how many of your detections convert.

Should I use an aggregator for arbitrage? Usually not for execution. Aggregator routes add accounts, compute, and CPI levels, and a route that exceeds a limit is worth less than a direct one that lands.

Back to all posts