Every block on Ethereum leaves money on the table for whoever notices first. A price gap between two Uniswap pools, a liquidation that pays a bounty, a pending swap that can be sandwiched, someone is going to catch it and get paid. That someone runs a searcher bot. This tutorial builds one from scratch: a working arbitrage searcher that watches pool prices, packages a profitable trade into a Flashbots bundle, and submits it to relays without ever touching the public mempool. You’ll end up with real code you can run on a local fork today and, if you choose, point at mainnet later.
This is a builder’s guide, not a promise of profit. MEV extraction on Ethereum is a competitive, low-margin business dominated by bots that have been running for years. Treat what follows as an engineering exercise that teaches you how blocks, bundles, and relays actually work under the hood.
What Is MEV and Why Build a Searcher Bot in 2026
Maximal extractable value, MEV, is the profit a block producer or a searcher can capture by choosing which transactions go into a block and in what order. A searcher bot is the piece of software that finds those opportunities, wraps them into one or more transactions, and asks a block builder to include them in a specific position. Arbitrage is the cleanest version of this: buy an asset cheap on one pool, sell it high on another, all inside a single atomic bundle so there’s no risk of the price moving against you between the two legs.
The scale of this activity is still meaningful going into late 2026. One industry tracker cited in recent coverage puts Ethereum MEV capture at roughly $24 million a month, which annualizes to north of a quarter billion dollars moving from regular users into builder and searcher profit. A separate EigenPhi-derived dataset referenced in 2026 reporting showed arbitrage MEV alone hitting $3.37 million over a single 30-day window, a useful reminder that arbitrage is still a real, if crowded, opportunity class. On the cost side of the ledger, a 2025 Ethereum MEV analysis found sandwich attacks accounted for roughly $289.8 million of about $561.9 million in total MEV transaction volume that year, or about 51.56% of it, mostly extracted from retail swaps with loose slippage settings.
Why bother learning this if the margins are thin and the competition is fierce? Because the mechanics, bundle construction, simulation, relay submission, gas accounting, show up everywhere in Ethereum infrastructure work. Understanding how a searcher operates also makes you a better builder of MEV-resistant applications, since you’ll know exactly what an attacker’s tooling looks like from the inside.
The block-building pipeline your bot plugs into is called proposer-builder separation. Validators no longer assemble their own blocks by default, they outsource that job to specialized builders who compete to pack the most valuable set of transactions into each slot, then pay the validator for the right to propose it. MEV-Boost is the software that lets a validator request bids from multiple builders and pick the highest one automatically. Your bundle doesn’t go straight to a validator, it goes to a builder through a relay, and that builder decides whether your transactions make the cut based on how much value they add to the block. Knowing that chain of custody, wallet, relay, builder, validator, matters more than knowing any single line of Solidity, because it’s where most of the friction and most of the competition actually lives.
| MEV Strategy | What It Captures | Bundle Complexity | Typical Risk |
|---|---|---|---|
| Two-pool arbitrage | Price gap between two DEX pools for the same pair | Low, one or two transactions | Gas spent with no fill if price closes first |
| Triangular arbitrage | Price gap across three pooled assets in a loop | Medium, needs careful path routing | Slippage compounds across three legs |
| Sandwich | Front-run and back-run a visible pending swap | Medium, needs mempool or order-flow access | Reputational and increasingly regulatory scrutiny |
| Liquidation | Bounty for closing an undercollateralized loan | Low to medium, protocol-specific calls | Heavy competition from specialized bots |
Prerequisites
You don’t need a trading desk to follow along, but get these pieces in place before Step 1.
| Tool | Version Used Here | Purpose |
|---|---|---|
| Node.js | 24.x LTS (“Krypton” line, latest build 24.21.0) | Runs the bot and scripting examples |
| ethers.js | 6.17.0 | Talks to Ethereum nodes, signs transactions |
| Foundry (forge, anvil, cast) | Latest version via foundryup | Local mainnet fork, testing, simulation |
| @flashbots/ethers-provider-bundle | Latest version via npm | Builds and sends Flashbots bundles |
| An RPC provider (Alchemy, Infura, or similar) | Free tier is enough to start | Reads chain state, provides a mainnet fork source |
| A burner wallet | N/A | Signs bundles, never your primary funds |
Before you touch a seed phrase in any script, generate a fresh burner wallet just for this project. Every example below runs against a local fork first, so nothing here needs to touch mainnet until you decide it should. If you haven’t set up a hardware wallet or an offline backup for your main holdings yet, sort that out separately before you start experimenting with bot code that signs and broadcasts transactions.
Step 1: Set Up Your Project and Install Dependencies
Start with a clean Node.js project and pull in the packages you’ll use through the rest of this guide.
mkdir mev-searcher-bot && cd mev-searcher-bot
npm init -y
npm install [email protected] @flashbots/ethers-provider-bundle dotenv
Install Foundry itself with the official installer if you don’t already have forge, anvil, and cast on your machine.
curl -L https://foundry.paradigm.xyz | bash
foundryup
Create a .env file to hold your RPC URL, a signing key for the burner wallet, and a separate key Flashbots uses purely for reputation on their relay, not for moving funds.
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
PRIVATE_KEY=0xyourburnerwalletprivatekey
FLASHBOTS_SIGNER_KEY=0xarandomkeyusedonlyforrelayauth
That Flashbots signer key isn’t a wallet you fund. Relays use it purely to identify your searcher and build a reputation score over time, so a freshly generated key with zero ETH in it is exactly right.
Step 2: Get an RPC Endpoint and a Flashbots-Ready Wallet
Sign up for a free-tier account with a provider like Alchemy and grab a mainnet HTTPS endpoint. You’ll use this both to read live pool state and, later, to fork mainnet locally with Anvil so you can test without spending real gas.
Generate the burner wallet with ethers directly rather than reusing anything from a browser extension.
const { Wallet } = require("ethers");
const wallet = Wallet.createRandom();
console.log("Address:", wallet.address);
console.log("Private key:", wallet.privateKey);
Fund this address with a small amount of ETH only when you’re ready to move past the local fork stage. At an ETH price around $2,397, roughly where major trackers including CoinGecko and CoinMarketCap showed it trading in mid-September 2026, even a fraction of an ETH covers plenty of testing once you’re live, especially with mainnet gas sitting in the sub-1 gwei range that Etherscan’s gas tracker has shown for standard transactions through most of 2026.
Step 3: Fork Mainnet Locally With Anvil
Anvil, Foundry’s local node, can fork mainnet at the current block so your bot sees real pool reserves and real contract state without spending a cent of gas. Run this in its own terminal and leave it running for the rest of the tutorial.
anvil --fork-url $MAINNET_RPC_URL --chain-id 1 --port 8545
Point your bot’s provider at http://127.0.0.1:8545 during development. Anvil gives you deterministic test accounts pre-funded with 10,000 ETH each, which is exactly what you want while you’re still working out bugs in your bundle logic. Switch back to your real RPC URL only when you move to Step 10’s backtesting and paper-trading stage.
Step 4: Write a Price-Monitoring Loop for Uniswap Pools
Your bot needs to know, block by block, what two pools think the same asset pair is worth. This example reads reserves from two liquidity pools and computes an implied price for each, using the constant-product formula most Uniswap-style AMMs run on.
const { ethers } = require("ethers");
const provider = new ethers.JsonRpcProvider(process.env.MAINNET_RPC_URL);
const PAIR_ABI = [
"function getReserves() view returns (uint112, uint112, uint32)"
];
async function getPoolPrice(poolAddress) {
const pool = new ethers.Contract(poolAddress, PAIR_ABI, provider);
const [reserve0, reserve1] = await pool.getReserves();
return Number(reserve1) / Number(reserve0);
}
async function watchPools(poolA, poolB, intervalMs = 3000) {
setInterval(async () => {
const [priceA, priceB] = await Promise.all([
getPoolPrice(poolA),
getPoolPrice(poolB)
]);
const spread = Math.abs(priceA - priceB) / Math.min(priceA, priceB);
console.log(`Pool A: ${priceA.toFixed(6)} | Pool B: ${priceB.toFixed(6)} | Spread: ${(spread * 100).toFixed(3)}%`);
}, intervalMs);
}
Polling every few seconds is fine for learning the mechanics. A competitive bot instead subscribes to new pending blocks or uses a mempool stream so it reacts within milliseconds, but that level of speed only matters once your detection and bundle logic already work correctly on the slower path.
Step 5: Detect Arbitrage Opportunities Across Pools
A spread alone doesn’t mean a trade is profitable. You need to size the trade against the pools’ actual depth and account for the 0.3% swap fee each pool typically charges, then compare the result against what gas will cost you.
function getAmountOut(amountIn, reserveIn, reserveOut, feeBps = 30) {
const amountInWithFee = amountIn * (10000 - feeBps);
const numerator = amountInWithFee * reserveOut;
const denominator = (reserveIn * 10000) + amountInWithFee;
return numerator / denominator;
}
function findProfitableTrade(reservesA, reservesB, testAmount) {
const outFromA = getAmountOut(testAmount, reservesA.in, reservesA.out);
const outFromB = getAmountOut(outFromA, reservesB.in, reservesB.out);
const grossProfit = outFromB - testAmount;
return { grossProfit, outFromA, outFromB };
}
Run this across a small range of trade sizes rather than one fixed amount. Because of how AMM pricing curves, the profitable trade size usually sits somewhere in the middle: too small and fees eat the edge, too large and slippage eats it from the other direction. A simple loop that tries five or six sizes and keeps the best one is enough to get started.
Step 6: Calculate Gas Costs and Minimum Profit Thresholds
A profitable-looking spread on paper can still lose money once gas and the priority fee you offer the block builder come out of it. Build the check directly into your decision logic instead of eyeballing it.
async function isTradeWorthIt(grossProfitWei, gasEstimate, provider) {
const feeData = await provider.getFeeData();
const gasCostWei = gasEstimate * feeData.maxFeePerGas;
const minerTipWei = (gasCostWei * 30n) / 100n; // ~30% of gas cost as builder tip
const totalCostWei = gasCostWei + minerTipWei;
const netProfitWei = grossProfitWei - totalCostWei;
return { profitable: netProfitWei > 0n, netProfitWei, totalCostWei };
}
With mainnet gas prices sitting well under 1 gwei for most of 2026 according to Etherscan’s gas tracker snapshots, base gas costs on a simple two-hop arbitrage are often just a few dollars. The bigger line item is usually the tip you offer the builder to win the block slot, since that’s the actual auction you’re competing in against every other searcher watching the same pools.
Step 7: Build and Sign a Flashbots Bundle
A bundle is just an ordered list of signed transactions that you ask a builder to include together, atomically, or not at all. The @flashbots/ethers-provider-bundle library wraps the signing and formatting for you.
const { FlashbotsBundleProvider } = require("@flashbots/ethers-provider-bundle");
async function buildBundle(provider, wallet, swapTx) {
const authSigner = new ethers.Wallet(process.env.FLASHBOTS_SIGNER_KEY);
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
authSigner,
"https://relay.flashbots.net"
);
const signedBundle = await flashbotsProvider.signBundle([
{ signer: wallet, transaction: swapTx }
]);
return { flashbotsProvider, signedBundle };
}
Everything about the transaction itself, the router address, the calldata, the gas limit, works exactly like a normal Ethereum transaction. The only difference is where you send it once it’s signed, which is the relay rather than the public mempool.
Step 8: Simulate the Bundle Before You Send It
Never submit a bundle blind. Flashbots lets you simulate against the exact block your bundle is targeting, so you catch reverts, wrong nonces, and stale price assumptions before you spend a single wei on a failed attempt.
const targetBlock = (await provider.getBlockNumber()) + 1;
const simulation = await flashbotsProvider.simulate(signedBundle, targetBlock);
if ("error" in simulation) {
console.log("Simulation failed:", simulation.error.message);
} else {
console.log("Simulated profit:", simulation.coinbaseDiff.toString());
}
A healthy simulation on a local Anvil fork looks like this:
Simulated profit: 4210000000000000
Gas used: 187342
Bundle hash: 0x8a2f...e19c
A failed one usually looks closer to this, and the fix is almost always in your slippage tolerance or your price assumptions, not in the bundle mechanics themselves.
Simulation failed: execution reverted: INSUFFICIENT_OUTPUT_AMOUNT
Step 9: Submit Bundles to Multiple Relays and Track Inclusion
One relay is not enough anymore. Flashbots’ own relay carried only around 2.5% to 2.7% of MEV-Boost payloads in mid-September 2026 snapshots from relay-tracking dashboards, a sharp drop from its early dominance right after the Merge. Builders and validators have spread across several relays, so a searcher that only submits to Flashbots is missing most of the block-building market.
const RELAYS = [
"https://relay.flashbots.net",
"https://rpc.titanbuilder.xyz",
"https://mainnet-relay.securerpc.com"
];
async function submitToAllRelays(providers, signedBundle, targetBlock) {
const results = await Promise.allSettled(
providers.map(fb => fb.sendRawBundle(signedBundle, targetBlock))
);
results.forEach((r, i) => {
console.log(RELAYS[i], r.status === "fulfilled" ? "submitted" : r.reason);
});
}
After submission, poll for inclusion using the bundle stats endpoint or simply check whether your transaction hash appears in the target block. If it doesn’t land within two or three blocks, resubmit with a fresh price check rather than blindly retrying the same stale bundle.
Step 10: Backtest and Paper-Trade Before Going Live
Before risking real ETH, run your detection and profit logic against historical blocks on your Anvil fork. Roll the fork back to a specific block height, replay a window of pool activity, and log every opportunity your bot would have flagged along with what actually happened to that price gap a block later.
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 21500000 --port 8546
Once backtesting shows consistent, correctly-sized profit estimates, switch to paper trading: run the bot against live mainnet data in read-only mode, log every bundle it would have sent, and compare those against what actually landed on-chain over a few days. Only fund the burner wallet and flip on real submission once paper results match your simulation numbers closely.
Step 11: Add Logging, Alerts, and a Kill Switch
A bot that runs unattended needs a way to shut itself off. Wire in a simple check that halts submissions if your wallet balance drops below a floor, or if a set number of bundles fail simulation in a row, since that pattern usually means your price feed has gone stale or a pool you’re watching has been drained.
let consecutiveFailures = 0;
const MAX_FAILURES = 5;
function checkKillSwitch(simulationFailed) {
consecutiveFailures = simulationFailed ? consecutiveFailures + 1 : 0;
if (consecutiveFailures >= MAX_FAILURES) {
console.error("Kill switch triggered: too many consecutive failures. Halting bot.");
process.exit(1);
}
}
Send logs somewhere you’ll actually check, a simple webhook into a chat channel works fine, rather than a file you’ll forget to open. Track win rate, average net profit per landed bundle, and total gas spent on failed attempts, since that last number is the one new searchers consistently underestimate.
Step 12: Deploy and Monitor Your First Live Runs
Move from your local fork to mainnet only after backtesting and paper trading both look clean. Start with a small trade-size cap, well below what your profit calculations say is optimal, and watch the first dozen or so live attempts closely rather than walking away.
node bot.js --network mainnet --max-trade-size 0.05 --dry-run false
Raise your trade-size cap gradually as landed bundles confirm your assumptions hold on real order flow, not just on a fork. Most of the surprises at this stage come from timing, other searchers reacting to the same spread faster than your bot does, rather than from bugs in the bundle logic itself.
Keep a running log of every attempt, including the ones that never land, and review it daily for the first week. You’re looking for patterns: a specific pool pair that never actually converts into a filled bundle, a time of day where gas spikes eat your margin, a relay that consistently rejects your submissions. Those patterns tell you where to spend your next round of engineering effort far more reliably than any theoretical improvement you could guess at from reading documentation alone.
The Complete Bot: Full Code Listing
Here’s a condensed version that wires the pieces from every step above into one runnable file. Save it as bot.js in the project you created in Step 1.
require("dotenv").config();
const { ethers } = require("ethers");
const { FlashbotsBundleProvider } = require("@flashbots/ethers-provider-bundle");
const provider = new ethers.JsonRpcProvider(process.env.MAINNET_RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const PAIR_ABI = ["function getReserves() view returns (uint112, uint112, uint32)"];
async function getPoolPrice(address) {
const pool = new ethers.Contract(address, PAIR_ABI, provider);
const [r0, r1] = await pool.getReserves();
return Number(r1) / Number(r0);
}
function getAmountOut(amountIn, reserveIn, reserveOut, feeBps = 30) {
const withFee = amountIn * (10000 - feeBps);
return (withFee * reserveOut) / (reserveIn * 10000 + withFee);
}
async function run(poolA, poolB) {
const authSigner = new ethers.Wallet(process.env.FLASHBOTS_SIGNER_KEY);
const flashbots = await FlashbotsBundleProvider.create(provider, authSigner, "https://relay.flashbots.net");
let failures = 0;
setInterval(async () => {
const [priceA, priceB] = await Promise.all([getPoolPrice(poolA), getPoolPrice(poolB)]);
const spread = Math.abs(priceA - priceB) / Math.min(priceA, priceB);
if (spread < 0.004) return; // skip if spread doesn't clear fees + gas
const targetBlock = (await provider.getBlockNumber()) + 1;
const swapTx = { to: poolA, data: "0x", gasLimit: 250000n, value: 0n };
const signedBundle = await flashbots.signBundle([{ signer: wallet, transaction: swapTx }]);
const sim = await flashbots.simulate(signedBundle, targetBlock);
if ("error" in sim) {
failures++;
console.log("Sim failed:", sim.error.message);
if (failures >= 5) { console.error("Kill switch triggered."); process.exit(1); }
return;
}
failures = 0;
await flashbots.sendRawBundle(signedBundle, targetBlock);
console.log("Bundle submitted for block", targetBlock);
}, 3000);
}
run(process.env.POOL_A_ADDRESS, process.env.POOL_B_ADDRESS);
Treat the swap transaction’s data field as a placeholder here, since a real router call needs the correct encoded calldata for whichever DEX contract you’re routing through. Wire in your router’s ABI and the encoded swap call before pointing this at real funds.
Common Pitfalls That Drain a Searcher Bot’s Profit
- Ignoring the tip auction. Your gross-profit math looks great until you realize three other bots saw the same spread and are bidding the builder tip up in real time. Build tip escalation into your logic instead of hardcoding a fixed percentage.
- Testing only on a fork with stale state. A fork pinned to an old block gives you clean, repeatable tests, but it also means your simulated profits won’t match what a live, constantly-shifting mempool produces.
- Underestimating slippage on the second leg. The first swap in an arbitrage moves the pool’s price, which changes what the second swap actually nets you. Always chain your profit calculation through both legs sequentially, never in parallel.
- Skipping simulation to save time. A single unsimulated bundle that reverts still costs nothing on Flashbots since failed bundles aren’t included on-chain, but a bad habit here eventually leaks into strategies where failure does cost gas.
- Relying on a single relay. With Flashbots’ own relay down to roughly 2.5% of MEV-Boost payloads by September 2026, a bot that only submits there is missing the large majority of available block-building capacity.
- Leaving the private key in plaintext scripts. Even a low-balance burner wallet key belongs in an environment variable loaded from a file you’ve excluded from version control, not pasted directly into a script you might accidentally commit.
Troubleshooting
Most searcher-bot problems fall into a handful of recurring categories. Here’s what to check first.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Simulation always returns INSUFFICIENT_OUTPUT_AMOUNT | Slippage tolerance set too tight for current pool depth | Widen the minimum output slightly or refresh the price right before building the bundle |
| Bundles never land, even after simulating clean | Tip too low relative to competing searchers | Increase the builder tip percentage and check relay-specific minimums |
| Anvil fork returns wrong reserves | Fork pinned to a stale or unspecified block | Pass an explicit –fork-block-number and confirm it against a block explorer |
| “nonce too low” errors on submission | A prior transaction from the same wallet already used that nonce | Fetch the latest pending nonce fresh before each bundle build |
| Flashbots relay returns 401 Unauthorized | Missing or malformed signature header on the request | Confirm the auth signer wallet is passed correctly to FlashbotsBundleProvider.create |
| Gas estimate wildly higher than expected | Router call reverting partway through estimation | Test the raw calldata against the fork with cast call before wiring it into the bot |
| Bot detects spreads that don’t exist on-chain | Stale RPC response due to load balancer caching | Switch to a dedicated archive endpoint or reduce polling interval |
| Bundle simulates profitable but real submission fails silently | Targeting a block number that’s already passed | Always fetch the current block height immediately before building the bundle, not seconds earlier |
| Kill switch never triggers despite repeated failures | Failure counter reset logic has an off-by-one bug | Log the counter value on every iteration during testing to confirm it increments correctly |
Advanced Tips
Once the basic loop works reliably, a few upgrades separate a toy bot from something closer to production-grade.
Switching to MEV-Share for Order Flow
Flashbots’ MEV-Share system lets searchers subscribe to a stream of partial transaction data from users who’ve opted in, rather than only reacting to public pool state. That gives you a faster signal for opportunities tied to specific pending trades instead of waiting for a price to already show up in on-chain reserves. Check the Flashbots documentation for the current event stream format before wiring it in, since the API surface has evolved since MEV-Share’s initial release.
Where Relays Stand in September 2026
Relay market share shifts constantly, and a 30-day snapshot from relay-tracking dashboards in September 2026 shows a meaningfully different picture than the early post-Merge era when Flashbots’ relay dominated almost every block.
| Relay | Approx. 30-Day Share |
|---|---|
| Ultra Sound | ~33% |
| Titan | ~28% |
| bloXroute (regulated) | ~16% |
| bloXroute (max profit) | ~10% |
| Aestus | ~8% |
| Flashbots | ~2.5% |
| Agnostic Gnosis | ~1% |
These figures come from a Rated Explorer-style 30-day relay view and will drift week to week, so treat them as a snapshot rather than a fixed reference. The practical takeaway for a searcher bot doesn’t change: submit to several relays in parallel, since MEV-Boost adds roughly 10% to 30% to validator rewards according to 2026 staking analyses, and validators have every incentive to keep spreading across whichever relays deliver the best blocks. Check relayscan.io or mev.wiki directly for current numbers before you finalize your relay list.
Flash-loan-funded arbitrage is another meaningful upgrade once your detection logic is solid: instead of capping trade size to your own wallet’s balance, you borrow the capital atomically within the same bundle and repay it before the transaction ends. That removes your capital constraint entirely, though it also raises your gas usage and adds another point of failure to debug. EigenPhi’s live MEV transaction data is a good reference for seeing real flash-loan arbitrage bundles in the wild before you build your own.
Solidity and Foundry documentation are also worth bookmarking directly, since the calldata encoding and router interfaces you’ll need change as DEX contracts get upgraded. The official Solidity docs and Foundry Book are the two references you’ll return to most, alongside ethereum.org’s MEV overview for the underlying concepts.
Is Running an MEV Bot Legal?
Arbitrage searching itself sits on solid ground. You’re reacting to publicly visible price differences and paying gas like anyone else, the same category of activity that keeps prices aligned across traditional markets. Sandwich attacks occupy murkier territory: they extract value from a specific counterparty’s trade by exploiting the ordering of transactions in a block, and they’ve drawn increasing scrutiny from both the Ethereum community and, in some jurisdictions, regulators looking at market-manipulation frameworks originally written for traditional finance. Nothing here is legal advice, and if you plan to run strategies beyond straightforward arbitrage, consult someone qualified in your jurisdiction before you deploy capital.
The bot you built in this tutorial only performs arbitrage: it never inspects a specific user’s pending transaction, and it never inserts itself around someone else’s trade. That distinction is worth preserving as you extend the code, since the moment you start reading and reacting to an individual counterparty’s pending swap, you’ve moved into a different, more contested category of activity with a very different risk profile attached to it.
Frequently Asked Questions
Do I need to run my own Ethereum node to build a searcher bot?
No. A free-tier RPC endpoint from a provider like Alchemy or Infura is enough for development and even for a modest live bot. Running your own node mainly helps once latency starts to matter competitively.
How much ETH do I need to start?
Enough to cover gas on a handful of small trades, a fraction of an ETH is plenty given gas prices under 1 gwei for most of 2026. The trade capital itself can start just as small while you validate your bot’s logic on live data.
Why did my bundle simulate profitably but never land on-chain?
Usually another searcher’s bundle offered a higher tip for the same block, or your target block number was already past by the time you submitted. Rebuild the bundle against the current block height immediately before sending, rather than reusing an older one.
Is Flashbots still the main way to submit MEV bundles?
It’s one of several. Relay market share has spread out significantly, with Ultra Sound and Titan each carrying a larger share of MEV-Boost payloads than Flashbots’ own relay as of September 2026 snapshots. Submit to multiple relays for real coverage.
Can I run this bot on a testnet first?
Testnets typically lack the liquidity depth and real arbitrage opportunities that make this exercise meaningful, so a local Anvil fork of mainnet is the better environment for development and backtesting before you ever touch real funds.
What’s the biggest reason arbitrage bots lose money?
Gas spent on bundles that simulate fine locally but lose the tip auction to a faster or better-capitalized competitor once submitted live. Track your win rate closely and be honest about whether the strategy is actually profitable after that cost.
Should I use Python instead of JavaScript for this?
Either works. web3.py (currently at 7.14.1) covers the same RPC and transaction-signing functionality as ethers.js, and Flashbots publishes bundle-relay libraries for both ecosystems, so pick whichever language you’re already comfortable debugging quickly.
Do I need to worry about MEV-Share if I’m just doing simple two-pool arbitrage?
Not at first. MEV-Share mainly helps strategies that need visibility into pending user transactions. Pure pool-to-pool arbitrage only needs accurate, fast reads of on-chain reserves, which the approach in this tutorial already covers.




