Moro SDK · ResearchReport #8 · updated 2026-09-14

Is there arbitrage between pools? 489 scan rounds on HyperEVM and BSC, and pancake-bakery-arbitrage re-run through the SDK

@moromoro/moro-sdk 1.4.3 → 1.4.3-dev.4 (feat/arb) HyperEVM · BSC 2026-09-13 17:20Z → 2026-09-14 02:56Z prices from the SDK's pool models · executions on mainnet forks
TL;DR

สรุปสั้น: สแกนหา arbitrage ระหว่าง pool ด้วย SDK รวม 489 รอบบนสองเชน: Hybra V3↔V4 ข้ามคืน (158 รอบ), ทุกคู่ pool บน HyperEVM (299 รอบ ประมาณ 7,244 cycle ต่อรอบ) และทำซ้ำ repo pancake-bakery-arbitrage บน BSC (32 รอบ 7,623 cycle ต่อรอบ) โดยใช้ตัวหาขนาดเทรดที่ดีที่สุดของ SDK ไม่มี cycle ไหนได้กำไรหลังหัก gas มี cycle ที่ได้กำไรก่อนหัก gas ทุกรอบ (1,471 บน HyperEVM, 200 บน BSC) แต่มากสุดแค่ $0.0015 และ $0.0074 ขณะที่ gas ต่อธุรกรรมอยู่ที่ $0.0066 และ $0.0134 คู่ router ของ repo ต้นฉบับขาดทุนทุกขนาด (ที่ 1,000 BNB ขาดทุน 83–95%) เพราะ pool นอกจาก Pancake แทบไม่มีสภาพคล่องแล้ว ฝั่ง execution: สัญญา MoroFlashArb ทำงานบน fork ทั้งสองเชน รวมถึง flash จาก CL pool บน HyperEVM แล้วขายผ่าน Moro router แบบ split ที่ SDK สร้าง calldata ให้

In short: across 489 rounds the SDK found cycles that make money before gas in almost every round, and none that survive gas. The largest was $0.0074 on BSC and $0.0015 on HyperEVM, against $0.0134 and $0.0066 of gas per transaction. The 2021 repo's own router pairs lose at every size today. The execution path it sketches, a flash swap repaid in the callback, works on both chains' forks, and with the SDK the second leg can be a full Moro router split.

0
net-positive cycles
158 Hybra · 299 HyperEVM · 32 BSC rounds
$0.0074
best profit before gas
BSC; HyperEVM $0.0015; gas $0.007–$0.013
0 / 20
repo router pairs profitable at any size
−8287 to −9493 bps at 1,000 BNB
2 / 2
fork executions of MoroFlashArb
BSC V2 flash · HyperEVM CL flash + SDK split

1 · What was scanned

scanchainscopewindow (UTC)roundscycles / roundprofit before gasafter gas
Hybra V3 ↔ V4HyperEVM64 pairs on both versions, both directions, 10 sizes $10–$10k13 Sep 17:20 → 23:591582,56000
all pool pairs, size gridHyperEVM273 Hybra-listed pairs × every SDK route, 5 leg modes, 10 sizes14 Sep 00:22 → 02:56299≈ 7,500grid best +4.43 bps once0
all pool pairs, optimal sizeHyperEVMsame, best 12 pool pairs per start token refined to their optimal size14 Sep 01:48 → 02:56377,2441,4710
pancake-bakery-arbitrage, generalisedBSC8 routing tokens × 52 SDK routes; repo-style V2 forks and all pools; optimal size14 Sep 01:50 → 02:56327,6232000

A cycle is start token → intermediate on one pool, then back on a different pool. Each round fetches pool state once (createRouteScanner + fetchAllReserves), then prices every cycle in memory with the SDK's pool models. The optimizer (optimizeCycleSize) searches the size that maximises profit: a two-leg cycle's profit is concave in size for constant-product and concentrated-liquidity pools and gas does not depend on size, so a ternary search finds the true optimum. BSC rounds took a median of 3.6 s.

2 · Why nothing clears: the money is smaller than the cost of taking it

HyperEVMBSC
cycles profitable before gas per round (median)406
best profit before gas$0.0015$0.0074
that cycleUBTC/USD₮0 at $0.95, prjx-v3-500 → hybra-v4-10BUSD→USDT→BUSD at $745, PNCK:V3F100 → DODO:V1USDT
median profit before gas of those cycles$0.00016—
gas per round trip (median, at live gas price)$0.0066$0.0134
best spread at a fixed $1,000 (all pools)grid at $10: median −0.15 bpsbest +0.06 bps, median −0.80 bps
best spread at $1,000, V2 forks only (the repo's view)—median −50.5 bps

Prices between pools on both chains sit within a fraction of a basis point of each other once fees are paid. The optimizer finds a profitable size almost every round, but it is small because a larger trade moves the pool faster than the spread pays. The overnight Hybra run agrees: its closest pairs were USDC/USDM (−$0.002 at $50) and USDC/USD₮0 (−$0.003 at $10). On BSC the recurring closest cycle was BUSD→USDT→BUSD DODO:V1USDT → PNCK:V3F100, best in 20 of 32 rounds.

modecyclebps at $1,000poolsfamilies
all poolsBUSD → USDT → BUSD+0.06PNCK:V3F100 → DODO:V1USDTcl/other
all poolsUSDT → BUSD → USDT+0.06DODO:V1USDT → PNCK:V3F100other/cl
all poolsUSDC → BUSD → USDC−1.54DODO:V1BUSD → PNCK:V3F100other/cl
all poolsDAI → USDT → DAI−2.19UNI:V3F100 → PNCK:V3F100cl/cl
all poolsWBNB → USDT → WBNB−2.59PNCK:V3F500 → PNCK:V3F100cl/cl
all poolsWETH → WBNB → WETH−4.14PNCK:V3F100 → PNCK:V3F500cl/cl
all poolsBTCB → WBNB → BTCB−5.40PNCK:V3F500 → PNCK:V3F100cl/cl
all poolsCAKE → WBNB → CAKE−19.21PNCK:V3F500 → PANCAKEV2cl/v2

BSC, last round: the best cycle per start token and mode at $1,000.

3 · pancake-bakery-arbitrage, run again in 2026

yuyasugano/pancake-bakery-arbitrage (2021–22) is a Node loop and a flash-swap contract. On every new BSC block it calls the contract's check() for six hard-coded BNB/BUSD router pairs (Pancake V2 against Panther, Ape and Bakery), asks each router's getAmountsOut for a fixed 1,000 BNB round trip, and when the profit clears $0.60 after gas it sends startArbitrage: borrow BUSD from one V2 pair, sell it on the other router, repay the pair in BNB inside pancakeCall.

The market it was written for is gone

repo (2021)measured 2026-09-14
BSC block time≈ 3 s ("<3 seconds" budget)0.45 s (400 blocks in 180 s)
gas price5 gwei0.05 gwei
WBNB/BUSD liquidity: Pancake V2 · Ape · Bakery · Panther—$9.04M · $181k · $135k · $12.5k
WBNB/USDT liquidity: Pancake V2 · Bakery · Ape · Panther—$77.7M · $20.4k · $4.0k · $254

The repo's exact question through the SDK (block 121,752,392, BNB $714.89)

leg 1 → leg 2flash on leg 1profit at 1,000 BNBin USDbpsbps at 1 BNBprofitable size
PANCAKEV2 → APEyes−889.9 BNB−$636,199−8899−121.4none
PANCAKEV2 → PANTHERyes−891.7 BNB−$637,446−8917−123.4none
PANCAKEV2 → BAKERYyes−915.3 BNB−$654,346−9153−158.9none
PANCAKEV2 → BISWAPyes−828.7 BNB−$592,404−8287−90.8none
APE → PANCAKEV2yes−890.2 BNB−$636,394−8902−127.6none
APE → PANTHERyes−941.2 BNB−$672,862−9412−196.8none
APE → BAKERYyes−949.0 BNB−$678,406−9490−231.9none
APE → BISWAPyes−926.6 BNB−$662,386−9266−164.7none
PANTHER → PANCAKEV2yes−891.9 BNB−$637,634−8919−128.4none
PANTHER → APEyes−941.2 BNB−$672,860−9412−195.7none
PANTHER → BAKERYyes−949.3 BNB−$678,676−9493−232.7none
PANTHER → BISWAPyes−927.3 BNB−$662,943−9273−165.5none
BAKERY → PANCAKEV2no−915.5 BNB−$654,491−9155−162.6none
BAKERY → APEno−949.0 BNB−$678,396−9490−229.4none
BAKERY → PANTHERno−949.3 BNB−$678,668−9493−231.3none
BAKERY → BISWAPno−938.8 BNB−$671,146−9388−199.4none
BISWAP → PANCAKEV2yes−829.0 BNB−$592,669−8290−94.5none
BISWAP → APEyes−926.5 BNB−$662,354−9265−162.3none
BISWAP → PANTHERyes−927.3 BNB−$662,914−9273−164.3none
BISWAP → BAKERYyes−938.8 BNB−$671,127−9388−199.5none

WBNB → BUSD → WBNB on every ordered pair of Pancake V2, Ape, Panther, Bakery and Biswap. At the repo's 1,000 BNB ($715k) the second pool is far too small, so the trip loses most of its value; at 1 BNB it still loses 91–233 bps, and the optimizer finds no profitable size for any pair. A 2,001-point log grid on the closest pair (PANCAKEV2 → BISWAP) peaks at -4.3e-09 BNB for a 0.000001 BNB trade: the only "profitable" size is no trade.

Which pools can lend (read from pool bytecode)

poolflash entrycallback it calls
Pancake V2 · Ape (BSC)swap(…, bytes)pancakeCall
Biswap (BSC)swap(…, bytes)BiswapCall
Panther, SDK factory (BSC)swap(…, bytes)pantherCall
Panther, the repo's factory (BSC)swap(…, bytes)none of the known names
Bakery (BSC)swap without a data argumentno flash swap: "bakery > pancake" can never execute
Hybra V3 and V4 (HyperEVM)flash() and CL swapuniswapV3SwapCallback, uniswapV3FlashCallback
Hybra V2 (HyperEVM)swap(…, bytes)hook (Velodrome style)
HyperSwap V3 (HyperEVM, seen in a fork trace)CL swaphyperswapV3SwapCallback

Bugs and risks in the repo

4 · What the SDK adds to that design

repowith the Moro SDK (1.4.3-dev.4, branch feat/arb)
check() eth_call per pair per blockone state fetch per round, then 7,623 (BSC) or 7,244 (HyperEVM) cycles priced in memory: createRouteScanner, searchCycles
4 V2 routers, one pair52 BSC routes and 31 HyperEVM routes: V2 forks, Pancake and Uniswap V3, Algebra, Curve, DODO, Hybra V3/V4, per-leg route filters
fixed 1,000 BNBoptimizeCycleSize: the profit-maximising size within 1 ppm, about 150 in-memory evaluations
leg 2 on a single routerpopulateSwap: Moro router calldata for a contract trader, so leg 2 can be a best-rate split across every DEX
CoinGecko pricesUSD prices from the same pool state as the cycle
Flashswap.solMoroFlashArb sketch: active-pool check in every callback, immutable executor, exact approvals reset to zero, owner-only start, real block deadline, on-chain V2 repay maths, V2 + CL legs

5 · Execution on mainnet forks

testchain · blockresultgas
flash-borrow 1,000 BUSD from Pancake V2 WBNB/BUSD, sell on ApeSwap, repay in WBNBBSC · latest (two runs)executed; pair K preserved; the round trip cost 0.0214–0.0225 WBNB (≈ $15–16), covered by pre-funding217,127 (startV2)
same trip without pre-fundingBSCreverts atomically: nothing borrowed is kept—
callback from anyone but the active pool · non-owner start · expired blockBSCall three revert with BadCallback, NotOwner, Expired—
flash 1 WHYPE on Hybra V3 WHYPE/USD₮0 0.25 % (79.10381 USD₮0 out), sell through the Moro router: swapSplit over 4 routes, calldata from populateSwapHyperEVM · 45,849,875executed and repaid in the callback; the round trip cost 0.00699 WHYPE (≈ 70 bps, mostly the 0.25 % pool fee)710,843 (startCL)

Two things learned on the way. getDepositAddress returns Moro's deposit contract for a route, not the pool (3,234 bytes, owner-controlled, calls pool.swap itself), so a flash leg needs the real pool address. And HyperEVM's small blocks carry 3,000,000 gas (measured at the head); a flash cycle with a ten-route split used 711k, so it fits, but a wider split may not.

6 · A better scope for the next scan

Code: korrio/moro-sdk-optimized · feat/arb (searchCycles, optimizeCycleSize, populateSwap, SDK 1.4.3-dev.4) · moro-bot: src/scan.ts (Hybra V3↔V4), src/cycles.ts (HyperEVM), src/pba.ts (BSC), flash/ (MoroFlashArb and fork tests). Prices are the SDK's off-chain pool models; executions are on forks, not mainnet. MoroFlashArb is a research sketch: not audited, not deployed.