Moro SDK · ResearchReport #4

Best-rate round 3: does a finer split step buy a better price?

@moromoro/moro-sdk 1.3.1 → 1.3.2 measured 2026-09-13 HyperEVM · BNB Chain quote-side only · router unchanged
TL;DR

ลดขั้นการแบ่ง volume จาก 1 % เป็น 0.1 % ช่วยเฉพาะเทรดที่สภาพคล่องบาง — คู่ที่มีสภาพคล่องดีได้เพิ่มแค่ 0–0.5 bps แต่ 1,000 ETH → CAKE บน BNB ได้เพิ่ม +102 bps ทำใน SDK แล้วด้วยวิธี coarse-to-fine (~90 ms แทน 1.5–2 s) แต่ยังส่งขึ้น chain ไม่ได้ เพราะ router รับ _volumes เป็นเปอร์เซ็นต์จำนวนเต็ม

The split optimiser assigned whole-percent shares (100 steps). We re-ran the search on the same pool state at 1/200, 1/500 and 1/1000. On liquid pairs the 1 % grid is already within half a basis point of optimal; on a thin, large trade the fine grid spreads the order over 25 pools and gains a full percent. SDK 1.3.2 makes the grid configurable and reaches fine grids cheaply; executing them needs a router with sub-percent volumes.

+102 bps
1,000 ETH → CAKE at 0.1 %
13 → 25 paths
0–0.5 bps
liquid pairs
HYPE→USDT0, BNB→USDT
~90 ms
CPU at 0.1 % (coarse-to-fine)
full grid: 1.5–2 s
blocked
on-chain execution
router takes integer percent

1 · Gain vs grid, same pool state

case1 % → 0.5 %1 % → 0.2 %1 % → 0.1 %paths
HyperEVM · 100 / 1,000 HYPE → USDT0+0.00 bps+0.00+0.004 / 3
HyperEVM · 10,000 HYPE → USDT0+0.14+0.23+0.263
HyperEVM · 10,000 USDT0 → WHYPE+0.00+0.01+0.016 → 7
BNB · 1,000 BNB → USDT+0.18+0.40+0.505 → 15
BNB · 100 ETH → CAKE+0.50+0.87+1.057 → 11
BNB · 1,000 ETH → CAKE+44.8+92.2+101.913 → 25

Pool state fetched once per pair (maxStateAgeMs) and re-optimised at each grid, so the grid is the only variable. Twelve of the 25 paths on the big case hold 0.1–0.5 % each: shares a 1 % grid cannot express.

2 · How the fine grid is reached cheaply

Evaluating every candidate path at every 0.1 % step costs 20 × 1,000 path simulations plus a large knapsack: 1.5–2 s per quote on BNB. Instead, 1.3.2 computes the exact 1 % optimum as before, then re-evaluates only a ±2 % window around each path's share (and 0–2 % for the other candidates) on the fine grid and re-optimises. On every benchmark case it reproduced the full-grid optimum (+101.85 vs +101.86 bps) in ~90 ms.

casefull 0.1 % gridcoarse-to-finesame result?
BNB · 1,000 ETH → CAKE1,490 ms96 msyes
BNB · 1,000 BNB → USDT2,127 ms93 msyes
HyperEVM · 1,000 USDT0 → WHYPE1,187 ms28 msyes

The knapsack itself was rewritten (back-pointers, double-precision values, exact bigint re-sum), which also made the plain 1 % search 20–30 % faster. Regression A/B 1.3.1 vs 1.3.2 at the default grid: 0.00 bps, same paths, 18/18 cases.

3 · What blocks execution

MoroRouterV1.swapSplit reads _volumes as integer percent. A 0.1 % split cannot be sent to it. 1.3.2 keeps the default at 1 %, lets integrators quote on finer grids (splitResolution), exposes volumeUnits / volumeDenominator, and makes swap() refuse a fine-grid quote instead of rounding it into a different split than the one quoted.

To capture the gain on-chain the router needs _volumes in a finer unit (basis points) or, simpler, absolute source amounts per path. After that, routerVolumeDenominator: 1000 unlocks execution with no other SDK change. Try it on Moro Swap: tick "0.1 % split grid (quote only)" and quote 1,000 ETH → CAKE on BNB Chain.

Write-up: benchmark/rounds/3.md, reproduce with benchmark/split-resolution.ts — korrio/moro-sdk-optimized.

← Report #3 · All reports · Report #5 → Report #4 · Finer split grid · Moro SDK research