Skip to content

Commit 51ee6aa

Browse files
fix(predict): prevent pay token alert flicker (#30108)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Fixes a Predict pay-with-any-token alert flicker where a blocking pay-token alert could briefly take precedence while payment quotes/totals were still settling. Problem: - Predict can have a valid external payment token selected while the Transaction Pay quote state is still catching up to the latest amount. - During that settling window, `blockingPayAlertMessage` could be treated as ready and suppress the active order banner / surface an insufficient-funds style message too early. - The payment settling key also used the raw total amount, making it more sensitive than the user-facing quote amount needs to be. Fix: - Normalize the payment settling key amount by rounding the required quote amount up to two decimals. - Pass `isPaySystemSettling` into `usePredictBuyError`. - Prevent blocking pay-token alerts from taking precedence while the pay system is still settling. - Add focused hook coverage for settling-time pay-alert suppression. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Fixed a bug that caused Predict pay-with-any-token alerts to flicker while quotes were loading ## **Related issues** https://consensyssoftware.atlassian.net/browse/CONF-1345 ## **Manual testing steps** ```gherkin Feature: Predict pay with any token Scenario: user changes the Predict order amount while paying with a token Given the user is on a Predict market buy flow And the user has selected a pay-with-any-token payment token When the user changes the order amount Then the pay-token alert does not flicker while the new quote is loading And the correct blocking alert is shown only after the quote/totals state has settled ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [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** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches Predict pay-with-any-token settling and error-surfacing logic, which could change when users see blocking balance alerts vs. order failure banners during quote updates. > > **Overview** > Prevents *blocking pay-token balance alerts* from flickering/overriding active-order error banners while the Transaction Pay system is still settling after token/amount changes. > > Normalizes the pay-system “settling key” by rounding the tracked quote amount up to 2 decimals, and threads `isPaySystemSettling` into `usePredictBuyError` so pay alerts are suppressed until settling completes. Adds a focused unit test to cover settling-time suppression behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d189f4d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 5d7085d commit 51ee6aa

4 files changed

Lines changed: 52 additions & 5 deletions

File tree

app/components/UI/Predict/views/PredictBuyWithAnyToken/PredictBuyWithAnyToken.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ const PredictBuyWithAnyToken = (props: PredictBuyPreviewProps) => {
257257
isInsufficientBalance,
258258
isConfirming,
259259
isPayFeesLoading,
260+
isPaySystemSettling,
260261
blockingPayAlertMessage,
261262
outcomeTokenPrice: outcomeToken?.price,
262263
isSheetMode,

app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyConditions.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,13 @@ export const usePredictBuyConditions = ({
150150
return null;
151151
}
152152

153-
return `${selectedPaymentTokenKey}:${totalPayForPredictBalance}`;
154-
}, [selectedPaymentTokenKey, totalPayForPredictBalance]);
153+
const amountValue = totalPayForPredictBalance || currentValue;
154+
const roundedQuoteAmount = new BigNumber(amountValue)
155+
.decimalPlaces(2, BigNumber.ROUND_UP)
156+
.toString(10);
157+
158+
return `${selectedPaymentTokenKey}:${roundedQuoteAmount}`;
159+
}, [currentValue, selectedPaymentTokenKey, totalPayForPredictBalance]);
155160

156161
// Tracks the token/amount pair for which a full quote-loading cycle has
157162
// completed. Keeping the amount in the key makes same-token amount changes

app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyError.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const defaultParams = {
106106
isBelowMinimum: false,
107107
isInsufficientBalance: false,
108108
isPayFeesLoading: false,
109+
isPaySystemSettling: false,
109110
blockingPayAlertMessage: null as string | null,
110111
// Inline banner UX is sheet-mode-only; default these tests to sheet mode so
111112
// banner / errorMessage suppression behavior is exercised. The dedicated
@@ -236,6 +237,28 @@ describe('usePredictBuyError', () => {
236237
expect(result.current.errorMessageSource).toBe('blocking_pay_alert');
237238
});
238239

240+
it('suppresses pay token balance alert message while pay system is settling', () => {
241+
mockActiveOrder = { error: 'order failed' };
242+
mockIsPredictBalanceSelected = false;
243+
mockGetPlaceOrderErrorOutcome.mockReturnValue({
244+
status: 'error',
245+
error: 'Order placement failed',
246+
});
247+
248+
const { result } = renderHook(() =>
249+
usePredictBuyError({
250+
...defaultParams,
251+
isPaySystemSettling: true,
252+
blockingPayAlertMessage: 'Insufficient payment token balance',
253+
}),
254+
);
255+
256+
expect(result.current.errorMessage).toBeUndefined();
257+
expect(result.current.buyErrorBanner).toEqual(
258+
expect.objectContaining({ variant: 'order_failed' }),
259+
);
260+
});
261+
239262
it('returns undefined when activeOrder has no error', () => {
240263
mockActiveOrder = {};
241264

app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyError.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ interface UsePredictBuyInfoParams {
4646
isBelowMinimum: boolean;
4747
isInsufficientBalance: boolean;
4848
isPayFeesLoading: boolean;
49+
isPaySystemSettling: boolean;
4950
blockingPayAlertMessage: string | null;
5051
outcomeTokenPrice?: number;
5152
// Inline banner UX (price_changed / order_failed) only exists inside the
@@ -63,6 +64,7 @@ export const usePredictBuyError = ({
6364
isBelowMinimum,
6465
isInsufficientBalance,
6566
isPayFeesLoading,
67+
isPaySystemSettling,
6668
blockingPayAlertMessage,
6769
outcomeTokenPrice,
6870
isSheetMode = false,
@@ -96,6 +98,7 @@ export const usePredictBuyError = ({
9698
!isPlacingOrder &&
9799
!isConfirming &&
98100
!isPredictBalanceSelected &&
101+
!isPaySystemSettling &&
99102
!!blockingPayAlertMessage
100103
) {
101104
return {
@@ -141,6 +144,7 @@ export const usePredictBuyError = ({
141144
isConfirming,
142145
preview,
143146
isPayFeesLoading,
147+
isPaySystemSettling,
144148
isPredictBalanceSelected,
145149
blockingPayAlertMessage,
146150
activeOrder?.error,
@@ -194,7 +198,11 @@ export const usePredictBuyError = ({
194198
const bannerWouldSuppress =
195199
isSheetMode &&
196200
activeOrder?.error &&
197-
!(blockingPayAlertMessage && !isPredictBalanceSelected);
201+
!(
202+
blockingPayAlertMessage &&
203+
!isPredictBalanceSelected &&
204+
!isPaySystemSettling
205+
);
198206
if (bannerWouldSuppress) {
199207
return undefined;
200208
}
@@ -214,6 +222,7 @@ export const usePredictBuyError = ({
214222
activeOrder?.error,
215223
blockingPayAlertMessage,
216224
isPredictBalanceSelected,
225+
isPaySystemSettling,
217226
isSheetMode,
218227
]);
219228

@@ -233,7 +242,11 @@ export const usePredictBuyError = ({
233242
return null;
234243
}
235244

236-
if (blockingPayAlertMessage && !isPredictBalanceSelected) {
245+
if (
246+
blockingPayAlertMessage &&
247+
!isPredictBalanceSelected &&
248+
!isPaySystemSettling
249+
) {
237250
return null;
238251
}
239252

@@ -274,6 +287,7 @@ export const usePredictBuyError = ({
274287
isConfirming,
275288
blockingPayAlertMessage,
276289
isPredictBalanceSelected,
290+
isPaySystemSettling,
277291
isSheetMode,
278292
]);
279293

@@ -298,7 +312,11 @@ export const usePredictBuyError = ({
298312
!isSheetMode ||
299313
isPlacingOrder ||
300314
isConfirming ||
301-
Boolean(blockingPayAlertMessage && !isPredictBalanceSelected);
315+
Boolean(
316+
blockingPayAlertMessage &&
317+
!isPredictBalanceSelected &&
318+
!isPaySystemSettling,
319+
);
302320

303321
const buyErrorBanner =
304322
currentBuyErrorBanner ??

0 commit comments

Comments
 (0)