Skip to content

fix(bridge): hide stale price impact while a new quote is loading#7711

Open
tenderdeve wants to merge 10 commits into
cowprotocol:developfrom
tenderdeve:fix/7429-bridge-quote-loading-price-impact
Open

fix(bridge): hide stale price impact while a new quote is loading#7711
tenderdeve wants to merge 10 commits into
cowprotocol:developfrom
tenderdeve:fix/7429-bridge-quote-loading-price-impact

Conversation

@tenderdeve

@tenderdeve tenderdeve commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Closes #7429.

When the destination token is switched in swap-and-bridge mode, the price impact field briefly flashes a huge / scary percentage derived from the previous quote while the new bridge quote is still being fetched.

PriceImpactIndicator already received the loading flag, but rendered the (stale) percentage and the spinner side by side. Gate the percentage on !priceImpactLoading so only the spinner shows while a fresh quote is being computed, matching the rest of the bridge-quote loading UX.

File

  • apps/cowswap-frontend/src/common/pure/PriceImpactIndicator/index.tsx

Testing steps

  1. Open the app on Ethereum, pick WETH → USDC and wait for the quote to load.
  2. Change the destination token to WETH on Base (triggers swap-and-bridge mode + a new quote).
  3. While the new quote is loading, the price impact area should show only the loading spinner — not a stale/scary percentage.
  4. Once the quote loads, the new price impact percentage appears as before.

Summary by CodeRabbit

  • Bug Fixes
    • Updated price impact rendering to avoid showing stale tooltips/percentages while quote data is still loading.
    • Improved loading-state handling so consumers display only a spinner during quote catch-up, and recompute once values stabilize.
    • Added coverage to ensure the “safety-valve” logic correctly resets when a new quote starts after unresolved USD inputs.

When the destination token is changed in swap-and-bridge mode, the
price impact briefly rendered a huge/scary value derived from the
previous quote while the new bridge quote was still being fetched.

`PriceImpactIndicator` already received the `loading` flag but rendered
the (stale) percentage *and* the spinner side by side. Gate the
percentage on `!priceImpactLoading` so only the spinner shows while a
fresh quote is being computed, matching the rest of the bridge-quote
loading UX.

Closes cowprotocol#7429
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

@tenderdeve is attempting to deploy a commit to the cow-dev Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 23, 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1f6a7f45-3147-4aaf-b353-ee2c550af69d

📥 Commits

Reviewing files that changed from the base of the PR and between d790ffd and 6354b1a.

📒 Files selected for processing (2)
  • apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.test.ts
  • apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts

Walkthrough

useFiatValuePriceImpact now treats quote freshness as part of its loading state, suppressing priceImpact while a new quote is still catching up or timing out. PriceImpactIndicator now avoids rendering its content until loading is false.

Changes

Price Impact Loading Suppression

Layer / File(s) Summary
useFiatValuePriceImpact: quote staleness and suppressed return
apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts, apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.test.ts
Imports useTradeQuote, combines quote loading and param-change signals into the hook’s loading state, resets the timeout on quote changes, and adds test coverage for re-arming the timeout after a new quote starts.
PriceImpactIndicator: gate rendering on loading state
apps/cowswap-frontend/src/common/pure/PriceImpactIndicator/index.tsx
Tightens the render condition from priceImpact alone to priceImpact && !priceImpactLoading.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: shoom3301, elena-zh, limitofzero

Poem

🐇 Hop, hop, the numbers calm down bright,
While quotes are loading, I hide from sight.
No spooky spike, no stale surprise,
Just fresh new impact when it arrives.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: hiding stale price impact while a new quote loads.
Description check ✅ Passed The description matches the template well with a summary, issue reference, and concrete testing steps.
Linked Issues check ✅ Passed The PR addresses #7429 by suppressing stale price impact during quote loading and showing only the spinner.
Out of Scope Changes check ✅ Passed The extra hook and test changes support the same fix and do not appear unrelated to the issue.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@elena-zh elena-zh added the trigger-preview Add to a fork PR to trigger CF-pages preview. See https://github.com/cowprotocol/cowswap/pull/7615 label Jun 23, 2026
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Pages preview mirror

Preview branch URL: https://github.com/cowprotocol/cowswap/tree/cf-preview/pr-7711
Mirror PR: #7712
Cloudflare Pages preview links will be posted on the mirror PR by the Cloudflare Pages GitHub integration once builds complete.

Source fork branch: tenderdeve/cowswap:fix/7429-bridge-quote-loading-price-impact
Approval target SHA: accd5863abba
Last mirrored SHA: d790ffde4649
Last comment update: @fairlighteth at 2026-07-14T15:04:08.072Z

  • Sync Cloudflare preview to approval target commit

@elena-zh elena-zh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @tenderdeve , thank you, but not fixed: the issue is reproducible with price impact in one chain.

Image

The previous fix only stopped \`PriceImpactIndicator\` from rendering a
stale percentage during USD-loading. The reporter still saw a scary
value on same-chain quote refreshes: when the destination token
changes, the trade form keeps the previous \`outputCurrencyAmount\` until
the new quote lands, and during that window \`useFiatValuePriceImpact\`
happily divides the new input USD by the old output USD, producing a
huge nonsense %.

Pull \`isLoading\` / \`hasParamsChanged\` out of \`useTradeQuote()\` and
fold them into the price-impact loading flag. While that combined flag
is true, return \`{ priceImpact: undefined, isLoading: true }\` so every
consumer falls back to the loading state (spinner) instead of rendering
the stale value.

Closes cowprotocol#7429
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@elena-zh updated in bad39f2 — the previous fix only suppressed the value while USD prices were loading. Same-chain repro hits a different window: the trade form keeps the stale outputCurrencyAmount until a fresh quote lands, and useFiatValuePriceImpact was dividing the new sell USD by the old buy USD. Folded useTradeQuote().isLoading + hasParamsChanged into the price-impact loading flag and returned { priceImpact: undefined, isLoading: true } during that window, so the indicator shows only the spinner.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts`:
- Line 40: The timeout effect that manages `hasLoadingTimedOut` only resets this
flag when `inputToken` or `outputToken` change, but for amount-only quote
refreshes (no token change), the flag can remain `true` after 15 seconds,
bypassing stale-value suppression. To fix this, modify the dependency array and
logic of the timeout effect (around lines 43-54) to also reset
`hasLoadingTimedOut` when `quoteParamsChanged` is true, ensuring the timeout is
reset whenever a new quote request is triggered regardless of whether the tokens
changed. This prevents the stale-value bypass window for amount-only refreshes
without resetting the timer on every keystroke.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7ace58b7-f587-4b6b-be06-6a920d75320c

📥 Commits

Reviewing files that changed from the base of the PR and between c2a8df1 and d790ffd.

📒 Files selected for processing (2)
  • apps/cowswap-frontend/src/common/pure/PriceImpactIndicator/index.tsx
  • apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts

@elena-zh elena-zh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now, thank you

@elena-zh
elena-zh requested a review from a team June 23, 2026 15:39
@elena-zh
elena-zh requested review from shoom3301 and removed request for a team June 24, 2026 09:02
@kernelwhisperer
kernelwhisperer self-requested a review July 6, 2026 10:25
// While a fresh quote is loading, don't expose the stale value at all — consumers
// hide the percentage when `priceImpact` is undefined, leaving just the spinner.
if (stillLoading) {
return { priceImpact: undefined, isLoading: true }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While a fresh quote is loading, don't expose the stale value at all —

This never happens because after the first quote is being loaded, the second quote (the fresh quote as you say) has hasLoadingTimedOut === true so stillLoading is never true again, after the first quote.

So this code and comment are incorrect

elena-zh and others added 2 commits July 7, 2026 12:10
…n change

The stale-value suppression relied on `hasLoadingTimedOut`, whose timeout only
reset when the token pair changed. After the first 15s it stayed true, so later
same-pair repricing exposed the stale price impact immediately instead of hiding
it while the fresh quote loaded. Add `quoteParamsChanged` to the timeout effect
so it re-arms for every new quote, while plain loading flicker still can't restart
it (a stuck quote keeps timing out). Adds a regression test.
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@kernelwhisperer you're right — the guard was dead after the first timeout. The 15s timeout only reset on a token-pair change, so once hasLoadingTimedOut flipped true it stayed true, and every later same-pair reprice showed the stale value immediately instead of suppressing it.

Fixed in 6354b1a: the timeout effect now also keys off quoteParamsChanged, so it re-arms for each genuinely new quote while plain loading flicker still can't restart it (a stuck quote keeps timing out). Added a regression test covering the new-quote-after-timeout case.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
swap-dev Ready Ready Preview Jul 7, 2026 12:50pm

Request Review

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ AI Review (Codex GPT-5, worked 21m): follow-up still misses an already-true quote-change state

Rechecked

  • Author response: 6354b1ae6 re-arms the timeout by adding quoteParamsChanged to the timeout effect dependency.
  • Code path: apps/cowswap-frontend/src/legacy/hooks/usePriceImpact/useFiatValuePriceImpact.ts:59.
  • Test coverage: useFiatValuePriceImpact.test.ts covers quoteParamsChanged: false -> true, but not a new same-pair quote while it is already true.

Result: Still broken. The fix re-arms the timeout when quoteParamsChanged flips to true, but useTradeQuotePolling can start another changed-params quote with pollQuoteRef.current(true) while the previous changed-params quote is still loading/timed out. In that path setLoading(true, ...) writes the same boolean again, the effect does not re-run, hasLoadingTimedOut stays true, and stale price impact can render immediately.

Suggested next step

  • Reset from a quote request key / amount-change signal, or include the relevant same-pair quote params in the timeout reset condition.
  • Add a regression test for the quoteParamsChanged: true -> true case.
🤖 Prompt for AI agents
Verify this follow-up finding against current PR code. The current timeout reset depends on quoteParamsChanged, so it only re-arms when the boolean changes. Check the case where hasLoadingTimedOut is true while quoteParamsChanged is already true, then another same-pair changed-params quote starts and setLoading(true, ...) writes the same boolean again. If still valid, reset the timeout from a quote request/params key or amount-change signal, and add a regression test for quoteParamsChanged staying true across a second same-pair quote request.

Generated using the pr-review skill from the CoW Protocol skills repo.

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.

Scary price impact when swap and bridge quote is loading

4 participants