Development of MEV Attack Detection System
MEV (Maximal Extractable Value) is the value that validators and searchers extract by reordering transactions within a block. Technically, this isn't always "attack"—some MEV is harmless (arbitrage between DEXs restores price equilibrium). But sandwich attacks, liquidation front-running, and time-bandit attacks directly harm users and protocols. MEV detection system solves two tasks: post-hoc analysis (what happened to user) and proactive protection (preventing specific attack types).
Typology of MEV Attacks
Sandwich Attack
Classic and most common type. Scheme:
- Victim sends swap to DEX with large slippage tolerance
- Attacker sees transaction in mempool
- Inserts buy BEFORE victim (front-run), raising price
- Victim executes at higher price
- Attacker sells AFTER victim (back-run), locking profit
Block identifier: three consecutive transactions with characteristic pattern.
Liquidation Front-Running
Position on lending protocol (Aave, Compound) becomes liquidatable. Multiple bots see this and race for liquidation reward. First wins—others wasted gas. Not attack on user but creates waste and MEV race.
More aggressive version: attacker manipulates price oracle (via flash loan in pool used as oracle) to force position under liquidation.
Arbitrage MEV
Technically not attack: if ETH price on Uniswap v3 differs from Curve by 0.3%—arbitrage bot equalizes price, getting profit. This benefits market. Nevertheless, protocols need understanding arbitrage flows.
JIT (Just-in-Time) Liquidity
Specific to Uniswap v3. Attacker sees large swap in mempool, adds concentrated liquidity exactly in right range before swap, collects fees, removes liquidity after. Honest LP loses fees. Debatable form of MEV—technically allowed by protocol.
Time-Bandit Attack
Reorg attack: if MEV in few blocks ago exceeds cost of chain reorganization—attacking validator can try rewriting history. On Ethereum post-Merge extremely difficult and expensive, but remains theoretical threat for PoS chains with small stake.
On-Chain Sandwich Detection
Identification Algorithm
Sandwich—three transactions in one block with specific relationships:
from dataclasses import dataclass
from typing import Optional
import pandas as pd
@dataclass
class SwapEvent:
tx_hash: str
block_number: int
tx_index: int # position in block
sender: str
token_in: str
token_out: str
amount_in: int
amount_out: int
pool: str
def detect_sandwiches(swaps_in_block: list[SwapEvent]) -> list[dict]:
sandwiches = []
# Group by pool
by_pool = {}
for swap in swaps_in_block:
by_pool.setdefault(swap.pool, []).append(swap)
for pool, pool_swaps in by_pool.items():
# Sort by position in block
pool_swaps.sort(key=lambda s: s.tx_index)
# Find pattern: A buys → victim buys → A sells (same token_in/out)
for i, front in enumerate(pool_swaps[:-2]):
victim = pool_swaps[i + 1]
back = pool_swaps[i + 2]
is_sandwich = (
front.sender == back.sender and # one attacker
front.sender != victim.sender and # victim different
front.token_in == victim.token_in and # same direction
back.token_in == front.token_out and # reversal
back.token_out == front.token_in
)
if is_sandwich:
# Calculate attacker profit
attacker_profit = (
back.amount_out - front.amount_in
)
# Calculate victim damage: execution price difference
victim_price = victim.amount_out / victim.amount_in
# fair_price need to calculate without front-run transaction (simulation)
sandwiches.append({
'front_tx': front.tx_hash,
'victim_tx': victim.tx_hash,
'back_tx': back.tx_hash,
'attacker': front.sender,
'pool': pool,
'attacker_profit_tokens': attacker_profit,
'block': front.block_number,
})
return sandwiches
Measuring Victim Damage
For accurate damage calculation, simulate how victim's transaction would execute without front-run. Requires archive node access and replay with state override:
async def simulate_swap_without_frontrun(
victim_tx: dict,
frontrun_tx: dict,
block_number: int,
rpc_url: str
) -> dict:
"""
Simulates victim_tx on state BEFORE frontrun_tx
"""
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(rpc_url))
# Get state at previous block
prev_block_state = block_number - 1
# eth_call with block_number = prev_block (without frontrun)
result = await w3.eth.call(
{
'from': victim_tx['from'],
'to': victim_tx['to'],
'data': victim_tx['input'],
'gas': victim_tx['gas'],
},
prev_block_state
)
return {'simulated_output': result}
Difference between actual amount_out and simulated—MEV extracted from victim.
Detection via Mempool Monitoring
Flashbots MEV-Share and MEV-Boost Data
Flashbots publishes MEV-Boost data via API: https://boost-relay.flashbots.net/relay/v1/data/bidtraces/proposer_payload_delivered. Historical data about blocks passed through MEV-Boost with MEV-bid information.
More detailed data—via Flashbots MEV-Share API: information about bundle content published post-execution.
Custom Mempool Observer
For real-time: connect to mempool via eth_subscribe("pendingTransactions"):
import { createPublicClient, webSocket, parseAbi, decodeEventLog } from 'viem';
const client = createPublicClient({
transport: webSocket('wss://mainnet.infura.io/ws/v3/KEY'),
});
// Subscribe to pending transactions
const unwatch = client.watchPendingTransactions({
onTransactions: async (hashes) => {
for (const hash of hashes) {
const tx = await client.getTransaction({ hash });
if (tx && isLargeSwap(tx)) {
// This is transaction with large slippage - possible victim
await analyzeMevRisk(tx);
}
}
},
});
Problem: most MEV bots now use private mempools (Flashbots bundles, MEV Blocker). Public mempool sees only partial picture.
Protective Mechanisms for Protocols
Commit-Reveal for Sensitive Operations
User first publishes transaction hash (commit), then reveal. Attacker doesn't know parameters until reveal:
contract CommitRevealSwap {
mapping(bytes32 => uint256) public commits;
uint256 public constant REVEAL_DELAY = 2; // blocks
function commit(bytes32 commitment) external {
commits[commitment] = block.number;
}
function reveal(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
bytes32 salt
) external {
bytes32 commitment = keccak256(
abi.encodePacked(msg.sender, tokenIn, tokenOut, amountIn, minAmountOut, salt)
);
require(commits[commitment] != 0, "No commit");
require(block.number >= commits[commitment] + REVEAL_DELAY, "Too early");
delete commits[commitment];
// execute swap
}
}
Tight Slippage + Deadline
Users with minAmountOut = 0 or very high slippage—easy victims. Protocol should:
- Calculate recommended slippage based on pool depth
- Warn about high slippage in UI
- Short deadline (not more than 10-15 minutes) to prevent stale transactions
MEV Blocker Integration
For frontend: route transactions through MEV Blocker (CoW Protocol) or Flashbots Protect instead of public mempool. Only trusted builders see transactions, not public searchers.
// Send via Flashbots Protect RPC
const provider = new ethers.JsonRpcProvider(
'https://rpc.flashbots.net',
1 // mainnet
);
This doesn't require smart contract changes—just RPC endpoint switch.
TWAP Oracle Instead of Spot Price
For protocols using AMM prices: Uniswap v3 TWAP (Time-Weighted Average Price) resistant to flash loan manipulation—can't shift TWAP in one block.
function getTWAP(address pool, uint32 secondsAgo) public view returns (uint256 price) {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = secondsAgo;
secondsAgos[1] = 0;
(int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
int24 arithmeticMeanTick = int24(tickCumulativesDelta / int56(uint56(secondsAgo)));
price = TickMath.getSqrtRatioAtTick(arithmeticMeanTick);
}
Analytics: Monitoring and Reporting
MEV Dashboard: visualization of historical MEV events per protocol. Answers: how many users suffered from sandwich last month? Which pools most attacked? What's average damage per event?
Eigenφhi, Flashbots MEV-Explore, EigenPhi—public tools for historical analysis. For custom monitoring—The Graph subgraph + Dune Analytics.
| Attack Type | Detection Method | Complexity | Accuracy |
|---|---|---|---|
| Sandwich | Block pattern | Medium | High |
| JIT liquidity | LP + swap timing | Medium | High |
| Oracle manipulation | Price deviation + flash loan | High | Medium |
| Liquidation front-run | Race transactions | Low | High |
| Time-bandit | Reorg detection | Very high | Low |
Stack and Timeline
Backend: Python (detection engine) + TypeScript (real-time mempool). Archive node access (Erigon self-hosted or Alchemy archive). PostgreSQL for event storage. Dune Analytics integration for visualization.
Development full-stack MEV detection system: 10-16 weeks. Post-hoc on-chain sandwich analysis only: 4-6 weeks. Flashbots Protect frontend integration: 1-2 days.







