All articles
Strategies·April 11, 2026·7 min read

Solana DEX Triangular Arbitrage via Jupiter Route Splitting

A working solana triangular arbitrage strategy: three-leg cyclical arb across Raydium, Orca and Meteora using Jupiter split-route quotes in one atomic bundle.

A triangular arb on Solana is three swaps that start and end in the same token: USDC to SOL, SOL to JUP, JUP back to USDC. If the product of the three exchange rates lands above 1.0 after fees, you keep the difference. The math is trivial. Everything that makes it profitable — or a guaranteed loss — happens in the plumbing around Jupiter's route splitting and how you pack the whole cycle into a single atomic transaction.

Most people who try this build it as three separate Jupiter swaps fired in sequence and wonder why they lose money on eight out of ten attempts. The leg-2 price moved while leg-1 was landing. This post is about the version that actually clears: one cycle, one bundle, split routes on every leg, and a hard revert if the cycle doesn't close green.

Why the cycle has to be atomic

The whole edge in a triangular arb is that the three legs are priced against each other at one instant. Solana blocks are ~400ms. If you send three transactions, even back-to-back, you're exposed to two block boundaries where anyone — including the searcher watching the same imbalance — can move the middle leg. By the time your JUP-to-USDC swap lands, the JUP price you quoted has decayed.

So the cycle goes into one transaction, and that transaction reverts atomically if the final USDC balance isn't strictly greater than the starting balance plus your minimum profit threshold. No partial fills, no "well leg one worked." Either the full triangle clears above your floor or nothing moves except the fee. This is the same atomicity discipline behind our two-venue atomic arbitrage work, just extended to a three-node graph.

The check itself is a balance assertion at the end of the instruction sequence:

let usdc_after = token_balance(&ctx.accounts.usdc_vault)?;
require!(
    usdc_after >= usdc_before
        .checked_add(min_profit_lamports)
        .ok_or(ArbError::Overflow)?,
    ArbError::CycleUnprofitable
);

If that require! fails, the transaction reverts and you're out nothing but the ~5000 lamport base fee plus whatever priority fee you bid. That asymmetry — tiny cost on failure, real profit on success — is the entire reason the strategy is worth running.

Where Jupiter's split routing changes the game

Jupiter doesn't route a swap through one pool. For any meaningful size it splits the order across multiple pools and DEXes to minimize price impact. A single "SOL to JUP" quote might come back as 60% through an Orca whirlpool, 30% through a Raydium CLMM pool, and 10% through a Meteora DLMM bin. That split is the difference between eating 40bps of impact and eating 12.

For triangular arb this matters more than it does for a plain swap, because impact compounds across three legs. A naive single-pool triangle that looks profitable in the quote will slip on every leg and close red. The split quote is what keeps each leg's realized rate close to its marginal rate.

You pull this from the Jupiter quote API with onlyDirectRoutes=false and a tuned maxAccounts, then feed the returned route plan straight into the swap instruction builder. The knob that actually matters:

  • slippageBps — set this per leg, not globally. Leg 1 (USDC to SOL) is deep and stable, so 10bps. Leg 3 (JUP back to USDC) is thinner, so 30–40bps. A single global slippage tolerance either over-protects the deep leg or under-protects the thin one.
  • maxAccounts — every split adds accounts, and a Solana transaction caps at 64 accounts (well, the legacy limit; with address lookup tables you get breathing room). A three-leg cycle with aggressive splitting on each leg blows past this fast. You will fight this constraint on every build.

That account ceiling is the real design constraint, and it's why you can't just crank splitting to the maximum on all three legs.

The address lookup table problem

Here's the gotcha nobody mentions until they hit it. Three split legs across Raydium, Orca and Meteora reference a lot of accounts: pool states, token vaults, tick arrays, oracle accounts, bin arrays for the DLMM leg. Pack them into a legacy transaction and you'll exceed the size limit before you've encoded the second leg.

The fix is address lookup tables (ALTs). You pre-create an ALT holding every account your hot pairs touch, and reference them by a 1-byte index instead of a 32-byte pubkey. A well-built ALT for your top ten cycle pairs turns a transaction that won't serialize into one that fits comfortably with room for priority-fee metadata.

Build the ALT ahead of time and refresh it when Jupiter's routing starts favoring pools you haven't cached. If your triangle suddenly routes through a Meteora pool that isn't in your table, the transaction inflates and either fails to serialize or gets bumped by size. The Meteora DLMM bin structure in particular pulls in a variable set of bin arrays depending on where price sits — the same dynamic that makes DLMM fee-farming rebalance bots tricky to keep in-range applies here to which accounts you need cached.

Bundling and landing

Sending the cycle as a normal transaction into the public mempool is how you get sandwiched or front-run on the very imbalance you found. Route it through Jetstream or a Jito bundle instead, with a tip that scales to the expected profit — typically 30–50% of the edge on a contested pair, less on a quiet one.

A few things I've learned landing these in production:

  1. Quote-to-submit latency is your real competitor. From the moment Jupiter returns the split route to the moment your bundle lands, the imbalance is decaying. Co-locate near a fast RPC, cache your ALTs, and pre-build as much of the instruction as you can. Every 50ms you shave is edge you keep.
  2. Simulate before you send, but don't trust the sim's price. Simulation catches account and compute-budget errors. It does not reflect the price at land time. The on-chain require! is your only real guard.
  3. Bid priority fee dynamically. A flat priority fee either overpays on dead blocks or loses the auction on hot ones. Tie it to recent block congestion and the size of the edge.

A rough profitability sketch

Say your quote shows a raw cycle return of 0.55% on a $10k notional. Subtract three legs of realized slippage (call it 0.22% combined with good split routing), Jupiter's implicit routing, the Jito tip (0.15% if contested), and priority + base fees (negligible in percentage terms at this size). You're left with roughly 0.18%, or about $18, on a cycle that clears in one block. That's thin, and it's why volume and hit-rate matter more than any single fat trade. The strategy lives or dies on landing hundreds of small clean cycles, not hunting for one 2% dislocation that a dozen searchers already see.

If you're routing recurring capital through cycles like this, the same execution-engine primitives — atomic bundling, dynamic tip bidding, ALT management — carry straight over to Jupiter trigger and recurring-order engines, and the just-in-time liquidity mechanics in our JIT LP on Meteora writeup share the same block-timing constraints you're fighting here.

What breaks in production

  • Stale ALTs silently inflate your transaction until it stops landing. Monitor serialized transaction size as a first-class metric.
  • Compute budget. Three split legs with tick-array crossings can exceed the default compute limit. Request a raised limit explicitly and measure actual CU usage per pair.
  • Route flapping. Jupiter's optimal split changes block to block. If you cache a route plan for even a few hundred milliseconds, you're trading on a stale graph.

None of this is exotic once it's built, but the failure modes are quiet — you don't crash, you just bleed on transactions that revert or land red. Instrument everything: hit rate, realized-vs-quoted edge per leg, size at submit, tip efficiency. A dashboard that surfaces revert reasons and per-leg slippage turns a black-box loss into a fixable one.

If you want a second set of eyes on the atomicity guarantees before you point real capital at it, our code review and audit service exists for exactly this kind of "one wrong balance check and it drains" execution code.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Solana#arbitrage#Jupiter#DEX#MEV