Skip to content

fix(predict): stabilize Predict pay-with-any-token quote updates cp-7.77.0#30064

Merged
caieu merged 7 commits into
mainfrom
predict/pwat-quote-bug
May 13, 2026
Merged

fix(predict): stabilize Predict pay-with-any-token quote updates cp-7.77.0#30064
caieu merged 7 commits into
mainfrom
predict/pwat-quote-bug

Conversation

@caieu

@caieu caieu commented May 12, 2026

Copy link
Copy Markdown
Contributor

Description

This fixes Predict pay-with-any-token quote and CTA timing issues that could appear when users changed payment tokens.

The quote trigger previously deduped updates only by amount, so a new transaction or selected token using the same amount could skip updatePendingAmount / updateTokenAmountCallback and fail to start the quote request. This PR keys those emissions by transaction id, token address, and chain id so the same amount is still emitted when the active transaction or payment token changes.

The Predict buy CTA could also remain enabled briefly while the payment selector was closing and before the new quote loading state appeared. This PR adds a Predict-only payment-selector navigation lock in usePredictBuyConditions, disables the buy CTA/pay row immediately when the selector opens, and releases the lock one second after the screen regains focus.

It also waits for account tokens before defaulting back to Predict balance, avoiding an early fallback while token data is still empty.

Finally, this keeps transaction-pay alert text visible in the Predict bottom-sheet flow, including while quote/pay-fee loading is in progress, and persists order-failed banners after the controller refreshes active order state, preventing alert/banner flicker.

When a pay-totals alert is returned with fee data, the displayed Total now still includes the deposit fee so users can see the actual amount required by the selected payment token.

Changelog

CHANGELOG entry: Fixed bugs that could prevent Predict pay-with-any-token quotes and alerts from refreshing correctly after changing payment tokens

Related issues

Fixes: PRED-882

Manual testing steps

Feature: Predict pay with any token quote refresh

  Scenario: user changes the payment token before placing a bet
    Given the Predict pay-with-any-token feature is enabled
    And the user opens a Predict buy preview with a valid bet amount
    And an external payment token is selected

    When the user opens the payment token selector
    Then the buy CTA is disabled while the selector is open

    When the user selects a different supported payment token
    And the selector closes
    Then the buy CTA remains disabled while the quote refresh starts
    And the quote request is triggered for the selected token and current bet amount
    And the buy CTA only becomes enabled after the quote state has settled

  Scenario: user sees payment and order errors in the Predict bottom sheet
    Given the user opens a Predict buy preview in bottom-sheet mode

    When the selected payment token cannot cover the quote
    Then the transaction-pay alert remains visible

    When order placement fails
    Then the order-failed banner remains visible until the user retries, changes the amount, or closes the sheet

Screenshots/Recordings

Before

N/A

After

N/A

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

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.

Note

Medium Risk
Touches Predict buy/payment gating, navigation timing, and error surfacing logic; issues could block order placement or hide critical alerts if the new lock/settling conditions misfire.

Overview
Predict pay-with-any-token quote/CTA timing is hardened. Adds a payment-selector navigation lock (with 1s delayed unlock + 5s safety timeout) and threads it through PredictBuyWithAnyToken/usePredictBuyConditions to disable the buy CTA and PredictPayWithRow immediately while the selector is opening/closing.

Quote settling and emissions are made token/tx-aware. usePredictBuyConditions now treats settling as a token+chain+amount key (not just token address), and PredictPayWithAnyTokenInfo dedupes updatePendingAmount/updateTokenAmount by transactionMeta.id + token address + chainId so selecting a new token/tx with the same amount still re-triggers quotes.

Error UX is refined for sheet mode. usePredictBuyError now returns errorMessageSource, prioritizes blocking pay-alert text (even while pay fees load), persists order_failed banners across controller refreshes, and clears persisted banners on token change; PredictBuyWithAnyToken conditionally suppresses only insufficient-balance helper text in Change Payment/Add Funds modes while keeping blocking pay alerts visible alongside banners.

Default token and fee display behavior is adjusted. usePredictDefaultPaymentToken waits for account tokens before falling back, and usePredictBuyInfo always computes depositFee from payTotals (including when pay alerts exist) without caching a “last accepted” fee during confirming.

Reviewed by Cursor Bugbot for commit ea34d01. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown
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.

@metamaskbotv2 metamaskbotv2 Bot added the team-predict Predict team label May 12, 2026
@caieu caieu marked this pull request as ready for review May 12, 2026 22:37
@caieu caieu requested a review from a team as a code owner May 12, 2026 22:37
@MarioAslau

Copy link
Copy Markdown
Contributor

Hey @caieu this is what i found

High severity

1. No safety timeout on isPaymentSelectorNavigationLocked

usePredictBuyConditions.ts (lines ~91–130)

The unlock pipeline is a strict blur → focus → schedule unlock sequence. The unlock is only scheduled when both isPaymentSelectorNavigationLockedRef.current === true and didBlurAfterPaymentSelectorOpenRef.current === true. The latter only flips inside the blur listener.

const unsubscribeFocus = navigation.addListener('focus', () => {
  if (
    isPaymentSelectorNavigationLockedRef.current &&
    didBlurAfterPaymentSelectorOpenRef.current
  ) {
    scheduleUnlock();
  }
});

Failure modes that leave the buy CTA permanently disabled:

  • The Routes.CONFIRMATION_PAY_WITH_MODAL registration is changed to presentation: 'transparentModal' in the future — transparentModal does not always blur the screen behind it.
  • The lock is set but the actual navigation.navigate(...) call is intercepted/throws/no-ops (e.g., guarded by another navigator).
  • The user backgrounds the app between lock-set and modal-open; on resume some configurations skip the blur event for the predict screen.

Recovery from a stuck lock requires the user to back out of the screen entirely. There is no escape path within the buy flow.

Recommendation: schedule an unconditional safety unlock (e.g., 5 s) inside lockPaymentSelectorNavigation itself, in parallel with the existing blur/focus handshake. Whichever fires first wins. Or drop the didBlurAfterPaymentSelectorOpenRef gate from the focus handler and just unlock-on-focus when the lock is set.


Medium severity

2. Dead import: useTransactionPayQuotes

usePredictBuyInfo.ts (line 4)

import {
  useTransactionPayQuotes,
  useTransactionPayTotals,
} from '../../../../../Views/confirmations/hooks/pay/useTransactionPayData';

useTransactionPayQuotes is imported but never referenced anywhere in the file at PR HEAD. Either drop the import or wire up the consumer it was added for.

3. persistedBuyErrorBanner survives token changes

usePredictBuyError.ts (lines ~280–296)

The persisted banner is cleared only via clearBuyErrorBanner (user retries / closes / amount change reset). The PR description and Gherkin spec only list "user retries, changes the amount, or closes the sheet" as recovery paths — token change is not listed.

Practical scenario: user gets an order_failed banner for token A, opens the payment selector, picks token B. The banner referring to a token they no longer have selected stays visible.

Recommendation: clarify intent in the spec. If the banner should clear on token change, also reset persistedBuyErrorBanner when selectedPaymentToken identity changes.

4. Blocking pay-alert short-circuit no longer waits for fees to settle

usePredictBuyError.ts (lines ~80–91)

if (
  !isPlacingOrder &&
  !isConfirming &&
  !isPredictBalanceSelected &&
  !!blockingPayAlertMessage
) {
  return { status: 'error', error: blockingPayAlertMessage, source: 'blocking_pay_alert' };
}

Old code waited for !isPayFeesLoading before surfacing the alert. New code surfaces it eagerly. Intentional per PR description ("keeps transaction-pay alert text visible…including while quote/pay-fee loading is in progress") — but during a token switch the upstream alert hooks (useInsufficientPayTokenBalanceAlert, useNoPayTokenQuotesAlert) can briefly hold the previous token's alert text, so the user sees stale alert wording for one or two frames.

Recommendation: scope the alert hook input to the new selectedPaymentToken so it returns null until recomputed for the new token, or gate the message on a settled flag for the alert hooks themselves (mirroring what isPaySystemSettling does for the CTA).

5. computedDepositFee no longer guards on hasBlockingPayAlerts

usePredictBuyInfo.ts (lines 53–62)

Old behaviour: when there were blocking pay alerts, computedDepositFee returned 0 so the displayed Total didn't include a fee for an unsupported token.

New behaviour: deposit fee is added to Total even with a blocking alert.

This is intentional ("…the displayed Total now still includes the deposit fee so users can see the actual amount required by the selected payment token"), but the UX result is "Deposit fee: $X" displayed alongside an alert that says "We can't quote this token". Worth a quick design check that the row label or a contextual hint makes the situation unambiguous.

6. Edge case: lock can outlive its intended scope

usePredictBuyConditions.ts (lines ~91–130)

If the user opens the payment selector then leaves the Predict tab entirely (e.g., switches to Wallet or Activity), the blur fires for the predict screen, but focus may not re-fire on the predict screen for a while. Combined with the lack of a safety timeout (issue 1), the lock can persist for an unbounded duration. Not catastrophic on its own — covered by issue 1's recommendation.


Low severity

7. Deeply nested ternaries in CTA disabled / opacity

PredictBuyWithAnyToken.tsx (lines ~349–369)

const isBuyActionButtonDisabled = isPaymentSelectorNavigationLocked
  ? true
  : !isBannerActive && (isChangePaymentMode || isAddFundsMode)
    ? false
    : isBannerActive
      ? isRetrying || !preview
      : !canPlaceBet;

The two named consts isBuyActionButtonDisabled and showBuyActionButtonReducedOpacity follow the same shape but diverge subtly. Easy to drift out of sync in future edits. Consider a single getBuyButtonState() helper returning { disabled, reducedOpacity } so the two can't drift apart.

8. Magic-ish constant PAYMENT_SELECTOR_NAVIGATION_UNLOCK_DELAY_MS = 1000

usePredictBuyConditions.ts

At least named, but consider co-locating with other Predict timing constants (e.g., metamask-mobile/app/components/UI/Predict/constants/transactions.ts) and adding a one-line comment explaining why 1 s — long enough to outlast the new-quote loading flip, short enough to not feel laggy.

9. Optional chain on onPaymentSelectorOpen?.()

PredictPayWithRow.tsx (line 71)

Both call sites in this PR pass the callback. The ? is defensive only. If the callback is intended to be required for the row's correct lock behaviour, drop the ? and require the prop. Otherwise leave as-is and document that callers without it bypass the lock.

10. Type-coercion cleanup in hasValidDepositAmount

PredictPayWithAnyTokenInfo.tsx (line 91)

const hasValidDepositAmount = useMemo(
  () => depositAmount !== '' && Boolean(transactionMeta),
  [depositAmount, transactionMeta],
);

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.19858% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.62%. Comparing base (3751d9a) to head (88f4e70).
⚠️ Report is 51 commits behind head on main.

Files with missing lines Patch % Lines
...PredictBuyWithAnyToken/hooks/usePredictBuyError.ts 89.65% 2 Missing and 4 partials ⚠️
.../PredictBuyWithAnyToken/PredictBuyWithAnyToken.tsx 89.47% 1 Missing and 1 partial ⚠️
...ctBuyWithAnyToken/hooks/usePredictBuyConditions.ts 95.45% 0 Missing and 2 partials ⚠️
...PayWithAnyTokenInfo/PredictPayWithAnyTokenInfo.tsx 90.90% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #30064      +/-   ##
==========================================
+ Coverage   81.54%   81.62%   +0.08%     
==========================================
  Files        5343     5369      +26     
  Lines      142128   143010     +882     
  Branches    32411    32644     +233     
==========================================
+ Hits       115899   116735     +836     
+ Misses      18299    18295       -4     
- Partials     7930     7980      +50     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

MarioAslau
MarioAslau previously approved these changes May 13, 2026

@MarioAslau MarioAslau 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 !

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePredictions, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
All 15 changed files are within the Predict/PredictBuyWithAnyToken feature area. The changes are focused on:

  1. Payment selector navigation locking (usePredictBuyConditions.ts): New mechanism to lock the CTA button while navigating to the payment selector modal, preventing race conditions. Uses navigation blur/focus listeners with timers.

  2. Error handling improvements (usePredictBuyError.ts): Added errorMessageSource type to distinguish error origins, added persisted error banner state that survives payment token switches, improved blocking pay alert handling.

  3. Deposit fee simplification (usePredictBuyInfo.ts): Removed complex state-based fallback for deposit fee calculation.

  4. Emission key tracking (PredictPayWithAnyTokenInfo.tsx): Improved to include token address/chainId to prevent stale emissions when switching payment tokens.

  5. PredictPayWithRow.tsx: Added onPaymentSelectorOpen callback prop.

  6. usePredictDefaultPaymentToken.ts: Simplified initialization - no longer resets on order change, waits for tokens array to be populated.

  7. constants/transactions.ts: Added PAYMENT_SELECTOR_NAVIGATION_UNLOCK_DELAY_MS and PAYMENT_SELECTOR_NAVIGATION_SAFETY_UNLOCK_MS constants.

SmokePredictions: Directly required - the buy flow with any token is the core changed functionality. Error handling, payment token selection, and CTA state management are all modified.

SmokeWalletPlatform: Required per SmokePredictions tag description - "Predictions is also a section inside the Trending tab (SmokeWalletPlatform); changes to Predictions views affect Trending."

SmokeConfirmations: Required per SmokePredictions tag description - "opening/closing positions are on-chain transactions." Additionally, the changed hooks (usePredictBuyConditions, PredictPayWithAnyTokenInfo) directly use confirmation-related hooks from Views/confirmations/hooks/pay/ (useTransactionPayData, useTransactionPayAvailableTokens), making confirmation flow testing important.

Performance Test Selection:
The changes are primarily logic/state management fixes in React hooks - payment selector navigation locking, error state persistence, and deposit fee calculation simplification. These are not rendering-heavy changes and don't affect list rendering, app startup, or other performance-sensitive paths. No performance tests are warranted.

View GitHub Actions results

@matallui matallui changed the title fix(predict): stabilize Predict pay-with-any-token quote updates fix(predict): stabilize Predict pay-with-any-token quote updates cp-7.77.0 May 13, 2026

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ea34d01. Configure here.

@sonarqubecloud

Copy link
Copy Markdown

@caieu caieu added this pull request to the merge queue May 13, 2026
Merged via the queue into main with commit ec66ea6 May 13, 2026
182 of 185 checks passed
@caieu caieu deleted the predict/pwat-quote-bug branch May 13, 2026 23:11
@github-actions github-actions Bot locked and limited conversation to collaborators May 13, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.78.0 Issue or pull request that will be included in release 7.78.0 label May 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.78.0 Issue or pull request that will be included in release 7.78.0 size-XL team-predict Predict team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants