Distant-pair route solve: routinely <15s hybrid search + live search progress UX #485
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#485
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
Bundle of two related follow-ups from the #484 Calculating… hang fix:
Searching 3 of 12 pairs…) that updates about every second, so traders see progress instead of a static Calculating… state during long multihop solves.These belong together: meaningful progress copy requires the indexer to expose progress, and a <15s p95 makes the UX tolerable even when progress is still in flight.
Current codebase
Indexer (
GET /api/v1/route/solveglobal best execution)When
amount_inis set andpool_onlyis not true, the indexer runs global best execution:get_all_assets/get_all_pairs) per requestfind_paths_top_k→ up to 5 simple paths, ≤4 hops (MAX_PATH_CANDIDATES,GET_DEFAULT_MAX_HOPS)global_v4) or LCD (global_v3)SOLVE_CONCURRENCY = 5(#324)simulate_swap_operationswhenROUTER_ADDRESSset (fidelity check)solver_version,paths_considered,lcd_hybrid_queries/db_hybrid_queries,optimality_scope,hybrid_notes— only on final JSONROUTE_CACHE_TTL = **12s**, max 512 entries; key = `solver_versionCold distant-pair solves often exceed 15s (and can approach the frontend’s 45s
INDEXER_ROUTE_SOLVE_TIMEOUT_MS). There is no mid-solve progress channel (no SSE, no job/progress poll, no chunked status). Clients only learnpaths_consideredafter the HTTP response completes.Authoritative docs:
docs/route-solver.md, ADR 0002,skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md.Frontend (Swap + Trade market)
getRouteSolvewith 45s timeout and AbortSignal (#484).simQuoteRefetchIntervalavoids cancel/restart whilefetchStatus === 'fetching'.Skill:
skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.mdexplicitly lists this indexer speedup + product progress UX as out-of-scope follow-ups for #484.Why this is needed
Searching x of y pairs…/ path stage), updating ~1 Hz, not a frozen Calculating… label.Constraints / guardrails
MAX_PATH_CANDIDATES, hop cap, grid points) without bumpingsolver_version, documenting the newoptimality_scope, and updating ADR /docs/route-solver.md.INDEXER_ROUTE_SOLVE_TIMEOUT_MSbelow production distant-pair latency until indexer p95 is proven <15s.RATE_LIMIT_LCD_HEAVY_RPS(and tighten if a new endpoint is added).hybrid_notes/ on-chainmin_receive/max_spreadremain authoritative).isFetching.ROUTE_SOLVER_DB_HYBRID/global_v4) path; LCD grid remains fallback — do not reintroduce pair-level LCD grids as the happy path.Relevant files
Indexer
indexer/src/api/best_execution.rs— top-K solve, concurrency, meta, budget constantsindexer/src/api/route_solver.rs— HTTP handlers,ROUTE_CACHE_TTL/hybrid_cache_key/execute_hybrid_route_solveindexer/src/api/hybrid_route_opt.rs— joint hybrid gridindexer/src/api/route_paths.rs—find_paths_top_kindexer/src/api/db_orderbook_sim.rs— mirror load / pricingindexer/tests/api_route_solve.rs,indexer/tests/api_route_solve_db_hybrid.rsdocs/route-solver.md,docs/adr/0002-global-best-execution-route-solver.mdFrontend
frontend-dapp/src/pages/SwapPage.tsx— sim query + Calculating copyfrontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx— market sim queryfrontend-dapp/src/services/indexer/client.ts—getRouteSolve, timeouts, AbortSignalfrontend-dapp/src/utils/quoteDebounce.ts(+ tests)frontend-dapp/src/types/index.ts—IndexerRouteSolveResponsedocs/frontend.md(submit-quote / Calculating section)skills/AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.mdRecommended direction
A. Indexer latency / cache (<15s)
Investigate and ship a combination of (order by expected impact):
get_all_pairs/ assets reload per request when safe).solver_version).graph_ms,enum_ms,candidate_ms[],router_sim_ms,cache_hit) in logs/metrics and optionally response debug fields (gated).Do not “fix” latency only by truncating search without documenting
search_truncated/ scope changes.B. Progress reporting (indexer → frontend)
Pick one mechanism (prefer simplest that supports ~1 Hz UI updates):
{ stage, done, total, label }events (~1/s or on each path/pair milestone), final event = fullRouteSolveResponsePOSTstarts solve →job_id;GET /route/solve/progress?job_id=returns counters; frontend polls every 1s; final GET returns bodyCounters should map to user-facing copy such as “Searching {x} of {y} pairs…” (or paths, if that is the accurate unit — prefer honest labels: pairs vs paths vs hops). Expose at least:
done,total,stage(enumerating|evaluating|simulating|done), optionaleta_ms.C. Frontend display
Acceptance criteria
Indexer performance
GET /api/v1/route/solvefor representative distant pairs (no direct pool; ≥2–3 hops) completes in <15s p95 on LocalTerra / QA with DB-hybrid enabled (document exact pair fixtures).solver_version/optimality_scoperemain accurate; any bound change is versioned + documented.Progress API
Frontend UX
Test plan (all paths)
Indexer — functional
donequickly; responsequote_kindunchanged.cached.pool_only=true/ discovery-only (noamount_in): no global hybrid progress spam; existing semantics.POST /route/solvewithhybrid_by_hop: unchanged merge + sim behavior.quote_kind+ notes; progress ends in terminal state.cargo test --test api_route_solve --test api_route_solve_db_hybrid -- --test-threads=1(+ new progress/latency tests).Frontend — functional
E2E / QA
e2e/helpers/multihop-hybrid-e2e.ts/ #422): assert quote settles and (if instrumented) progress events observed.Test plan — attack / hack / abuse vectors
job_id— use unguessable IDs; do not leak quote bodies or trader/tier data on progress until authorized same as solve; prefer opaque secrets over sequential IDs.token_in/outwithout going through the real solver path and keying rules.estimated_amount_out(#283).LCD_HYBRID_SIM_BUDGET).max_maker_fills, huge amounts, identical pair flood — clamp (#379), rate-limit, cache bounded at 512 entries.optimality_scope.Verification criteria
docs/route-solver.md,docs/frontend.md, and agent skills updated for progress contract + latency targetsmake verify-issue-NNNscript for latency + progress smokeRelated
AGENTS_FRONTEND_SWAP_QUOTE_REFETCH.mdmarked as related to #484
mentioned in commit
24594c5f4cmentioned in merge request !1021
mentioned in commit
9925165ddementioned in issue #493
marked as related to #493
mentioned in issue #589
mentioned in issue #595
mentioned in issue #694
marked as related to #694