Skip to content

fix(swapper): show the proper slippage tag on Portals trades - #12597

Merged
kaladinlight merged 3 commits into
developfrom
fix/portals-slippage-tag
Aug 17, 2026
Merged

fix(swapper): show the proper slippage tag on Portals trades#12597
kaladinlight merged 3 commits into
developfrom
fix/portals-slippage-tag

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Aug 17, 2026

Copy link
Copy Markdown
Member

Description

Portals trades showed a purple Custom slippage tag reading 0.00%, and — as a second effect of the same root cause — under-quoted by the full slippage tolerance.

Two bugs compounding:

1. Portals advertised a 0% tolerance. /v2/portal only returns a distinct minOutputAmount on validated orders. Unvalidated ones return the post-slippage minimum in both amount fields, so the buffer recovered from the amounts was structurally always zero. That hits every rate (getPortalsTradeRate is always validate: false) and any quote whose validated call fails — an unapproved ERC20 sell, or a pair where the requested tolerance is too tight ("Insufficient buy: slippage too high").

2. MaxSlippage read 0% as user-set. bnOrZero(userSlippagePercentage) coerced an unset preference to 0, so any quote reporting 0% matched and was labelled Custom. Portals was simply the swapper that always did.

Because buyAmountAfterFees was that same post-slippage minimum, Portals also quoted ~2.5% low on every rate — losing quote comparisons against swappers it routes through. In a $10 ETH→USDC test, Portals routed via 0x V2 and showed 9.696949 USDC while the Zrx quote beside it showed 9.943. It now shows 9.945589 — within 0.03% of Zrx, as it should be.

Changes

  • Recover the pre-slippage output (min / (1 - requested)) when the amounts carry no buffer
  • Advertise max(requested, applied) so receive-at-least never promises more than Portals guarantee
  • Add the Portals fee back to buyAmountBeforeFees, but only on a positive buy-asset match — their fee comes out of the sell asset on ERC20 sells, and the amounts they report are already net of it
  • Drop the cross-chain amount special case: it forced the buffer to zero by construction
  • MaxSlippage now requires a user preference to actually exist before showing Custom

The tag fix is swapper-agnostic — any swapper reporting 0% was mislabelled.

Issue (if applicable)

closes #11341

Linear: SS-5270

Risk

Medium. No on-chain transaction is constructed or modified here — transactionData still passes through from Portals verbatim, and endpoints.ts never reads the slippage field. The change is confined to quote/rate-time display values.

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

Portals only for the amount and tolerance changes. MaxSlippage is shared, so the tag change touches every swapper — with no preference set it now always reads Auto, which also fixes the auto-slippage swappers (Relay/Across/Debridge) that could report 0% and get mislabelled Custom.

Second-order value shifts on Portals, both in the more-correct direction: the Mixpanel event payload (buyAmountBeforeFees) and the "extra delta" denominator on the trade success screen.

Untestable right now: Portals cross-chain 500s upstream on every pair, direction and endpoint I tried — including directly against api.portals.fi, bypassing our proxy. So the cross-chain path could not be validated end to end. The display invariant holds by construction under either possible cross-chain semantics, but it deserves a re-check when Portals restore that endpoint. Worth its own ticket: cross-chain Portals quotes currently fail outright, not just display wrong.

Testing

Engineering

Validated against the live Portals API — 30+ calls across 5 chains, both validate modes, four slippage values:

  • Unvalidated responses collapse outputAmount onto minOutputAmount, always
  • That collapsed amount is quoted × (1 - requested): implied quoted output stayed constant to 0.0074% across a 0.1→2.5% sweep, and the recovered value matched a validated simulation within 0.03%
  • outputAmount is net of feeAmount: their sum was byte-identical (1911487049) across three feePercentage values back to back
  • The fee is sell-side on ERC20 sells and buy-side on native sells, verified on WETH→USDC, USDC→FOX, FOX→ETH and ETH→USDC
  • expected × (1 - tolerance) lands on Portals' own minOutputAmount in both market regimes — when they apply more slippage than requested and when they apply less

Degenerate payloads (missing minimum, both amounts zero, min above output, one-base-unit buffer) all fall back to the requested tolerance with no NaN/Infinity. A checksummed outputToken produces an identical buy amount to a lower-cased one — that comparison guards a 3,000,000× amount inflation if the casing ever drifts, so it is deliberately case-insensitive.

Existing suites pass: 192 swapper tests, 16 web state tests.

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Not behind a flag. On any Portals-routable pair (e.g. ETH→USDC on Ethereum or Base), open the trade details expander:

  1. Default settings, input screen. Expect Auto 2.50% — not Custom, not 0.00%. develop shows Custom 0.00%.
  2. Confirm screen, already-approved or native sell. Expect Auto at or just above 2.50%, e.g. 2.52%. The small upward drift between screens is the real headroom Portals left after simulating — expected, not a bug.
  3. Confirm screen, unapproved ERC20 sell. The original report's path. Expect Auto 2.50%.
  4. Custom slippage. Settings → Custom → 1%. Expect Custom 1.00% on both screens; switch back to Auto and confirm the tag returns.
  5. Amounts. Compare Portals against another swapper on the same pair — it should now be roughly 2.5% higher than on develop and competitive rather than systematically last. Price impact drops by about the same amount.
  6. Other swappers unaffected — Zrx/Relay/THORChain: Auto with no preference, Custom with one, and THOR/Maya streaming still shows Auto with no percentage.

Follow-ups (not in this PR)

  • DEFAULT_PORTALS_SLIPPAGE_DECIMAL_PERCENTAGE is 2.5%, which is why the honest number reads high — their estimate endpoint suggests ~0.25% is realistic
  • The false "ShapeShift can't set slippage on Portals" warning in the advanced quote list is exact string equality on the tolerance; it should key off isAutoSlippageSupportedBySwapper
  • usePriceImpact truncates the percentage to 2dp before scaling it to fiat, so the -$X.XX on a quote card can be off by sellAmount × 0.0001 — $0.05 on $500, $5 on $50k
  • Portals cross-chain is down upstream

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved swap quotes and rates by accurately accounting for output-token fees and slippage.
    • Corrected fee handling across buy and sell assets, including case-insensitive fee matching.
    • Slippage calculations now reflect both requested and applied values for more reliable trade details.
    • Fixed custom-slippage indicators so they appear only when a user-defined slippage value is available.

Portals only report a distinct minOutputAmount on validated orders. Every rate,
and any quote whose validated call fails (an unapproved ERC20 sell, or a pair
where the requested tolerance is too tight), reports the post-slippage minimum
in both amount fields - so the buffer recovered from those amounts was always
zero, and MaxSlippage read a zero tolerance as a user-set one because bnOrZero
coerced an unset preference to zero.

Recover the pre-slippage output from the tolerance we requested when the amounts
carry no buffer, advertise the wider of the requested and applied tolerances,
and require a positive buy-asset match before adding the Portals fee back to the
buy amount. Portals report amounts net of their fee, and take that fee in the
sell asset on ERC20 sells.

Also fixes Portals under-quoting by the full tolerance (2.5% by default), since
the post-slippage minimum was being displayed as the expected output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaladinlight
kaladinlight requested a review from a team as a code owner August 17, 2026 18:22
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa111b58-2f8c-44b3-9744-940c31066339

📥 Commits

Reviewing files that changed from the base of the PR and between 6f0eb28 and 58be9fe.

📒 Files selected for processing (5)
  • .claude/guidelines/global-rules.md
  • packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts
  • packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.ts
  • packages/swapper/src/swappers/PortalsSwapper/utils/getPortalsTradeContext.ts
  • src/components/MultiHopTrade/components/TradeInput/components/MaxSlippage.tsx

Included review availability: Your plan includes up to 2 reviews per rolling hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Portals trade context now calculates fee-aware slippage from the output token and tolerance. Quote and rate flows pass the new parameters. The UI avoids marking trades as custom when user slippage is undefined. Comment guidance was updated.

Changes

Portals slippage handling

Layer / File(s) Summary
Calculate Portals slippage
packages/swapper/src/swappers/PortalsSwapper/utils/getPortalsTradeContext.ts
The context derives fee-aware output amounts, calculates applied slippage, compares requested and applied tolerance, and stores the selected value.
Wire quote and rate flows
packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts, packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.ts
Both flows pass outputToken and slippageTolerancePercentage to getPortalsTradeContext.
Correct custom-slippage detection
src/components/MultiHopTrade/components/TradeInput/components/MaxSlippage.tsx
The custom-slippage comparison now requires a defined user value.

Documentation rules

Layer / File(s) Summary
Add comment guidance
.claude/guidelines/global-rules.md
Comments are limited to non-obvious facts and generally one line.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 58be9

This change corrects Portals slippage labels and quote amounts without changing transaction construction or contract interactions. It is merge-ready after normal checks, with no actionable merge-blocking risk remaining.

Suggested reviewers: 0xapotheosis

Poem

A rabbit checks the slippage trail,
With output tokens in the sail.
Fees are counted, tags align,
Comments stay short and clear in line.
Hop, hop—the trade details shine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The .claude/guidelines/global-rules.md documentation-rule changes are unrelated to the Portals slippage-tag objectives in issue #11341. Remove the unrelated .claude/guidelines/global-rules.md changes or move them to a separate pull request.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the Portals slippage-tag fix, which is the primary user-visible change.
Linked Issues check ✅ Passed The changes address issue #11341 by preventing unset slippage from producing a Custom tag and by displaying the proper slippage classification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/portals-slippage-tag

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kaladinlight
kaladinlight enabled auto-merge (squash) August 17, 2026 19:38
@kaladinlight
kaladinlight merged commit f01a2b5 into develop Aug 17, 2026
4 checks passed
@kaladinlight
kaladinlight deleted the fix/portals-slippage-tag branch August 17, 2026 19:49
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.

Show proper tag on slippage for Portals Trades

1 participant