design: research parallel split routing (amount split across paths) #1203
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#1203
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Investigate aggregator-style split routing: splitting one offer amount across two or more independent paths in a single settlement (for example 70% A→B and 30% A→C→B), versus today’s single-winner path.
This is a research + architecture spike. Do not ship a new CosmWasm message, indexer
solver_version, retail Swap UI, or columbus-5 migrate in this work item. The deliverable is a written recommendation: go / no-go / later, with search complexity, execute architecture, gas, tax / wrap / greedy interactions, and a threat model.Not this ticket (related, do not reopen or expand):
pool_input+book_input)split_router(retail Split treasury)Docs already name the gap without a ticket: route-solver.md Future work and Non-goals (top-5 paths, no convex split search across paths). #310 mentioned “convex split search” as later algorithm work.
Industry references for the research note (explanatory, not a mandate to copy): 1inch / 0x / Uniswap smart-order routing (amount split across pools), Jupiter split routes. CL8Y also has a hybrid AMM + FIFO book per hop, community-tax net ranking (#615), and Terra Classic gas that already bites multi-hop (#681, #587). Those constraints are first-class in the write-up.
Current codebase
Solver picks one path
GET /api/v1/route/solvewithamount_inruns global best execution (ADR 0002, route-solver.md):route_paths::find_paths_top_k,MAX_PATH_CANDIDATES = 5.book_inputon a 17-point grid + 2 coordinate-descent passes (hybrid_route_opt.rs).estimated_amount_out_net(#615);estimated_amount_outstays pre-taxraw_out.hops[]/router_operations[]/intermediate_tokens[].OPTIMALITY_SCOPE(best_execution.rs):Losing paths are discarded. There is no amount allocation across two winners. Alternate paths are compared only as whole-offer candidates.
Regression that encodes winner-takes-all:
route_solve_global_picks_best_path_not_shortestinindexer/tests/api_route_solve.rs.Discovery GET (no
amount_in) and POST/route/solvestill use first BFS path only. POSThybrid_by_hopoverrides the pool/book split on that one path.Router execute is one sequential chain
smartcontracts/contracts/router/src/msg.rsExecuteSwapOperations { operations, max_spread, minimum_receive, … }is TerraSwap-shaped: one CW20Sendhook, ≤MAX_HOPS(4) sequential hops. Each hop’s offer is the balance-delta of the previous ask (router/src/contract.rs, hop accounting skill). There is no “fan-out 40% / 60% then join” message.Direct 1-hop retail still goes pair.swap, not the router (
swapRouting.ts, #249).SimulateSwapOperationsprices one operation list. A parallel split would need N sims plus a join, or a new query.Frontend assumes one route
cw20RouteSolveQuote.tsmaps onerouter_operationsarray.swapRouteDisplay.tsreconcilesintermediate_tokenswith that single ops path (#450 / SEC-I02 H09). A split quote would be a new spoofing surface if any displayed path disagreed with any submitted leg.SWAP_GAS_PER_HOP,ROUTER_SWAP_OPS_MIN_GAS_PER_HOP, hybrid maker/scan —constants.ts,hybridSwapGas.ts). Auto-gas already overshoots on hybrid multi-hop (#681). Two full routes in one tx (or two msgs) is a new gas model.executeTerraContractMulti) for wrap+router, pay-invoice, LP rollback — not for splitting one CW20 offer across two router hooks. CW20Senddelivers the full balance to one contract per message.Search cost today (before any path-split)
Worst-case pair sims:
LCD_HYBRID_SIM_BUDGET = 5 × 4 × 85 = 1700(best_execution.rs)./route/solveis LCD-heavy (10 RPS/IP). Distant solves were a hang class (#484, #485). Naive “split fractions × path pairs × hybrid grid” multiplies that budget.Why the work is needed
Constant-product impact is convex: two half-size fills on disjoint liquidity can beat one full-size fill on the single best path. That is the whole point of aggregator split routing. CL8Y already enumerates up to five paths and then throws away all but one, so the topology work is paid and the convex split is never tried.
When two factory pairs share legs (or a direct pair plus a 2-hop via a deep hub), large retail size can walk one AMM + book while a second venue stays unused. Makers on the unused pair see no flow; takers eat extra impact. That is the same incentive story as #501 / #704, one layer up (across paths, not pool vs book).
Without a researched no-go, the next implementer is likely to:
ExecuteSwapOperationsand strand CW20 in the router on partial fail.estimated_amount_out_netincorrectly when buy-tax applies to only one leg (#615).This spike exists so the next ticket is either a bounded algorithm + execute design, or an explicit do not ship with the convex-search line in route-solver.md updated.
Constraints / guardrails
solver_version, no Swap UI, no indexer API additive fields on columbus-5. Simulations stay in/tmpor LocalTerra; do not change production quoting.max_spread/minimum_receive/ per-hopmin_returnremain authoritative (ADR 0001). A split quote is still a snapshot.MAX_HOPS = 4is per operation list, not a global “8 hops if you run two routers.” Any execute design must state hop accounting.min_returnuses raw; Option-2 wasm skips middle-hop sells of catalogued tax tokens. Unmigrated 11611 Honest. A path split that sells a tax token on one branch and not the other must not hide extra debit.executeTerraContractMultias a free splitter: two CW20Sends need two balances (or an on-chain splitter). Partial fill (path A succeeds, path Bmax_spread) is a user-funds design choice — all-or-nothing vs best-effort must be explicit.LCD_HYBRID_SIM_BUDGET/#485p95 without a version bump plan.Relevant files
indexer/src/api/best_execution.rsMAX_PATH_CANDIDATES,OPTIMALITY_SCOPE, sim budgetindexer/src/api/route_paths.rsindexer/src/api/hybrid_route_opt.rsindexer/src/api/route_solver.rspool_onlydocs/route-solver.mddocs/adr/0002-global-best-execution-route-solver.mddocs/adr/0001-hybrid-quoting-and-routing.mdskills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.mdpicks_best_path_not_shortestskills/AGENTS_INDEXER_TAX_AWARE_ROUTING.mdsmartcontracts/contracts/router/src/{msg,contract}.rsMAX_HOPS; no fan-outskills/AGENTS_ROUTER_HOP_ACCOUNTING.mdfrontend-dapp/src/utils/cw20RouteSolveQuote.tsfrontend-dapp/src/utils/swapRouteDisplay.tsfrontend-dapp/src/services/terraclassic/swapRouting.tsfrontend-dapp/src/services/terraclassic/transactions.tsfrontend-dapp/src/utils/constants.ts+hybridSwapGas.tsindexer/tests/api_route_solve.rssmartcontracts/contracts/community-tax-token/src/tax.rssplit_tax— do not mix into this designRecommended direction
Define the product question in one sentence: “For a given
(token_in, token_out, amount_in)snapshot, does splitting the offer across ≤K factory paths raiseestimated_amount_out_netenough to justify extra gas, quote latency, and execute complexity?” Answer with numbers, not slogans.Measure first (LocalTerra or recorded mainnet mirrors, no prod change).
AMOUNT_CACHE_BUCKET= 1e6 raw), compare: (a) current winner 100%; (b) discrete splits{0, 25, 50, 75, 100}%on path1 vs path2, each path still running the existing hybrid grid; (c) greedy-only vs Pattern C on each leg.SWAP_GAS_PER_HOP/ hybrid maker gas, not a guessed fee).token_out(net vs raw) and with an empty book on one path (#493 short-circuit).Execute architectures to rank (go / no-go per option):
Sendcannot fork.ExecuteSplitOperations(new wasm): one hook, N sequential sub-chains with explicitoffer_portion, join ontoken_out, oneminimum_receiveon the sum. Needs migrate, hop-cap policy, dust/remainder rules, blacklist per hop. Highest product fidelity; highest review cost.legs[]with portions; clients who already split off-chain can POST. Official dApp stays single-path until B exists. Documents the math without retail risk.If any option is go, bound the search before writing code. Suggested default to disprove or confirm, not to ship: at most 2 paths, split grid 5 fractions, each path reuses current hybrid optimizer (no nested 17×17). New budget must fit
#485and LCD-heavy RPS. Bumpsolver_versiononly in a follow-up feat.UI / security (only if B or C is recommended for a later feat): one Share/quote surface must list all legs; H09-style alignment on every ops list; never display path A and sign path B+C leftovers. Silent fail-closed on freeze/gem/hostile ids (#489 lecture ban still applies).
Write the note on this issue (research gate): findings, recommendation, further study, tradeoffs, alternatives, then raw evidence (links, tables,
/tmpsim commands). Do not implement product code in that gate.Acceptance criteria
LCD_HYBRID_SIM_BUDGETand quote p95; whether empty-book short-circuit (#493) still holds.max_spread/ pause / blacklist after leg 1 would have filled; recommended all-or-nothing vs best-effort.docs/route-solver.mdFuture work / Non-goals (follow-up docs ticket OK; do not silently leave “convex split search” as implied upcoming work).Test plan (research + later-feat paths)
Research gate (this ticket):
raw_out/net_out; document if split winstoken_outmin_returndiscussion uses rawSWAP_GAS_PER_HOP/ hybrid maker; call out #681-class overshoothybrid_by_hopunchangedlegs[]unless option CIf a later feat ships (not this ticket), functional paths to require then:
amount_inminimum_receiveon sum of legs (architecture B)pool_only=truemake verify-issue-209/ 501 / 615 / 485 stay greenTest plan (attack, hack, and abuse)
token_in/javascript:/ overlong bech32 in a second legshowGemson a hidden split leg (production)max_maker_fills=2^32-1on each split legmin_receiveon sum; no “MEV-aware” claimlegsqueryVerification criteria
/tmpsim). No prod config change.route-solver.mdedits for Future work.python3 scripts/check_route_solver_docs.py.Out of scope
solver_version./route/solve.Sku::SplitRouter.cl8y-agent-control: queued
implementjob4e916c11-c7da-4c64-90f8-f206f4074dcf(not executed; no Hetzner VM)./agent implement
cl8y-agent-control: queued
implementjobeed2ef36-124e-414b-9a9e-eba25cde1d9b(not executed; no Hetzner VM).cl8y-agent-control: queued
design_authorjob36033125-b538-4c78-930e-72922104e336(not executed; no Hetzner VM).cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.