Moro SDK · ResearchReport #6

"Splits in splits": what splitting at every hop is worth, and how to get it

plan for @moromoro/moro-sdk 1.4.0 prototype measured 2026-09-13 HyperEVM · BNB Chain no contract change needed for phase 1
TL;DR

วัดแล้วก่อนลงมือ: การแบ่ง volume ซ้ำในทุก hop (เช่น 100 % BNB → PancakeV2 → BTCB แล้วค่อยแบ่ง 30/70 เข้า UniV3 สองพูล) ได้เพิ่ม ≤ 1 bps บนคู่ที่สภาพคล่องดี แต่ได้ +13 ถึง +20 bps บนเทรดขนาดกลางบน BNB (1,000 BNB → USDT, 100 ETH → CAKE) และข้อค้นพบสำคัญคือ router ปัจจุบันทำได้อยู่แล้ว เพราะมัน execute path ตามลำดับ — สิ่งที่ขาดคือ SDK ต้อง simulate แบบมี state ต่อเนื่อง ไม่ใช่ snapshot อิสระ

Today each split path forwards 100 % of a hop's output into one next route, and candidate paths may not share a pool. We prototyped hop-level splitting on real state to see what it is worth before designing anything.

+12.7 bps
1,000 BNB → USDT
net of extra gas
+19.5 bps
100 ETH → CAKE
net of extra gas
≤ 1 bps
liquid, normal-size trades
HYPE→USDT0, BNB→USDT 100, BNB→BTCB
phase 1
SDK-only
sequential simulation + greedy allocation

1 · Measured potential (prototype on real pool state)

casetoday (split)hop-level splitextra sub-routesgain after extra gas
BNB · BNB → USDT 10071,542.7971,543.37+10−0.05 bps
BNB · BNB → USDT 1,000712,542.14713,449.36+34+12.68
BNB · BNB → ETH 30086.419586.4248+11+0.55
BNB · ETH → CAKE 100112,162.84112,383.39+37+19.51
BNB · ETH → CAKE 1,000 (thin)603,158.88915,346.36+82+5,176 (do not trade)
HyperEVM · HYPE → USDT0 1,00077,790.9977,797.40+6+0.82
HyperEVM · kHYPE → USDT0 2,000159,016.70159,054.97+5+2.40
HyperEVM · USDT0 → WHYPE 10,000128.2350128.2361+8+0.08

Method: take the SDK's real split quote; on the same pool state, re-route every hop of every path through the best 1 %-grid split across all pools of that pair, keeping pool exclusivity. "today" is re-derived from that state and reconciles with the quote's own amountOut. Extra gas is priced with the quote's own dest-per-gas ratio. The winning splits consist of many small sub-routes (34–82), so a minimum share (≈ 3–5 %) will keep most of the price at a fraction of the gas.

2 · Why the SDK cannot express it today

3 · The insight: the router already executes paths sequentially

Two top-level paths that share their first pool execute on-chain exactly like a hop-level split: path A trades BNB → PancakeV2 → BTCB → UniV3 3000 → ETH, then path B trades BNB → PancakeV2 → BTCB → UniV3 500 → ETH. For constant-product and concentrated-liquidity pools, trading x₁ then x₂ equals trading x₁+x₂ at once (up to rounding). The price is reachable with SDK-only changes; a router feature only saves the duplicated first-hop gas (≈ 100–150k per duplicate: negligible on BNB / HyperEVM, material on Ethereum L1).

4 · Plan

phasewhateffortacceptance
1 · SDK only (1.4.0)mutable pool state in the models (simulateSwap, snapshot/restore); greedy marginal allocation with state carried across paths instead of the independent-path knapsack (seeded with today's solution); relax pool exclusivity; maxSplitPaths and minPathSharePercent; duplicated-hop gas in the optimiser; fork replay of the winning split to prove the simulation is honest≈ 6–7 daysΔ ≥ 0 on every A/B case; ≥ +10 bps on 1,000 BNB → USDT and ≥ +15 bps on 100 ETH → CAKE; gas ≤ 2× today; quote CPU ≤ 300 ms
2 · router-native hops_data per path becomes a list of hops { subRoutes[], volumes[] } over the same pair; the router splits the hop's balance by volumes and merges before the next hop; SDK encodes the tree, compressor packs itcontract + SDKonly if phase 1 shows duplicated-hop gas matters (Ethereum L1, very fragmented pairs)

Risks: execution order must match simulation (verify in the router source); simulation fidelity (fee-on-reserve rounding, tick edge cases) bounded by fork replay; dust paths and CPU (≈ 60k simulated hops per quote at 0.1 %). One latent SDK issue to fix on the way: DexManager keys pairs by the exact token string used at registration (config case for correspondent tokens) — normalise to lower-case.

Full plan: benchmark/rounds/4-splits-in-splits-plan.md; prototype: benchmark/layered-split.ts — korrio/moro-sdk-optimized.

← Report #5 · All reports Report #6 · Splits in splits · Moro SDK research