Skip to content

fix(swap): quote-time refund-risk boundaries — fee/slippage gate + route minimums#369

Merged
BitHighlander merged 1 commit into
developfrom
fix/swap-refund-boundaries
Jul 18, 2026
Merged

fix(swap): quote-time refund-risk boundaries — fee/slippage gate + route minimums#369
BitHighlander merged 1 commit into
developfrom
fix/swap-refund-boundaries

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

A $17.76 BTC→ETH swap (tx 1a642237…) was refunded on-chain with 'Emit asset 0.0083818 less than price limit' and returned $14.52 — an 18% loss for nothing. On small amounts the fixed outbound fee exceeds the slippage allowance, so the emitted amount is guaranteed to land below the memo's price limit, and the refund burns another outbound fee. Nothing gated this at quote time.

Boundaries added (getSwapQuote, every route)

  1. Fees vs slippage allowance: fees.totalBps >= slippageBps → throw "swap amount too low" at quote time. Basis points are the only fee field with integration-independent units — fees.outbound arrives in base units or decimal depending on the swapper (the SwapDialog literally has a looksLikeBaseUnits heuristic for it), so no gate is built on it.
  2. Route minimum for ALL integrations: minAmountIn was only enforced inside the NEAR Intents block; THORChain routes sailed past their floor. Now enforced route-agnostically.
  3. recommended_min_amount_in extraction (parseQuoteResponse): THORNode publishes exactly this floor — always 1e8 base units of the sell asset — now converted to decimal and folded into minAmountIn, gated to THORChain/Maya routes only (other swappers' units vary; no generic fallback per the no-silent-format-mismatch rule).

Tests

Two new regression tests pin the 1e8→decimal conversion and the non-THORChain skip; bun test __tests__/swap-parsing.test.ts 51/51. tsc differential: 0 new errors (2 pre-existing in swap.ts untouched).

…ute minimums

A $17.76 BTC→ETH swap was refunded on-chain ('Emit asset less than
price limit') and came back $14.52 — on small amounts the fixed
outbound fee exceeds the slippage allowance, so the emit lands below
the memo's limit and the refund burns another outbound fee. Nothing
gated this at quote time.

Two unit-safe boundaries in getSwapQuote, every route:
- fees.totalBps >= slippageBps → throw 'amount too low' (bps are the
  only fee field with integration-independent units; fees.outbound is
  base-units-or-decimal depending on swapper).
- minAmountIn enforced for ALL integrations — previously only the NEAR
  Intents block checked it, so THORChain routes sailed past their floor.

parseQuoteResponse now also extracts THORChain's
recommended_min_amount_in into minAmountIn (always 1e8 base units of
the sell asset → converted to decimal; gated to THORChain/Maya routes
only, other swappers' units vary). Regression tests pin the units
conversion and the non-THORChain skip (51/51).
@BitHighlander
BitHighlander merged commit 8c5e582 into develop Jul 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant