fix(swap): wrap-enter native routes must GET /route/solve and drop ~100% hop-spread winners #1218

Closed
opened 2026-09-10 06:38:46 +00:00 by LVS · 7 comments

Routing LUNC -> USTR yields a high slippage

Route LUNC>cLUNC>UST1>USTR

But when breaking the step up by 1 trade

Route LUNC>cLUNC

The router then finds a different route with less slippage

Route continued cLUNC>cUSTC>UST1>USTR

The issue - routing may favour less hops, causing high slippage (no cLUNC/UST1 pool), investigate why the cLUNC>cUSTC hop was left out of the original route

Routing LUNC -> USTR yields a high slippage Route LUNC>cLUNC>UST1>USTR But when breaking the step up by 1 trade Route LUNC>cLUNC The router then finds a different route with less slippage Route continued cLUNC>cUSTC>UST1>USTR The issue - routing may favour less hops, causing high slippage (no cLUNC/UST1 pool), investigate why the cLUNC>cUSTC hop was left out of the original route

+1 on ranking: mainnet Swap still quotes LUNC→USTR as LUNC→cLUNC→UST1→USTR with ~100% worst-hop spread (~50 USTR on a 10M LUNC size) while the wrap-then-cUSTC path is far better.

Missing constraint: GET_DEFAULT_MAX_HOPS is already 4 (#323), so LUNC→cLUNC→cUSTC→UST1→USTR is enumerable. Independent split of the same size (10M LUNC wrap → 9.8M cLUNC, then cLUNC→cUSTC→UST1→USTR) quotes ~48.4K USTR with ~5.15% worst-hop spread. Rank by simulated output (and reject 100% hop spread) rather than hop count / top-k that drops the wrap+cUSTC candidate.

+1 on ranking: mainnet Swap still quotes LUNC→USTR as LUNC→cLUNC→UST1→USTR with ~100% worst-hop spread (~50 USTR on a 10M LUNC size) while the wrap-then-cUSTC path is far better. Missing constraint: `GET_DEFAULT_MAX_HOPS` is already 4 (#323), so LUNC→cLUNC→cUSTC→UST1→USTR is enumerable. Independent split of the same size (10M LUNC wrap → 9.8M cLUNC, then cLUNC→cUSTC→UST1→USTR) quotes ~48.4K USTR with ~5.15% worst-hop spread. Rank by simulated output (and reject 100% hop spread) rather than hop count / top-k that drops the wrap+cUSTC candidate.

Native wrap-enter / unwrap-exit still never reach GET /api/v1/route/solve. Swap treats any native uluna/uusd pay or receive as nativeRouteInfo (findRouteWithNativeSupport → first BFS after wrap substitution, then simulateNativeSwap). Indexer solve inputs remain CW20 contract ids only, so top-K / output ranking on this ticket cannot see wrap-then-cUSTC (or USTC wrap-enter uusd→cUSTC→cLUNC) until native I/O is mapped to wrap-mapper CW20s before solve.

Keep execute pool-only on native paths (H596-7); do not attach hybrid to executeNativeSwap. Unwrap-exit must still set unwrap_output after the wrapped ask. Direct 1:1 wrap/unwrap stays mapper-only.

Native wrap-enter / unwrap-exit still never reach GET `/api/v1/route/solve`. Swap treats any native `uluna`/`uusd` pay or receive as `nativeRouteInfo` (`findRouteWithNativeSupport` → first BFS after wrap substitution, then `simulateNativeSwap`). Indexer solve inputs remain CW20 contract ids only, so top-K / output ranking on this ticket cannot see wrap-then-cUSTC (or USTC wrap-enter `uusd`→cUSTC→cLUNC) until native I/O is mapped to wrap-mapper CW20s before solve. Keep execute pool-only on native paths (H596-7); do not attach hybrid to `executeNativeSwap`. Unwrap-exit must still set `unwrap_output` after the wrapped ask. Direct 1:1 wrap/unwrap stays mapper-only.

+1 on native wrap-enter never reaching GET /api/v1/route/solve.

Missing constraint: native LUNC (uluna) → listed CL8Y (terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3, 18-dec product CW20) is another wrap-enter ask on the same client path (findRouteWithNativeSupport → first BFS findRoute after uluna→cLUNC substitution, then simulateNativeSwap). It does not go through quoteCw20ViaRouteSolve. Mixed 6-dec cLUNC / 18-dec CL8Y hops must be ranked by simulated net out (and reject ~100% hop spread), not hop count.

Keep H596-7: native execute stays pool-only. Do not fold CW20 USTR→USDT (#1257) into this ticket.

+1 on native wrap-enter never reaching GET `/api/v1/route/solve`. Missing constraint: native LUNC (`uluna`) → listed CL8Y (`terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3`, 18-dec product CW20) is another wrap-enter ask on the same client path (`findRouteWithNativeSupport` → first BFS `findRoute` after `uluna`→cLUNC substitution, then `simulateNativeSwap`). It does not go through `quoteCw20ViaRouteSolve`. Mixed 6-dec cLUNC / 18-dec CL8Y hops must be ranked by simulated net out (and reject ~100% hop spread), not hop count. Keep H596-7: native execute stays pool-only. Do not fold CW20 USTR→USDT (#1257) into this ticket.

Summary

Retail Swap / of native LUNC (uluna) → USTR quotes LUNC → cLUNC → UST1 → USTR with ~100% worst-hop spread. Breaking the same size into wrap-then-CW20 (cLUNC → USTR) finds cLUNC → cUSTC → UST1 → USTR with far more USTR. The client prefers the first BFS after wrap substitution; that path never calls GET /api/v1/route/solve.

This is one product surface: native wrap-enter / unwrap-exit must use the indexer solver on the wrap-mapper CW20s, then submit those hops pool-only. Bundle (do not split):

  1. Wrap-enter / unwrap-exit mapping — substitute uluna→cLUNC and uusd→cUSTC (and the reverse on unwrap-exit) before quoteCw20ViaRouteSolve. amount_in is post–wrap-fee mint on wrap-enter. Direct 1:1 wrap/unwrap stays mapper-only.
  2. Quote = submit ops — executeNativeSwap must use the solver hop list (stripped of hybrid / book_input), not a second findRouteWithNativeSupport BFS. Unwrap-exit still sets unwrap_output after the wrapped ask.
  3. Fail-closed hop ranking — among enumerable paths (hop cap already 4, #323), winner is max simulated net out. Drop ~100% hop-spread / implausible-ask candidates before filling top-K so a thin 2-hop cannot occupy the slot that would have scored wrap-then-cUSTC.
  4. Same wrap-enter family — native LUNC → listed CL8Y (terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3, 18-dec) is the same client branch. Rank by simulated net out; do not fold mixed-decimal CW20→CW20 honesty (#1257).

Given columbus-5 Swap / with native LUNC pay and USTR ask, wrap env set, and a size in the 10M LUNC class
When the ticket quotes
Then Route is wrap + cLUNC → cUSTC → UST1 → USTR (or another solver winner with honest hop spread), You Receive is the ~48k-USTR class not ~50 USTR, and submit uses those same pool-only hops plus wrap_deposit.

Probe already on this issue: 10M LUNC wrap → ~9.8M cLUNC; wrap-then-cUSTC quotes ~48.4k USTR / ~5.15% worst-hop spread vs the BFS 2-hop ~50 USTR / ~100% spread.

Recommended title: fix(swap): wrap-enter native routes must GET /route/solve and drop ~100% hop-spread winners.

Not duplicates (do not merge)

Issue Why it is not this ticket
#209 / #191 / #323 Shipped CW20 solver: top-5 by hop count, winner max estimated_amount_out_net, GET hop cap 4. Native I/O still never reaches it.
#501 / #596 CW20 Swap/Trade GET always-on hybrid. H596-7 keeps native wrap execute pool-only — keep that.
#615 Tax net ranking on CW20 solve. Not wrap-enter mapping.
#369 / #585 Zero-reserve skip / F6 freeze skip. Keep; not a substitute for wrap mapping.
#1257 Listed USTR→USDT mixed 18/6 CW20 path already hits /route/solve. Scale/identity, not wrap-enter.
#1264 USTC→USTR wrap+2hop execute gas. Quote ranking is here; do not retune envelopes in this MR.
#1265 Research census of leftover solve failures. F0 owner is this issue. Do not implement 1218 inside 1265.
#1256 Typeable You Receive on wrap/native. Reverse-quote UX.
#1203 Parallel split across paths. Feature spike; this ticket is still one winner path.
#690 / #691 Other-DEX / IBC edges. Not the missing cUSTC hop.
#484 / #485 Calculating hang / distant p95. Latency, not wrong-path ranking.
#1280 Retail GET hop-0-only declared hybrid. Native execute must still strip hybrid (H596-7).
#678 5 / 30 / 99 slippage chrome. Blocking a 100% BFS quote is not the same as selecting the honest path.

Already implemented — do not re-spec: indexer route_solve_global_picks_best_path_not_shortest (longer path wins inside top-5); GET_DEFAULT_MAX_HOPS = 4; wrap-fee netting on simulateNativeSwap / executeNativeSwap; hop-preflight anyHopExceedsMaxSpread can disable submit without changing the chosen route.


Current codebase

Native pay/receive never calls the solver

SwapPage.tsx: if either token is a native denom and the pair is not direct 1:1 wrap/unwrap, nativeRouteInfo = findRouteWithNativeSupport(...). That branch returns before quoteCw20ViaRouteSolve. indexerCw20Eligible requires both ids to start with terra1 and !nativeRouteInfo.

findRouteWithNativeSupport substitutes wrap-mapper CW20s, then findRoute BFS returns the first path (hop cap 4). After uluna→cLUNC, a factory edge cLUNC–UST1 makes cLUNC → UST1 → USTR the first hit. cLUNC → cUSTC → UST1 → USTR is never considered. simulateNativeSwap / executeNativeSwap call that BFS again; submit comment: “Pool-only hops from client BFS findRoute — never copy hybrid.”

Indexer solve inputs are CW20 contract ids only (docs/route-solver.md). Advanced “Indexer route check” on Swap already errors for natives.

Solver ranking (CW20 only)

Optimized GET /api/v1/route/solve with amount_in:

  1. Graph nodes = indexed CW20s. Natives without substitution are not routable.
  2. find_paths_top_k — ≤ MAX_PATH_CANDIDATES (5) simple paths, ≤ GET_DEFAULT_MAX_HOPS (4), shortest first. OPTIMALITY_SCOPE: optimal within that set, not all simple paths.
  3. Per path: joint hybrid grid; retail emit hop-0-only hybrid (#1280).
  4. Winner = max estimated_amount_out_net (#615).
  5. DB hop sim can mark a pool leg implausible (ask_out > ask reserve or 18-dec-into-6-dec full drain) in db_orderbook_sim.rs. That is not applied as a path-enumeration skip when filling top-K, and it does not run on the native BFS path at all.

If wrap-enter mapped to token_in=cLUNC / token_out=USTR, the 3-hop is enumerable. Top-K can still drop it when five shorter (thin) paths fill the cap — hence skip ~100% spread while filling K, not “raise K and hope.”

Slippage chrome vs route choice

Hop preflight can set anyHopExceedsMaxSpread and Swap disables submit (“Hop spread exceeds slippage protection”). Indexer slippage_percent ≥99% is theater (swapQuoteAmountScale.ts) — on the CW20 solve path. Native BFS quotes still paint the 2-hop Route / You Receive. Blocking submit without selecting wrap-then-cUSTC is not a fix.

Hub ids (columbus-5, identity by contract):

Leg Id
LUNC uluna
USTC uusd
cLUNC wrap-mapper / VITE_LUNC_C_TOKEN_ADDRESS (tokenlist pin terra1437qsl…)
cUSTC terra1nap4dxh9tv35v0ynd9m4k6zt6c0dq6weszc4j5m564kjls56hu7qcr56ch
UST1 registry / terra1f0eqgy9w7e5e7up97vjudqwx38tesf8ylx75x2lv3nwm0clry0pqmgfy72
USTR terra1vy3kc0swag2rhn7jz6n72jp0l2ns0p6r6ez5grxq5uhj2rvs97fqfsetxv
CL8Y terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3

There is no factory pair USTR/cUSTC (#1264); the honest LUNC→USTR hub walk is wrap + cUSTC + UST1.


Why this implementation is needed

Traders who pay native LUNC (the wallet asset) get a ~100% impact quote while the same size, after wrap, is a normal hub walk. Splitting the trade by hand (wrap, then swap) is not retail UX. Hop cap 4 already allows the extra cUSTC hop. The official dApp promised best execution on Swap (#209 / #596) but H596-7 left natives on client BFS, so the solver never sees wrap-then-cUSTC.

Without fail-closed skip of ~100% hop-spread candidates, mapping to GET is not enough: top-5 shortest can still be five thin 1–2 hop paths, and OPTIMALITY_SCOPE will honestly never score the 3-hop.


Constraints / guardrails

  • H596-7: native wrap+multihop execute stays pool-only. Do not attach Pattern C / greedy book_input to executeNativeSwap. Strip solver hybrid the same way wrap+N-hop already strips it (#1264 / #1280).
  • Direct 1:1 wrap/unwrap stays mapper-only (isDirectWrapUnwrap). Do not send those to /route/solve.
  • Do not raise GET_DEFAULT_MAX_HOPS (already 4) or MAX_PATH_CANDIDATES as the primary fix. Skip unusable paths then fill K. LCD/SOLVE_CONCURRENCY budget stays documented.
  • Do not implement parallel split routing (#1203) or other-DEX hops (#690).
  • Do not fold #1257 USTR→USDT decimal honesty or #1264 gas envelopes. A 3-hop wrap combo must still use existing wrap+N-hop gas helpers (hop count drives the envelope).
  • Identity is contract / denom, not ticker (#715). Gems stay hidden (#562).
  • Copy (#489): no lecture banner “we ignored a shorter route.” One Route row aligned with submit (#158 / #450).
  • Fail closed: drop ~100% hop-spread / ask-out > ask-reserve winners; do not paint theater You Receive. Keep 5 / 30 / 99 chrome (#678). Expert cannot waive ≥99%.
  • amount_in for solve = net CW20 after wrap fee on wrap-enter; You Receive on unwrap-exit still nets mapper unwrap fee + InstantWithdraw burn tax (#512 / #516).
  • No CosmWasm migrate. No indexer native-denom graph nodes (substitution at the client, or a documented wrap-map in the GET handler — pick one; do not do both).
  • Public comments: columbus-5 contract ids and public JSON fields only. No hosts, keys, or deploy pipeline.

Relevant files

Path Why
frontend-dapp/src/pages/SwapPage.tsx Native vs CW20 quote branch; submit executeNativeSwap vs indexerOperations
frontend-dapp/src/utils/cw20RouteSolveQuote.ts GET /route/solve + wallet sim; wrap/native must not copy hop-0 hybrid onto wrap hops
frontend-dapp/src/services/terraclassic/router.ts findRoute / findRouteWithNativeSupport / simulateNativeSwap / executeNativeSwap
frontend-dapp/src/utils/swapRouteDisplay.ts Route chip vs ops; wrap-enter symbols
frontend-dapp/src/services/terraclassic/swapRoutePreflight.ts Worst-hop spread; submit disable
frontend-dapp/src/utils/swapQuoteAmountScale.ts ≥99% theater block
indexer/src/api/route_paths.rs find_paths_top_k shortest-first cap
indexer/src/api/best_execution.rs Winner merge, OPTIMALITY_SCOPE, candidate eval
indexer/src/api/route_solver.rs GET handler, hop cap, cache
indexer/src/api/db_orderbook_sim.rs hop_sim_implausible — reuse for path skip, do not reinvent
indexer/tests/api_route_solve.rs route_solve_global_picks_best_path_not_shortest
docs/route-solver.md / docs/adr/0002-global-best-execution-route-solver.md Optimality scope amendment: skip-unusable then fill K
skills/AGENTS_FRONTEND_HYBRID_ALWAYS_ON.md H596-7
skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md One Route row
scripts/check_route_solver_docs.py Constant drift

  1. Client wrap map (required): For non-direct native pay and/or native receive, resolve wrap CW20s, netCw20AfterNativeWrap when paying native, then quoteCw20ViaRouteSolve. Apply unwrap fee/tax on the ask if receive is native. On indexer/wallet failure, keep today’s BFS fallback (H596-5 degradation) — do not silently submit a 100% spread BFS when a solve winner existed this quote tick.
  2. Submit alignment: Thread solver ops (hybrid stripped) into executeNativeSwap (or a sibling that accepts ops). Stop re-running BFS at broadcast. Set unwrap_output iff native ask.
  3. Indexer skip-unusable: When enumerating top-K, do not keep a candidate whose worst hop is ~100% spread or hop_sim_implausible. Continue search until K usable paths or hop cap exhausted. Winner remains max estimated_amount_out_net. Fixture: 2-hop ~100% drain vs 3-hop honest hub — 3-hop wins; paths_considered counts scored usable paths.
  4. Do not bump MAX_PATH_CANDIDATES in this MR unless skip-unusable still cannot admit the wrap-then-cUSTC fixture under a pinned hub graph (then a one-line constant + LCD budget comment + docs drift check — not a second issue).
  5. Docs / skill: ADR 0002 amendment: native wrap substitution is in-scope of retail GET; H596-7 execute still pool-only. verify-issue-1218 script.
  6. Trade market: pair-scoped CW20; out of scope unless a native denom appears on /trade (it should not).

Acceptance criteria

  • AC1. Swap / native LUNC → USTR (wrap env on): Route includes cLUNC → cUSTC → UST1 → USTR (or a solver winner with worst-hop spread ≪ 100%), not BFS-only cLUNC → UST1 → USTR, for the 10M LUNC-class size. You Receive is the wrap-then-cUSTC class, not ~50 USTR.
  • AC2. Quote and submit use the same hop list. executeNativeSwap does not BFS a different path than the Route chip.
  • AC3. Those hops are pool-only on execute (hybrid: null / no book_input). Wrap-enter still wrap_deposit then CW20 send to router. Unwrap-exit still unwrap_output.
  • AC4. Direct 1:1 LUNC↔cLUNC / USTC↔cUSTC never call /route/solve.
  • AC5. Native USTC (uusd) wrap-enter to a listed hub CW20 uses the same mapping (symmetric to LUNC). Do not retune #1264 gas here.
  • AC6. Native LUNC → listed CL8Y wrap-enter reaches GET /route/solve with token_in = cLUNC. Rank by simulated net out; reject ~100% hop spread. Mixed 6/18 hops must not print theater You Receive.
  • AC7. Indexer (or client, if skip is client-only): a ~100% hop-spread 2-hop cannot beat an enumerable honest 3-hop. Top-K is filled with usable paths first.
  • AC8. Indexer GET without native substitution still rejects raw token_in=uluna (CW20-only graph) unless the chosen design is server-side wrap-map — then document it and keep one mapping site.
  • AC9. 5 / 30 / 99 chrome and Expert ≥99% non-waive unchanged. Gem bridge reject unchanged.
  • AC10. scripts/qa/verify-issue-1218.sh + docs/skill pointers. python3 scripts/check_route_solver_docs.py green if constants/docs change.

Test plan (all paths)

ID Path How to test
P1 Wrap-enter LUNC→USTR Swap quote Vitest: native uluna + USTR calls getRouteSolve(cLUNC, USTR, netAfterWrap) not simulateNativeSwap BFS-only. Fixture graph: thin cLUNC–UST1 + deep cLUNC–cUSTC–UST1–USTR. Winner hops include cUSTC.
P2 Submit alignment Same executeNativeSwap (or wrapper) receives those ops; spies show no second findRoute that returns the 2-hop.
P3 H596-7 Same Serialized router execute has hybrid: null on every hop.
P4 Direct wrap LUNC→cLUNC Still mapper-only; no /route/solve.
P5 Unwrap-exit USTR→LUNC Solve USTR→cLUNC, then unwrap fee/tax on ask; unwrap_output true.
P6 USTC wrap-enter uusd→USTR token_in = cUSTC; hub walk not first BFS-only.
P7 LUNC→CL8Y wrap-enter 6→18 Hits solve; no 10^9-scale You Receive; ~100% hop rejected.
P8 Indexer skip-unusable api_route_solve Pinned pairs: 2-hop implausible vs 3-hop honest → 3-hop estimated_amount_out; search_truncated still honest. Existing route_solve_global_picks_best_path_not_shortest stays green.
P9 Degradation Indexer 5xx BFS fallback allowed; if fallback worst-hop ~100%, submit stays blocked (anyHopExceedsMaxSpread / ≥99%).
P10 CW20 control cLUNC→USTR (no native) Still quoteCw20ViaRouteSolve only; no wrap_deposit.
P11 Tax net wrap-enter into a tax CW20 ask Display net; min_return pre-tax (#615).
P12 Freeze / zero reserve Frozen or empty hop Still skipped (#585 / #369); not chosen as winner.

Negative / skip:

  • N1. Raising hop cap or K instead of wrap-map + skip-unusable → fail.
  • N2. Attaching hybrid/book to native execute → fail AC3.
  • N3. Implementing #1257 / #1264 / #1265 / #1203 in this MR → fail.
  • N4. Route chip showing cUSTC while signed ops are UST1-only → fail AC2 / #450.

Optional chain: LocalTerra wrap seed + Swap LUNC→hub CW20 when wrap mapper is deployed — quote-only is enough if lcd mock covers P1/P8.


Test plan (attack, hack, and abuse)

Evaluate each vector. Do not write exploits, PoCs, or attack procedures.

ID Vector Expect Fail if
A1 Theater quote Implausible You Receive as a screenshot “price” Skip ~100% hop / implausible ask; ≥99% hide + block Paints ~50 USTR or 10^9-scale out as fillable
A2 Path spoof Route chip ≠ signed ops Display from submit ops (#450) Shows wrap-then-cUSTC while tx BFS-swaps UST1
A3 Hybrid on wrap Book leg on native wrap+N-hop H596-7 strip book_input on wrap execute “to fix ranking”
A4 Native id injection token_in=uluna on public GET 404 / reject unless documented wrap-map Solver treats uluna as a CW20 node
A5 Cache alias Wrap-fee net vs gross, 6 vs 18 dec Existing amount bucket + tax/discount identity Shares quotes across wrap-fee or decimal identity
A6 Frozen hop Frozen code_id still routed F6 skip Quotes frozen-only markets
A7 Gem bridge Hidden test gem as intermediate shouldRejectGemBridgeQuote Routes via retail-hidden gems
A8 LCD / solve DoS Extra wrap-enter solves Existing LCD-heavy rate limit; no new unmetered endpoint New progress/solve fan-out without cap
A9 MITM indexer Compromised hop list Wallet sim + max_spread / min receive still bind execute Trusts estimated_amount_out without sim / floors

Verification criteria

  • make verify-issue-1218 (new): Vitest P1–P7 / P9–P11 class tests + indexer P8 (Postgres + lcd mock / lib as appropriate) + greps that executeNativeSwap no longer maps ops solely from findRouteWithNativeSupport on the wrap-enter submit path.
  • Manual / optional LocalTerra: Swap LUNC→USTR Route shows cUSTC; worst-hop spread not ~100%; one SWAP with wrap + router.
  • Docs: ADR 0002 / route-solver.md / H596-7 note wrap-enter GET; scripts/check_route_solver_docs.py green.
  • Reviewers can see, for P1, fixture hops + amounts (or a public solve JSON summary with contract ids) — not a screenshot of the old 2-hop as “fixed.”

Out of scope

  • Mixed-decimal CW20 USTR→USDT (#1257).
  • Wrap+2hop execute gas (#1264) / gas census (#1222).
  • Remaining-failure research memo (#1265).
  • Parallel split routing (#1203).
  • Other-DEX v2 / IBC USDC intermediates (#690 / #691).
  • Typeable You Receive (#1256).
  • Exact-out / indexer reverse solve.
  • CosmWasm migrate; turning hybrid on for native execute.

First-pass model recommendation

Recommendation: grok-high

Rationale: Cross-cutting retail quote protocol: Swap native branch (SwapPage.tsx), wrap execute (router.ts), indexer top-K skip-unusable (route_paths.rs / best_execution.rs), and H596-7 (must not “fix” ranking by attaching a book to wrap hops). More than three production files, not a local known edit, and the product patch is not a single constant bump. Composer criteria fail on subsystem count and wrap vs solver ownership. Verify with Vitest + indexer route-solve fixtures, not live-mainnet-only acceptance. Advisory planning text, not a runner argument.

## Summary Retail Swap `/` of **native LUNC (`uluna`) → USTR** quotes **LUNC → cLUNC → UST1 → USTR** with ~**100%** worst-hop spread. Breaking the same size into wrap-then-CW20 (cLUNC → USTR) finds **cLUNC → cUSTC → UST1 → USTR** with far more USTR. The client prefers the **first BFS after wrap substitution**; that path **never** calls `GET /api/v1/route/solve`. This is **one** product surface: **native wrap-enter / unwrap-exit must use the indexer solver on the wrap-mapper CW20s, then submit those hops pool-only.** Bundle (do not split): 1. **Wrap-enter / unwrap-exit mapping** — substitute `uluna`→cLUNC and `uusd`→cUSTC (and the reverse on unwrap-exit) **before** `quoteCw20ViaRouteSolve`. `amount_in` is post–wrap-fee mint on wrap-enter. Direct 1:1 wrap/unwrap stays mapper-only. 2. **Quote = submit ops** — `executeNativeSwap` must use the **solver hop list** (stripped of `hybrid` / `book_input`), not a second `findRouteWithNativeSupport` BFS. Unwrap-exit still sets `unwrap_output` after the wrapped ask. 3. **Fail-closed hop ranking** — among enumerable paths (hop cap already **4**, [#323](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/323)), winner is max simulated **net** out. Drop ~**100%** hop-spread / implausible-ask candidates **before** filling top-K so a thin 2-hop cannot occupy the slot that would have scored wrap-then-cUSTC. 4. **Same wrap-enter family** — native LUNC → listed CL8Y (`terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3`, 18-dec) is the same client branch. Rank by simulated net out; do **not** fold mixed-decimal **CW20→CW20** honesty ([#1257](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1257)). **Given** columbus-5 Swap `/` with native LUNC pay and USTR ask, wrap env set, and a size in the 10M LUNC class **When** the ticket quotes **Then** Route is wrap + **cLUNC → cUSTC → UST1 → USTR** (or another solver winner with honest hop spread), You Receive is the ~48k-USTR class not ~50 USTR, and submit uses those same pool-only hops plus `wrap_deposit`. Probe already on this issue: 10M LUNC wrap → ~9.8M cLUNC; wrap-then-cUSTC quotes ~48.4k USTR / ~5.15% worst-hop spread vs the BFS 2-hop ~50 USTR / ~100% spread. Recommended title: `fix(swap): wrap-enter native routes must GET /route/solve and drop ~100% hop-spread winners`. ### Not duplicates (do not merge) | Issue | Why it is not this ticket | | --- | --- | | [#209](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/209) / [#191](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/191) / [#323](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/323) | Shipped CW20 solver: top-5 by hop count, winner max `estimated_amount_out_net`, GET hop cap 4. Native I/O still never reaches it. | | [#501](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/501) / [#596](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/596) | CW20 Swap/Trade GET always-on hybrid. **H596-7** keeps native wrap execute pool-only — keep that. | | [#615](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/615) | Tax **net** ranking on CW20 solve. Not wrap-enter mapping. | | [#369](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/369) / [#585](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/585) | Zero-reserve skip / F6 freeze skip. Keep; not a substitute for wrap mapping. | | [#1257](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1257) | Listed **USTR→USDT** mixed 18/6 CW20 path **already** hits `/route/solve`. Scale/identity, not wrap-enter. | | [#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264) | USTC→USTR wrap+2hop **execute gas**. Quote ranking is here; do not retune envelopes in this MR. | | [#1265](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1265) | Research census of leftover solve failures. F0 owner is **this** issue. Do not implement 1218 inside 1265. | | [#1256](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1256) | Typeable You Receive on wrap/native. Reverse-quote UX. | | [#1203](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1203) | Parallel **split** across paths. Feature spike; this ticket is still **one** winner path. | | [#690](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/690) / [#691](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/691) | Other-DEX / IBC edges. Not the missing cUSTC hop. | | [#484](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/484) / [#485](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/485) | Calculating hang / distant p95. Latency, not wrong-path ranking. | | [#1280](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1280) | Retail GET hop-0-only declared hybrid. Native execute must still strip hybrid (**H596-7**). | | [#678](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/678) | 5 / 30 / 99 slippage chrome. Blocking a 100% BFS quote is not the same as selecting the honest path. | **Already implemented — do not re-spec:** indexer `route_solve_global_picks_best_path_not_shortest` (longer path wins **inside** top-5); `GET_DEFAULT_MAX_HOPS = 4`; wrap-fee netting on `simulateNativeSwap` / `executeNativeSwap`; hop-preflight `anyHopExceedsMaxSpread` can **disable submit** without changing the chosen route. --- ## Current codebase ### Native pay/receive never calls the solver [`SwapPage.tsx`](frontend-dapp/src/pages/SwapPage.tsx): if either token is a native denom and the pair is not direct 1:1 wrap/unwrap, `nativeRouteInfo = findRouteWithNativeSupport(...)`. That branch **returns before** `quoteCw20ViaRouteSolve`. `indexerCw20Eligible` requires both ids to start with `terra1` **and** `!nativeRouteInfo`. [`findRouteWithNativeSupport`](frontend-dapp/src/services/terraclassic/router.ts) substitutes wrap-mapper CW20s, then [`findRoute`](frontend-dapp/src/services/terraclassic/router.ts) BFS **returns the first path** (hop cap 4). After `uluna`→cLUNC, a factory edge cLUNC–UST1 makes **cLUNC → UST1 → USTR** the first hit. **cLUNC → cUSTC → UST1 → USTR** is never considered. `simulateNativeSwap` / `executeNativeSwap` call that BFS again; submit comment: *“Pool-only hops from client BFS `findRoute` — never copy hybrid.”* Indexer solve inputs are **CW20 contract ids only** ([`docs/route-solver.md`](docs/route-solver.md)). Advanced “Indexer route check” on Swap already errors for natives. ### Solver ranking (CW20 only) Optimized `GET /api/v1/route/solve` with `amount_in`: 1. Graph nodes = indexed CW20s. Natives without substitution are not routable. 2. `find_paths_top_k` — ≤ `MAX_PATH_CANDIDATES` (5) simple paths, ≤ `GET_DEFAULT_MAX_HOPS` (4), **shortest first**. `OPTIMALITY_SCOPE`: optimal **within** that set, not all simple paths. 3. Per path: joint hybrid grid; retail **emit** hop-0-only hybrid ([#1280](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1280)). 4. Winner = max `estimated_amount_out_net` ([#615](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/615)). 5. DB hop sim can mark a pool leg implausible (`ask_out > ask reserve` or 18-dec-into-6-dec full drain) in [`db_orderbook_sim.rs`](indexer/src/api/db_orderbook_sim.rs). That is **not** applied as a path-enumeration skip when filling top-K, and it does not run on the native BFS path at all. If wrap-enter mapped to `token_in=cLUNC` / `token_out=USTR`, the 3-hop is enumerable. Top-K can still **drop** it when five shorter (thin) paths fill the cap — hence skip ~100% spread **while filling K**, not “raise K and hope.” ### Slippage chrome vs route choice Hop preflight can set `anyHopExceedsMaxSpread` and Swap disables submit (“Hop spread exceeds slippage protection”). Indexer `slippage_percent` ≥99% is theater ([`swapQuoteAmountScale.ts`](frontend-dapp/src/utils/swapQuoteAmountScale.ts)) — on the **CW20** solve path. Native BFS quotes still **paint** the 2-hop Route / You Receive. Blocking submit without selecting wrap-then-cUSTC is not a fix. Hub ids (columbus-5, identity by contract): | Leg | Id | | --- | --- | | LUNC | `uluna` | | USTC | `uusd` | | cLUNC | wrap-mapper / `VITE_LUNC_C_TOKEN_ADDRESS` (tokenlist pin `terra1437qsl…`) | | cUSTC | `terra1nap4dxh9tv35v0ynd9m4k6zt6c0dq6weszc4j5m564kjls56hu7qcr56ch` | | UST1 | registry / `terra1f0eqgy9w7e5e7up97vjudqwx38tesf8ylx75x2lv3nwm0clry0pqmgfy72` | | USTR | `terra1vy3kc0swag2rhn7jz6n72jp0l2ns0p6r6ez5grxq5uhj2rvs97fqfsetxv` | | CL8Y | `terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3` | There is **no** factory pair USTR/cUSTC ([#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264)); the honest LUNC→USTR hub walk is wrap + cUSTC + UST1. --- ## Why this implementation is needed Traders who pay **native LUNC** (the wallet asset) get a ~100% impact quote while the same size, after wrap, is a normal hub walk. Splitting the trade by hand (wrap, then swap) is not retail UX. Hop cap 4 already allows the extra cUSTC hop. The official dApp promised best execution on Swap ([#209](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/209) / [#596](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/596)) but **H596-7** left natives on client BFS, so the solver never sees wrap-then-cUSTC. Without fail-closed skip of ~100% hop-spread candidates, mapping to GET is not enough: top-5 shortest can still be five thin 1–2 hop paths, and `OPTIMALITY_SCOPE` will honestly never score the 3-hop. --- ## Constraints / guardrails - **H596-7:** native wrap+multihop **execute** stays **pool-only**. Do not attach Pattern C / greedy `book_input` to `executeNativeSwap`. Strip solver `hybrid` the same way wrap+N-hop already strips it ([#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264) / [#1280](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1280)). - Direct 1:1 wrap/unwrap stays mapper-only (`isDirectWrapUnwrap`). Do not send those to `/route/solve`. - Do **not** raise `GET_DEFAULT_MAX_HOPS` (already 4) or `MAX_PATH_CANDIDATES` as the primary fix. Skip unusable paths **then** fill K. LCD/`SOLVE_CONCURRENCY` budget stays documented. - Do **not** implement parallel split routing ([#1203](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1203)) or other-DEX hops ([#690](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/690)). - Do **not** fold [#1257](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1257) USTR→USDT decimal honesty or [#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264) gas envelopes. A 3-hop wrap combo must still use existing wrap+N-hop gas helpers (hop count drives the envelope). - Identity is contract / denom, not ticker ([#715](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/715)). Gems stay hidden ([#562](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/562)). - Copy ([#489](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/489)): no lecture banner “we ignored a shorter route.” One Route row aligned with submit ([#158](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/158) / [#450](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/450)). - Fail closed: drop ~100% hop-spread / ask-out > ask-reserve winners; do not paint theater You Receive. Keep 5 / 30 / 99 chrome ([#678](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/678)). Expert cannot waive ≥99%. - `amount_in` for solve = **net CW20 after wrap fee** on wrap-enter; You Receive on unwrap-exit still nets mapper unwrap fee + InstantWithdraw burn tax ([#512](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/512) / [#516](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/516)). - No CosmWasm migrate. No indexer native-denom graph nodes (substitution at the client, or a documented wrap-map in the GET handler — pick **one**; do not do both). - Public comments: columbus-5 contract ids and public JSON fields only. No hosts, keys, or deploy pipeline. --- ## Relevant files | Path | Why | | --- | --- | | `frontend-dapp/src/pages/SwapPage.tsx` | Native vs CW20 quote branch; submit `executeNativeSwap` vs `indexerOperations` | | `frontend-dapp/src/utils/cw20RouteSolveQuote.ts` | GET `/route/solve` + wallet sim; wrap/native must not copy hop-0 hybrid onto wrap hops | | `frontend-dapp/src/services/terraclassic/router.ts` | `findRoute` / `findRouteWithNativeSupport` / `simulateNativeSwap` / `executeNativeSwap` | | `frontend-dapp/src/utils/swapRouteDisplay.ts` | Route chip vs ops; wrap-enter symbols | | `frontend-dapp/src/services/terraclassic/swapRoutePreflight.ts` | Worst-hop spread; submit disable | | `frontend-dapp/src/utils/swapQuoteAmountScale.ts` | ≥99% theater block | | `indexer/src/api/route_paths.rs` | `find_paths_top_k` shortest-first cap | | `indexer/src/api/best_execution.rs` | Winner merge, `OPTIMALITY_SCOPE`, candidate eval | | `indexer/src/api/route_solver.rs` | GET handler, hop cap, cache | | `indexer/src/api/db_orderbook_sim.rs` | `hop_sim_implausible` — reuse for path skip, do not reinvent | | `indexer/tests/api_route_solve.rs` | `route_solve_global_picks_best_path_not_shortest` | | `docs/route-solver.md` / `docs/adr/0002-global-best-execution-route-solver.md` | Optimality scope amendment: skip-unusable then fill K | | `skills/AGENTS_FRONTEND_HYBRID_ALWAYS_ON.md` | **H596-7** | | `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` | One Route row | | `scripts/check_route_solver_docs.py` | Constant drift | --- ## Recommended direction 1. **Client wrap map (required):** For non-direct native pay and/or native receive, resolve wrap CW20s, `netCw20AfterNativeWrap` when paying native, then `quoteCw20ViaRouteSolve`. Apply unwrap fee/tax on the ask if receive is native. On indexer/wallet failure, keep today’s BFS fallback (**H596-5** degradation) — do not silently submit a 100% spread BFS when a solve winner existed this quote tick. 2. **Submit alignment:** Thread solver ops (hybrid stripped) into `executeNativeSwap` (or a sibling that accepts ops). Stop re-running BFS at broadcast. Set `unwrap_output` iff native ask. 3. **Indexer skip-unusable:** When enumerating top-K, do not keep a candidate whose worst hop is ~100% spread or `hop_sim_implausible`. Continue search until K **usable** paths or hop cap exhausted. Winner remains max `estimated_amount_out_net`. Fixture: 2-hop ~100% drain vs 3-hop honest hub — 3-hop wins; `paths_considered` counts scored usable paths. 4. **Do not** bump `MAX_PATH_CANDIDATES` in this MR unless skip-unusable still cannot admit the wrap-then-cUSTC fixture under a pinned hub graph (then a one-line constant + LCD budget comment + docs drift check — not a second issue). 5. **Docs / skill:** ADR 0002 amendment: native wrap substitution is in-scope of retail GET; **H596-7** execute still pool-only. `verify-issue-1218` script. 6. **Trade market:** pair-scoped CW20; out of scope unless a native denom appears on `/trade` (it should not). --- ## Acceptance criteria - [ ] AC1. Swap `/` native LUNC → USTR (wrap env on): Route includes **cLUNC → cUSTC → UST1 → USTR** (or a solver winner with worst-hop spread ≪ 100%), not BFS-only **cLUNC → UST1 → USTR**, for the 10M LUNC-class size. You Receive is the wrap-then-cUSTC class, not ~50 USTR. - [ ] AC2. Quote and submit use the **same** hop list. `executeNativeSwap` does not BFS a different path than the Route chip. - [ ] AC3. Those hops are pool-only on execute (`hybrid: null` / no `book_input`). Wrap-enter still `wrap_deposit` then CW20 send to router. Unwrap-exit still `unwrap_output`. - [ ] AC4. Direct 1:1 LUNC↔cLUNC / USTC↔cUSTC never call `/route/solve`. - [ ] AC5. Native USTC (`uusd`) wrap-enter to a listed hub CW20 uses the same mapping (symmetric to LUNC). Do not retune [#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264) gas here. - [ ] AC6. Native LUNC → listed CL8Y wrap-enter reaches GET `/route/solve` with `token_in` = cLUNC. Rank by simulated net out; reject ~100% hop spread. Mixed 6/18 hops must not print theater You Receive. - [ ] AC7. Indexer (or client, if skip is client-only): a ~100% hop-spread 2-hop cannot beat an enumerable honest 3-hop. Top-K is filled with **usable** paths first. - [ ] AC8. Indexer GET without native substitution still rejects raw `token_in=uluna` (CW20-only graph) unless the chosen design is server-side wrap-map — then document it and keep one mapping site. - [ ] AC9. 5 / 30 / 99 chrome and Expert ≥99% non-waive unchanged. Gem bridge reject unchanged. - [ ] AC10. `scripts/qa/verify-issue-1218.sh` + docs/skill pointers. `python3 scripts/check_route_solver_docs.py` green if constants/docs change. --- ## Test plan (all paths) | ID | Path | How to test | | --- | --- | --- | | P1 Wrap-enter LUNC→USTR | Swap quote | Vitest: native `uluna` + USTR calls `getRouteSolve(cLUNC, USTR, netAfterWrap)` not `simulateNativeSwap` BFS-only. Fixture graph: thin cLUNC–UST1 + deep cLUNC–cUSTC–UST1–USTR. Winner hops include cUSTC. | | P2 Submit alignment | Same | `executeNativeSwap` (or wrapper) receives those ops; spies show no second `findRoute` that returns the 2-hop. | | P3 H596-7 | Same | Serialized router execute has `hybrid: null` on every hop. | | P4 Direct wrap | LUNC→cLUNC | Still mapper-only; no `/route/solve`. | | P5 Unwrap-exit | USTR→LUNC | Solve `USTR→cLUNC`, then unwrap fee/tax on ask; `unwrap_output` true. | | P6 USTC wrap-enter | `uusd`→USTR | `token_in` = cUSTC; hub walk not first BFS-only. | | P7 LUNC→CL8Y | wrap-enter 6→18 | Hits solve; no 10^9-scale You Receive; ~100% hop rejected. | | P8 Indexer skip-unusable | `api_route_solve` | Pinned pairs: 2-hop implausible vs 3-hop honest → 3-hop `estimated_amount_out`; `search_truncated` still honest. Existing `route_solve_global_picks_best_path_not_shortest` stays green. | | P9 Degradation | Indexer 5xx | BFS fallback allowed; if fallback worst-hop ~100%, submit stays blocked (`anyHopExceedsMaxSpread` / ≥99%). | | P10 CW20 control | cLUNC→USTR (no native) | Still `quoteCw20ViaRouteSolve` only; no wrap_deposit. | | P11 Tax net | wrap-enter into a tax CW20 ask | Display net; `min_return` pre-tax ([#615](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/615)). | | P12 Freeze / zero reserve | Frozen or empty hop | Still skipped ([#585](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/585) / [#369](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/369)); not chosen as winner. | Negative / skip: - N1. Raising hop cap or K **instead of** wrap-map + skip-unusable → fail. - N2. Attaching hybrid/book to native execute → fail AC3. - N3. Implementing [#1257](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1257) / [#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264) / [#1265](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1265) / [#1203](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1203) in this MR → fail. - N4. Route chip showing cUSTC while signed ops are UST1-only → fail AC2 / [#450](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/450). Optional chain: LocalTerra wrap seed + Swap LUNC→hub CW20 when wrap mapper is deployed — quote-only is enough if lcd mock covers P1/P8. --- ## Test plan (attack, hack, and abuse) Evaluate each vector. Do **not** write exploits, PoCs, or attack procedures. | ID | Vector | Expect | Fail if | | --- | --- | --- | --- | | A1 Theater quote | Implausible You Receive as a screenshot “price” | Skip ~100% hop / implausible ask; ≥99% hide + block | Paints ~50 USTR or 10^9-scale out as fillable | | A2 Path spoof | Route chip ≠ signed ops | Display from submit ops ([#450](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/450)) | Shows wrap-then-cUSTC while tx BFS-swaps UST1 | | A3 Hybrid on wrap | Book leg on native wrap+N-hop | **H596-7** strip | `book_input` on wrap execute “to fix ranking” | | A4 Native id injection | `token_in=uluna` on public GET | 404 / reject unless documented wrap-map | Solver treats `uluna` as a CW20 node | | A5 Cache alias | Wrap-fee net vs gross, 6 vs 18 dec | Existing amount bucket + tax/discount identity | Shares quotes across wrap-fee or decimal identity | | A6 Frozen hop | Frozen `code_id` still routed | F6 skip | Quotes frozen-only markets | | A7 Gem bridge | Hidden test gem as intermediate | `shouldRejectGemBridgeQuote` | Routes via retail-hidden gems | | A8 LCD / solve DoS | Extra wrap-enter solves | Existing LCD-heavy rate limit; no new unmetered endpoint | New progress/solve fan-out without cap | | A9 MITM indexer | Compromised hop list | Wallet sim + `max_spread` / min receive still bind execute | Trusts `estimated_amount_out` without sim / floors | --- ## Verification criteria - `make verify-issue-1218` (new): Vitest P1–P7 / P9–P11 class tests + indexer P8 (Postgres + lcd mock / lib as appropriate) + greps that `executeNativeSwap` no longer maps ops solely from `findRouteWithNativeSupport` on the wrap-enter submit path. - Manual / optional LocalTerra: Swap LUNC→USTR Route shows cUSTC; worst-hop spread not ~100%; one SWAP with wrap + router. - Docs: ADR 0002 / `route-solver.md` / **H596-7** note wrap-enter GET; `scripts/check_route_solver_docs.py` green. - Reviewers can see, for P1, fixture hops + amounts (or a public solve JSON summary with contract ids) — not a screenshot of the old 2-hop as “fixed.” --- ## Out of scope - Mixed-decimal CW20 USTR→USDT ([#1257](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1257)). - Wrap+2hop execute gas ([#1264](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1264)) / gas census ([#1222](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1222)). - Remaining-failure research memo ([#1265](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1265)). - Parallel split routing ([#1203](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1203)). - Other-DEX v2 / IBC USDC intermediates ([#690](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/690) / [#691](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/691)). - Typeable You Receive ([#1256](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1256)). - Exact-out / indexer reverse solve. - CosmWasm migrate; turning hybrid on for native execute. --- ## First-pass model recommendation Recommendation: grok-high Rationale: Cross-cutting retail quote protocol: Swap native branch (`SwapPage.tsx`), wrap execute (`router.ts`), indexer top-K skip-unusable (`route_paths.rs` / `best_execution.rs`), and **H596-7** (must not “fix” ranking by attaching a book to wrap hops). More than three production files, not a local known edit, and the product patch is not a single constant bump. Composer criteria fail on subsystem count and wrap vs solver ownership. Verify with Vitest + indexer route-solve fixtures, not live-mainnet-only acceptance. Advisory planning text, not a runner argument.
PlasticDigits changed title from DEX Routing to fix(swap): wrap-enter native routes must GET /route/solve and drop ~100% hop-spread winners 2026-09-21 04:18:49 +00:00

Merged as PR #1297. Wrap-enter maps wrap CW20s then GET /route/solve; indexer skip-unusable while filling K. Native wrap hops stay pool-only.

Leftover: LocalTerra / LUNC→USTR Route wrap-then-cUSTC. Tracked on #1300.

Merged as PR #1297. Wrap-enter maps wrap CW20s then GET /route/solve; indexer skip-unusable while filling K. Native wrap hops stay pool-only. Leftover: LocalTerra `/` LUNC→USTR Route wrap-then-cUSTC. Tracked on #1300.

Verified on origin/main in the issue worktree: make verify-issue-1218 passed both rounds (10/10 steps), including the native uluna 400 guard, skip-unusable honest 3-hop fixture, 105 frontend tests, and route-solver docs/skill checks. The verifier now isolates SQLx history in dex_indexer_test_1218; its local worktree change is commit dd9e6301. H1218-1–H1218-8 remain documented and crosslinked in docs/testing.md, docs/frontend.md, docs/integrators.md, docs/indexer-invariants.md, and the frontend/indexer agent skills.

The implementation shipped in PR #1297. Post-merge leftovers were tracked by #1300, now closed. The optional LocalTerra E7/E8 transaction check was not run; issue acceptance is covered by the passing fixture and UI checks. Broader remaining solve-failure research stays separate in #1265 / ADR 0007.

Closing #1218.

Verified on origin/main in the issue worktree: make verify-issue-1218 passed both rounds (10/10 steps), including the native uluna 400 guard, skip-unusable honest 3-hop fixture, 105 frontend tests, and route-solver docs/skill checks. The verifier now isolates SQLx history in dex_indexer_test_1218; its local worktree change is commit dd9e6301. H1218-1–H1218-8 remain documented and crosslinked in docs/testing.md, docs/frontend.md, docs/integrators.md, docs/indexer-invariants.md, and the frontend/indexer agent skills. The implementation shipped in PR #1297. Post-merge leftovers were tracked by #1300, now closed. The optional LocalTerra E7/E8 transaction check was not run; issue acceptance is covered by the passing fixture and UI checks. Broader remaining solve-failure research stays separate in #1265 / ADR 0007. Closing #1218.

Follow-up: verifier and documentation improvements are now reviewable in PR #1341 (commit dd9e6301): #1341. The PR targets main; its Woodpecker PR check is green.

Follow-up: verifier and documentation improvements are now reviewable in PR #1341 (commit dd9e6301): https://git.cl8y.com/code/cl8y-dex-terraclassic/pulls/1341. The PR targets main; its Woodpecker PR check is green.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1218
No description provided.