Moro SDK · ResearchReport #2
Moro Swap · Reports · #1 · #2

Optimisation round 1: same prices, 92 % fewer requests

@moromoro/moro-sdk 1.1.0 → 1.2.0 A/B measured 2026-09-13 HyperEVM · BNB Chain branch feat/optimize
TL;DR

เวอร์ชัน 1.2.0 ให้ราคาเท่าเดิมทุกหลัก แต่ยิง RPC น้อยลง 92 % บน HyperEVM — จาก 26 request ต่อ quote เหลือ 2 — เพราะไม่ถาม pool ที่ไม่มีอยู่จริงอีกต่อไป ทดสอบแบบ A/B กับเวอร์ชัน 1.1.0 บน npm ที่ block เดียวกัน 18 กรณี ราคาต่างกัน 0.00 bps ทุกกรณี

Report #1 found that most of the SDK's RPC calls went to pools that were never deployed. Round 1 fixes exactly that problem (a pool registry plus a runtime negative cache), folds the extra decimals() request into the multicall, caches immutable state, retunes batch sizes and disables routes that never have a live pool. Every change was A/B-tested against the published 1.1.0 with the same workload at the same block. Prices are byte-identical on all 18 cases.

26 → 2
requests / quote · HyperEVM
−92 %
1,217 → 408
pool-state calls · HyperEVM
−66 %, wasted 735 → 0
6 → 5
requests / quote · BNB
calls −34 %, time −13…37 %
0.00
Δ price (bps), 18 / 18 cases
same route chosen every time
≈4 → ≈50
quotes / min on the free HyperEVM RPC
100 req/min budget
676 → 52
Alchemy CU per HyperEVM quote
44 k → 577 k quotes / month free
Try it yourself: on Moro Swap, get any quote, then press “Compare with previous SDK v1.1.0”. The page runs the same trade through both versions and shows requests, calls, bytes, time and the amount you receive side by side.

1 · Requests per quote, before → after

One request = one JSON-RPC call to the node. This is what rate limits and most paid plans count.
v1.1.0v1.2.0

BNB's official endpoint is fastest with small batches (report #1 §4), so the BNB batch size was set to 300 as a compromise: fewer requests than 1.1.0 and 13–37 % faster. HyperEVM uses 250-call batches: two requests per quote; the public node rejects batches of ~1,300.

2 · Pool-state calls inside the multicalls

Calls the node actually executes. The wasted part (pools that do not exist or revert) went from 60–62 % to 0 % on HyperEVM.
v1.1.0v1.2.0
Table view
paircalls 1.1.0calls 1.2.0wasted 1.1.0wasted 1.2.0bytes 1.1.0bytes 1.2.0

3 · Price check: the only metric that matters to a trader

Both versions quoted 1 / 100 / 1,000 units of three pairs per chain, twice, back-to-back at a pinned block. Δ = (1.2.0 − 1.1.0) / 1.1.0 in basis points; the gate fails a round at −5 bps.

chaincasesworst Δ pricesame route chosenverdict
HyperEVMHYPE→USDT0, USDT0→WHYPE, kHYPE→USDT0 × 1 / 100 / 1,0000.00 bps9 / 9pass
BNB ChainBNB→USDT, BUSD→USDT, ETH→CAKE × 1 / 100 / 1,0000.00 bps9 / 9pass
Caveat we discovered while building the harness. The HyperEVM public endpoint ignores the block tag on eth_call and silently serves the latest block (BNB's honors it). So on that endpoint two quotes 16 s apart can differ by 1–2 bps purely from market movement. The harness now detects this, keeps A and B back-to-back, and warns. For price-exact claims use a provider that honors block tags (Alchemy, QuickNode, own node).

4 · What changed, in plain terms

#changeeffectrisk
R1Pool registry. A small table per chain (5.7 KB HyperEVM, 39 KB BNB) of which pools actually exist for each DEX route, generated by scanning the chain once. Pools known to be missing are never asked. Pairs outside the table are asked once and remembered for an hour if empty.HyperEVM calls −65 %, wasted → 0table is a snapshot; a brand-new pool is invisible until regenerated (round 2 moves it to a shared KV store)
R2Batch sizes per chain from measurements, plus a cap on parallel requests and a “premium” preset for paid nodes.26 → 2 requests on HyperEVM+15 % latency on HyperEVM's public node (two big batches vs 25 tiny ones)
R3Token decimals ride inside the multicall instead of their own request.−1 request per quotenone
R4Cache for immutable state (token decimals, factory fee, Curve A) with TTL.−172 calls per warm BNB quotefirst quote per session still pays
R5Disabled 8 routes that never had a live pool for any tested pair (5 HyperEVM fee tiers, 3 BNB long-tail DEXes).−231 calls per HyperEVM quoteneeds a periodic route-health check
R8Retry, back-off and batch splitting when a node rejects a request.no more whole-quote failuresretries add latency under sustained rate limiting
R10Interface fix: reserve checks optional for multi-asset models; unused dependencies removed.correctness; smaller installsnone

5 · What is still on the table

6 · Reproduce

# A = npm @moromoro/moro-sdk@1.1.0, B = this branch's build; prices gated at 5 bps
npx ts-node -T -O '{"module":"commonjs"}' benchmark/ab-test.ts --chain hyperevm --a baseline --b dist --runs 2 --pause 15000
npx ts-node -T -O '{"module":"commonjs"}' benchmark/ab-test.ts --chain bsc      --a baseline --b dist --runs 2

# regenerate a pool registry after adding routes or tokens
npx ts-node -T -O '{"module":"commonjs"}' benchmark/build-pool-registry.ts --chain hyperevm

Round write-up with every row: benchmark/rounds/1.md; raw samples: benchmark/results/2026-09-13-*-round1-v1.2.0.json; protocol: benchmark/AB-TESTING.md — all on korrio/moro-sdk-optimized. The npm package stays at 1.1.0 until the team publishes.

← Report #1 · All reports Report #2 · Optimisation round 1 · Moro SDK research