Building Your First Atomic Arbitrage in 2026: Theory to First Fill
**Answer first** — An atomic arbitrage is a single transaction that buys an asset cheap on Pool A, sells it expensive on Pool B, and reverts entirely if the round-trip isn't profit

Answer first — An atomic arbitrage is a single transaction that buys an asset cheap on Pool A, sells it expensive on Pool B, and reverts entirely if the round-trip isn't profitable. In 2026, the easiest first one to build is a two-pool ETH/USDC arb on Base or Arbitrum — same asset pair, different DEXes (Uniswap V3 vs Aerodrome / SushiSwap), small price drift after a large swap on one pool. This article walks through it: pool math, calldata, slippage, simulation, and what your first live fill should actually look like.
If you don't yet know What is MEV, start there.
What Makes an Arbitrage "Atomic"
Three properties:
- Single transaction. Buy and sell are in the same tx, not two sequential ones.
- Conditional execution. A
require(profit > 0, ...)revert if profit doesn't materialize. - Flash-borrowable. Capital can be borrowed and repaid within the tx.
The atomicity is what protects you from execution risk. If anything goes wrong mid-tx, the whole thing reverts and you pay only gas, not principal.
The Theory in 60 Seconds
Two pools price the same asset slightly differently. After a large swap on Pool A pushes its price away from market, Pool B is cheaper. You buy on B, sell on A, capture the spread.
For two AMM pools using constant-product (x * y = k):
Effective_price_A = (reserve_y_A / reserve_x_A) × (1 - fee_A)
Effective_price_B = (reserve_y_B / reserve_x_B) × (1 - fee_B)
If Effective_price_A > Effective_price_B + slippage_buffer, an arb exists.
The optimal trade size is the size that equalizes the two pools' marginal prices. There's a closed-form solution, but you can use binary search or borrow Foundry's helpers.
Choosing Your First Pair
Pick a pair with:
- High volume on both pools. Low-volume = stale prices but no fill depth.
- Same fee tier on both pools (e.g. 5bps Uniswap V3 vs 5bps Aerodrome). Different fee tiers complicate math.
- Direct pair, not multi-hop. Save multi-hop for later.
- Liquid base asset. ETH/USDC, ETH/USDT, USDC/USDT. Not exotic.
Recommended starter pair on Base: WETH/USDC. Two pools to compare:
- Aerodrome stable pool (fee 1bps)
- Uniswap V3 0.05% tier (fee 5bps)
These have hundreds of millions in TVL and predictable spread behavior.
The Tools
You need:
- A WSS endpoint to your target chain (e.g. Base WSS via Alchemy or self-hosted).
- Foundry installed locally for simulation.
- A test wallet funded with $50–100 of ETH for gas + working capital.
- ABI for both DEXes' router contracts.
This entire setup runs on a laptop. We're not in production yet.
Step 1: Watch Spread
Subscribe to swaps on both pools and compute spread on every block:
const aero = new ethers.Contract(AERO_POOL, AERO_ABI, provider);
const uni = new ethers.Contract(UNI_POOL, UNI_ABI, provider);
provider.on('block', async (n) => {
const [r0a, r1a] = await aero.getReserves();
const [r0u, r1u] = await uni.getReserves();
// ... compute price each side, then spread
});
Watch for an hour. Most blocks: spread <1bp. Some blocks (after large swaps): spread 5–30bp. Note the frequency of profitable spreads.
Step 2: Profitability Threshold
Define "profitable" precisely. For a $5,000 working size:
gross_profit = trade_size × spread_bps / 10000
gas_cost = gas_units × gas_price (in USD)
slippage = trade_size × your_slippage_buffer
fees_paid = trade_size × (fee_A + fee_B)
net_profit = gross_profit − gas_cost − slippage − fees_paid
On Base, gas for a 2-pool arb tx is ~250–400k gas units. At typical 2026 gas price of ~0.001 gwei, that's $0.04–$0.10. Cheap.
For a $5,000 trade with 8bp spread:
- Gross: $4.00
- Gas: $0.08
- Slippage buffer: $1.50 (3bp)
- Fees: $3.00 (paid via pool slippage already)
- Net: ~$2.40
That's a thin margin. Filter for spreads > 10bp on a $5k trade as your minimum.
Step 3: Calldata Construction
Two-pool arb calldata structure:
1. Approve router_B (if not pre-approved) - skip in production with infinite approvals
2. swap on router_B: USDC → WETH
3. swap on router_A: WETH → USDC
4. require(usdc_out > usdc_in + min_profit, "unprofitable")
For simplicity, use a dispatcher contract that does all four in one call. FRB Agent ships one (verified on every chain), or write your own minimal contract.
Minimal Solidity:
function executeArb(
bytes calldata buyCalldata,
bytes calldata sellCalldata,
address tokenOut,
uint256 minProfit
) external {
uint256 startBalance = IERC20(tokenOut).balanceOf(address(this));
(bool ok1, ) = router_B.call(buyCalldata);
require(ok1, "buy failed");
(bool ok2, ) = router_A.call(sellCalldata);
require(ok2, "sell failed");
uint256 endBalance = IERC20(tokenOut).balanceOf(address(this));
require(endBalance >= startBalance + minProfit, "no profit");
}
Real implementations are harder (handle native ETH, multiple routers, callbacks for V3 pools). Treat the above as illustrative.
Step 4: Simulate Every Attempt
Before submitting, simulate the tx on a forked chain:
anvil --fork-url $RPC_URL --fork-block-number latest
cast send --private-key $TEST_KEY $DISPATCHER \
"executeArb(bytes,bytes,address,uint256)" \
$BUY_CALLDATA $SELL_CALLDATA $WETH 1000000
If simulation succeeds with profit > min_profit, you're cleared to send. Skipping simulation is the #1 cause of reverts in beginner bots.
Step 5: Submit
For Base, submit to the public mempool (no PBS in 2026 yet on most L2s). Use a moderate priority fee to land in the next block:
const tx = await wallet.sendTransaction({
to: DISPATCHER,
data: dispatchCalldata,
gasLimit: 500000,
maxFeePerGas: ethers.parseUnits('0.0015', 'gwei'),
maxPriorityFeePerGas: ethers.parseUnits('0.0005', 'gwei')
});
const receipt = await tx.wait();
console.log('Status:', receipt.status, 'Gas used:', receipt.gasUsed);
If receipt.status === 1, you landed it. Look at receipt.logs for the actual profit captured.
Step 6: Measure What Happened
Your first 20 attempts will mostly:
- Revert (missed by another arber): 50–70%
- Land but underwhelm vs simulation: 15–25%
- Land profitably: 10–25%
That's normal. The ratio improves as you tune (lower latency, better signal filtering, smarter sizing).
What to log:
- Spread observed at decision time
- Spread observed at landing time
- Predicted profit vs realized profit
- Gas used vs estimated
- Why each revert happened (Foundry traces help)
Step 7: Tighten the Loop
After 100 attempts, you'll see patterns:
- Spreads above some threshold land more reliably
- Certain hours have higher arb frequency
- Specific competitors are faster than you in certain windows
Use the patterns to filter what you submit. Submitting fewer, higher-quality attempts beats spraying.
Common First-Bot Bugs
- Approval missing: contract reverts on first call. Add infinite approval in deploy script.
- Wrong pool fee tier: math says profit, real swap says loss because the math used 5bp pool but called 30bp pool.
- Stale reserves: read reserves at block N, submit at block N+1 — state drifted. Re-read inside the contract or use callback-based pricing.
- Slippage cap too tight: trades that would be slightly profitable revert. Calibrate to actual realized vs simulated.
- Token decimals: USDC has 6, WETH has 18. Off-by-12 in math is a common bug.
What's Next After Two-Pool
Once two-pool arb runs cleanly:
- Three-pool triangular arb: USDC → WETH → DAI → USDC.
- Multi-DEX with pathfinding: dynamically pick which two of N pools to use.
- JIT liquidity: provide and pull LP around an incoming swap.
- Cross-chain arb: requires bridge cost modelling (advanced).
The skill stack compounds: pool math → calldata → simulation → latency → bidding. Two-pool atomic is the foundation.
FAQ
Do I have to write the smart contract myself?
For learning, yes. For production, FRB Agent ships verified dispatcher contracts on every supported chain. Use the built-ins in production; write your own in dev to understand them.
Why are most attempts unprofitable?
You're competing with bots that have lower latency, better signal filtering, and pre-approved liquidity. Beginners get the leftover spreads. That's fine — there are leftovers worth catching.
How much capital should I start with for arb?
$2k–5k. Below that, gas-to-capital is hostile. Above that, you can scale once tuned.
Is this strategy sandwich attack?
No. Two-pool arb between DEXes doesn't extract from a user's swap — it equalizes prices across pools. It's protocol-positive.
Can I run this on Ethereum L1 instead?
Technically yes, economically painful as a beginner. L1 gas of $10–50 per attempt eats most spreads at <$50k trade size. Start on L2.
Related Reading
- Advanced ETH Arbitrage Strategies
- Mempool Scanning Filters & Latency
- Backrun vs Sandwich Strategy
- How to Backtest a MEV Strategy
- Crypto Arbitrage Bot Guide
This is an engineering tutorial. Deploy capital only after running on testnet or with paper-trade mode. Not financial advice.
Step after reading
Launch FRB dashboard
Connect your wallet, pair the node client with a 6-character PIN, and assign the contract mentioned above.
Need the signed build?
Download & verify FRB
Grab the latest installer, compare SHA‑256 to Releases, then follow the Safe start checklist.
Check Releases & SHA‑256Related Articles
Further reading & tools
Discussion
No notes yet. Add the first observation, or share the link with your team on X (@MCFRB).