How a MEV bot hunts without a mempool

Part 1 of a 5-part series on an unverified arbitrage bot operating on Arbitrum.

I came across this contract on Arbiscan’s top gas spenders list. Digging through the operator’s deployment history turned up eight production contracts shipped over two
months.

This first part covers what the bot does, observed purely from the outside. Later parts get into the bytecode: obfuscated calldata, a hand-rolled ln/exp implementation in
the hot path, and how it sizes trades.

All findings here are derived from on-chain behavior: transaction patterns, revert rates, and execution traces. The contracts are unverified. I’ve chosen not to link specific transactions or addresses to avoid spotlighting the operator, but everything here was built from public chain data.

The bot’s chronotype

There’s a much simpler analysis that can be done before diving straight into opcodes, and that is simply observing the contract’s behavior. If you look at the way it submit transactions, you’ll notice a curious patter. See the following chart, for example:

The chart represents a 12-hour slice of the bots transaction history. The most noticeable thing is that the distribution of transactions over time is not uniform. There are long periods of sleep (sometimes minutes, sometimes hours), where the contract doesn’t submit any transaction at all, until it wakes up and fires a barrage of transactions in a short burst. Let’s zoom in on one of those.

The anatomy of a burst


167 seconds, 666 blocks, 1984 transactions, 8 profitable executions, $6.55 revenue, $2.53 net profit.

A couple of observations.

  • Most transactions revert. Each revert is a price check that came back negative. The contract just did some computing and aborted. Each of those costed a fraction of a cent.
  • Profit doesn’t land on the first block and stop. The bot keeps probing after a successful execution. It stays active as long as the opportunity window is open for whatever it’s hunting for, and only goes silent when it doesn’t.

That second observation is actually measurable, and we can observe that profitable bursts are longer than unprofitable ones. Looking from the outside in, we can only speculate as to why. Maybe it’s because the conditions that produced a hit tend to produce several, or maybe there’s a more complex heuristic the operator is tracking. In any case, the results are unambiguous.


“Profitable” means the burst covered its own gas costs (revenue − gas cost > 0). 1,784 bursts across the collected data, Apr–Jun 2026. Unprofitable median: 5s. Profitable median: 84s.

But the bigger question is: what is it looking for?

The shape of a winning transaction

Counting only takes us so far. To work out what the bot is actually doing, we have to stop tallying transactions and start reading them.

The reverting ones are no help, which is rather the point of them: they move nothing and leave nothing behind. The profitable ones are a different story, because a transaction that ends in profit has to show its work. Every token that changes hands emits a transfer event, and Arbiscan will lay them all out for you in order.

What they show is a loop. The contract takes a quantity of some token out of one pool, swaps it for a second token in another pool, swaps that back into the first token somewhere else, and returns what it borrowed. Same token at the start and at the end, two or three venues in between, and the amount that comes home is bigger than the amount that left. Nobody is being front-run and no pending transaction is being raced. The contract is walking a circle through several markets and arriving back where it started holding more than it set out with.

That is an arbitrage.

The other half of the description come from its behavior. If the operator knew, at the moment of firing, which loop was going to pay, they would send one transaction and collect. What we see instead in the example above is 1,984 of them, of which 8 made money. Nobody fires two thousand transactions at a target they can already see. The volume is the tell which indicates the bot has no idea which of its attempts will land, so it takes them all.

This is a strong signal that we’re looking at a particular type of arbitrage, called blind arbitrage.

Meet blind arbitrage

Markets are imperfect, and that imperfection is the whole opportunity. Prices across DEX pools drift out of alignment constantly. When the implied price of routing token A through pool X to pool Y is cheaper than going directly through pool Z, there’s money on the table. It’s not caused by a pending transaction, it’s the current state of the chain at that exact moment. You can’t see the opportunity coming, you can only see it when it has already materialized.

This is what we commonly call an arbitrage. An actor borrows token A with no capital upfront (the loan is an atomic IOU settled at the end of the transaction), routes through the cheaper path, ends up with more A than it started with, repays the loan, keeps the difference. If at settlement the numbers don’t clear, the whole thing reverts as if it never happened.

The blind part is a consequence of where this is happening.

On Ethereum mainnet, every transaction that hasn’t yet landed in a block sits in the mempool, visible to anyone watching. That transparency is the engine behind most MEV: for example, a bot sees a large pending swap, jumps ahead of it with a higher gas bid, and extracts value from the price impact it’s guaranteed to create. Front-running, sandwich attacks, just-in-time liquidity provision, all of them are built on top of advance knowledge of what’s about to happen.

But as I mentioned before, this bot is operating on Arbitrum and things are different on this network. There is no pending transactions pool to monitor (yes, you have the sequencer feed, but what you see there has already been committed). You don’t have visibility into big swaps that can be front-run, or sandwiches that can be assembled. The only thing that exists is the market state, right now, on-chain, in the current block.

The hard part, however, isn’t executing but finding the edge in the first place, with no signal, no tip, and no visibility into what’s coming. As a MEV bot operator looking for these types of arbitrage to capture, you’re left with two choices.

  • Off-chain montitoring. You listen on the sequencer for transactions that already mutated chain state and created an arbitrage opportunity. This is simpler to reason about, and you can throw a lot of hardware at the problem to execute your already highly optimized arb detection code, write the hot path in assembly and/or bribe a few townships to lay a fiber cable through an 1800s church and get the data just a few microseconds after it was put on the wire.
  • On-chain monitoring. You understand that in this game, being a microsecond too late is the same as being a few hours too late, so you build a smart contract that contains a function that monitors these pools, and it executes the arbitrage if there is one, or does nothing otherwise.

While off-chain monitoring gives you block-level resolution, this type of on-chain monitoring gets you intra-block resolution (of course, that depends on your sample rate, how often you’re probing).

You basically fire transactions in bursts, and hope some of them will be profitable. Which is exactly the pattern we’ve been staring at since the first chart.

When competing for this type of MEV, on-chain probing isn’t winning a speed race. On Arbitrum every transaction funnels into the same first-come-first-served sequencer, yours and your competitor’s alike, so raw submission speed isn’t where the edge lives. What on-chain probing changes is when you get to act.

An off-chain watcher can only react to a state change after it has already seen it, always a step behind the thing it’s responding to. A pre-positioned probe is already in flight, acting on the live state as the block forms and executing the instant the numbers work, which is the intra-block resolution we just described.

The probe-and-execute cycle

Each transaction must encode a complete, self-contained arbitrage attempt. The bot reads current pool state, runs its profitability calculation against a minimum threshold, and makes a binary decision before touching any assets: commit or abort.

If the math clears, execution proceeds through Uniswap v4’s flash accounting. Rather than moving tokens on every hop, the protocol tracks what the bot owes and is owed as a signed balance per token, and requires every one of those balances to net to zero before the transaction can close.

That sounds like it leaves no room for profit, but it’s the reverse: the bot finishes the routing holding more of token A than it borrowed, and it zeroes that surplus by withdrawing it to itself. Balancing the books and taking the profit are the same action. If any balance can’t be settled, the whole thing reverts.


What a cross-venue arbitrage looks like

The sequence goes like this:

  1. Flash-borrow token A from pool X (no capital required, just an IOU settled at transaction end)
  2. Swap A for B through pool Y at the current quoted rate.
  3. Swap B for A through pool Z at a different quoted rate.
  4. Repay the flash borrow to pool X.
  5. If step 4 leaves a positive balance: profit. If not, revert.

The MEV bot runs a profitability check, you could call it step 0 above, right before any of that executes. It is what makes the revert-heavy burst pattern possible. It is cheap to compute whether the arbitrage will clear, but executing it is expensive.

Why here, and not on mainnet

The edge we just described comes at a cost, and the cost is what decides where this strategy can live at all. Each probe is a transaction, and every one of them costs gas. If a successful arbitrage nets you $0.10 on average and each probe costs $0.05, you need a hit rate above 50% just to break even. The math only works if the cost per probe is small enough that a 1% hit rate, or even less, is still profitable.

That constraint rules out most of the industry’s favourite real estate. To be clear, what I’m saying is that arbitrage is everywhere, and Ethereum mainnet has more of it than any other chain, fought over by some of the most sophisticated operations in the business. What mainnet does not have is anyone hunting it like this.

At mainnet gas prices a single failed probe can cost more than one of these entire bursts earned, and a strategy that expects to be wrong 99 times out of 100 would burn itself to the ground in an afternoon.

Mainnet searchers buy their certainty somewhere cheaper. They watch the mempool, simulate the opportunity off-chain where being wrong is free, and hand a finished bundle to a block builder so that gas is only ever spent on an attempt that actually lands. It is the same hunt, funded from the opposite direction: being wrong is expensive, so make very sure before you spend anything.

Arbitrum inverts that. There’s no mempool to watch and no bundle market to sell you certainty in advance, so the off-chain route is largely closed. What you get instead is gas cheap enough that being wrong costs a fraction of a cent. Under those conditions the cheapest way to find out whether a loop pays is simply to try it, thousands of times, and let the chain answer. The brute force in that first chart is what optimal looks like when a wrong guess is nearly free.

A more interesting question

Now we know that every time the bot fires a transaction it’s attempting to nail an arbitrage. Each probe costs money, and with margins this thin, they can’t afford to just fire away 24/7.

The bursts where it does profit stand out because the bot wakes up barely seconds before the money-making transactions, as if it already knew what was going to happen. Without a mempool to monitor, how’s this possible?

The bot’s behavior is not scheduled, and it’s definitely not a coincidence. The charts below show profitable executions by hour of day, aggregated across all eight contracts observed. The distribution is not uniform.


Left: burst starts (blue) and profitable executions (green) by hour of day (UTC). Activity is clearly concentrated in the 00h–07h window and noticeably reduced from 08h–14h. Right: distribution of gaps between consecutive bursts (log scale). A scheduled system would show a narrow spike at a fixed interval, but this spread covers six orders of magnitude.

The bot appears to have some signal that tells it when conditions are favorable and when they’re not. During the long sleep windows, it isn’t probing at all. It isn’t failing constantly and waiting, it’s simply off.

What that signal is isn’t visible from the transaction history alone. It could be off-chain price feed data. It could be a parameter baked into the calldata that encodes a timing window. It could be something else entirely.

What we do know is that every transaction the bot fires carries a compact payload, which is the bot’s full set of instructions for a given burst. And yet, it changes slightly between bursts.

When I shifted my focus to the calldata, I noticed something really odd: the selectors didn’t seem random. Calldata itself looked short, structured, but didn’t make any sense. Some byte sequences mapped to what looked like addresses, however, looking them up in Arbiscan yielded no results at all, none of them were contracts, just empty EOAs that had never transacted before. Strong indication this was properly formatted garbage data.

That was the first hint I got about the possibility of the calldata being obfuscated. Like we stated before, this type of MEV bot operation requires extreme gas efficiency, because of how thin the margins can be. Why would operators smart enough to build this burden the hot path with expensive de-obfuscation code? What could possibly be worth hiding badly enough to willingly add code that lowers their profits?

After a long time of looking at my screen and decompiled/bytecode output, I was able to crack it. Turns out what they were hiding was valuable indeed.


Part 2 goes up next week. I’m posting the full series here as it goes out, but it’s also on my Substack if you’d rather get it by email: https://devilsintheopcodes.substack.com