Direct-hybrid swap shows raw indexer output/slippage with no wallet reconcile, unlike the multi-hop path #471
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#471
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?
Came out of the frontend leg of the security sweep (the #381 hardening umbrella, same indexer-trust family as #449/#450). The direct-hybrid swap path trusts the indexer's "you receive" and slippage numbers straight through to the pre-sign summary, while the multi-hop path in the same file recomputes them against the wallet. So on a direct CW20↔CW20 hybrid trade a tampered/buggy indexer can display an output the chain won't actually hand you.
Where
frontend-dapp/src/utils/directHybridQuote.ts— the indexer branch ofquoteDirectHybridSwap:directHybridQuote.ts:66return_amount: idx.estimated_amount_outdirectHybridQuote.ts:72routeSlippagePercent: idx.slippage_percentBoth are lifted verbatim from
postRouteSolve(line 60) with no wallet cross-check. The only thing this branch runs against the wallet ispreflightSwapRouteSpread(line 64), which checks per-hop spread — it does not recompute the return amount.Those values flow into the summary in
SwapPage.tsx:SwapPage.tsx:851const outputAmount = simData?.return_amount ?? ''SwapPage.tsx:860-862expectedSlippagePctfromsimData.routeSlippagePercentContrast the multi-hop indexer branch, which DOES reconcile —
SwapPage.tsx:544runssimulateMultiHopSwapon the wallet and returnsresult.amountasreturn_amount(line 561), and derives slippage from that wallet amount viaresolveRouteSlippagePercent(result.amount, ...)(lines 564-568). The direct path skips that entirely.The disclosure copy makes it worse:
directHybridQuote.ts:26(and :28) tells the user the estimate is "checked against your wallet before submit." In this branch nothing shown is checked against the wallet. The only wallet-derived guard is the submit floor,computeDirectHybridMinReturnatSwapPage.tsx:797-807— and that value is never surfaced in the UI, it's passed straight intoswap(...)asminReturn(line 810).How to hit it
willSubmitHybridtrue) so the quote goes throughquoteDirectHybridSwap→ indexerPOST /route/solveatdirectHybridQuote.ts:60, not the LCD fallback at line 81.estimated_amount_out(and/or a lowslippage_percent).min_returnfromcomputeDirectHybridMinReturn— which can be well under the displayed figure.Impact
UX / display-integrity, not a direct drain — funds are still floored by
min_returnon submit, so a manipulated indexer can't make you accept an arbitrarily bad fill silently; worst case the tx reverts or fills at the wallet floor. But the user signs against a number that isn't real, and the "checked against your wallet before submit" copy actively tells them it is. That's a trust gap, and it's exactly the direct-path blind spot #450 closed on multi-hop.Fix direction
Two clean options:
quoteDirectHybridSwap, run the wallet sim (simulateHybridSwap, which the fallback at line 81 already uses) and reconcile — show the wallet amount asreturn_amountand derive slippage from it likeresolveRouteSlippagePercentdoes, only using the indexer number as a hint. On a big mismatch, warn instead of silently displaying the indexer figure.computeDirectHybridMinReturnfloor as a "minimum received" line in the summary so what's signed is visible, and drop/qualify the "checked against your wallet" copy for this branch since it isn't.First option is the real fix and keeps the two paths consistent.
Mirror the multi hop path approvdd
mentioned in commit
8750c78583mentioned in merge request !1005
mentioned in commit
a793eb6b84mentioned in issue #559