feat(swap+indexer): default hybrid multihop UX, optimization splits, GET hybrid routes (≤3 hops, intermediates) #101

Closed
opened 2026-04-22 03:23:52 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-04-22 03:23:52 +00:00 (Migrated from gitlab.com)

Summary

Transform default swap UX so hybrid (pool + on-chain limit book) multi-hop is the primary path for users, including optimizer-driven splits (how much of the input per hop / pool vs book) rather than manual typing.

Extend the indexer with a first-class GET (read: cacheable, idempotent) API for hybrid-optimized multihop routes up to 3 hops, returning hop-by-hop hybrid parameters and an explicit list of intermediate tokens (and pair addresses per hop) for transparency and client construction of router operations.

Current baseline (repo)

  • Simple swap (SwapPage): hybrid (book leg) is opt-in and only on direct single-hop CW20 routes; postRouteSolve can merge hybrid_by_hop for LCD simulate_swap_operations, but GET /api/v1/route/solve is pool-only (hybrid: null per route_solver.rs header). Multihop execute/simulate in UI is pool-only today.
  • Router and TS types support hybrid on per-hop terra_swap operations (router.ts SwapOperation).

Requirements

Frontend (dApp)

  • Default experience: the standard swap form assumes hybrid-capable routing when the route supports it; pool-only is a fallback or advanced toggle, not the default.
  • Optimization: users must not be required to hand-split pool vs book. The UI should present an optimizer result (split per hop, expected out, max maker fills strategy, and clear disclosure of model limits / L8-style execution risk).
  • Multihop: execute and display quotes for up to 3 hops with hybrid_by_hop aligned to indexer-solved routes. Handle wrap/native paths that cannot use the indexer solver explicitly (user messaging).
  • Settings: keep manual hybrid controls (split typing, max makers, hints) only under Advanced (covered in separate epic for “simple vs advanced”).
  • Accessibility & clarity: every submit path shows whether the trade is indexer-optimized quote vs chain simulation vs degraded pool-only.

Indexer

  • New or extended route endpoint(s), e.g. GET /api/v1/route/solve semantics upgrade (breaking change allowed behind version or new path) such that a client can:
    • Request: token_in, token_out, amount_in (raw), and flags for “best hybrid” mode.
    • Response: BFS/shortest or policy-defined path ≤ 3 hops; for each hop: pair address, intermediate token (after the hop), and HybridSwapParams-shaped fields (or null for pool-only hop) after optimization.
    • Include list of intermediate tokens in order: [token_in, mid1, mid2, token_out] as applicable; document hop indexing.
  • Performance: must tolerate production traffic; consider caching keys (token_in, token_out, amount_bucket, mode) and stale-while-revalidate.
  • Correctness: define agreement with on-chain simulate_swap_operations and pair HybridSimulation (document divergence sources).

Acceptance criteria

  • E2E (or contract-backed integration) proves 2-hop and 3-hop hybrid quotes and submits match LCD simulation within documented tolerance.
  • openapi/utoipa and frontend-dapp Indexer* types updated.
  • docs/architecture or docs/reviews matrix row updated: hybrid multihop no longer “manual UI only”.

References

  • indexer/src/api/route_solver.rs
  • frontend-dapp/src/pages/SwapPage.tsx
  • frontend-dapp/src/services/terraclassic/router.ts
  • docs/reviews/20260409T030009Z/ARCHITECTURE_GAP_MATRIX.md (hybrid / route rows)

Labels suggested

frontend, indexer, hybrid, router, launch-blocker

## Summary Transform **default swap UX** so **hybrid (pool + on-chain limit book) multi-hop** is the **primary path** for users, including **optimizer-driven splits** (how much of the input per hop / pool vs book) rather than manual typing. Extend the **indexer** with a first-class **GET** (read: cacheable, idempotent) API for **hybrid-optimized multihop routes** up to **3 hops**, returning **hop-by-hop `hybrid` parameters** and an explicit list of **intermediate tokens** (and pair addresses per hop) for transparency and client construction of router operations. ## Current baseline (repo) - **Simple swap** (`SwapPage`): hybrid (book leg) is **opt-in** and only on **direct single-hop** CW20 routes; `postRouteSolve` can merge `hybrid_by_hop` for **LCD `simulate_swap_operations`**, but **GET** ` /api/v1/route/solve` is **pool-only** (`hybrid: null` per `route_solver.rs` header). **Multihop execute/simulate in UI** is **pool-only** today. - **Router** and TS types support `hybrid` on **per-hop** `terra_swap` operations (`router.ts` `SwapOperation`). ## Requirements ### Frontend (dApp) - **Default experience**: the standard swap form assumes **hybrid-capable** routing when the route supports it; pool-only is a **fallback** or **advanced** toggle, not the default. - **Optimization**: users must not be required to hand-split pool vs book. The UI should present an **optimizer result** (split per hop, expected out, max maker fills strategy, and clear disclosure of model limits / L8-style execution risk). - **Multihop**: **execute** and **display quotes** for **up to 3 hops** with `hybrid_by_hop` aligned to indexer-solved routes. Handle wrap/native paths that cannot use the indexer solver explicitly (user messaging). - **Settings**: keep **manual** hybrid controls (split typing, max makers, hints) only under **Advanced** (covered in separate epic for “simple vs advanced”). - **Accessibility & clarity**: every submit path shows whether the trade is **indexer-optimized quote** vs **chain simulation** vs **degraded** pool-only. ### Indexer - New or extended route endpoint(s), e.g. **`GET /api/v1/route/solve` semantics upgrade** (breaking change allowed behind version or new path) such that a client can: - Request: `token_in`, `token_out`, `amount_in` (raw), and flags for **“best hybrid”** mode. - Response: BFS/shortest or policy-defined path **≤ 3 hops**; for each hop: **pair address**, **intermediate token** (after the hop), and **`HybridSwapParams`-shaped** fields (or `null` for pool-only hop) **after optimization**. - Include **list of intermediate tokens** in order: `[token_in, mid1, mid2, token_out]` as applicable; document hop indexing. - **Performance**: must tolerate production traffic; consider caching keys `(token_in, token_out, amount_bucket, mode)` and stale-while-revalidate. - **Correctness**: define agreement with on-chain `simulate_swap_operations` and pair `HybridSimulation` (document divergence sources). ## Acceptance criteria - [ ] E2E (or contract-backed integration) proves **2-hop and 3-hop** hybrid **quotes** and **submits** match **LCD simulation** within documented tolerance. - [ ] `openapi`/utoipa and `frontend-dapp` `Indexer*` types updated. - [ ] `docs/architecture` or `docs/reviews` matrix row updated: hybrid multihop no longer “manual UI only”. ## References - `indexer/src/api/route_solver.rs` - `frontend-dapp/src/pages/SwapPage.tsx` - `frontend-dapp/src/services/terraclassic/router.ts` - `docs/reviews/20260409T030009Z/ARCHITECTURE_GAP_MATRIX.md` (hybrid / route rows) ## Labels suggested `frontend`, `indexer`, `hybrid`, `router`, `launch-blocker`
PlasticDigits commented 2026-04-22 03:24:50 +00:00 (Migrated from gitlab.com)

mentioned in issue #107

mentioned in issue #107
PlasticDigits commented 2026-04-22 03:24:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #108

mentioned in issue #108
PlasticDigits commented 2026-04-22 03:24:53 +00:00 (Migrated from gitlab.com)
Coordinated with best-execution / simple vs advanced: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/108 and launch gate https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/107
PlasticDigits commented 2026-04-22 04:59:55 +00:00 (Migrated from gitlab.com)

mentioned in commit 844607a438

mentioned in commit 844607a43804ca154ecdb789267e326c961bb657
PlasticDigits commented 2026-04-22 05:00:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #106

mentioned in issue #106
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-04-22 05:35:38 +00:00
PlasticDigits commented 2026-04-22 05:36:04 +00:00 (Migrated from gitlab.com)

@brouie — #101 implemented and merged to main (commit range includes `64f73fa`).

Indexer

  • New `GET /api/v1/route/solve` query params: `hybrid_optimize=true` (requires `amount_in`), optional `max_maker_fills`. Hybrid mode uses max 3 hops (default GET stays 4 hops, pool-only `hybrid: null`).
  • New module `indexer/src/api/hybrid_route_opt.rs`: per-hop grid search on `book_input` via pair `HybridSimulation`, pool `Simulation` fallback per hop if all hybrid candidates fail (`quote_kind: indexer_hybrid_lcd_degraded`).
  • Response additions: `intermediate_tokens`, `quote_kind`, `hybrid_notes`; short-TTL in-memory cache on hybrid GET.
  • `POST /api/v1/route/solve` unchanged except response now always includes the new fields where applicable.

dApp

  • CW20↔CW20 (non-wrap, non-native): default quote path calls indexer hybrid GET, maps `router_operations` to wallet ops, `simulate_swap_operations` for display, `execute_swap_operations` on submit. Quote source and Route (indexer) lines in the swap panel. Settings Advanced retains manual book split (overrides indexer when book leg > 0 on direct pairs).

Tests / docs

  • Indexer: `api_route_solve` extended (2-hop hybrid GET + require `amount_in`); wiremock hybrid LCD helper.
  • Frontend: `routeOperations` unit test, client GET params test, SwapPage test mocks `getRouteSolve`; E2E checks Quote source line.
  • Docs: `indexer-invariants.md`, `integrators.md`, ADR 0001, `limit-orders.md`, audit L8 row, architecture gap matrix.

Verification

  • Indexer: `cargo test --test api_route_solve` (needs Postgres `dex_indexer_test`).
  • Frontend: `pnpm test:run` / `pnpm exec vitest run` from `frontend-dapp`.
  • Manual: swap two indexed CW20s with amount > 0; confirm quote source and (if book liquidity) hybrid warning; compare with LCD/router sim.

Please verify behavior against your expectations for 2–3 hop hybrid and indexer load; sequential per-hop optimization is not globally optimal across hops (noted in `hybrid_notes`).

@brouie — **#101 implemented and merged to `main`** (commit range includes \`64f73fa\`). ### Indexer - New \`GET /api/v1/route/solve\` query params: \`hybrid_optimize=true\` (requires \`amount_in\`), optional \`max_maker_fills\`. Hybrid mode uses **max 3 hops** (default GET stays **4 hops**, pool-only \`hybrid: null\`). - New module \`indexer/src/api/hybrid_route_opt.rs\`: per-hop **grid search** on \`book_input\` via pair \`HybridSimulation\`, **pool \`Simulation\` fallback** per hop if all hybrid candidates fail (\`quote_kind: indexer_hybrid_lcd_degraded\`). - Response additions: \`intermediate_tokens\`, \`quote_kind\`, \`hybrid_notes\`; short-TTL in-memory cache on hybrid GET. - \`POST /api/v1/route/solve\` unchanged except response now always includes the new fields where applicable. ### dApp - CW20↔CW20 (non-wrap, non-native): default quote path calls indexer hybrid GET, maps \`router_operations\` to wallet ops, **\`simulate_swap_operations\`** for display, **\`execute_swap_operations\`** on submit. **Quote source** and **Route (indexer)** lines in the swap panel. Settings **Advanced** retains manual book split (overrides indexer when book leg > 0 on direct pairs). ### Tests / docs - Indexer: \`api_route_solve\` extended (2-hop hybrid GET + require \`amount_in\`); wiremock hybrid LCD helper. - Frontend: \`routeOperations\` unit test, client GET params test, SwapPage test mocks \`getRouteSolve\`; E2E checks **Quote source** line. - Docs: \`indexer-invariants.md\`, \`integrators.md\`, ADR 0001, \`limit-orders.md\`, audit L8 row, architecture gap matrix. ### Verification - Indexer: \`cargo test --test api_route_solve\` (needs Postgres \`dex_indexer_test\`). - Frontend: \`pnpm test:run\` / \`pnpm exec vitest run\` from \`frontend-dapp\`. - Manual: swap two indexed CW20s with amount > 0; confirm quote source and (if book liquidity) hybrid warning; compare with LCD/router sim. Please verify behavior against your expectations for **2–3 hop** hybrid and indexer load; sequential per-hop optimization is **not globally optimal** across hops (noted in \`hybrid_notes\`).
PlasticDigits commented 2026-05-29 03:09:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #209

mentioned in issue #209
PlasticDigits commented 2026-05-29 03:09:58 +00:00 (Migrated from gitlab.com)

marked as related to #209

marked as related to #209
PlasticDigits commented 2026-05-29 03:19:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #216

mentioned in issue #216
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#101
No description provided.