Skip to content

Web3 Infrastructure

JITO, Jupiter, and the Solana MEV Ecosystem: A Trading Infrastructure Tour

Solana MEV in 2026 is where Ethereum MEV was in 2021 - rapidly maturing and profitable for infrastructure-savvy participants. Here is how the full stack fits together.

14 min
#jito #jupiter #solana #mev #bundles #block-engine #arbitrage #searchers #defi

There is a moment in every maturing MEV ecosystem when the easy alpha dries up and infrastructure becomes the differentiator. On Ethereum, that moment was roughly 2022-2023: after Flashbots standardized the bundle submission protocol and MEV-Boost got to 90% validator adoption, the low-hanging arb opportunities were gone and the winners were the teams with better simulation, lower latency, and more sophisticated bundle construction.

Solana MEV in 2026 is about 18 months behind that curve. The Jito ecosystem has standardized the bundle submission layer. Jupiter has become the canonical routing layer for on-chain execution. But the infrastructure knowledge gap between teams that know how these systems work at the protocol level and teams that are just calling Jupiter’s V6 API is still large. That gap is where the edge lives.

This post is the infrastructure tour I wish I had before I started building ZeroCopy’s Solana execution layer.

Jito’s Architecture

Jito Labs built the Solana MEV stack in three interconnected components. Understanding all three is necessary before you can reason clearly about how to build on top of them.

Jito-Solana (Modified Validator Client)

Jito-Solana is a fork of the official Agave validator client with one key addition: a bundle processing module that accepts tip bundles from the Jito Block Engine. When a Jito-Solana validator is the current slot leader, it processes bundles from the Block Engine in addition to standard transactions.

The validator’s modified block building loop works as follows: during the leader slot, instead of only pulling transactions from the TPU port, the validator also queries the Jito Block Engine for the highest-tip valid bundle. If a bundle exists and its tip exceeds a minimum threshold, the bundle transactions are prepended to the block ahead of standard transactions. The tip transaction itself pays to one of eight rotating Jito tip accounts - these addresses are pre-registered on-chain and recognized by the block engine as valid tip receivers.

As of early 2026, over 60% of Solana stake is running Jito-Solana. This means that roughly 60% of leader slots will process bundles. For a searcher submitting bundles, this is the relevant statistic: your bundle has a 60% chance of landing in the next available slot, and if it does not land in a Jito-Solana leader’s slot, it fails silently and you need to detect and retry.

Jito Block Engine

The Block Engine is the off-chain service that receives bundles from searchers and relays them to Jito-Solana validators. It is Jito’s equivalent of an MEV-Boost relay, but with a tighter integration since it is purpose-built for Solana’s sub-second block times.

The Block Engine’s function:

  1. Receive bundles from searchers via gRPC or REST API
  2. Simulate each bundle against the current chain state
  3. Order bundles by tip amount
  4. Forward the highest-tip valid bundle to the current leader validator every ~50ms
  5. Handle tip account rotation and signature verification

The Block Engine is a centralized service operated by Jito. This is a trust assumption: Jito could theoretically front-run searchers by observing bundle contents before forwarding them. In practice, Jito has staked their reputation and significant validator economics on being a neutral operator, and there have been no documented front-running incidents. But the trust assumption exists and should be understood.

Regional Block Engines are deployed in major cloud regions to minimize latency between searcher submission and leader forwarding. Latency matters enormously - a bundle submitted 150ms after the triggering transaction has a much lower probability of landing in the same block as the triggering transaction.

The Bundle Format

A Jito bundle is a group of 2 to 5 transactions that must be included atomically and in the specified order within a single block slot. If any transaction in the bundle fails or cannot be included, the entire bundle fails. The final transaction in the bundle (or one of the transactions) must be a tip payment to a Jito tip account.

Key properties:

  • Atomic inclusion - all transactions land together or none do. This is the fundamental primitive that makes sandwich attacks and arbitrage feasible.
  • Ordered execution - transactions execute in the exact order specified. Your buy transaction lands before the target transaction before your sell transaction.
  • Tip priority - the Block Engine orders bundles by tip. Higher tip = higher probability of landing in a given slot.
  • No partial reverts - unlike individual transactions, a bundle that causes any transaction to revert is completely excluded. You get atomic success or atomic failure.

How to Submit a Bundle to Jito

The bundle submission API accepts JSON-RPC over HTTP. Here is a Python implementation of bundle construction and submission:

import base58
import base64
import json
import time
from typing import List
import httpx
from solders.transaction import VersionedTransaction
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import transfer, TransferParams
from solders.message import MessageV0
from solders.instruction import Instruction, AccountMeta

# Jito Block Engine endpoints (use the one geographically closest to your server)
JITO_ENDPOINTS = {
    "mainnet": "https://mainnet.block-engine.jito.wtf",
    "amsterdam": "https://amsterdam.mainnet.block-engine.jito.wtf",
    "frankfurt": "https://frankfurt.mainnet.block-engine.jito.wtf",
    "ny": "https://ny.mainnet.block-engine.jito.wtf",
    "tokyo": "https://tokyo.mainnet.block-engine.jito.wtf",
}

# Current Jito tip accounts (rotate periodically - fetch from on-chain config in production)
JITO_TIP_ACCOUNTS = [
    "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
    "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
    "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
    "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
    "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
    "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
    "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
    "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
]

def build_tip_instruction(payer: Pubkey, tip_lamports: int) -> Instruction:
    """
    Build a SOL transfer instruction to a random Jito tip account.
    In production, rotate through tip accounts to avoid predictable patterns.
    """
    import random
    tip_account = Pubkey.from_string(random.choice(JITO_TIP_ACCOUNTS))
    return transfer(
        TransferParams(
            from_pubkey=payer,
            to_pubkey=tip_account,
            lamports=tip_lamports,
        )
    )

def submit_bundle(
    transactions: List[VersionedTransaction],
    tip_lamports: int,
    payer: Keypair,
    recent_blockhash: str,
    endpoint: str = JITO_ENDPOINTS["mainnet"],
) -> str:
    """
    Submit a bundle to Jito Block Engine.

    Args:
        transactions: 1-4 transactions (a tip transaction will be appended)
        tip_lamports: tip amount in lamports (1 SOL = 1e9 lamports)
                     typical range: 100_000 to 5_000_000 lamports ($0.01 - $0.50)
        payer: keypair that signs the tip transaction
        recent_blockhash: must be fresh (< 150 slots old)
        endpoint: Block Engine endpoint to use

    Returns:
        bundle_id (UUID string)
    """
    if len(transactions) > 4:
        raise ValueError("Bundle can contain at most 4 user transactions + 1 tip = 5 total")

    # Build the tip transaction
    tip_ix = build_tip_instruction(payer.pubkey(), tip_lamports)

    tip_message = MessageV0.try_compile(
        payer=payer.pubkey(),
        instructions=[tip_ix],
        address_lookup_table_accounts=[],
        recent_blockhash=recent_blockhash,
    )
    tip_tx = VersionedTransaction(tip_message, [payer])

    # Combine user transactions + tip transaction
    bundle_txs = list(transactions) + [tip_tx]

    # Serialize transactions to base64 (Jito API expects base64-encoded wire format)
    serialized_txs = [
        base64.b64encode(bytes(tx)).decode("utf-8")
        for tx in bundle_txs
    ]

    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "sendBundle",
        "params": [serialized_txs],
    }

    with httpx.Client(timeout=5.0) as client:
        response = client.post(
            f"{endpoint}/api/v1/bundles",
            json=payload,
            headers={"Content-Type": "application/json"},
        )
        response.raise_for_status()
        result = response.json()

    if "error" in result:
        raise RuntimeError(f"Bundle submission error: {result['error']}")

    bundle_id = result["result"]
    return bundle_id

def check_bundle_status(bundle_id: str, endpoint: str = JITO_ENDPOINTS["mainnet"]) -> dict:
    """
    Check the status of a submitted bundle.

    Possible statuses:
    - "Invalid": bundle was invalid (malformed, expired blockhash)
    - "Pending": not yet processed by the block engine
    - "Failed": simulation failed or no valid leader slot found
    - "Landed": bundle was included in a block
    """
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "getBundleStatuses",
        "params": [[bundle_id]],
    }

    with httpx.Client(timeout=5.0) as client:
        response = client.post(
            f"{endpoint}/api/v1/bundles",
            json=payload,
            headers={"Content-Type": "application/json"},
        )
        response.raise_for_status()
        result = response.json()

    if not result.get("result", {}).get("value"):
        return {"status": "Unknown", "bundle_id": bundle_id}

    return result["result"]["value"][0]

def submit_bundle_with_retry(
    transactions: List[VersionedTransaction],
    tip_lamports: int,
    payer: Keypair,
    get_blockhash_fn,  # callable that returns fresh blockhash
    max_retries: int = 3,
    endpoint: str = JITO_ENDPOINTS["mainnet"],
) -> str:
    """
    Submit bundle with retry on blockhash expiry.

    A blockhash is valid for ~150 slots (~60 seconds). If the bundle
    does not land, the blockhash expires and you need a fresh one.
    """
    for attempt in range(max_retries):
        recent_blockhash = get_blockhash_fn()

        bundle_id = submit_bundle(
            transactions=transactions,
            tip_lamports=tip_lamports,
            payer=payer,
            recent_blockhash=recent_blockhash,
            endpoint=endpoint,
        )

        # Poll for confirmation (max 30 seconds)
        deadline = time.monotonic() + 30
        while time.monotonic() < deadline:
            status = check_bundle_status(bundle_id, endpoint)

            if status.get("confirmation_status") == "confirmed":
                return bundle_id

            if status.get("err"):
                # Bundle failed - if expired blockhash, retry
                if "BlockhashNotFound" in str(status.get("err", "")):
                    break  # retry with fresh blockhash
                else:
                    raise RuntimeError(f"Bundle failed: {status['err']}")

            time.sleep(0.4)  # Check every slot (~400ms)

        if attempt < max_retries - 1:
            print(f"Bundle {bundle_id} expired, retrying (attempt {attempt + 2}/{max_retries})")

    raise TimeoutError(f"Bundle did not land after {max_retries} attempts")

Jupiter: The Routing Layer

Jupiter Aggregator is the canonical routing engine for Solana DEX execution. When a market maker or searcher wants to swap tokens on-chain, they are almost certainly routing through Jupiter unless they have a specific reason to go direct to a DEX.

Jupiter’s V6 API routes a swap across all major Solana DEXs simultaneously - Orca, Raydium, Meteora, Lifinity, Phoenix, and dozens of smaller pools - finds the optimal split across multiple routes, and constructs the transaction automatically. For most use cases, Jupiter’s routing will beat direct DEX execution by 10-30 basis points on execution quality.

The V6 API workflow:

import httpx
from decimal import Decimal

JUPITER_API = "https://quote-api.jup.ag/v6"

def get_jupiter_quote(
    input_mint: str,
    output_mint: str,
    amount_ui: Decimal,
    input_decimals: int,
    slippage_bps: int = 50,  # 0.5% slippage tolerance
) -> dict:
    """
    Get a Jupiter quote for a swap.

    Args:
        input_mint: token mint address for input token
        output_mint: token mint address for output token
        amount_ui: human-readable amount (e.g., 1000.0 for 1000 USDC)
        input_decimals: decimal places for input token (USDC = 6, SOL = 9)
        slippage_bps: slippage tolerance in basis points

    Returns:
        Quote object with route plan, expected output, price impact
    """
    amount_raw = int(amount_ui * (10 ** input_decimals))

    params = {
        "inputMint": input_mint,
        "outputMint": output_mint,
        "amount": str(amount_raw),
        "slippageBps": slippage_bps,
        "onlyDirectRoutes": False,  # Allow multi-hop routes
        "asLegacyTransaction": False,  # Use versioned transactions (lower fees)
    }

    with httpx.Client(timeout=3.0) as client:
        response = client.get(f"{JUPITER_API}/quote", params=params)
        response.raise_for_status()
        return response.json()

def get_jupiter_swap_transaction(
    quote: dict,
    user_public_key: str,
    wrap_and_unwrap_sol: bool = True,
) -> str:
    """
    Get the swap transaction from Jupiter for a given quote.
    Returns base64-encoded transaction ready to sign and submit.
    """
    payload = {
        "quoteResponse": quote,
        "userPublicKey": user_public_key,
        "wrapAndUnwrapSol": wrap_and_unwrap_sol,
        "dynamicComputeUnitLimit": True,  # Let Jupiter estimate CU limit
        "prioritizationFeeLamports": "auto",  # Jupiter auto-sets priority fee
    }

    with httpx.Client(timeout=5.0) as client:
        response = client.post(
            f"{JUPITER_API}/swap",
            json=payload,
            headers={"Content-Type": "application/json"},
        )
        response.raise_for_status()
        return response.json()["swapTransaction"]

For trading infrastructure purposes, the key properties of Jupiter:

Price impact calculation - Jupiter’s quote includes priceImpactPct, the expected slippage from your trade size. Trades above 0.1% price impact warrant review; above 0.5% price impact, you should consider splitting into smaller transactions or using a different venue.

Route simulation - the quote endpoint simulates the entire route and accounts for pool fees at each hop. What you see in the quote is close to what you get on execution, assuming slippage stays within tolerance.

Compute unit estimation - Solana transactions have a compute unit (CU) limit. A multi-hop Jupiter swap can consume 200,000-600,000 CUs depending on the route complexity. Using dynamicComputeUnitLimit: true lets Jupiter set the CU limit accurately, which avoids CU-exhaustion reverts.

The Arbitrage Opportunity Stack

Where does the MEV come from on Solana in 2026? The main categories:

CEX-DEX arbitrage - the dominant strategy. When ETH or SOL price moves on Binance or OKX, Solana DEX prices lag by 100-500ms (the time for CEX price movement to be reflected in on-chain pool prices via arbitrageurs). A searcher with low-latency CEX connectivity and a Jito bundle pipeline can capture this price discrepancy before it is fully arbitraged away. Profitability scales with the size of the price move and the DEX liquidity depth.

Cross-DEX arbitrage - price discrepancies between Orca and Raydium for the same pair. These are smaller and faster to close than CEX-DEX arb, but volume is high and the opportunities are continuous. Jupiter’s routing actually makes cross-DEX arb easier to access since it surfaces the best quote across venues automatically.

Perps basis arb - funding rate differentials between Solana-native perpetuals platforms (Drift, Mango, Flash Trade) and CEX perps (Binance, Bybit). When Drift’s SOL-PERP funding rate diverges significantly from Binance’s, there is a carry trade available. This is slower-moving (funding rates change every 1-8 hours) but requires capital rather than speed.

Liquidation racing - DeFi lending protocols on Solana (Kamino, Marginfi, Save) issue liquidation bonuses (typically 5-10%) to whoever liquidates an undercollateralized position first. When asset prices drop sharply, the race to land liquidation transactions is intense. Jito bundles are essentially mandatory here - a liquidation submitted without a bundle has nearly zero chance against teams running bundles.

Latency Requirements and Infrastructure Placement

The MEV landscape on Solana is brutally latency-sensitive. To give concrete numbers:

  • A Jito bundle must reach the Block Engine before the current leader slot ends (~400ms window)
  • The Block Engine forwards bundles to the leader every ~50ms
  • Round-trip from your server to the Block Engine: 5-50ms depending on region and routing
  • Simulation of your triggering condition and bundle construction: 10-50ms

The budget math: if you see a triggering event (a large swap hitting an AMM, a liquidation becoming eligible), you have roughly 150-200ms to simulate your response, build the bundle, and get it to the Block Engine before the slot window closes. If you miss this slot, you need to compete in the next slot - by which point other searchers have also seen the opportunity.

This means:

  • Your server should be colocated with or very close to a Jito Block Engine node. The low-latency Block Engine endpoints are in Tokyo, NY, Amsterdam, and Frankfurt.
  • Your simulation layer must be pre-warmed with current chain state. Fetching account data on demand is too slow.
  • Your transaction construction must be templated - assembling a bundle from scratch takes too long. Keep pre-built template transactions and swap in the variable components (amounts, accounts) at runtime.

How This Breaks in Production

Tip account rotation - Jito rotates its canonical tip accounts periodically. If you hardcode tip account addresses rather than fetching them from the on-chain configuration, your tip transactions will land in unknown accounts (not Jito validators) and your bundles will be excluded. Fetch the current tip account list from the Block Engine’s configuration endpoint on startup and refresh it every 10 minutes.

Bundle size limits - 5 transactions per bundle is a hard limit. Complex arbitrage strategies that span multiple DEX pools can easily exceed this. If your arbitrage requires more than 4 trading transactions plus the tip, you must redesign the strategy to be more efficient or split it across multiple bundles (which breaks atomicity guarantees).

Blockhash staleness - a Solana blockhash is valid for 150 slots (~60 seconds). Under high load, the Block Engine queue can back up, and your bundle may sit in queue until the blockhash expires. Your submission code must detect BlockhashNotFound errors and resubmit with a fresh blockhash. The implementation I showed earlier handles this with a retry loop.

Jupiter V6 timeout under load - Jupiter’s quote API has rate limits on the public endpoint. Under high traffic conditions (market volatility), quote latency can spike to 500ms+ or timeouts occur. For production trading infrastructure, get a dedicated Jupiter API endpoint or run Jupiter’s open-source aggregator locally (the jupiter-core Rust library can be embedded).

Jito block engine regional failover - the mainnet Block Engine endpoint is load-balanced across regions, but a regional outage can increase latency significantly. Build a multi-endpoint submission strategy: submit to 2-3 regional endpoints in parallel and track which one is fastest for your server’s location. The first confirmation wins; cancel the others.

Non-Jito leader slots - 40% of validator stake is not running Jito-Solana. During a non-Jito leader slot, your bundle will not be processed. Your monitoring should track whether leader slots are Jito-enabled (validator identity is available from the cluster) and fall back to standard transaction submission for non-Jito slots if atomicity is not strictly required for your strategy.

Solana MEV infrastructure in 2026 rewards the teams who have internalized the system at the protocol level. Jupiter gives you the routing layer. Jito gives you the bundle layer. The edge is in how precisely you understand both, and how efficiently your stack moves between the triggering signal and the landed bundle.

Continue Reading

Enjoyed this?

Get one deep infrastructure insight per week.

Free forever. Unsubscribe anytime.

You're in. Check your inbox.