Solana · MEV

Solana MEV bot architecture in Rust

"MEV bot" sounds like a single program. In practice, a production Solana arbitrage engine is half a dozen subsystems that each have to be fast and exactly correct, wired together so the slowest one sets your ceiling. Get any of them wrong and you don't lose gracefully — you submit transactions that revert and bleed fees, or worse, land transactions that lose real money. This is a tour of the parts that actually matter, drawn from building one in Rust.

1. Event ingestion: see opportunities first

Everything starts with seeing on-chain state change before your competitors act on it. There are three tiers of "fast," and serious engines use more than one:

  • gRPC / Geyser streams — account and transaction updates pushed from an RPC node. Reliable, structured, and the backbone of most engines.
  • ShredStream — raw shreds (the fragments validators gossip before a block is finalized), decoded and reassembled yourself. This buys you hundreds of milliseconds over confirmed data, at the cost of writing a UDP reassembler and an entry decoder.
  • Mempool / pending feeds — where available, the earliest possible signal.

In Rust, the pattern that holds up is to split the I/O from the processing: a receiver task pulls bytes off the socket as fast as the kernel delivers them and hands them to a bounded channel; worker tasks parse and evaluate. If the workers fall behind, the bounded channel applies backpressure and you drop rather than queue unbounded — because a stale opportunity is worthless and an OOM is fatal.

// Decouple the network from the engine; never queue unbounded.
let (tx, rx) = mpsc::channel::<RawEvent>(2048);
tokio::spawn(receiver_loop(socket, tx));        // pure I/O
tokio::spawn(engine_loop(rx, quoter.clone()));  // parse + evaluate

2. DEX adapters and the parity problem

To quote an arbitrage you must reproduce each DEX's swap math exactly — constant-product for classic AMMs, concentrated-liquidity tick math for CLMMs, bin math for DLMMs. The trap is that "close enough" is useless: if your quote disagrees with the on-chain program by one lamport, your transaction reverts and you've paid to learn that.

The discipline that prevents this is parity testing against mainnet. For every DEX adapter, you capture a real on-chain swap, feed the same inputs into your Rust implementation, and assert your output matches the chain's byte-for-byte — both the computed amounts and the encoded instruction data. We've integrated the major venues this way — Raydium (V4 and CLMM), Orca Whirlpool, the Meteora family, PumpSwap — and the rule is non-negotiable: no adapter ships until parity passes against live data.

Hard-won lesson: verifying that account lists match is not enough. An encoder can produce the right accounts and still serialize the wrong instruction bytes. Always read the upstream IDL/Params struct and compare the raw instruction data against a captured transaction — not just its length.

3. The 1232-byte wall: transaction size

Solana caps a transaction at 1232 bytes. A multi-hop arbitrage touches many accounts, and each fresh account pubkey costs 32 bytes — so real routes blow through the limit fast. Two tools get you under it:

  • Address Lookup Tables (ALTs) — pre-publish the accounts you touch repeatedly into on-chain tables, then reference them by a one-byte index instead of a 32-byte key. A well-built ALT registry (a global tier-1 table plus per-pool tier-2 tables) is often the difference between a route that fits and one that doesn't.
  • Composable wrapper programs — a small on-chain program your bot calls that performs the swap sequence internally, collapsing what would be many top-level instructions into one.

Designing for the size limit is not an optimization you bolt on later; it shapes the whole instruction-building layer from day one.

4. Submission: routing to land

Finding the opportunity is half the job; landing the transaction is the other half. Solana's leader schedule and congestion mean a single submission path is fragile, so production engines fan out across senders:

  • Jito bundles for atomic, tip-based inclusion.
  • Direct TPU submission to the current and next leaders.
  • Staked RPC providers (Helius, Astralane, Nozomi, ZeroSlot, and others) as parallel paths.

Rust's async model makes it natural to fire these concurrently and take whichever lands first. Pair this with a rotating multi-RPC client — round-robin across keys with a short cooldown on errors — so no single rate limit or outage stalls you.

5. The part everyone skips: non-bleed safety

The reason most MEV bots lose money isn't that they can't find arbitrage — it's that they submit unprofitable transactions all day. The fixes are architectural, not optional:

  • EV-gate every submission. Compute expected value after fees, tip, and priced-in failure probability. If it isn't positive, don't send. Silent-and-flat beats active-and-bleeding.
  • Daily spend cap with auto-halt. A hard ceiling that stops the bot when hit, no human in the loop.
  • Reconciliation. Match every PnL claim the bot makes against on-chain truth. A bot that reports "+$278" while the wallet is actually down is worse than useless — and that bug hides for weeks without a reconciliation loop.

6. Why Rust specifically

This is a system where a 5-millisecond GC pause loses the race, a data race corrupts a quote, and a panic at the wrong moment strands capital. Rust gives you the microsecond hot paths and the compile-time guarantees in the same language — see why Rust for low-latency systems for the latency argument in detail. For an engine that moves real money on every decision, that combination isn't a preference; it's the requirement.

The takeaway

A Solana MEV engine is an integration problem disguised as a speed problem. Ingestion, parity-correct adapters, transaction-size engineering, multi-path submission, and non-bleed safety each have to be right, and the architecture's job is to keep them fast without letting any one of them quietly lose you money. The teams that win treat correctness and safety as first-class, not as something to add after "it works."

We built exactly this — ~86k lines of Rust, with an on-chain wrapper deployed and verifiable on Solana mainnet. It's available to license, or we can build yours.