FABRKNT
Reth Expert — Production Engineering
Production Engineering
Lesson 9 of 25·CONTENT20 min45 XP

Treat this page as a workbench, not a blog post. The goal is to extract a reusable mental model from the source and carry it into the rest of the Fabrknt stack.

Course
Reth Expert — Production Engineering
Lesson role
CONTENT
Sequence
9 / 25

Lesson 9 — MEV in practice — mempool, ExEx, simulation

Question

MEV in production: watch mempool + simulate bundles + submit to builder. 3-component pipeline; ~1000 lines of Rust per searcher.

Principle (minimum model)

  • Mempool watcher. Subscribe to pending txs; parse for opportunities. Use Alloy.
  • Revm simulation. Fork mainnet; run candidate bundle through revm; compute profit. Use forked-anvil or CacheDB.
  • Bundle submitter. Sign + submit to Flashbots / mev-share / custom builder. Use eth_sendBundle.
  • Strategies. Liquidation (oracle-driven) + arbitrage (DEX-driven) + sandwich (mempool-driven). Each ~500-1000 lines.
  • Profit optimisation. Gas + builder tip + slippage. The math is the differentiator.
  • ExEx integration. Tempo's tidx + MEV searcher = combined on-chain + off-chain analysis. Production trend.
  • Risk. Front-runner could front-run you. Use private submission (Flashbots) to mitigate.
  • Production realities. ~$100M / year MEV captured across all searchers. Heavy competition; razor margins.

Worked example + steps

MEV in practice — mempool, ExEx, simulation

A pending tx hits the mempool. 80 milliseconds later, your bundle either landed or it didn't — beaten by a competing searcher who decoded the same tx 5ms faster, simulated the outcome 10ms faster, and submitted to the same builder 2ms ahead. MEV (Maximal Extractable Value — the profit a tx-orderer can pull out of pending transactions) is systems engineering meeting game theory at single-digit-millisecond timescales. This lesson is the shape of a serious 2026-era pipeline.

1. The pipeline

flowchart LR
    M[Mempool<br/>ExEx + devp2p] --> D[Decoder<br/>Alloy sol!]
    D --> S[Simulator<br/>Revm + DB]
    S --> St[Strategy<br/>Rust logic]
    St --> B[Bundle Builder<br/>Alloy encode]
    B --> Sub[Submit<br/>Flashbots / direct]

Each box is a Rust module. Latency budget for the whole loop in production: < 100 ms before the next block.

2. Mempool ingest

Two paths:

  • Mempool subscription via Alloy WebSocket (pending_transactions filter) — easy, slow
  • devp2p directly — you join the network, receive raw transaction announcements, parse RLP yourself — fastest, hardest

For a serious searcher, devp2p is non-negotiable. Reth exposes it via its networking crates.

3. Decoding — the real ExEx pattern

paradigmxyz/reth-exex-examples/op-bridge is a real production-shaped indexer that decodes contract events from every block. Here's the core decoding pattern, lifted verbatim:

use alloy_sol_types::{sol, SolEventInterface};

sol!(L1StandardBridge, "l1_standard_bridge_abi.json");
use crate::L1StandardBridge::{
    ETHBridgeFinalized, ETHBridgeInitiated, L1StandardBridgeEvents,
};

fn decode_chain_into_events(
    chain: &Chain,
) -> impl Iterator<Item = (...)> {
    chain
        .blocks_and_receipts()
        .flat_map(|(block, receipts)| {
            block.body().transactions_iter()
                .zip(receipts.iter())
                .map(move |(tx, receipt)| (block, tx, receipt))
        })
        .flat_map(|(block, tx, receipt)| {
            receipt.logs.iter()
                .filter(|log| OP_BRIDGES.contains(&log.address))
                .map(move |log| (block, tx, log))
        })
        .filter_map(|(block, tx, log)| {
            L1StandardBridgeEvents::decode_raw_log(log.topics(), &log.data.data)
                .ok()
                .map(|event| (block, tx, log, event))
        })
}

This is production-shape MEV decoding. Two flat_maps and a filter_map, stacked:

  1. chain.blocks_and_receipts() — every block in the committed chain, paired with its receipts
  2. For each (block, receipt) — zip transactions with their receipts and flatten
  3. For each (block, tx, receipt) — filter logs to known bridge addresses, then decode

The final filter_map is where sol! earns its place. L1StandardBridgeEvents::decode_raw_log is auto-generated; it tries each variant of the event enum and returns Ok(Event) on the matching topic0. No hand-rolled ABI parsing. Type-safe on the way out.

Then you pattern-match on the typed event:

match event {
    L1StandardBridgeEvents::ETHBridgeInitiated(ETHBridgeInitiated {
        amount, from, to, ..
    }) => {
        // Insert deposit into your DB
    }
    L1StandardBridgeEvents::ETHBridgeFinalized(ETHBridgeFinalized {
        amount, from, to, ..
    }) => {
        // Insert withdrawal into your DB
    }
    _ => continue,
}

For an MEV searcher, replace "bridge addresses" with "DEX router addresses" and the deposit/withdrawal handlers with "swap detection + sandwich opportunity scoring." Same shape, different filter set.

4. Simulation

Pre-trade, you simulate against a forked state with Revm. Real shape:

use revm::Evm;
use revm::primitives::{TxKind, U256};

let mut evm = Evm::builder()
    .with_db(forked_db)            // mainnet state at block N
    .with_external_context(())
    .build();

evm.cfg_mut().chain_id = 1;
evm.tx_mut().caller = bot_address;
evm.tx_mut().transact_to = TxKind::Call(target);
evm.tx_mut().data = tx_data;

let result = evm.transact()?;
let profit = compute_profit(&result.state);

A bundled simulation (your tx + the victim tx + your tx) tells you the realized profit before you pay gas. Hot path; profile aggressively. The forked_db is typically built on AlloyDB (which we saw in the Database trait lesson) plus an LRU cache layer so identical reads don't re-hit the network.

5. ExEx as a private mempool

ExEx receives every block at zero latency. That makes it the perfect place for:

  • A custom indexer of DEX trades
  • A "warm cache" of pool reserves so simulation doesn't have to re-fetch
  • A reorg-aware state diff feed

Your searcher consumes the ExEx feed and spends the saved time on simulation.

6. Bundle submission

Two main routes:

RouteLatencyPrivacy
Flashbots / MEV-sharemediumstrong (no public mempool)
Direct to a builderlowdepends on builder

Bundles are JSON-RPC; the wire format is small. Race between competing searchers is decided in single-digit milliseconds.

7. Things that will burn you

  1. Reorgs. Your bundle can land, then disappear. Always reconcile with reality after ChainReorged notifications in ExEx.
  2. Stale state in the simulator. Use the exact parent block of the slot you're targeting, not "latest."
  3. Gas griefing. Adversaries publish high-gas transactions just to push yours out. Pay attention to the priority fee curve in real time.
  4. Toxic flow. Some "opportunities" are sandwich bait. Run a classifier; not all profit is real.

Final check: your bundle landed in block 1000. The chain reorgs and block 1000 is replaced. Where is your money — your ETH, the victim's ETH, the gas you paid? Trace the P&L through the reorg. If you can't, you don't yet understand why the ChainReverted handler exists in ExEx — re-read the Intermediate ExEx lesson.

Summary (3 lines)

  • MEV pipeline = mempool watcher + revm simulation + bundle submitter. ~1000 lines of Rust per strategy.
  • Strategies: liquidation / arbitrage / sandwich. Profit math is the differentiator.
  • Risk: front-running mitigated via Flashbots private. ExEx + searcher integration = production trend. ~$100M/year captured.