How to Build an MEV Bot: Complete Step-by-Step Guide (2026)
Maximal Extractable Value (MEV) bots are automated programs that monitor the Ethereum mempool for profitable transaction ordering opportunities. They have collectively extracted billions of dollars from on-chain activity since 2020. The most famous example is JaredFromSubway, a sandwich bot that has accumulated over $22 million in profit by front-running and back-running DEX swaps on Uniswap and other automated market makers. In this guide, we will walk through every step of building your own MEV bot from scratch, covering mempool monitoring, transaction decoding, profitability simulation, smart contract development, and Flashbots bundle submission.
Before we begin, it is important to understand that MEV extraction is an extremely competitive space. Professional operations run colocated servers with sub-millisecond latency to block builders, use custom EVM implementations for faster simulation, and deploy highly optimized Solidity contracts. This tutorial gives you the foundational knowledge, but competing at the highest level requires significant infrastructure investment. If you want to skip the development process and access a battle-tested MEV system, our terminal provides ready-to-use MEV infrastructure.
What Is an MEV Bot and How Does It Work?
An MEV bot is a piece of software that extracts value from blockchain transactions by manipulating transaction ordering within a block. On Ethereum and other EVM-compatible chains, when a user submits a transaction it first enters the mempool, a public waiting area where pending transactions sit before validators include them in a block. MEV bots watch this mempool in real time and identify opportunities to profit.
The three primary MEV strategies are sandwich attacks, arbitrage, and liquidations. Sandwich attacks involve placing a buy transaction before a victim's swap and a sell transaction after it, profiting from the price impact the victim's trade creates. Arbitrage bots detect price discrepancies between different DEXs and execute atomic trades to capture the spread. Liquidation bots monitor lending protocols like Aave and Compound and trigger liquidation calls on undercollateralized positions to earn bounties.
MEV bots interact with the Ethereum network through JSON-RPC providers, subscribe to pending transactions via WebSocket connections, simulate trade outcomes using local EVM execution, and submit transaction bundles through private channels like Flashbots to avoid the public mempool. The entire cycle from detection to bundle submission must happen within milliseconds to remain competitive.
What Do You Need Before Building an MEV Bot?
Building a functional MEV bot requires a solid foundation in several technical areas. You do not need to be an expert in all of them from day one, but you should be comfortable reading and writing code in at least one of these stacks:
- Solidity — You need to write the on-chain executor contract that performs atomic swaps. Understanding of EVM opcodes, gas mechanics, and low-level calls (
delegatecall,staticcall, inline assembly) is essential for gas optimization. - TypeScript or Rust — The off-chain bot logic is typically written in TypeScript (using ethers.js or viem) for prototyping, or Rust (using alloy-rs or ethers-rs) for production-grade performance. Rust is preferred for competitive MEV because it offers lower latency and better memory management.
- AMM mechanics — You must understand how constant product market makers (x * y = k) work, how Uniswap V2 and V3 calculate prices, how liquidity is distributed across ticks in V3, and how slippage tolerance affects trade execution. Without this knowledge you cannot calculate profitability.
- Ethereum internals — Knowledge of how the mempool works, how transactions are propagated across nodes, how blocks are built post-merge (proposer-builder separation), and how EIP-1559 fee mechanics affect gas pricing.
- Infrastructure — Access to a full Ethereum archive node (self-hosted Geth/Reth or a paid provider like Alchemy or QuickNode), a Flashbots RPC endpoint, and ideally a colocated server near major block builders for reduced latency.
Step 1: How Do You Set Up the Development Environment?
Start by initializing your project with a Solidity development framework and a TypeScript runtime for the off-chain bot. We recommend Foundry for smart contract development because of its speed and built-in fuzzing support, paired with a Node.js TypeScript project for the bot logic.
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Initialize the smart contract project
mkdir mev-bot && cd mev-bot
forge init contracts
cd contracts && forge install OpenZeppelin/openzeppelin-contracts
# Initialize the TypeScript bot
cd .. && mkdir bot && cd bot
npm init -y
npm install ethers@6 dotenv ws typescript @types/node
npx tsc --initYour project structure should look like this: a contracts/ directory containing your Solidity executor contract with Foundry configuration, and a bot/ directory containing your TypeScript mempool scanner and bundle submitter. Keep your private keys and RPC URLs in a .env file that is gitignored.
Configure your foundry.toml to target the correct Solidity version and optimizer settings. For MEV contracts, you want the optimizer enabled with a high number of runs (at least 10,000) because the contract will be called many times and you want minimal gas overhead per execution. Set the EVM version to the latest supported by mainnet.
Step 2: How Do You Connect to the Ethereum Mempool?
The mempool is where all pending transactions wait before inclusion in a block. To monitor it, you need a WebSocket connection to an Ethereum node that supports the eth_subscribe method with the newPendingTransactions parameter. Not all providers support full transaction objects in this subscription; some only return transaction hashes, which require a follow-up eth_getTransactionByHash call that adds latency.
import { WebSocketProvider, ethers } from 'ethers';
import dotenv from 'dotenv';
dotenv.config();
const wsProvider = new WebSocketProvider(process.env.WS_RPC_URL!);
// Subscribe to pending transactions with full tx data
wsProvider.on('pending', async (txHash: string) => {
try {
const tx = await wsProvider.getTransaction(txHash);
if (!tx || !tx.to) return;
// Filter for Uniswap V2 Router and V3 Router addresses
const UNISWAP_V2_ROUTER = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D';
const UNISWAP_V3_ROUTER = '0xE592427A0AEce92De3Edee1F18E0157C05861564';
const UNIVERSAL_ROUTER = '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD';
const target = tx.to.toLowerCase();
if (
target === UNISWAP_V2_ROUTER.toLowerCase() ||
target === UNISWAP_V3_ROUTER.toLowerCase() ||
target === UNIVERSAL_ROUTER.toLowerCase()
) {
console.log('DEX swap detected:', txHash);
await analyzeTransaction(tx);
}
} catch (err) {
// Transaction may have been mined already
}
});For production use, running your own Geth or Reth node is strongly recommended. Public RPC providers add 50-200ms of latency to each request, which is unacceptable for competitive MEV. Self-hosted nodes with the txpool namespace enabled allow you to call txpool_content to get the entire mempool state at once rather than processing transactions one by one. Some operators also peer directly with other nodes in the Flashbots network to receive transactions even earlier.
Step 3: How Do You Decode Pending Transactions?
Once you have captured a pending transaction targeting a DEX router, you need to decode its calldata to understand what the user is trying to do. Every function call on Ethereum is encoded as a 4-byte function selector followed by ABI-encoded parameters. You can decode this using the router's ABI and ethers.js Interface class.
import { ethers } from 'ethers';
// Uniswap V2 Router ABI (relevant swap methods)
const V2_ROUTER_ABI = [
'function swapExactETHForTokens(uint amountOutMin, address[] path, address to, uint deadline)',
'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline)',
'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline)',
];
const iface = new ethers.Interface(V2_ROUTER_ABI);
function decodeSwap(tx: ethers.TransactionResponse) {
try {
const decoded = iface.parseTransaction({ data: tx.data, value: tx.value });
if (!decoded) return null;
const methodName = decoded.name;
const path: string[] = decoded.args.path;
const amountOutMin = decoded.args.amountOutMin;
// Calculate slippage tolerance from amountOutMin
// Higher slippage = more profitable sandwich opportunity
const tokenIn = path[0];
const tokenOut = path[path.length - 1];
return {
methodName,
tokenIn,
tokenOut,
amountOutMin: BigInt(amountOutMin),
value: tx.value,
gasPrice: tx.gasPrice,
hash: tx.hash,
};
} catch {
return null; // Not a recognized swap method
}
}For Uniswap V3 and the Universal Router, decoding is more complex. V3 uses the exactInputSingle and exactInput structs with encoded paths that pack fee tiers between token addresses. The Universal Router uses a command-based architecture where multiple operations are batched into a single transaction with a bytes commands array and bytes[] inputs array. You need to decode each command individually. Pay particular attention to the amountOutMinimum field, as this determines the victim's slippage tolerance and directly impacts how much value you can extract from a sandwich.
Step 4: How Do You Calculate Sandwich Profitability?
After decoding a pending swap, you need to simulate whether a sandwich attack would be profitable. This involves calculating the price impact of three sequential trades: your front-run buy, the victim's swap, and your back-run sell. For Uniswap V2 pairs using the constant product formula (x * y = k), the math is deterministic.
// Simulate sandwich profitability for Uniswap V2
function simulateSandwich(
reserveIn: bigint, // Current pool reserve of input token
reserveOut: bigint, // Current pool reserve of output token
victimAmountIn: bigint,
victimAmountOutMin: bigint,
frontRunAmountIn: bigint
): { profit: bigint; backRunAmountOut: bigint } {
const FEE = 997n; // 0.3% fee: multiply by 997, divide by 1000
// 1. Front-run: bot buys tokenOut
const frontAmountInWithFee = frontRunAmountIn * FEE;
const frontNumerator = frontAmountInWithFee * reserveOut;
const frontDenominator = reserveIn * 1000n + frontAmountInWithFee;
const frontAmountOut = frontNumerator / frontDenominator;
// Update reserves after front-run
const r0AfterFront = reserveIn + frontRunAmountIn;
const r1AfterFront = reserveOut - frontAmountOut;
// 2. Victim swap executes at worse price
const victimAmountInWithFee = victimAmountIn * FEE;
const victimNumerator = victimAmountInWithFee * r1AfterFront;
const victimDenominator = r0AfterFront * 1000n + victimAmountInWithFee;
const victimAmountOut = victimNumerator / victimDenominator;
// Check: victim must still get >= amountOutMin or tx reverts
if (victimAmountOut < victimAmountOutMin) {
return { profit: -1n, backRunAmountOut: 0n };
}
// Update reserves after victim
const r0AfterVictim = r0AfterFront + victimAmountIn;
const r1AfterVictim = r1AfterFront - victimAmountOut;
// 3. Back-run: bot sells tokenOut back
const backAmountInWithFee = frontAmountOut * FEE;
const backNumerator = backAmountInWithFee * r0AfterVictim;
const backDenominator = r1AfterVictim * 1000n + backAmountInWithFee;
const backRunAmountOut = backNumerator / backDenominator;
const profit = backRunAmountOut - frontRunAmountIn;
return { profit, backRunAmountOut };
}The key insight is that your front-run amount needs to be optimized. Too small and the profit does not cover gas costs. Too large and you push the victim's swap output below their amountOutMin, causing their transaction to revert and your sandwich to fail. You need to binary search for the optimal front-run amount that maximizes your back-run profit while still allowing the victim's trade to succeed. For V3 pools, the calculation is significantly more complex because liquidity is concentrated in discrete tick ranges, so you need to iterate through active ticks and account for tick crossings.
Skip the Development Complexity
Building a competitive MEV bot takes months of development and thousands in infrastructure costs. Our terminal provides access to battle-tested MEV strategies with institutional-grade execution, the same infrastructure behind JaredFromSubway's $22M+ in extracted value.
Access MEV Terminal →Step 5: How Do You Build the Executor Smart Contract?
The on-chain component of your MEV bot is an executor contract that performs swaps atomically. Atomicity is critical: if any part of the sandwich fails, the entire transaction must revert so you do not lose funds. The contract needs to be gas-optimized because every wei spent on gas directly reduces your profit margin.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IUniswapV2Pair {
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function getReserves() external view returns (uint112, uint112, uint32);
function token0() external view returns (address);
function token1() external view returns (address);
}
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
interface IWETH {
function deposit() external payable;
function withdraw(uint256 amount) external;
function transfer(address to, uint256 amount) external returns (bool);
}
contract MEVExecutor {
address private immutable owner;
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
modifier onlyOwner() {
require(msg.sender == owner, "!owner");
_;
}
constructor() {
owner = msg.sender;
}
// Execute sandwich: front-run buy + back-run sell
// Pair addresses and amounts are passed as params to avoid storage reads
function executeSandwich(
address pair,
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut,
bool zeroForOne
) external onlyOwner {
// Transfer tokenIn to pair
IERC20(tokenIn).transfer(pair, amountIn);
// Execute swap
if (zeroForOne) {
IUniswapV2Pair(pair).swap(0, amountOut, address(this), "");
} else {
IUniswapV2Pair(pair).swap(amountOut, 0, address(this), "");
}
}
// Multicall for atomic execution of multiple operations
function multicall(bytes[] calldata calls) external onlyOwner {
for (uint i = 0; i < calls.length; i++) {
(bool success,) = address(this).delegatecall(calls[i]);
require(success, "call failed");
}
}
// Withdraw profits
function withdraw(address token) external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance > 0) IERC20(token).transfer(owner, balance);
}
function withdrawETH() external onlyOwner {
IWETH(WETH).withdraw(IERC20(WETH).balanceOf(address(this)));
(bool ok,) = owner.call{value: address(this).balance}("");
require(ok);
}
receive() external payable {}
}This is a simplified version. Production MEV contracts like those used by JaredFromSubway are far more optimized. They use inline assembly to avoid ABI encoding overhead, directly compute pair addresses using CREATE2 instead of making external calls, use bitwise operations instead of conditionals, and minimize storage reads by passing all parameters via calldata. Some top MEV operators write their contracts directly in EVM bytecode or use Huff, a low-level assembly language for the EVM, to squeeze out every possible gas optimization.
Your contract also needs to handle multi-hop swaps (A to B to C), support both V2 and V3 pools, and include safety checks that verify the profitability on-chain before executing. A common pattern is to check the final WETH balance and revert if it is not greater than the starting balance, guaranteeing that every executed bundle is profitable or costs nothing (it simply reverts).
Step 6: How Do You Submit Bundles via Flashbots?
Flashbots is a research and development organization that provides private transaction submission infrastructure. Instead of broadcasting your transactions to the public mempool where they can be front-run by other bots, you send a bundle of transactions directly to block builders. A bundle is an ordered list of transactions that must all be included in a specific block or not at all.
import { FlashbotsBundleProvider } from '@flashbots/ethers-provider-bundle';
import { ethers, Wallet } from 'ethers';
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const authSigner = new Wallet(process.env.FLASHBOTS_AUTH_KEY!, provider);
const executorWallet = new Wallet(process.env.EXECUTOR_KEY!, provider);
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
authSigner,
'https://relay.flashbots.net'
);
async function submitSandwichBundle(
victimTxHash: string,
frontRunTx: string, // Signed front-run transaction
backRunTx: string // Signed back-run transaction
) {
const blockNumber = await provider.getBlockNumber();
const targetBlock = blockNumber + 1;
const bundle = [
{ signedTransaction: frontRunTx }, // Your front-run
{ hash: victimTxHash }, // Victim's pending tx
{ signedTransaction: backRunTx }, // Your back-run
];
// Simulate the bundle first
const simulation = await flashbotsProvider.simulate(bundle, targetBlock);
if ('error' in simulation) {
console.error('Simulation error:', simulation.error);
return;
}
// Check profitability after gas costs
const totalGasUsed = simulation.results.reduce(
(acc, r) => acc + BigInt(r.gasUsed), 0n
);
const gasPrice = simulation.results[0].gasPrice;
const gasCost = totalGasUsed * BigInt(gasPrice);
console.log('Simulated profit (before tip):', simulation.coinbaseDiff);
console.log('Gas cost:', gasCost);
// Submit to multiple builders for better inclusion rate
const bundleSubmission = await flashbotsProvider.sendBundle(bundle, targetBlock);
// Also target the next block in case current one is missed
await flashbotsProvider.sendBundle(bundle, targetBlock + 1);
console.log('Bundle submitted for block', targetBlock);
// Wait for resolution
const resolution = await bundleSubmission.wait();
console.log('Bundle resolution:', resolution);
}The eth_sendBundle method accepts the ordered array of transactions and a target block number. Flashbots relays the bundle to participating block builders who will include it if the bundle is profitable for them. You need to include a builder tip (coinbase transfer) in your back-run transaction to incentivize builders to include your bundle over competing bundles. A common strategy is to give 90-99% of your MEV profit to the builder as a tip, keeping only a small percentage for yourself. This is because competition is fierce and builders will always choose the bundle that pays them the most.
In addition to Flashbots, you should submit your bundles to multiple block builders simultaneously: Titan Builder, rsync builder, Beaver Build, and others. Each builder has their own relay endpoint and may win different block slots. Some MEV operators also run their own builders or have private orderflow agreements to guarantee inclusion. The flash loan arbitrage strategy follows a similar bundle submission pattern but includes a flash loan borrow and repay within the atomic bundle.
Step 7: How Do You Deploy, Monitor, and Optimize?
Deploy your executor contract to mainnet using Foundry's forge create command. Fund it with enough WETH to execute your front-run trades. Start with small amounts (0.1-0.5 ETH) while you validate your bot's performance, and scale up gradually as you confirm profitability.
# Deploy the executor contract
forge create --rpc-url $RPC_URL \
--private-key $DEPLOYER_KEY \
--optimizer-runs 10000 \
src/MEVExecutor.sol:MEVExecutor
# Verify on Etherscan (optional but recommended for debugging)
forge verify-contract --chain mainnet \
--compiler-version v0.8.24 \
<DEPLOYED_ADDRESS> src/MEVExecutor.sol:MEVExecutorMonitoring is critical for a production MEV bot. Track these metrics in real time: bundle inclusion rate (what percentage of your submitted bundles make it into blocks), average profit per successful bundle, gas efficiency (how much gas your contract uses vs. competitors), latency from transaction detection to bundle submission, and revert rate (failed simulations indicate bugs or stale state). Use a time-series database like InfluxDB or a service like Grafana Cloud to visualize these metrics.
Gas optimization is an ongoing process. Profile your contract with forge test --gas-report and look for expensive operations. Common optimizations include: using unchecked blocks for arithmetic that cannot overflow, caching storage variables in memory, packing multiple values into a single storage slot, using calldata instead of memory for read-only array parameters, and minimizing external calls.
Error handling is equally important. Your bot should gracefully handle WebSocket disconnections, RPC rate limiting, node synchronization issues, and reverted simulations. Implement exponential backoff for retries, maintain a connection pool to multiple RPC providers for redundancy, and set up alerts for when your bot stops finding profitable opportunities (which could indicate a bug or a shift in market conditions).
Why Do Most DIY MEV Bots Fail?
Despite the apparent simplicity of the concept, the overwhelming majority of self-built MEV bots lose money. Here is why:
- Latency — Professional MEV operations like JaredFromSubway run colocated infrastructure with single-digit millisecond latency to block builders. If your bot runs on a VPS in a different datacenter, you will lose every competitive auction. Latency is measured in microseconds at the top level, and even 10ms of disadvantage means your bundles are consistently outbid.
- Competition — Hundreds of sophisticated teams are running MEV bots simultaneously. Many of these teams have PhDs in computer science and mathematics, custom EVM implementations, and millions of dollars in capital. When you submit a bundle, you are competing against all of them for the same opportunity.
- Gas costs — Failed simulations are free, but any on-chain testing or miscalculated bundles cost real ETH. A bug that causes your bot to submit unprofitable bundles at scale can drain your wallet in hours. Builder tips further compress margins: even profitable sandwiches may net only a few dollars after giving 95% to the builder.
- Evolving landscape — The MEV ecosystem changes rapidly. New DEX designs with built-in MEV protection (like UniswapX intent-based orders), evolving Flashbots infrastructure (MEV-Share, SUAVE), and protocol-level changes (account abstraction, encrypted mempools) constantly shift the playing field. Your bot needs continuous updates to remain functional.
- Capital requirements — Effective sandwich attacks on large trades require significant upfront capital. A $100K swap on Uniswap might need a $50K+ front-run to generate meaningful profit. Under-capitalized bots can only target small trades, where profit margins barely cover gas.
JaredFromSubway's success ($22M+ in extracted value) is the exception, not the rule. That bot operates with custom infrastructure, proprietary optimizations, and deep liquidity that took years to develop. Most tutorials online, including this one, provide the foundational knowledge but cannot replicate the production infrastructure needed to compete at the highest level.
Frequently Asked Questions
How much does it cost to build and run an MEV bot?
The development itself can be done for free using open-source tools like Foundry and ethers.js. However, production infrastructure costs $500-$5,000 per month for dedicated nodes, colocated servers, and RPC subscriptions. You also need starting capital for front-run trades, typically 1-10 ETH minimum. A self-hosted archive node alone requires 2TB+ of NVMe SSD storage and significant bandwidth. Total first-year cost for a competitive setup ranges from $15,000 to $100,000+.
Is building an MEV bot legal?
MEV extraction on public blockchains is generally considered legal because it operates within the protocol's rules. Validators and searchers are free to order transactions as they choose. However, the regulatory landscape is evolving. Some jurisdictions may consider certain MEV strategies (particularly sandwich attacks) as a form of market manipulation. Always consult legal counsel in your jurisdiction before deploying an MEV bot, especially if you are operating at scale or targeting regulated assets.
Can I build an MEV bot on chains other than Ethereum?
Yes. MEV opportunities exist on all EVM-compatible chains including Arbitrum, Polygon, BSC, Base, and Avalanche. Some L2s like Arbitrum have a centralized sequencer that makes traditional mempool-based MEV more difficult, but backrun and arbitrage opportunities still exist through the sequencer's transaction feed. BSC has a particularly active MEV ecosystem with different builder dynamics. The core concepts from this guide apply across chains, but you need to adapt the provider URLs, router addresses, and bundle submission endpoints for each chain.
What programming language is best for MEV bots?
TypeScript with ethers.js or viem is the best starting point for learning and prototyping because of its large ecosystem and ease of use. For production MEV bots that compete for real opportunities, Rust is the preferred choice due to its zero-cost abstractions, memory safety, and raw performance. Libraries like alloy-rs and reth provide Rust-native Ethereum primitives. Some top MEV operators use a hybrid approach: Rust for the latency-critical hot path (mempool scanning, simulation, bundle submission) and TypeScript for monitoring, analytics, and strategy configuration.
Ready to Extract MEV Without Building From Scratch?
JaredFromSubway's MEV terminal gives you access to the same infrastructure that has generated $22M+ in profit. No development required. Connect your wallet and start within minutes.
Launch MEV Terminal →