Imagine discovering a nuance in a Ethereum’s new transaction model that allows you to almost double your gas fees paid back — consistently. This is the wild west of ERC4337 frontrunning.
In sophomore year of college, I was reading through the specifications of ERC-4337 to learn more about Account Abstraction. ERC-4337 introduces UserOperations (UserOps), which are special transactions signed by smart wallets. These transactions are relayed on-chain by actors called bundlers. Since UserOps are signed, ERC-4337 verifies authorization by the smart wallet before executing them within its context.
Interestingly, smart wallets don’t directly cover the gas costs for on-chain relaying. Instead, bundlers front the gas fees, and the smart wallet reimburses them during the UserOp execution.
Here’s the structure of a UserOp (as of version 0.6):
struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}
Notably, the UserOp contains the data on how much it’ll pay the bundler for relaying through the maxFeePerGas
and maxPriorityFeePerGas
fields.
But take this AA transaction: the bundler paid 0**.0048 POL and got refunded 0.**0091 POL, almost double what it paid!
AA bundles often overpay for gas to avoid getting stuck during on-chain congestion. But they regularly overpay significantly, and this leaves opportunity on the table:
We’ll take UserOps that are being relayed for cheap, below their refund value, bump up the gas price a bit, and then receive their refund — which will be more than what it costed to relay them onchain.
To do this, we have to execute before the original bundler’s userop takes the refund for themself. Whoever executes after the other will revert, ultimately taking a loss.
To do this strategy, there needs to be 4337 bundles being sent onchain, and we have to be able to see pending transactions. Fortunately, Polygon has decent AA usage and has an open mempool.
I was broke and needed to find a way snoop on the Polygon mempool without running my own node, which would be expensive.
BlockNative had this “explorer” for AA transactions that showed pending ones on Polygon. I poked around in my browser console and found that they were using their mempool APIs (which I didn’t want to pay for) to serve this, and their API keys were leaked in the requests. The explorer is down now, but here’s a snapshot from wayback machine: https://web.archive.org/web/20231201172934/https://4337.blocknative.com/
I dug through the network requests and found a sequence of websocket messages that would authenticate me to the endpoint, and wrote up a simple script using [ethers.](<http://ethers.sj>)js
to run the strategy.
Immediately, people noticed: