IntermediateMedium Severity

Front-Running & MEV Attacks

Understanding transaction ordering attacks, sandwich attacks, and how MEV (Maximal Extractable Value) impacts every user on the blockchain.

9 min readUpdated Jan 2025By Hexific Security Team
$600M+
MEV Extracted (2023)
~$1M
Daily Sandwich Attacks
90%+
Blocks Have MEV
Mitigable

What is MEV (Maximal Extractable Value)?

MEV refers to the maximum value that can be extracted from block production beyond the standard block reward and gas fees. This is done by including, excluding, or reordering transactions within a block.

🎯 Who Extracts MEV?

🤖 Searchers

Bots that scan the mempool for profitable opportunities and submit transactions to extract value.

🏗️ Builders

Entities that construct blocks by ordering transactions to maximize extracted value.

✅ Validators

Receive bids from builders and propose the most profitable block to the network.

The Dark Forest

The Ethereum mempool is often called "the dark forest" - a hostile environment where any profitable transaction can be detected and exploited by MEV bots within milliseconds.

How Front-Running Works

Front-running occurs when an attacker observes a pending transaction in the mempool and submits their own transaction with a higher gas price to get included before the victim's transaction.

1

Victim Submits Transaction

User submits a DEX swap for 10 ETH → USDC at market price

2

Attacker Detects in Mempool 👁️

MEV bot sees the pending tx and calculates profit opportunity

3

Front-Run Transaction 💥

Bot submits same trade with higher gas to execute first, moving the price

4

Victim Gets Worse Price

User's trade executes at a worse rate due to price impact

5

Attacker Profits

Bot may also back-run (sell after) to capture the price difference

Types of MEV Attacks

🥪

Sandwich Attacks

The attacker places a buy order before and a sell order after a victim's large trade, profiting from the price movement they cause.

Most CommonDEX Swaps

Liquidation Front-Running

Bots race to liquidate undercollateralized positions on lending protocols, earning liquidation bonuses (typically 5-10%).

High CompetitionLending
🔄

DEX Arbitrage

Exploiting price differences between DEXs by buying low on one and selling high on another within the same block.

Generally BeneficialPrice Efficiency
🎯

NFT Sniping

Front-running NFT mints or underpriced listings to acquire valuable assets before regular users.

NFT MarketsMints
⏮️

Back-Running

Placing a transaction immediately after a target transaction to profit from its effects (e.g., after oracle updates or large trades).

Oracle UpdatesState Changes

Time-Bandit Attacks

Re-mining past blocks to capture MEV that was already extracted. Rare but theoretically possible with sufficient hashpower.

TheoreticalConsensus Risk

Sandwich Attack Deep Dive

Sandwich attacks are the most common form of MEV extraction affecting regular users. Let's understand exactly how they work:

🥪 Anatomy of a Sandwich Attack

Block N, Tx 1:Attacker buys ETH (pushes price UP ⬆️)Front-run
Block N, Tx 2:Victim buys ETH at WORSE price 💸Target
Block N, Tx 3:Attacker sells ETH (profits from price ⬇️)Back-run
Attacker Profit = Price Impact × Trade Size - Gas Costs

Real Example: Uniswap Sandwich

sandwich-example.js
1// Victim wants to swap 10 ETH for USDC
2// Current price: 1 ETH = 2000 USDC
3// Slippage tolerance: 1%
4
5// === ATTACKER FRONT-RUN ===
6// Attacker buys 50 ETH with USDC
7// New price: 1 ETH = 2050 USDC (price pushed up)
8
9// === VICTIM TRANSACTION ===
10// Victim's 10 ETH swap executes
11// Expected: 20,000 USDC (at 2000)
12// Actual:   19,500 USDC (at 1950 effective due to combined impact)
13// Loss:     500 USDC (2.5% worse)
14
15// === ATTACKER BACK-RUN ===
16// Attacker sells 50 ETH for USDC
17// Receives: 102,500 USDC
18// Original: 100,000 USDC spent
19// Profit:   2,500 USDC - gas costs ≈ 2,400 USDC

Who Gets Sandwiched?

Anyone swapping on DEXs with loose slippage settings or trading large amounts relative to pool liquidity. Even "safe" 1% slippage can be exploited on low-liquidity pairs.

The MEV Ecosystem

Understanding the players and infrastructure helps you protect yourself:

Flashbots

A research organization that created infrastructure to democratize MEV extraction and reduce its negative externalities.

  • • MEV-Boost: PBS implementation for validators
  • • Flashbots Protect: RPC for sandwich protection
  • • MEV-Share: Redistributes MEV to users

🔒 Private Mempools

Submit transactions privately to avoid public mempool exposure.

  • • Flashbots Protect RPC
  • • MEV Blocker (by CoW Protocol)
  • • Blocknative Private Pool
  • • Eden Network

🏗️ Block Builders

Specialized entities that construct blocks to maximize value extraction.

  • • Flashbots Builder
  • • BloXroute
  • • Builder0x69
  • • Beaver Build

📊 MEV Dashboards

Track MEV extraction and analyze blockchain activity.

  • mevboost.pics
  • eigenphi.io
  • zeromev.org
  • flashbots-data.flashbots.net

Vulnerable Code Patterns

❌ Pattern 1: No Slippage Protection

VulnerableSwap.sol
1// 🚨 VULNERABLE: No minimum output specified
2contract VulnerableSwap {
3    IUniswapV2Router public router;
4    
5    function swapExactETHForTokens(address token) external payable {
6        address[] memory path = new address[](2);
7        path[0] = router.WETH();
8        path[1] = token;
9        
10        // amountOutMin = 0 means accept ANY output!
11        // Attacker can sandwich for maximum extraction
12        router.swapExactETHForTokens{value: msg.value}(
13            0,  // 💥 No slippage protection!
14            path,
15            msg.sender,
16            block.timestamp
17        );
18    }
19}

❌ Pattern 2: Public Commit Without Protection

VulnerableAuction.sol
1// 🚨 VULNERABLE: Reveals intent before execution
2contract VulnerableAuction {
3    mapping(address => uint256) public pendingBids;
4    
5    // Step 1: User reveals their max bid publicly
6    function commitBid(uint256 amount) external {
7        pendingBids[msg.sender] = amount;
8        // 💥 Anyone can see this in mempool and front-run!
9    }
10    
11    // Step 2: Execute bid
12    function executeBid() external {
13        uint256 bid = pendingBids[msg.sender];
14        // Attacker already front-ran with bid + 1
15        _processBid(msg.sender, bid);
16    }
17}

❌ Pattern 3: Predictable Liquidation Triggers

VulnerableLending.sol
1// 🚨 VULNERABLE: Predictable liquidation conditions
2contract VulnerableLending {
3    function liquidate(address user) external {
4        require(isUnderwater(user), "Not liquidatable");
5        
6        // Anyone can see when positions become liquidatable
7        // Bots race to call this function first
8        uint256 bonus = calculateBonus(user);
9        _executeLiquidation(user, msg.sender, bonus);
10    }
11    
12    // 💥 Problem: No mechanism to fairly distribute liquidations
13    // or prevent front-running of liquidation transactions
14}

Protection Strategies

🛡️

Private Transaction RPCs

Use Flashbots Protect, MEV Blocker, or other private mempools to hide transactions from searchers until they're included in a block.

📏

Tight Slippage Settings

Set appropriate slippage tolerance (0.1-0.5% for stables, 0.5-1% for volatile pairs). Reject transactions that exceed your tolerance.

🔐

Commit-Reveal Schemes

Hide transaction details with hashed commits, then reveal after inclusion. Prevents front-running by hiding intent.

🐄

Batch Auctions (CoW Protocol)

Use protocols that batch orders off-chain and settle at uniform clearing prices, eliminating ordering-based MEV.

⏱️

Time-Delayed Execution

Add mandatory delays between intent declaration and execution. Makes front-running less predictable and profitable.

💰

MEV-Share Rebates

Use MEV-Share to receive a portion of the MEV your transactions generate back as a rebate.

Using Flashbots Protect

The easiest way to protect yourself from sandwich attacks is to use Flashbots Protect RPC:

🔧 Setup Instructions

MetaMask
  1. Settings → Networks → Add Network
  2. Network Name: Flashbots Protect
  3. RPC URL: https://rpc.flashbots.net
  4. Chain ID: 1
  5. Symbol: ETH
With MEV-Share Rebates
https://rpc.flashbots.net?hint=hash&hint=logs&hint=default_logs&hint=special_logs&hint=calldata&hint=contract_address&hint=function_selector&builder=flashbots&builder=f1b.io

Programmatic Usage

flashbots-protect.ts
1// Using ethers.js with Flashbots Protect
2import { ethers } from 'ethers';
3
4// Connect to Flashbots Protect RPC
5const provider = new ethers.JsonRpcProvider(
6  'https://rpc.flashbots.net'
7);
8
9// Your transactions are now private!
10const wallet = new ethers.Wallet(privateKey, provider);
11
12// Regular transaction - but submitted privately
13const tx = await wallet.sendTransaction({
14  to: uniswapRouter,
15  data: swapCalldata,
16  value: ethers.parseEther("1"),
17  // Transactions go to Flashbots builders, not public mempool
18});
19
20// Wait for inclusion
21const receipt = await tx.wait();
22console.log("Protected swap executed:", receipt.hash);

MEV Blocker Alternative

CoW Protocol's MEV Blocker (rpc.mevblocker.io) offers similar protection with additional refund mechanisms. Try both and see which works better for your use case.

Contract-Level Defenses

✅ Pattern 1: Enforce Slippage in Contract

SecureSwap.sol
1// ✅ SECURE: Contract enforces slippage protection
2contract SecureSwap {
3    IUniswapV2Router public router;
4    
5    function swapWithProtection(
6        address tokenOut,
7        uint256 minAmountOut,  // User specifies minimum
8        uint256 deadline
9    ) external payable {
10        require(minAmountOut > 0, "Must specify min output");
11        require(deadline > block.timestamp, "Expired");
12        
13        address[] memory path = new address[](2);
14        path[0] = router.WETH();
15        path[1] = tokenOut;
16        
17        // ✅ Reverts if output is below user's minimum
18        router.swapExactETHForTokens{value: msg.value}(
19            minAmountOut,
20            path,
21            msg.sender,
22            deadline
23        );
24    }
25    
26    // Helper: Calculate minimum with slippage
27    function getMinOutput(
28        uint256 amountIn, 
29        address tokenOut,
30        uint256 slippageBps  // e.g., 50 = 0.5%
31    ) external view returns (uint256) {
32        address[] memory path = new address[](2);
33        path[0] = router.WETH();
34        path[1] = tokenOut;
35        
36        uint256[] memory amounts = router.getAmountsOut(amountIn, path);
37        uint256 expectedOut = amounts[1];
38        
39        // Apply slippage tolerance
40        return expectedOut * (10000 - slippageBps) / 10000;
41    }
42}

✅ Pattern 2: Commit-Reveal Scheme

SecureAuction.sol
1// ✅ SECURE: Two-phase commit-reveal auction
2contract SecureAuction {
3    struct Commit {
4        bytes32 hash;
5        uint256 commitBlock;
6    }
7    
8    mapping(address => Commit) public commits;
9    uint256 public constant REVEAL_DELAY = 10; // blocks
10    
11    // Phase 1: Commit hash of bid (hides amount)
12    function commitBid(bytes32 bidHash) external {
13        commits[msg.sender] = Commit({
14            hash: bidHash,
15            commitBlock: block.number
16        });
17        // Attacker can't see bid amount - only hash!
18    }
19    
20    // Phase 2: Reveal actual bid after delay
21    function revealBid(uint256 amount, bytes32 salt) external payable {
22        Commit memory commit = commits[msg.sender];
23        
24        // Verify delay has passed
25        require(
26            block.number >= commit.commitBlock + REVEAL_DELAY,
27            "Too early to reveal"
28        );
29        
30        // Verify hash matches
31        bytes32 expectedHash = keccak256(
32            abi.encodePacked(msg.sender, amount, salt)
33        );
34        require(commit.hash == expectedHash, "Invalid reveal");
35        require(msg.value == amount, "Amount mismatch");
36        
37        // Process bid - front-running is now pointless
38        _processBid(msg.sender, amount);
39        
40        delete commits[msg.sender];
41    }
42}

✅ Pattern 3: Fair Ordering with Chainlink

FairOrdered.sol
1// ✅ SECURE: Using Chainlink's Fair Sequencing Service (FSS)
2// Note: FSS is still in development - this shows the concept
3
4contract FairOrderedContract {
5    // Chainlink FSS will ensure fair transaction ordering
6    // based on received time, not gas price
7    
8    address public sequencer;
9    
10    modifier fairOrdered(bytes memory proof) {
11        // Verify transaction was ordered fairly by sequencer
12        require(
13            _verifySequencerProof(proof),
14            "Invalid sequencer proof"
15        );
16        _;
17    }
18    
19    function executeOrder(
20        bytes calldata orderData,
21        bytes calldata sequencerProof
22    ) external fairOrdered(sequencerProof) {
23        // Transaction order guaranteed by Chainlink FSS
24        // Front-running is not possible
25        _processOrder(orderData);
26    }
27}

Testing for Front-Running Vulnerabilities

FrontRunningTest.t.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.19;
3
4import "forge-std/Test.sol";
5import "../src/SecureSwap.sol";
6
7contract FrontRunningTest is Test {
8    SecureSwap swap;
9    address attacker = makeAddr("attacker");
10    address victim = makeAddr("victim");
11    
12    function setUp() public {
13        swap = new SecureSwap(address(router));
14        vm.deal(victim, 10 ether);
15        vm.deal(attacker, 100 ether);
16    }
17    
18    // ✅ Test: Slippage protection prevents sandwich
19    function test_SlippageProtectionPreventsLoss() public {
20        // Get expected output
21        uint256 minOut = swap.getMinOutput(1 ether, USDC, 50); // 0.5%
22        
23        // Attacker front-runs to move price
24        vm.prank(attacker);
25        router.swapExactETHForTokens{value: 50 ether}(
26            0, path, attacker, block.timestamp
27        );
28        
29        // Victim's transaction should revert due to slippage
30        vm.prank(victim);
31        vm.expectRevert("UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");
32        swap.swapWithProtection{value: 1 ether}(USDC, minOut, block.timestamp);
33    }
34    
35    // ✅ Test: Zero slippage is rejected
36    function test_RevertWhen_NoSlippageSet() public {
37        vm.prank(victim);
38        vm.expectRevert("Must specify min output");
39        swap.swapWithProtection{value: 1 ether}(USDC, 0, block.timestamp);
40    }
41    
42    // ✅ Test: Commit-reveal prevents front-running
43    function test_CommitRevealPreventsInformationLeak() public {
44        // Victim commits hidden bid
45        bytes32 salt = keccak256("secret");
46        bytes32 commitHash = keccak256(
47            abi.encodePacked(victim, 5 ether, salt)
48        );
49        
50        vm.prank(victim);
51        auction.commitBid(commitHash);
52        
53        // Attacker cannot determine bid amount from hash
54        // (Would need to brute force, impractical for large range)
55        
56        // Fast forward past reveal delay
57        vm.roll(block.number + 11);
58        
59        // Victim reveals
60        vm.prank(victim);
61        auction.revealBid{value: 5 ether}(5 ether, salt);
62    }
63    
64    // ✅ Fuzz: Slippage always enforced
65    function testFuzz_SlippageEnforced(
66        uint256 tradeAmount,
67        uint256 frontRunAmount
68    ) public {
69        tradeAmount = bound(tradeAmount, 0.1 ether, 5 ether);
70        frontRunAmount = bound(frontRunAmount, 1 ether, 50 ether);
71        
72        uint256 minOut = swap.getMinOutput(tradeAmount, USDC, 100); // 1%
73        
74        // Simulate front-run
75        vm.prank(attacker);
76        router.swapExactETHForTokens{value: frontRunAmount}(
77            0, path, attacker, block.timestamp
78        );
79        
80        vm.prank(victim);
81        // Either succeeds within slippage or reverts - never worse
82        try swap.swapWithProtection{value: tradeAmount}(
83            USDC, minOut, block.timestamp
84        ) {
85            uint256 received = IERC20(USDC).balanceOf(victim);
86            assertGe(received, minOut, "Received less than minimum");
87        } catch {
88            // Reverted due to slippage - this is expected protection
89        }
90    }
91}

🛠️ MEV Detection Tools

EigenPhi

Analyze MEV transactions and identify sandwich attacks on your protocol.

Flashbots Explorer

View MEV bundles and track searcher activity.

ZeroMEV

Free tool to check if your past transactions were sandwiched.

Tenderly

Simulate transactions to see MEV exposure before execution.

✅ MEV Protection Checklist

Using private RPC (Flashbots Protect / MEV Blocker)
Slippage protection enforced in contract
Appropriate slippage tolerance set (not 0)
Deadline parameter used to prevent stale txs
Commit-reveal for sensitive operations
No predictable profitable patterns in mempool
Consider batch auction protocols for swaps
Test with simulated front-running scenarios
Monitor for MEV extraction on your protocol
Educate users about MEV risks

📚 Quick Reference

Private RPCs

  • • rpc.flashbots.net
  • • rpc.mevblocker.io
  • • protect.eden.network

MEV-Resistant DEXs

  • • CoW Protocol (batch auction)
  • • 1inch Fusion
  • • Hashflow (RFQ)

Slippage Guidelines

  • • Stablecoins: 0.1-0.3%
  • • Major pairs: 0.5-1%
  • • Volatile: 1-3%