วัดแล้วก่อนลงมือ: การแบ่ง 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.
| case | today (split) | hop-level split | extra sub-routes | gain after extra gas |
|---|---|---|---|---|
| BNB · BNB → USDT 100 | 71,542.79 | 71,543.37 | +10 | −0.05 bps |
| BNB · BNB → USDT 1,000 | 712,542.14 | 713,449.36 | +34 | +12.68 |
| BNB · BNB → ETH 300 | 86.4195 | 86.4248 | +11 | +0.55 |
| BNB · ETH → CAKE 100 | 112,162.84 | 112,383.39 | +37 | +19.51 |
| BNB · ETH → CAKE 1,000 (thin) | 603,158.88 | 915,346.36 | +82 | +5,176 (do not trade) |
| HyperEVM · HYPE → USDT0 1,000 | 77,790.99 | 77,797.40 | +6 | +0.82 |
| HyperEVM · kHYPE → USDT0 2,000 | 159,016.70 | 159,054.97 | +5 | +2.40 |
| HyperEVM · USDT0 → WHYPE 10,000 | 128.2350 | 128.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.
getCandidatePaths forbids two candidate paths from sharing a pool, because every path is evaluated on a fresh snapshot: a shared pool's second use would be over-estimated.(routes[], tokens[]); there is no "hop k splits into sub-routes" node.| phase | what | effort | acceptance |
|---|---|---|---|
| 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 it | contract + SDK | only 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.