chore(runway): cherry-pick fix(quick-buy): enable Buy CTA in lockstep with quote loader#31238
Merged
Merged
Conversation
… with quote loader (#31218) ## **Description** In the QuickBuy bottom sheet, the **Buy** button sometimes took longer to become enabled than the amount-section quote loader took to disappear. The user expects the CTA to become tappable as soon as the quote completes. **Reason for the change:** the loader and the Buy button were gated by two conditions that drifted out of sync for one render. The loader is driven by `isBlockingQuoteLoad` (gated by `isQuoteLoading`), which clears the instant the fetch resolves. The button is gated by `isConfirmDisabled`, which includes `isPendingQuoteRefresh` — and that value was derived from `settledSourceTokenAmountRef`, a ref updated only inside a post-commit `useEffect`. On the render where the quote arrived, the loader hid but the ref had not caught up, and ref writes do not schedule a re-render, so the CTA only re-enabled on the next unrelated render (a redux tick, gas-estimate update, quote countdown, etc.) — producing the variable lag. **Solution:** replace the timing/effect-based "settled amount" tracking with a synchronous derivation. A displayed quote is for the current amount when the quote's own `srcTokenAmount` equals the requested amount (the request is built with `calcTokenValue(sourceTokenAmount, decimals).toFixed(0)` and the bridge echoes that exact value back). `isPendingQuoteRefresh` now derives from this synchronous check, so `isBlockingQuoteLoad` and `isConfirmDisabled` settle on the **same render** the matching quote lands. Background refreshes of the same amount keep `srcTokenAmount` unchanged, so the CTA stays enabled as before. ## **Changelog** CHANGELOG entry: Fixed the QuickBuy Buy button sometimes staying disabled briefly after the quote finished loading. ## **Related issues** Fixes: An issue where he QuickBuy Buy button sometimes staying disabled briefly after the quote finished loading. ## **Manual testing steps** ```gherkin Feature: QuickBuy Buy button enablement Scenario: Buy button enables as soon as the quote loads Given I open the QuickBuy bottom sheet for a token When I enter or select an amount and the quote loader spins Then the loader disappears when the quote completes And the Buy button becomes enabled on the same frame (no lag) Scenario: Buy button stays disabled while a new amount is being quoted Given a quote is already displayed for an amount When I change the amount so a new quote is fetched Then the Buy button is disabled until the quote for the new amount arrives ``` ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [x] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Made with [Cursor](https://cursor.com) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Localized QuickBuy confirm/disable logic with added unit tests; no auth, submission, or bridge API changes beyond when the button enables. > > **Overview** > Fixes QuickBuy **Buy** staying disabled briefly after the quote loader finishes by replacing post-commit ref tracking of the “settled” amount with a synchronous **`isActiveQuoteForCurrentAmount`** check. > > **`isPendingQuoteRefresh`** now derives from whether the on-screen quote matches the committed amount: atomic units from **`calcTokenValue`** on the requested **`sourceTokenAmount`** are compared to **`activeQuote.sentAmount`** (full wallet deduction), not **`quote.srcTokenAmount`**, so gas-included/sponsored quotes still enable the CTA when the displayed quote is valid. > > Tests add default **`sentAmount`** on quote fixtures plus cases for same-render loader/CTA sync and post-fee **`srcTokenAmount`** with matching **`sentAmount`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4124eb2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
Contributor
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - PR targets a release or stable branch (release/* or stable) All E2E tests pre-selected. |
sleepytanya
approved these changes
Jun 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
In the QuickBuy bottom sheet, the Buy button sometimes took longer
to become enabled than the amount-section quote loader took to
disappear. The user expects the CTA to become tappable as soon as the
quote completes.
Reason for the change: the loader and the Buy button were gated by
two conditions that drifted out of sync for one render. The loader is
driven by
isBlockingQuoteLoad(gated byisQuoteLoading), whichclears the instant the fetch resolves. The button is gated by
isConfirmDisabled, which includesisPendingQuoteRefresh— and thatvalue was derived from
settledSourceTokenAmountRef, a ref updated onlyinside a post-commit
useEffect. On the render where the quote arrived,the loader hid but the ref had not caught up, and ref writes do not
schedule a re-render, so the CTA only re-enabled on the next unrelated
render (a redux tick, gas-estimate update, quote countdown, etc.) —
producing the variable lag.
Solution: replace the timing/effect-based "settled amount" tracking
with a synchronous derivation. A displayed quote is for the current
amount when the quote's own
srcTokenAmountequals the requested amount(the request is built with
calcTokenValue(sourceTokenAmount, decimals).toFixed(0)and the bridge echoes that exact value back).isPendingQuoteRefreshnow derives from this synchronous check, soisBlockingQuoteLoadandisConfirmDisabledsettle on the samerender the matching quote lands. Background refreshes of the same
amount keep
srcTokenAmountunchanged, so the CTA stays enabled asbefore.
Changelog
CHANGELOG entry: Fixed the QuickBuy Buy button sometimes staying
disabled briefly after the quote finished loading.
Related issues
Fixes: An issue where he QuickBuy Buy button sometimes staying disabled
briefly after the quote finished loading.
Manual testing steps
Screenshots/Recordings
Before
N/A
After
N/A
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Performance checks (if applicable)
SRPs
to import wallets with many accounts and tokens
performance metrics
trace()for usage andaddTokenfor an example
For performance guidelines and tooling, see the Performance
Guide.
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Made with Cursor
Note
Low Risk
Localized QuickBuy confirm/disable logic with added unit tests; no
auth, submission, or bridge API changes beyond when the button enables.
Overview
Fixes QuickBuy Buy staying disabled briefly after the quote loader
finishes by replacing post-commit ref tracking of the “settled” amount
with a synchronous
isActiveQuoteForCurrentAmountcheck.isPendingQuoteRefreshnow derives from whether the on-screenquote matches the committed amount: atomic units from
calcTokenValueon the requestedsourceTokenAmountarecompared to
activeQuote.sentAmount(full wallet deduction), notquote.srcTokenAmount, so gas-included/sponsored quotes stillenable the CTA when the displayed quote is valid.
Tests add default
sentAmounton quote fixtures plus cases forsame-render loader/CTA sync and post-fee
srcTokenAmountwithmatching
sentAmount.Reviewed by Cursor Bugbot for commit
4124eb2. Bugbot is set up for automated
code reviews on this repo. Configure
here.
Co-authored-by: Cursor cursoragent@cursor.com c09f43c