[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-16-bundling-conflict-resolution-packing":3},{"article":4,"author":59},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":39,"related_articles":40},"d0000000-0000-0000-0000-000000000116","a0000000-0000-0000-0000-000000000006","Deep EVM #16: Bundling and Conflict Resolution — Packing Profitable Transactions","deep-evm-16-bundling-conflict-resolution-packing","Pack multiple arbitrage transactions into conflict-free bundles. Use bitmask conflict detection, tiered batching strategies, and submit via Flashbots, MEV-Share, and direct builder APIs.","## Why Bundling Matters\n\nA single arbitrage transaction is straightforward: simulate it, submit it, profit. But a production MEV bot finds dozens of profitable cycles per block. Each cycle produces one transaction. Submitting them individually is wasteful and risky:\n\n1. **Gas overhead:** Each transaction pays a 21,000 base gas cost. Bundling N swaps into one transaction pays the base cost once.\n2. **Atomicity:** If transaction A and transaction B both touch the same pool, A's execution changes the state that B depends on. Submitted separately, B may revert. Bundled together, you can order them correctly.\n3. **Builder preference:** Block builders prefer larger bundles that pay more total MEV — your bundle competes for inclusion against other searchers' bundles.\n\n## Conflict Detection: The Bitmask Approach\n\nTwo arbitrage cycles conflict if they share a pool. When cycle A swaps through Pool X, it changes Pool X's reserves. Cycle B, which also uses Pool X, was simulated against the old reserves and will produce a different (likely worse) outcome.\n\nWe detect conflicts using bitmasks. Assign each pool an index (0 to N-1). Each cycle gets a bitmask indicating which pools it touches:\n\n```rust\nuse bitvec::prelude::*;\n\n#[derive(Clone)]\npub struct CycleWithMask {\n    pub cycle: ProfitableCycle,\n    pub pool_mask: BitVec,\n}\n\npub fn build_pool_masks(\n    cycles: &[ProfitableCycle],\n    pool_index: &HashMap\u003CAddress, usize>,\n    total_pools: usize,\n) -> Vec\u003CCycleWithMask> {\n    cycles\n        .iter()\n        .map(|c| {\n            let mut mask = bitvec![0; total_pools];\n            for pool in &c.cycle.pools {\n                if let Some(&idx) = pool_index.get(&pool.address) {\n                    mask.set(idx, true);\n                }\n            }\n            CycleWithMask {\n                cycle: c.clone(),\n                pool_mask: mask,\n            }\n        })\n        .collect()\n}\n```\n\nTwo cycles conflict if their bitmasks have any bit in common:\n\n```rust\nfn conflicts(a: &BitVec, b: &BitVec) -> bool {\n    \u002F\u002F Bitwise AND: if any bit is set in both, they conflict\n    a.iter()\n        .zip(b.iter())\n        .any(|(x, y)| *x && *y)\n}\n```\n\nThis is O(N\u002F64) per comparison — each 64-bit word is checked with a single AND instruction. For 80,000 pools, the bitmask is 10 KB. Comparing two masks takes ~150 AND operations. This is orders of magnitude faster than iterating through pool address lists.\n\n## Greedy Bundle Packing\n\nGiven a set of profitable cycles, find the maximum-profit conflict-free subset. This is a weighted maximum independent set problem on a conflict graph — NP-hard in general. We use a greedy approximation:\n\n```rust\npub fn pack_bundle(\n    candidates: &mut Vec\u003CCycleWithMask>,\n) -> Vec\u003CCycleWithMask> {\n    \u002F\u002F Sort by profit descending\n    candidates.sort_by(|a, b| {\n        b.cycle.expected_profit.cmp(&a.cycle.expected_profit)\n    });\n\n    let total_pools = candidates\n        .first()\n        .map(|c| c.pool_mask.len())\n        .unwrap_or(0);\n\n    let mut bundle: Vec\u003CCycleWithMask> = Vec::new();\n    let mut used_pools = bitvec![0; total_pools];\n\n    for candidate in candidates.iter() {\n        \u002F\u002F Check if this candidate conflicts with any already-selected cycle\n        if conflicts(&candidate.pool_mask, &used_pools) {\n            continue;\n        }\n\n        \u002F\u002F No conflict — add to bundle\n        used_pools |= &candidate.pool_mask;\n        bundle.push(candidate.clone());\n    }\n\n    bundle\n}\n```\n\nThe greedy approach selects the most profitable cycle first, marks its pools as used, then selects the next most profitable non-conflicting cycle, and so on. It does not find the global optimum, but it runs in O(N * P\u002F64) time where N is the number of candidates and P is the number of pools. In practice, the greedy solution is within 5-10% of optimal.\n\n### When Greedy Fails\n\nConsider: Cycle A profits 1 ETH and touches pools {1, 2, 3}. Cycles B and C each profit 0.6 ETH and touch pools {1} and {2, 3} respectively. Greedy picks A (1 ETH). Optimal picks B + C (1.2 ETH).\n\nFor a more accurate solution, use branch-and-bound or dynamic programming on the conflict graph. But in production, the greedy approach is fast and good enough — the simulation estimates have uncertainty anyway.\n\n## Re-Simulation After Packing\n\nAfter selecting the bundle, re-simulate the entire sequence in order. The first cycle executes against the current state. The second cycle must be simulated against the state *after* the first cycle executes:\n\n```rust\npub fn validate_bundle(\n    simulator: &mut Simulator,\n    bundle: &[CycleWithMask],\n) -> Vec\u003CValidatedTx> {\n    let mut validated = Vec::new();\n\n    \u002F\u002F Snapshot the current DB state\n    let snapshot = simulator.snapshot();\n\n    for entry in bundle {\n        let calldata = build_execution_calldata(\n            &entry.cycle,\n            entry.cycle.optimal_input,\n        );\n\n        let result = simulator.simulate_swap(\n            bot_address(),\n            bot_contract(),\n            calldata.clone(),\n            U256::ZERO,\n        );\n\n        match result {\n            Ok(sim) if sim.success => {\n                \u002F\u002F Commit this transaction's state changes\n                \u002F\u002F (the CacheDB now reflects post-swap state)\n                validated.push(ValidatedTx {\n                    calldata,\n                    gas_used: sim.gas_used,\n                    expected_profit: entry.cycle.expected_profit,\n                });\n            }\n            _ => {\n                \u002F\u002F This cycle is no longer profitable after prior swaps\n                \u002F\u002F changed the state. Skip it.\n                tracing::debug!(\n                    \"Cycle {:?} failed during bundle validation\",\n                    entry.cycle.cycle.id\n                );\n            }\n        }\n    }\n\n    \u002F\u002F Restore snapshot (don't persist simulation state)\n    simulator.restore(snapshot);\n\n    validated\n}\n```\n\nCycles that were individually profitable may fail during bundle validation because an earlier cycle in the bundle altered the pool state they depend on. This is expected — the conflict detection is conservative (pools not in common are considered non-conflicting) but does not account for indirect effects like token balance changes.\n\n## Tiered Batching Strategies\n\nDifferent MEV opportunities have different urgency and competition levels. Use a tiered approach:\n\n### Tier 1: Event-Driven (Reactive)\n\nTriggered by mempool transactions or on-chain events. When you see a large swap in the mempool, immediately simulate back-run arbitrage opportunities. Latency is critical — you are competing with other searchers who see the same transaction.\n\n```rust\npub async fn on_pending_tx(\n    &mut self,\n    tx: Transaction,\n) {\n    \u002F\u002F Decode the transaction\n    let affected_pools = identify_affected_pools(&tx);\n    if affected_pools.is_empty() { return; }\n\n    \u002F\u002F Simulate state after this tx\n    let mut fork = self.simulator.fork();\n    fork.apply_transaction(&tx);\n\n    \u002F\u002F Find arbitrage in the post-tx state\n    let cycles = self.cycle_index.get_cycles_for_pools(&affected_pools);\n\n    for cycle in cycles {\n        if let Some(arb) = find_optimal_input(&mut fork, cycle, ...) {\n            self.submit_backrun(tx.hash, arb).await;\n        }\n    }\n}\n```\n\n### Tier 2: Block-Based (Rotation)\n\nAt the start of each block, re-evaluate all recently profitable cycles and a rotating subset of all cycles. This catches opportunities from state changes you did not explicitly observe (direct transfers, oracle updates, governance actions).\n\n### Tier 3: Top-N Scouting\n\nMaintain a \"top N\" list of highest-liquidity pools. Every K blocks, do a full DFS from scratch on the top-N pool subgraph to discover new cycles. This adapts to pool creation and removal.\n\n## Bundle Submission\n\n### Flashbots Bundles\n\nThe standard submission path. A bundle is an ordered list of signed transactions submitted to a Flashbots-compatible builder:\n\n```rust\nuse ethers_flashbots::{FlashbotsMiddleware, BundleRequest};\n\npub async fn submit_flashbots_bundle(\n    client: &FlashbotsMiddleware\u003CProvider\u003CHttp>, LocalWallet>,\n    transactions: Vec\u003CBytes>,\n    target_block: u64,\n) -> Result\u003CBundleHash> {\n    let mut bundle = BundleRequest::new();\n\n    for tx in transactions {\n        bundle = bundle.push_transaction(tx);\n    }\n\n    bundle = bundle\n        .set_block(target_block)\n        .set_simulation_block(target_block - 1)\n        .set_simulation_timestamp(0);  \u002F\u002F use block timestamp\n\n    let result = client\n        .send_bundle(&bundle)\n        .await?;\n\n    Ok(result.bundle_hash)\n}\n```\n\nFlashbots bundles have a key property: **all-or-nothing execution**. Either every transaction in the bundle succeeds, or none are included. This prevents partial execution where the first swap succeeds but the second reverts, leaving you with an intermediate token you did not want.\n\n### MEV-Share\n\nMEV-Share is Flashbots' protocol for redistributing MEV to users. When a user submits a transaction through Flashbots Protect, searchers can bid to back-run it. The user receives a share of the MEV.\n\nTo participate as a searcher:\n\n```rust\npub async fn submit_mev_share_bundle(\n    client: &MevShareClient,\n    user_tx_hash: H256,\n    backrun_tx: Bytes,\n    refund_percent: u64,  \u002F\u002F % of profit to refund to user\n) -> Result\u003C()> {\n    let bundle = MevShareBundle {\n        inclusion: Inclusion {\n            block: target_block,\n            max_block: target_block + 2,\n        },\n        body: vec![\n            BundleItem::HashItem { hash: user_tx_hash },\n            BundleItem::TxItem {\n                tx: backrun_tx,\n                can_revert: false,\n            },\n        ],\n        validity: Validity {\n            refund: vec![Refund {\n                body_idx: 0,\n                percent: refund_percent,\n            }],\n        },\n    };\n\n    client.send_bundle(bundle).await?;\n    Ok(())\n}\n```\n\nMEV-Share bundles reference the user's transaction by hash. The builder places your back-run immediately after the user's transaction.\n\n### Direct Builder APIs\n\nSome builders accept bundles directly, bypassing Flashbots:\n\n- **Titan Builder:** `https:\u002F\u002Frpc.titanbuilder.xyz`\n- **BeaverBuild:** `https:\u002F\u002Frpc.beaverbuild.org`\n- **Rsync Builder:** `https:\u002F\u002Frsync-builder.xyz`\n\n```rust\npub async fn submit_to_builders(\n    bundles: &[SignedBundle],\n    builders: &[BuilderEndpoint],\n) {\n    \u002F\u002F Submit to ALL builders simultaneously\n    let futures: Vec\u003C_> = builders\n        .iter()\n        .map(|builder| {\n            submit_bundle_to_builder(builder, bundles)\n        })\n        .collect();\n\n    let results = futures::future::join_all(futures).await;\n\n    for (builder, result) in builders.iter().zip(results) {\n        match result {\n            Ok(_) => tracing::info!(\"Submitted to {}\", builder.name),\n            Err(e) => tracing::warn!(\"Failed {}: {}\", builder.name, e),\n        }\n    }\n}\n```\n\nAlways submit to multiple builders. You are competing for block inclusion — the more builders see your bundle, the higher the probability it gets included.\n\n## Coinbase Transfer: Paying the Builder\n\nMEV bundles do not pay for inclusion via gas price. Instead, the last transaction in the bundle sends a direct ETH transfer to `block.coinbase` (the builder's fee recipient):\n\n```huff\n#define macro PAY_COINBASE() = takes(1) returns(0) {\n    \u002F\u002F takes: [payment_amount]\n    0x00 0x00 0x00 0x00     \u002F\u002F retSize, retOff, argSize, argOff\n    swap4                   \u002F\u002F [amount, 0, 0, 0, 0]\n    coinbase                \u002F\u002F [coinbase, amount, 0, 0, 0, 0]\n    gas call                \u002F\u002F [success]\n    pop\n}\n```\n\nThe payment amount is typically 90-99% of the expected profit. The searcher keeps 1-10% as their margin. Higher payments increase inclusion probability but reduce profit. This is a constant game-theoretic balance.\n\n## End-to-End Flow\n\nPutting it all together, here is the per-block flow of a production MEV bot:\n\n```\n1. New block arrives (t=0)\n2. Update pool reserves from events (t=0-500ms)\n3. Identify affected cycles via cycle index (t=500ms-1s)\n4. Simulate affected cycles + rotating batch (t=1-4s)\n   - Binary search for optimal inputs\n   - Deadline-aware: highest profit first\n5. Build bitmasks, pack conflict-free bundle (t=4-5s)\n6. Re-simulate bundle in sequence (t=5-6s)\n7. Sign transactions, compute coinbase payment (t=6-7s)\n8. Submit to Flashbots + direct builders (t=7-8s)\n9. Wait for inclusion (t=8-12s)\n10. Log results, update profit tracking (t=12s)\n```\n\nThe entire pipeline runs within the 12-second block time. Every millisecond counts.\n\n## Monitoring and Observability\n\nProduction MEV bots need comprehensive monitoring:\n\n```rust\n#[derive(Default)]\npub struct BlockMetrics {\n    pub cycles_evaluated: u64,\n    pub cycles_profitable: u64,\n    pub bundles_submitted: u64,\n    pub bundles_included: u64,\n    pub total_profit_wei: U256,\n    pub total_gas_cost_wei: U256,\n    pub simulation_time_ms: u64,\n    pub deadline_expired: bool,\n}\n```\n\nTrack inclusion rate (bundles_included \u002F bundles_submitted), average profit per included bundle, and deadline misses. A healthy bot has >20% inclusion rate and \u003C5% deadline misses.\n\n## Summary\n\nBundling transforms individual arbitrage transactions into optimized, conflict-free packages that maximize profit per block. Bitmask conflict detection runs in microseconds. Greedy packing finds near-optimal bundles. Re-simulation validates the bundle against realistic state. Multi-builder submission maximizes inclusion probability. This completes our Deep EVM series — from raw opcodes in Huff to production MEV infrastructure. The stack: Huff contracts for on-chain execution, Rust for off-chain simulation and bundling, and the Flashbots ecosystem for fair block inclusion.","\u003Ch2 id=\"why-bundling-matters\">Why Bundling Matters\u003C\u002Fh2>\n\u003Cp>A single arbitrage transaction is straightforward: simulate it, submit it, profit. But a production MEV bot finds dozens of profitable cycles per block. Each cycle produces one transaction. Submitting them individually is wasteful and risky:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Gas overhead:\u003C\u002Fstrong> Each transaction pays a 21,000 base gas cost. Bundling N swaps into one transaction pays the base cost once.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Atomicity:\u003C\u002Fstrong> If transaction A and transaction B both touch the same pool, A’s execution changes the state that B depends on. Submitted separately, B may revert. Bundled together, you can order them correctly.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Builder preference:\u003C\u002Fstrong> Block builders prefer larger bundles that pay more total MEV — your bundle competes for inclusion against other searchers’ bundles.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"conflict-detection-the-bitmask-approach\">Conflict Detection: The Bitmask Approach\u003C\u002Fh2>\n\u003Cp>Two arbitrage cycles conflict if they share a pool. When cycle A swaps through Pool X, it changes Pool X’s reserves. Cycle B, which also uses Pool X, was simulated against the old reserves and will produce a different (likely worse) outcome.\u003C\u002Fp>\n\u003Cp>We detect conflicts using bitmasks. Assign each pool an index (0 to N-1). Each cycle gets a bitmask indicating which pools it touches:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use bitvec::prelude::*;\n\n#[derive(Clone)]\npub struct CycleWithMask {\n    pub cycle: ProfitableCycle,\n    pub pool_mask: BitVec,\n}\n\npub fn build_pool_masks(\n    cycles: &amp;[ProfitableCycle],\n    pool_index: &amp;HashMap&lt;Address, usize&gt;,\n    total_pools: usize,\n) -&gt; Vec&lt;CycleWithMask&gt; {\n    cycles\n        .iter()\n        .map(|c| {\n            let mut mask = bitvec![0; total_pools];\n            for pool in &amp;c.cycle.pools {\n                if let Some(&amp;idx) = pool_index.get(&amp;pool.address) {\n                    mask.set(idx, true);\n                }\n            }\n            CycleWithMask {\n                cycle: c.clone(),\n                pool_mask: mask,\n            }\n        })\n        .collect()\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Two cycles conflict if their bitmasks have any bit in common:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn conflicts(a: &amp;BitVec, b: &amp;BitVec) -&gt; bool {\n    \u002F\u002F Bitwise AND: if any bit is set in both, they conflict\n    a.iter()\n        .zip(b.iter())\n        .any(|(x, y)| *x &amp;&amp; *y)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This is O(N\u002F64) per comparison — each 64-bit word is checked with a single AND instruction. For 80,000 pools, the bitmask is 10 KB. Comparing two masks takes ~150 AND operations. This is orders of magnitude faster than iterating through pool address lists.\u003C\u002Fp>\n\u003Ch2 id=\"greedy-bundle-packing\">Greedy Bundle Packing\u003C\u002Fh2>\n\u003Cp>Given a set of profitable cycles, find the maximum-profit conflict-free subset. This is a weighted maximum independent set problem on a conflict graph — NP-hard in general. We use a greedy approximation:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn pack_bundle(\n    candidates: &amp;mut Vec&lt;CycleWithMask&gt;,\n) -&gt; Vec&lt;CycleWithMask&gt; {\n    \u002F\u002F Sort by profit descending\n    candidates.sort_by(|a, b| {\n        b.cycle.expected_profit.cmp(&amp;a.cycle.expected_profit)\n    });\n\n    let total_pools = candidates\n        .first()\n        .map(|c| c.pool_mask.len())\n        .unwrap_or(0);\n\n    let mut bundle: Vec&lt;CycleWithMask&gt; = Vec::new();\n    let mut used_pools = bitvec![0; total_pools];\n\n    for candidate in candidates.iter() {\n        \u002F\u002F Check if this candidate conflicts with any already-selected cycle\n        if conflicts(&amp;candidate.pool_mask, &amp;used_pools) {\n            continue;\n        }\n\n        \u002F\u002F No conflict — add to bundle\n        used_pools |= &amp;candidate.pool_mask;\n        bundle.push(candidate.clone());\n    }\n\n    bundle\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The greedy approach selects the most profitable cycle first, marks its pools as used, then selects the next most profitable non-conflicting cycle, and so on. It does not find the global optimum, but it runs in O(N * P\u002F64) time where N is the number of candidates and P is the number of pools. In practice, the greedy solution is within 5-10% of optimal.\u003C\u002Fp>\n\u003Ch3>When Greedy Fails\u003C\u002Fh3>\n\u003Cp>Consider: Cycle A profits 1 ETH and touches pools {1, 2, 3}. Cycles B and C each profit 0.6 ETH and touch pools {1} and {2, 3} respectively. Greedy picks A (1 ETH). Optimal picks B + C (1.2 ETH).\u003C\u002Fp>\n\u003Cp>For a more accurate solution, use branch-and-bound or dynamic programming on the conflict graph. But in production, the greedy approach is fast and good enough — the simulation estimates have uncertainty anyway.\u003C\u002Fp>\n\u003Ch2 id=\"re-simulation-after-packing\">Re-Simulation After Packing\u003C\u002Fh2>\n\u003Cp>After selecting the bundle, re-simulate the entire sequence in order. The first cycle executes against the current state. The second cycle must be simulated against the state \u003Cem>after\u003C\u002Fem> the first cycle executes:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn validate_bundle(\n    simulator: &amp;mut Simulator,\n    bundle: &amp;[CycleWithMask],\n) -&gt; Vec&lt;ValidatedTx&gt; {\n    let mut validated = Vec::new();\n\n    \u002F\u002F Snapshot the current DB state\n    let snapshot = simulator.snapshot();\n\n    for entry in bundle {\n        let calldata = build_execution_calldata(\n            &amp;entry.cycle,\n            entry.cycle.optimal_input,\n        );\n\n        let result = simulator.simulate_swap(\n            bot_address(),\n            bot_contract(),\n            calldata.clone(),\n            U256::ZERO,\n        );\n\n        match result {\n            Ok(sim) if sim.success =&gt; {\n                \u002F\u002F Commit this transaction's state changes\n                \u002F\u002F (the CacheDB now reflects post-swap state)\n                validated.push(ValidatedTx {\n                    calldata,\n                    gas_used: sim.gas_used,\n                    expected_profit: entry.cycle.expected_profit,\n                });\n            }\n            _ =&gt; {\n                \u002F\u002F This cycle is no longer profitable after prior swaps\n                \u002F\u002F changed the state. Skip it.\n                tracing::debug!(\n                    \"Cycle {:?} failed during bundle validation\",\n                    entry.cycle.cycle.id\n                );\n            }\n        }\n    }\n\n    \u002F\u002F Restore snapshot (don't persist simulation state)\n    simulator.restore(snapshot);\n\n    validated\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cycles that were individually profitable may fail during bundle validation because an earlier cycle in the bundle altered the pool state they depend on. This is expected — the conflict detection is conservative (pools not in common are considered non-conflicting) but does not account for indirect effects like token balance changes.\u003C\u002Fp>\n\u003Ch2 id=\"tiered-batching-strategies\">Tiered Batching Strategies\u003C\u002Fh2>\n\u003Cp>Different MEV opportunities have different urgency and competition levels. Use a tiered approach:\u003C\u002Fp>\n\u003Ch3>Tier 1: Event-Driven (Reactive)\u003C\u002Fh3>\n\u003Cp>Triggered by mempool transactions or on-chain events. When you see a large swap in the mempool, immediately simulate back-run arbitrage opportunities. Latency is critical — you are competing with other searchers who see the same transaction.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub async fn on_pending_tx(\n    &amp;mut self,\n    tx: Transaction,\n) {\n    \u002F\u002F Decode the transaction\n    let affected_pools = identify_affected_pools(&amp;tx);\n    if affected_pools.is_empty() { return; }\n\n    \u002F\u002F Simulate state after this tx\n    let mut fork = self.simulator.fork();\n    fork.apply_transaction(&amp;tx);\n\n    \u002F\u002F Find arbitrage in the post-tx state\n    let cycles = self.cycle_index.get_cycles_for_pools(&amp;affected_pools);\n\n    for cycle in cycles {\n        if let Some(arb) = find_optimal_input(&amp;mut fork, cycle, ...) {\n            self.submit_backrun(tx.hash, arb).await;\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Tier 2: Block-Based (Rotation)\u003C\u002Fh3>\n\u003Cp>At the start of each block, re-evaluate all recently profitable cycles and a rotating subset of all cycles. This catches opportunities from state changes you did not explicitly observe (direct transfers, oracle updates, governance actions).\u003C\u002Fp>\n\u003Ch3>Tier 3: Top-N Scouting\u003C\u002Fh3>\n\u003Cp>Maintain a “top N” list of highest-liquidity pools. Every K blocks, do a full DFS from scratch on the top-N pool subgraph to discover new cycles. This adapts to pool creation and removal.\u003C\u002Fp>\n\u003Ch2 id=\"bundle-submission\">Bundle Submission\u003C\u002Fh2>\n\u003Ch3>Flashbots Bundles\u003C\u002Fh3>\n\u003Cp>The standard submission path. A bundle is an ordered list of signed transactions submitted to a Flashbots-compatible builder:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use ethers_flashbots::{FlashbotsMiddleware, BundleRequest};\n\npub async fn submit_flashbots_bundle(\n    client: &amp;FlashbotsMiddleware&lt;Provider&lt;Http&gt;, LocalWallet&gt;,\n    transactions: Vec&lt;Bytes&gt;,\n    target_block: u64,\n) -&gt; Result&lt;BundleHash&gt; {\n    let mut bundle = BundleRequest::new();\n\n    for tx in transactions {\n        bundle = bundle.push_transaction(tx);\n    }\n\n    bundle = bundle\n        .set_block(target_block)\n        .set_simulation_block(target_block - 1)\n        .set_simulation_timestamp(0);  \u002F\u002F use block timestamp\n\n    let result = client\n        .send_bundle(&amp;bundle)\n        .await?;\n\n    Ok(result.bundle_hash)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Flashbots bundles have a key property: \u003Cstrong>all-or-nothing execution\u003C\u002Fstrong>. Either every transaction in the bundle succeeds, or none are included. This prevents partial execution where the first swap succeeds but the second reverts, leaving you with an intermediate token you did not want.\u003C\u002Fp>\n\u003Ch3>MEV-Share\u003C\u002Fh3>\n\u003Cp>MEV-Share is Flashbots’ protocol for redistributing MEV to users. When a user submits a transaction through Flashbots Protect, searchers can bid to back-run it. The user receives a share of the MEV.\u003C\u002Fp>\n\u003Cp>To participate as a searcher:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub async fn submit_mev_share_bundle(\n    client: &amp;MevShareClient,\n    user_tx_hash: H256,\n    backrun_tx: Bytes,\n    refund_percent: u64,  \u002F\u002F % of profit to refund to user\n) -&gt; Result&lt;()&gt; {\n    let bundle = MevShareBundle {\n        inclusion: Inclusion {\n            block: target_block,\n            max_block: target_block + 2,\n        },\n        body: vec![\n            BundleItem::HashItem { hash: user_tx_hash },\n            BundleItem::TxItem {\n                tx: backrun_tx,\n                can_revert: false,\n            },\n        ],\n        validity: Validity {\n            refund: vec![Refund {\n                body_idx: 0,\n                percent: refund_percent,\n            }],\n        },\n    };\n\n    client.send_bundle(bundle).await?;\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>MEV-Share bundles reference the user’s transaction by hash. The builder places your back-run immediately after the user’s transaction.\u003C\u002Fp>\n\u003Ch3>Direct Builder APIs\u003C\u002Fh3>\n\u003Cp>Some builders accept bundles directly, bypassing Flashbots:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Titan Builder:\u003C\u002Fstrong> \u003Ccode>https:\u002F\u002Frpc.titanbuilder.xyz\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>BeaverBuild:\u003C\u002Fstrong> \u003Ccode>https:\u002F\u002Frpc.beaverbuild.org\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Rsync Builder:\u003C\u002Fstrong> \u003Ccode>https:\u002F\u002Frsync-builder.xyz\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub async fn submit_to_builders(\n    bundles: &amp;[SignedBundle],\n    builders: &amp;[BuilderEndpoint],\n) {\n    \u002F\u002F Submit to ALL builders simultaneously\n    let futures: Vec&lt;_&gt; = builders\n        .iter()\n        .map(|builder| {\n            submit_bundle_to_builder(builder, bundles)\n        })\n        .collect();\n\n    let results = futures::future::join_all(futures).await;\n\n    for (builder, result) in builders.iter().zip(results) {\n        match result {\n            Ok(_) =&gt; tracing::info!(\"Submitted to {}\", builder.name),\n            Err(e) =&gt; tracing::warn!(\"Failed {}: {}\", builder.name, e),\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Always submit to multiple builders. You are competing for block inclusion — the more builders see your bundle, the higher the probability it gets included.\u003C\u002Fp>\n\u003Ch2 id=\"coinbase-transfer-paying-the-builder\">Coinbase Transfer: Paying the Builder\u003C\u002Fh2>\n\u003Cp>MEV bundles do not pay for inclusion via gas price. Instead, the last transaction in the bundle sends a direct ETH transfer to \u003Ccode>block.coinbase\u003C\u002Fcode> (the builder’s fee recipient):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">#define macro PAY_COINBASE() = takes(1) returns(0) {\n    \u002F\u002F takes: [payment_amount]\n    0x00 0x00 0x00 0x00     \u002F\u002F retSize, retOff, argSize, argOff\n    swap4                   \u002F\u002F [amount, 0, 0, 0, 0]\n    coinbase                \u002F\u002F [coinbase, amount, 0, 0, 0, 0]\n    gas call                \u002F\u002F [success]\n    pop\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The payment amount is typically 90-99% of the expected profit. The searcher keeps 1-10% as their margin. Higher payments increase inclusion probability but reduce profit. This is a constant game-theoretic balance.\u003C\u002Fp>\n\u003Ch2 id=\"end-to-end-flow\">End-to-End Flow\u003C\u002Fh2>\n\u003Cp>Putting it all together, here is the per-block flow of a production MEV bot:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>1. New block arrives (t=0)\n2. Update pool reserves from events (t=0-500ms)\n3. Identify affected cycles via cycle index (t=500ms-1s)\n4. Simulate affected cycles + rotating batch (t=1-4s)\n   - Binary search for optimal inputs\n   - Deadline-aware: highest profit first\n5. Build bitmasks, pack conflict-free bundle (t=4-5s)\n6. Re-simulate bundle in sequence (t=5-6s)\n7. Sign transactions, compute coinbase payment (t=6-7s)\n8. Submit to Flashbots + direct builders (t=7-8s)\n9. Wait for inclusion (t=8-12s)\n10. Log results, update profit tracking (t=12s)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The entire pipeline runs within the 12-second block time. Every millisecond counts.\u003C\u002Fp>\n\u003Ch2 id=\"monitoring-and-observability\">Monitoring and Observability\u003C\u002Fh2>\n\u003Cp>Production MEV bots need comprehensive monitoring:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">#[derive(Default)]\npub struct BlockMetrics {\n    pub cycles_evaluated: u64,\n    pub cycles_profitable: u64,\n    pub bundles_submitted: u64,\n    pub bundles_included: u64,\n    pub total_profit_wei: U256,\n    pub total_gas_cost_wei: U256,\n    pub simulation_time_ms: u64,\n    pub deadline_expired: bool,\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Track inclusion rate (bundles_included \u002F bundles_submitted), average profit per included bundle, and deadline misses. A healthy bot has &gt;20% inclusion rate and &lt;5% deadline misses.\u003C\u002Fp>\n\u003Ch2 id=\"summary\">Summary\u003C\u002Fh2>\n\u003Cp>Bundling transforms individual arbitrage transactions into optimized, conflict-free packages that maximize profit per block. Bitmask conflict detection runs in microseconds. Greedy packing finds near-optimal bundles. Re-simulation validates the bundle against realistic state. Multi-builder submission maximizes inclusion probability. This completes our Deep EVM series — from raw opcodes in Huff to production MEV infrastructure. The stack: Huff contracts for on-chain execution, Rust for off-chain simulation and bundling, and the Flashbots ecosystem for fair block inclusion.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:22.936601Z","Deep EVM #16: Bundling and Conflict Resolution — Packing MEV Transactions","Pack multiple MEV arbitrage transactions into conflict-free bundles using bitmask detection, greedy packing, and submission via Flashbots and direct builder APIs.","mev bundling conflict resolution",null,"index, follow",[22,27,31,35],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000019","MEV","mev","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000013","Security","security",{"id":36,"name":37,"slug":38,"created_at":26},"c0000000-0000-0000-0000-000000000009","Web3","web3","Engineering",[41,47,53],{"id":42,"title":43,"slug":44,"excerpt":45,"locale":12,"category_name":39,"published_at":46},"d0200000-0000-0000-0000-000000000003","Why Bali Is Becoming Southeast Asia's Impact-Tech Hub in 2026","why-bali-becoming-southeast-asia-impact-tech-hub-2026","Bali ranks #16 among Southeast Asian startup ecosystems. With a growing concentration of Web3 builders, AI sustainability startups, and eco-travel tech companies, the island is carving a niche as the region's impact-tech capital.","2026-03-28T10:44:37.748283Z",{"id":48,"title":49,"slug":50,"excerpt":51,"locale":12,"category_name":39,"published_at":52},"d0200000-0000-0000-0000-000000000002","ASEAN Data Protection Patchwork: A Developer's Compliance Checklist","asean-data-protection-patchwork-developer-compliance-checklist","Seven ASEAN countries now have comprehensive data protection laws, each with different consent models, localization requirements, and penalty structures. Here is a practical compliance checklist for developers building multi-country applications.","2026-03-28T10:44:37.374741Z",{"id":54,"title":55,"slug":56,"excerpt":57,"locale":12,"category_name":39,"published_at":58},"d0200000-0000-0000-0000-000000000001","Indonesia's $29 Billion Digital Transformation: Opportunities for Software Companies","indonesia-29-billion-digital-transformation-opportunities-software-companies","Indonesia's IT services market is projected to reach $29.03 billion in 2026, up from $24.37 billion in 2025. Cloud infrastructure, AI, e-commerce, and data centers are driving the fastest growth in Southeast Asia.","2026-03-28T10:44:37.349311Z",{"id":13,"name":60,"slug":61,"bio":62,"photo_url":19,"linkedin":19,"role":63,"created_at":64,"updated_at":64},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]