[pull] main from MetaMask:main#775
Merged
Merged
Conversation
<!-- 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** **Root cause:** The Polymarket API returns `null` for the `orderPriceMinTickSize` field on some sports markets. Our code was calling `.toString()` on that value without a null check, which threw a runtime error. Since the entire batch of events was parsed in a single `.map()`, one bad event crashed the whole thing and returned an empty list. **Fix:** 1. Changed `market.orderPriceMinTickSize.toString()` to `market.orderPriceMinTickSize?.toString() ?? '0.01'` to safely handle the null. 2. Updated the TypeScript type to `number | null` to correctly reflect what the API actually returns. 3. Changed the event parsing loop from `.map()` to `.flatMap()` with a per-event `try/catch`, so a single malformed event is skipped and logged instead of taking down the whole batch. <!-- 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? --> ## **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: football prediction markets not loading ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3259 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/user-attachments/assets/3fb77be4-1060-45c3-adb2-cb75cd4f41a8 <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/464094d7-94ea-4002-8531-498bb6d173da <!-- [screenshots/recordings] --> ## **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. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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) - [ ] 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](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 - [ ] 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** > Changes market parsing defaults and error handling for Polymarket events; while localized, it can affect tick-size-dependent pricing behavior and which events appear if parsing failures occur. > > **Overview** > Fixes Polymarket sports markets failing to load when the API returns a `null` `orderPriceMinTickSize`. > > Updates the Polymarket market type to allow `orderPriceMinTickSize: number | null`, safely derives `tickSize` with a fallback default, and makes `parsePolymarketEvents` resilient by parsing each event in a `try/catch` (logging and skipping only the bad event instead of failing the whole batch). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1f8de4a. 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: Luis Taniça <matallui@gmail.com>
…30452) ## **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? --> **Reason:** Users with a MetaMask Card and a Money Account need clearer paths to link spending to their Money balance, copy that matches design (including mUSD cashback and flexible APY), and less noisy linkage feedback. Card Home previously lacked a dedicated entry point; the link confirmation sheet used generic art and older copy. **Solution:** - **Card Home:** When `useMoneyAccountCardLinkage` reports `canLink`, render a `MoneyMetaMaskCard` in `link` mode between dividers, with `hideCardImage` so the hero card image is not duplicated, `apy` from `useMoneyAccountBalance`, and metal vs virtual cashback via `CardType`. Tapping the header, subtitle row, or **Link card** calls `startLinkFlow({ screen: Routes.CARD.HOME })`. CTA stays visible while `cardHomeDataStatus` is `loading` (stale-while-revalidate) when other conditions still allow linking. - **MoneyMetaMaskCard (link mode):** Support `hideCardImage` (vertical bullets only), and when `apy` is `undefined`, use `link_subtitle_no_apy` and omit the APY bullet instead of forcing `0%`. - **MoneyLinkCardSheet:** Replace the mUSD coin with the user’s card art from `selectCardHomeData` (`mm_card_metal` for `CardType.METAL`, else `mm_card_regular`), Figma-aligned sizing, updated sheet description plus `link_card_sheet_description_no_apy` when vault APY is not yet available. - **useMoneyAccountCardLinkage:** Single-line pending/success toasts, updated error copy, pending spinner color `IconDefault`, and error icon `Error` instead of `Danger`. - **Spending Limit (from #30320):** Money Account preselection, Spend and Earn promo UI (`SpendAndEarnPromoCard`, `ShimmerOverlay`), and related `useSpendingLimit` / view tests. - **Other (same branch vs `main`):** Bridge insufficient-native reserve handling and tests; send confirmation alert modal and fee / percentage amount hook adjustments with tests. ## **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: Added a Link Money Account section on Card Home, redesigned the link-card bottom sheet (card art and copy), improved Money Account card-linkage toasts, extended Spending Limit with Spend and Earn / Money preselection, and tightened bridge and send confirmation edge cases. ## **Related issues** Fixes: Refs: #30320 ## **Manual testing steps** ```gherkin Feature: Money Account linking and related card flows Scenario: Card Home shows link CTA when user can link Given I am a cardholder with Money Account requirements met, card home data loaded, and I am not already delegated for card spending When I open Card Home Then I see the Link MetaMask Card block with dividers and Link card action Scenario: Link card opens confirmation sheet with correct art and copy Given I tap Link card or the link header from Card Home (or open the link sheet from the Money link flow) When the Spend and earn bottom sheet appears Then I see the title Spend and earn, description mentioning mUSD back and up to my vault APY when available, and the card image matches my card type (metal vs virtual) Scenario: Confirm link still runs linkage after sheet dismiss Given the link confirmation sheet is open When I tap Link card on the sheet Then the sheet closes and the in-app linkage flow continues (pending then success or error toast) Scenario: No APY yet uses shorter description Given vault APY is not yet available to the client When I open the link confirmation sheet Then the body copy does not include an APY clause and no raw i18n placeholders appear Scenario: Spending Limit Spend and Earn promo (Refs #30320) Given I open Spending Limit under conditions where the Spend and Earn promo applies When I review the screen Then I see the promo treatment and Money Account is preselected where configured ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A ### **After** https://github.com/user-attachments/assets/67079894-786e-4806-a6eb-fa4dd8ee4b3d ## **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) - [ ] 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](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 - [ ] 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. <!-- Generated with the help of the pr-description AI skill --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk: introduces new Money Account linking CTA paths and conditional rendering on Card Home, plus updates toast/copy logic that could affect linkage UX and user navigation if conditions are wrong. > > **Overview** > Adds a **Money Account linking entry point on Card Home**: when `useMoneyAccountCardLinkage().canLink` is true, Card Home renders a `MoneyMetaMaskCard` “link” section (with dividers), passes live `apy` from `useMoneyAccountBalance`, detects metal vs virtual via `CardType`, and routes all presses to `startLinkFlow({ screen: Routes.CARD.HOME })`. > > Refines the **linking UX and copy**: `MoneyMetaMaskCard` now supports `hideCardImage` (Card Home variant) and switches to *no-APY* subtitle/bullets when APY is unavailable; `MoneyLinkCardSheet` swaps the illustration to card art based on `selectCardHomeData` and uses no-APY description when needed. Updates linkage toasts in `useMoneyAccountCardLinkage` (single-line pending/success, new error copy/icon/spinner color) and refreshes/enhances tests and i18n strings accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3128d05. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
<!-- 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? --> Removes the Feature Announcements notification setting by deleting FeatureAnnouncementToggle (and its test) and dropping the corresponding useFeatureAnnouncementToggle hook. Replaced by AUS notification preference in #30106 ## **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: ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/GE-244 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **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. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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) - [ ] 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](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 - [ ] 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] > **Low Risk** > Low risk removal of unused UI toggle and associated hook/tests; primary risk is any missed call sites leading to missing functionality, but searches indicate no remaining references. > > **Overview** > Removes the **Feature Announcements** notification setting by deleting `FeatureAnnouncementToggle` (and its test) and dropping the corresponding `useFeatureAnnouncementToggle` hook. > > Cleans up `useSwitchNotifications` and its tests by removing the feature-announcement toggle flow (action/selector usage and list-refresh behavior) so only the remaining notification/account toggle hooks are exercised. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b5ee03. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Move the feature-flag check from AssetPollingProvider into each polling
hook so polling no-ops with empty input when unified assets state is
off, while keeping hook call order stable for the provider.
<!--
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?
-->
## **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: gate asset controller polling on unified assets in
hooks
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: my feature name
Scenario: user [verb for user action]
Given [describe expected initial app state]
When user [verb for user action]
Then [describe expected outcome]
```
## **Screenshots/Recordings**
<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->
### **Before**
<!-- [screenshots/recordings] -->
### **After**
<!-- [screenshots/recordings] -->
## **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.
-->
- [ ] 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).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] 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)
- [ ] 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](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
- [ ] 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**
> Changes when asset-related controllers start/stop polling by moving
unified-assets feature-flag gating into each polling hook; risk is
moderate because it affects background polling behavior and could change
network/load characteristics if the flag state is misinterpreted.
>
> **Overview**
> Moves unified-assets feature-flag gating from `AssetPollingProvider`
into each asset polling hook (`useCurrencyRatePolling`,
`useTokenRatesPolling`, `useTokenDetectionPolling`,
`useTokenBalancesPolling`, `useMultichainAssetsRatePolling`) by
resolving normal inputs and then passing an empty `input` to
`usePolling` when the flag is enabled.
>
> Updates `AssetPollingProvider` to always mount all polling hooks
(stable call order) and adjusts/adds tests to assert that polling does
**not** start when unified assets state is enabled, while provider tests
no longer cover the provider-level feature-flag branch.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ee35a16. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description**
This PR delivers two related pieces of work for the Predict crypto
up/down details screen:
1. **A reusable `usePredictOrderbook` hook** that streams live
Polymarket orderbook depth into the existing `LivelineChart` so the
chart shows real bid/ask activity overlaid on the price line. The hook
is plumbed through the existing controller / provider / WebSocket stack
— no new APIs needed at the engine layer.
The data flow:
```mermaid
flowchart LR
Hook["usePredictOrderbook(tokenId)"] -->
Controller["PredictController.subscribeToOrderbook"]
Controller --> Provider["PolymarketProvider.subscribeToOrderbook"]
Provider --> WSManager["WebSocketManager.subscribeToOrderbook"]
Provider --> REST["getOrderBook (utils.ts)"]
REST -->|"seed first snapshot"| WSManager
WS["Polymarket market WS"] --> WSManager
Hook --> Chart["LivelineChart orderbook prop"]
```
The WebSocket manager handles per-token reference counting, a 250 ms
emit throttle to protect the RN↔WebView bridge, `book` event ingestion,
and opportunistic top-of-book pruning on `price_change` events. The REST
bootstrap runs at the provider layer (where the active CLOB protocol is
known) and seeds the cache before the first WS event arrives.
2. **Chart UX/UI polish** to match the Figma reference for `BTC Up or
Down`:
- Removed the oversized green/red price pill (badge) — only the orange
dot remains.
- Removed the 36 px dead space above the chart (`padding.top: 48 → 12`).
- Made the chart line extend closer to the y-axis labels
(`padding.right: 80 → 64`).
- Pushed time-axis labels above the WebView's bottom clip zone so they
actually render (`padding.bottom: 48 → 80`).
- Dropped trailing `.00` from the y-axis price labels per design
(whole-dollar formatter).
- Added a compact 12-hour `h:mm:ss` `formatTime` so the time-axis labels
fit the live 30s window without overlap-culling.
- Extracted `CRYPTO_UP_DOWN_FORMAT_VALUE` and
`CRYPTO_UP_DOWN_FORMAT_TIME` as named exports so the WebView-bridged
formatter bodies can be regression-tested with `it.each`.
The PR also includes a self-review pass that fixed:
- Stale orderbook state surviving WebSocket reconnects (cleared
`orderbookState` + throttle timers in `disconnectMarket`).
- Dead `error` field on the orderbook hook's public type.
- Empty-string `tokenId` slipping past the subscribe guard.
- Misleading comment vs. implementation for `applyTopOfBook` (it prunes,
doesn't splice).
- Vague "with correct props" test name renamed per the repo's
unit-testing guidelines.
- Dead `createMockChartRef` helper + 19 unused `chartRef` locals + 2
orphaned `appendPoint` assertions in `useCryptoUpDownChartData.test.ts`
(the hook signature dropped its `chartRef` arg upstream).
- Magic-number documentation for chart padding and chart-height bounds.
### Deferred to follow-ups (intentional)
- `patch-package` on `liveline` for: (a) orderbook depth fade threshold
(currently fades only in the top 45% — design wants midpoint), (b)
time-axis edge-fade reduction, (c) palette opacity bumps. All require
modifying `node_modules/liveline/dist/index.js` and rebundling via `yarn
build:liveline-webview`; tracked separately because they coordinate with
chart-team work.
- Imperative `setOrderbook` on `LivelineChartRef` to skip a full
`SET_PROPS` round-trip on every depth tick (perf optimization that needs
upstream `liveline` cooperation).
- i18n keys for `Price to beat` / `Current price` / `Target` (no
`predict.crypto_up_down` namespace exists yet — broader Predict-feature
concern).
## **Changelog**
CHANGELOG entry: Added live orderbook depth visualization and refined
the chart layout for Crypto Up/Down markets.
## **Related issues**
Refs:
<!-- Fill in any internal ticket reference (e.g. Linear PRDT-NNNN or
GitHub issue) before publishing -->
## **Manual testing steps**
```gherkin
Feature: Crypto Up/Down details screen — live chart with orderbook depth
Scenario: user opens a live Crypto Up/Down market and sees the redesigned chart
Given the user has the Predict feature flag enabled
And the user has navigated to a live "BTC Up or Down" market
When the details screen finishes loading
Then the chart line renders with the orange dot at the current price
And no green or red price pill appears next to the dot
And the y-axis price labels render to the right of the chart without trailing ".00"
And there is no large empty band between the "Price to beat" header and the chart line
And time labels in "h:mm:ss" format render along the bottom of the chart, above the action buttons
And the dashed "Target" reference line renders at the target price
Scenario: user observes live orderbook depth on the chart
Given the user is on a live Crypto Up/Down market with active liquidity
When the user watches the chart for a few seconds
Then small green ($) labels for bids and red ($) labels for asks animate upward from the bottom-left of the chart
And the labels update within ~250 ms of new orderbook events
And on reconnect (e.g. toggle airplane mode briefly) the orderbook re-bootstraps without showing stale prices
Scenario: user navigates away and the chart cleans up
Given the chart is rendering with active orderbook depth
When the user taps the back button
Then the chart unmounts and the orderbook subscription is released
And no further WebSocket traffic for that token is observed in the network log
```
## **Screenshots/Recordings**
### **Before**
<!-- Light-mode current chart (padding.bottom = 48, badge enabled, no
formatTime): time labels missing at bottom, large green price pill
obscures the dot, large empty band above the chart line, trailing ".00"
on y-axis labels. -->
### **After**
<!-- Dark-mode current chart: time labels (e.g. `2:51:20, 2:51:30`)
render above the action buttons, dot-only price marker, no top gap,
whole-dollar y-axis labels, live green/red orderbook depth flowing up
the left of the chart. -->
Uploading upDown.mov…
## **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
- `usePredictOrderbook.test.ts` (new) — subscribe/unsubscribe lifecycle,
snapshot mapping, mount/unmount safety, connection polling.
- `WebSocketManager.test.ts` — extended with positive coverage for
`book` events, REST seed, reference counting, reconnect, throttle,
opportunistic TOB prune.
- `PolymarketProvider.test.ts` — `subscribeToOrderbook` REST bootstrap
success/failure.
- `PredictController.test.ts` — passthrough +
`MESSENGER_EXPOSED_METHODS` registration.
- `PredictCryptoUpDownChart.test.tsx` — orderbook wiring, prop
forwarding, `CRYPTO_UP_DOWN_FORMAT_VALUE` and
`CRYPTO_UP_DOWN_FORMAT_TIME` `it.each` regression suites.
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- JSDoc on `usePredictOrderbook`, `CRYPTO_UP_DOWN_FORMAT_VALUE`,
`CRYPTO_UP_DOWN_FORMAT_TIME`, `CHART_HEIGHT_VIEWPORT_FRACTION`, and the
new WebSocketManager orderbook methods.
- [ ] 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.
- Apply at PR creation time: `team-predict` (or the appropriate Predict
team label), `area-predict`. Add `needs-qa` if the on-device cadence of
orderbook depth needs review.
#### Performance checks (if applicable)
- [x] I've tested on Android
- Pending — primary development was iOS; should be smoke-tested on an
Android mid-range device because the WebView bridge cadence differs.
- [x] I've tested with a power user scenario
- Not applicable to the orderbook layer specifically; the chart is sized
for a single market view.
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
- Not added in this PR. The WebSocketManager already logs orderbook
lifecycle events via `DevLogger`. If Sentry traces are desired on
`subscribeToOrderbook` REST + WS handshake timing, can add in a
follow-up.
## **Pre-merge reviewer checklist**
- [x] 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.
---
## Appendix — quality gate
| Check | Result |
|---|---|
| `yarn lint:tsc` | clean (0 errors) |
| `yarn jest` (focused Predict suites: chart, details, hooks,
WebSocketManager) | 197 / 197 tests pass |
| ESLint on changed files | 0 errors. 7 pre-existing warnings
(deprecated controller types, `runAfterInteractions`, an existing
`React` shadow in mocks) all predate this branch — not introduced here.
|
## Appendix — files touched (high level)
- New: `app/components/UI/Predict/hooks/usePredictOrderbook.ts` (+ test)
- Modified: `WebSocketManager.ts` (orderbook subscription map, throttle,
lifecycle), `PolymarketProvider.ts` (passthrough + REST seed),
`PredictController.ts` (passthrough + `MESSENGER_EXPOSED_METHODS`),
`types/index.ts` (`OrderbookLevel`/`Snapshot`/`Callback`),
`providers/types.ts` (optional method in interface).
- Chart UX: `PredictCryptoUpDownChart.tsx` (+ test),
`PredictCryptoUpDownDetails.tsx`, `useCryptoUpDownChartData.ts` (+
test).
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Adds a new real-time orderbook subscription pipeline
(controller/provider/WebSocketManager) and feeds it into a
WebView-backed chart, which can impact live data correctness and
reconnect/unsubscribe behavior.
>
> **Overview**
> **Adds live orderbook depth streaming for Predict crypto Up/Down
charts.** Introduces a reusable `usePredictOrderbook` hook, exposes
`PredictController.subscribeToOrderbook`, and implements
`PolymarketProvider.subscribeToOrderbook` with a REST `getOrderBook`
bootstrap that seeds the WS cache.
>
> **Extends `WebSocketManager` to support `book` events.** Adds
per-token orderbook subscriptions with cached snapshot replay, REST
seeding, and a 250ms emit throttle; updates
unsubscribe/reconnect/cleanup logic and connection status to account for
orderbook subscribers.
>
> **Updates chart wiring and formatting.** `PredictCryptoUpDownChart`
now forwards `orderbook` into `LivelineChart`, adds a compact
`CRYPTO_UP_DOWN_FORMAT_TIME` formatter, and expands test coverage across
the new hook, controller/provider passthroughs, WS behavior, and
formatter regression cases; also tweaks Crypto Up/Down details chart
height calculation constant usage and fixes websocket mock tests to use
OS-assigned ports.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
33c3365. 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: ghgoodreau <hunter.goodreau@consensys.net>
Co-authored-by: Cursor <cursoragent@cursor.com>
<!--
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?
-->
Some of our workflows such as `build.yml` relies on the `main` workflow
file being run. This is to allow other branches to build while still
locking down the workflow from the main branch. This adds a source
branch input to the `build.yml` workflow to allow building from source
branches
## **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:
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: my feature name
Scenario: user [verb for user action]
Given [describe expected initial app state]
When user [verb for user action]
Then [describe expected outcome]
```
## **Screenshots/Recordings**
<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->
### **Before**
<!-- [screenshots/recordings] -->
### **After**
<!-- [screenshots/recordings] -->
With source input
<img width="267" height="427" alt="image"
src="https://github.com/user-attachments/assets/905d085d-f4dd-4374-bd28-cdf030e80e48"
/>
## **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.
-->
- [ ] 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).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] 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)
- [ ] 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](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
- [ ] 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**
> Medium risk because it changes how `build.yml` resolves the git ref
for checkout and build metadata, which can break callers if they don’t
pass `source_branch` or if the value differs from the triggering ref.
>
> **Overview**
> Makes `source_branch` a **required** input for
`.github/workflows/build.yml` and uses it consistently for all
checkouts/metadata (removing fallbacks to `github.ref_name`).
>
> Adds `source_branch` to manual `workflow_dispatch` runs (defaulting to
`main`) and updates `expo-dev-build.yml` to pass the current ref name
into the reusable build workflow.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8bd6417. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- 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** **note to reviewer:** please view the diff with the link to reduce noise: https://github.com/MetaMask/metamask-mobile/pull/30220/changes?w=1 Migrates the Stake screen and modal navigators from @react-navigation/stack to @react-navigation/native-stack([POC](https://docs.google.com/document/d/1_oDELkGRLUgaAeMs2NSzG9w1UyjsD5VSKQlW7yW5oZU/edit?tab=t.0#heading=h.gba8s4wq9tzu)), and replaces getStakingNavbar + useEffect(setOptions(...)) with an in-screen HeaderStandard on three Stake flows. **Why**: Setting headers via useEffect + setOptions on native stack causes a visible flicker on first paint (the default native header renders, then is replaced); for these screens we now configure headerShown: false at the route level and render the header in-screen with HeaderStandard, which eliminates the flicker. What changed 1. **Stake/routes/index.tsx**: - createStackNavigator → createNativeStackNavigator for both StakeScreenStack and StakeModalStack. - Modal stack switched to clearNativeStackNavigatorOptions + transparentModalScreenOptions (native-stack equivalents of the prior preset). - screenOptions.headerShadowVisible: false. - headerShown: false on STAKE, STAKE_CONFIRMATION, UNSTAKE_CONFIRMATION, EARNINGS_HISTORY. 2. StakeConfirmationView, UnstakeConfirmationView, StakeEarningsHistoryView: replaced legacy header setup with HeaderStandard; back button preserves the existing analytics events (STAKE_CONFIRMATION_BACK_CLICKED, UNSTAKE_CONFIRMATION_BACK_CLICKED) via useAnalytics. **Note** I am leaving migration EarnWithDrawInputView.tsx out on purpose because this navbar has more logic so it's better for the team to do the migration 4. UnstakeConfirmationView.styles.ts: mainContainer height: '100%' → flex: 1 so the footer (Cancel / Continue) lays out correctly under the new flex parent + HeaderStandard. 5. EarnInputView: removed the now-redundant useEffect(() => navigation.setOptions({ headerShown: false })) (the route owns it). 6. Tests: navigation mocks now include goBack; StakeEarningsHistoryView.test.tsx was rewritten to assert on the rendered header title instead of setOptions / getStakingNavbar (which the screen no longer calls). Android test build: https://github.com/MetaMask/metamask-mobile/actions/runs/25891826048 ## **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:null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> **Please note that some of these flows are not representing what app is behaving right now. I hardcoded some of the screens to see the navigation behaviours** ### **Before** <img width="382" height="786" alt="Stake before 1" src="https://github.com/user-attachments/assets/9c6920e6-fa0e-4e39-aafe-2004dc7cfd0a" /> <img width="382" height="786" alt="stake after 2" src="https://github.com/user-attachments/assets/69140cba-c85d-4989-909c-3e24b70080e8" /> <img width="382" height="786" alt="stake confirmation before" src="https://github.com/user-attachments/assets/06e2a687-f797-4213-930e-269c343c004f" /> ### **After** <img width="382" height="786" alt="stake after 1" src="https://github.com/user-attachments/assets/f0be027d-495c-49c1-8a27-fe76783fa2b8" /> <img width="382" height="786" alt="stake after 2" src="https://github.com/user-attachments/assets/518f33a1-70d9-4e9f-b170-6d4213382698" /> <img width="382" height="786" alt="stake confirmation after" src="https://github.com/user-attachments/assets/dc79e675-7838-44b3-9ece-1c28b39f9af4" /> <!-- [screenshots/recordings] --> ## **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) - [ ] 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](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 - [ ] 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`. --> - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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** > Refactors Stake navigation and header rendering, which can impact screen transitions/back behavior and modal presentation; mitigated by updated tests covering back actions and header titles. > > **Overview** > **Stake navigation is migrated to native-stack.** `Stake/routes` switches from `@react-navigation/stack` to `@react-navigation/native-stack`, applies native-stack modal presets (`clearNativeStackNavigatorOptions` + `transparentModalScreenOptions`), and sets `headerShown: false` on Stake-related screens to avoid native header flicker. > > **Stake headers are now rendered in-screen.** `StakeConfirmationView`, `UnstakeConfirmationView`, and `StakeEarningsHistoryView` replace `setOptions(getStakingNavbar(...))` with `HeaderStandard`, including explicit back handlers that preserve existing MetaMetrics back-click events; `UnstakeConfirmationView` layout is adjusted (`height: '100%'` → `flex: 1`) to fit the new wrapper. > > **Tests are updated accordingly.** View tests now assert rendered header titles and verify `goBack` + analytics tracking via `HeaderStandard` back button test IDs, and `EarnInputView` removes a redundant `navigation.setOptions({ headerShown: false })` now handled at the route level. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0c8c21d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…st, and IntroModal to design-system-react-native (#30183) <!-- 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? --> This PR updates `MultichainAccountConnect`, `PrivateKeyList`, `AddressList`, and `IntroModal`/`LearnMoreBottomSheet` to use the design-system `Toast`, `Text`, `BottomSheet`, and related types/imports. ## **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: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MUL-1692 ## **Manual testing steps** ```gherkin Feature: Multichain account design system migration Scenario: user connects multichain accounts to a dapp Given the app receives a multichain account connection request When the user reviews the account and network permissions And the user confirms the connection Then the connection flow completes successfully And the permissions updated toast is shown Scenario: user copies an address from the Address List Given the user is viewing the Multichain Account Address List When the user taps the copy button for an address Then the address is copied to the clipboard And the copied-to-clipboard toast is shown Scenario: user reveals and copies a private key Given the user is viewing the Private Key List When the user enters the correct password And taps copy for a private key row Then the private key is copied with expiration And the copied toast is shown Scenario: user opens the Learn More bottom sheet Given the Multichain Accounts intro modal is visible When the user opens the Learn More bottom sheet Then the bottom sheet renders correctly And the checkbox enables the confirmation button And the back and close buttons dismiss the sheet ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> https://github.com/user-attachments/assets/6ad8efb8-d265-4287-becd-d19c6422e1fd <img width="300" alt="Screenshot 2026-05-19 at 10 33 13 PM" src="https://github.com/user-attachments/assets/86fb530f-9510-4489-bb8a-e16b16a830d4" /> <img width="300" alt="Screenshot 2026-05-19 at 10 33 26 PM" src="https://github.com/user-attachments/assets/2076920a-8594-4012-ad83-75f089f70a47" /> ## **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. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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) - [ ] 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](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 - [ ] 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** > Mostly UI/refactor work, but it touches the multichain dapp connect flow and private key copying screens where regressions could impact key user journeys (toasts, navigation headers, and permission-updated feedback). No changes to permission decisions or key material handling beyond how feedback is displayed. > > **Overview** > Migrates Multichain Accounts UI surfaces (`MultichainAccountConnect`, `AddressList`, `PrivateKeyList`, and `IntroModal/LearnMoreBottomSheet`) off the legacy `ToastContext`/component-library primitives onto `@metamask/design-system-react-native` (`toast`, `Toaster`, `BottomSheet`, and `Text`). Copy and connect confirmation feedback now uses design-system `toast()` calls with per-screen `<Toaster />` mounting, including a favicon accessory for the *permissions updated* toast in `MultichainAccountConnect`. > > Updates `MultichainAddressRow` copy API to make `toastMessage` optional and only trigger the legacy row-managed toast when **both** `toastRef` and `toastMessage` are provided, enabling callers to own toast behavior. Refreshes tests/stories accordingly (new toast mocks and assertions, added header back-button test), and includes small UI tweaks like tab bar padding and a reduced network-name max width in `MultichainPermissionsSummary`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 903fa37. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## **Description**
Predict market cards on the Explore feed now open the Buy/Sell preview
as an in-place bottom sheet (matching the dedicated Predict feed) when
the `predictBottomSheet` feature flag is on, instead of routing to the
full-page bet slip.
### What changed
1. **Mounted `PredictPreviewSheetProvider` at the `HomeTabs` level** in
`app/components/Nav/Main/MainNavigator.js`, wrapping the
`Tab.Navigator`. The provider was previously only mounted inside
`PredictScreenStack`, so triggering `openBuySheet`/`openSellSheet` from
any other tab fell back to navigation. Mounting at `HomeTabs` makes the
sheet usable from Explore (and any future tab that needs it) while
keeping the existing in-Predict behavior untouched (`PredictScreenStack`
still mounts its own provider; the inner one shadows the outer for
usage).
2. **Why mount at `HomeTabs` and not inside the tab itself?**
`BottomSheet` from `@metamask/design-system-react-native` uses `absolute
inset-0` for its container. If the provider is mounted inside an
individual tab's content area, the sheet's parent is smaller than the
viewport and the sheet gets clipped at the top of the screen and below
by the bottom tab bar. Mounting at `HomeTabs` puts the sheet's parent at
the full-viewport `Home` Stack.Screen card, which is the smallest level
above the tab bar that gives correct dimensions.
3. **Fixed a duplicate-toast and stale-suppression bug** that the new
placement exposes. With both `HomeTabs` and `PredictScreenStack`
providers now mounted simultaneously while the user is inside the
Predict stack, both used to:
- independently fire the state-based "Try again" failure toast on
`activeOrder.error` transitions (no dedup in `ToastService`);
- increment the same `_providerSheetModeCount` counter that gates
`shouldSuppressLegacyOrderFailureToast()`, which then swallowed the
legacy failure toast in tabs/flows where the active provider could not
actually fire its own toast (e.g. Wallet/Trade/Money/Rewards, or
HomepageDiscoveryTabs which mounts in `disableBottomSheet` mode).
Replaced the module-level counter with a registration **stack**
(`_sheetModeProviders`). Each entry holds the provider's id and a
`hasBuyParams()` getter. The topmost (most recently mounted, innermost
in the tree) entry is the only "active" one:
- The state-based toast effect in `PredictPreviewSheetContext.tsx` bails
out unless the current provider is active — so only the innermost
provider fires the Retry toast.
- `shouldSuppressLegacyOrderFailureToast()` now consults the active
entry's `hasBuyParams()`, so the legacy toast is only suppressed when
the active provider will actually surface its own toast.
4. **Test coverage** for the multi-provider scenario in
`PredictPreviewSheetContext.test.tsx`:
- Topmost provider fires the failure toast exactly once when both are
mounted.
- Outer provider becomes active again after the inner unmounts.
- Outer (sheet-mode) provider still fires when the inner provider is
mounted with `disableBottomSheet`.
- `shouldSuppressLegacyOrderFailureToast` correctly tracks the topmost
provider across mount/unmount.
5. Added `PredictPreviewSheetProvider` to the Predict barrel
(`app/components/UI/Predict/index.ts`) for consistency, and a rationale
comment in `MainNavigator.js` explaining why the wrap lives at
`HomeTabs` (so a future maintainer doesn't move it back inside a tab).
### Files touched
- `app/components/Nav/Main/MainNavigator.js`
- `app/components/Nav/Main/MainNavigator.test.tsx` (mock update)
- `app/components/UI/Predict/contexts/PredictPreviewSheetContext.tsx`
-
`app/components/UI/Predict/contexts/PredictPreviewSheetContext.test.tsx`
- `app/components/UI/Predict/index.ts`
## **Changelog**
CHANGELOG entry: Added in-place buy/sell bottom sheet to Predict market
cards on the Explore feed when the `predictBottomSheet` feature flag is
enabled.
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: Predict bottom sheet on Explore feed
Scenario: open buy sheet from Explore with feature flag ON
Given the predictBottomSheet feature flag is enabled
And the user is on the Explore tab
And the Explore feed is showing Predict market cards
When the user taps "Yes" on a Predict market card
Then a buy preview bottom sheet opens in place
And the sheet is anchored above the bottom tab bar (no clipping at the top or bottom)
And the user can swipe the sheet down to dismiss it
Scenario: navigation fallback when feature flag is OFF
Given the predictBottomSheet feature flag is disabled
And the user is on the Explore tab
When the user taps "Yes" on a Predict market card
Then the app navigates to the full-page bet slip (legacy behavior, unchanged)
Scenario: Predict tab behavior is unchanged
Given the predictBottomSheet feature flag is enabled
And the user is on the Predict tab
When the user taps an outcome on a market card
Then the buy preview bottom sheet opens in place (existing behavior)
And there is exactly one Retry toast if the order subsequently fails
Scenario: only the topmost provider fires the failure toast
Given the predictBottomSheet feature flag is enabled
And the user opened and dismissed a buy sheet from Explore
And the user navigated to the Predict tab and opened/dismissed another buy sheet
And the user is now on the Predict tab with both sheets dismissed
When the active Predict order transitions to a failed state
Then the user sees exactly one "Try again" toast (not two)
And tapping Retry reopens the most recently used sheet's market context
```
## **Screenshots/Recordings**
### **Before**
<!-- Recording: tapping Yes on an Explore market card navigates to the
full-page bet slip (or, with the earlier broken fix, opens a clipped
sheet). -->
### **After**
<!-- Recording: tapping Yes on an Explore market card opens the bottom
sheet in place, anchored above the tab bar, with no clipping. -->
https://github.com/user-attachments/assets/6bcb24e9-b81c-4c8f-b193-295440cd5805
## **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)
- [ ] I've tested on Android
- [x] I've tested with a power user scenario
- [ ] I've instrumented key operations with Sentry traces for production
performance metrics
> _Performance checks N/A: this PR only repositions an existing React
provider higher in the tree and refactors a module-level counter into a
registration stack. No new subscriptions/renders on hot paths; the
failure-toast effect now does strictly less work in non-active
providers._
## **Pre-merge reviewer checklist**
- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] 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 top-level navigation composition and refactors module-scoped
toast suppression/dedup logic for Predict order failures; regressions
could affect bottom sheet rendering or toast behavior across tabs.
>
> **Overview**
> Enables Predict market cards opened outside the Predict tab (e.g.
Explore) to use the in-place Buy/Sell preview bottom sheet by mounting
`PredictPreviewSheetProvider` above the home `Tab.Navigator`.
>
> Refactors `PredictPreviewSheetContext` to handle multiple
simultaneously-mounted providers via a registration stack: only the
topmost sheet-mode provider can fire the state-driven Retry toast, and
legacy order-failure toast suppression now depends on the active
provider having remembered buy params (reducing stale suppression).
Tests are updated/added to cover multi-provider dedup and the new
suppression behavior, and `PredictPreviewSheetProvider` is exported from
the Predict barrel.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
51b7817. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!--
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**
This PR prevents stale or effectively settled Predict markets from
occupying discovery feed surfaces.
- Adds an isolated `marketStaleness` utility for probability thresholds,
formal market status, daily/game expiry, time penalties, ranking
penalties, and highlighted-market exceptions.
- Applies the shared filter at hook/view-model boundaries for Predict
feed data, search results, featured carousel data, and the World Cup
feed without changing fetch parameters, page size, cursors, or backfill
behavior.
- Preserves highlighted-market metadata from
`PredictController.getMarkets`, keeping open highlighted markets pinned
first while still excluding closed/resolved highlighted markets.
- Removes the old `PredictMarketMultiple` exact `0`/`1` outcome filter
so display components render the outcomes already prepared by the shared
feed model.
Branch review summary:
- Base: `main`
- Branch:
`predict/PRED-747-filter-dead-resolved-markets-from-predict-feed-4-layer-staleness-system`
- Changed files: 16
- Commits: 7 scoped commits covering plan, utility, integration, tests,
and component cleanup
Automated coverage added/updated:
- Pure staleness policy tests for outcome thresholds, invalid
probabilities, outcome groups, formal status, time expiry, ranking
penalties, and highlighted-market behavior.
- Hook integration tests for feed, search, carousel, and World Cup
filtering.
- Controller tests for highlighted-market metadata and closed/resolved
highlight exclusion.
- Component regression coverage confirming `PredictMarketMultiple` no
longer owns price-based stale filtering.
## **Changelog**
CHANGELOG entry: Fixed stale Predict markets appearing in discovery
feeds and search results.
## **Related issues**
Fixes: PRED-747
Related: PRED-707, PRED-533, PRED-744
## **Manual testing steps**
```gherkin
Feature: Predict discovery staleness filtering
Scenario: user views Predict discovery surfaces with stale and live markets
Given Predict discovery data includes live markets, markets where all outcomes are at or beyond the dead probability thresholds, partially stale markets, and highlighted open markets
When user opens the Trending tab, Predict search, featured carousel, or World Cup feed
Then markets with no displayable live outcomes are not shown
And partially stale markets show only live outcomes
And open highlighted markets remain pinned first
And closed or resolved highlighted markets are not shown
And pagination state, cursors, and page sizes remain unchanged
```
## **Screenshots/Recordings**
N/A - this is feed data filtering/ranking behavior covered by automated
tests, with no intended layout or visual styling change.
### **Before** Left / **After** Right
https://github.com/user-attachments/assets/04690e72-6692-4601-9730-150eb6495b6b
https://github.com/user-attachments/assets/df5a33c5-43bf-49f5-bc42-af5603c0cca4
## **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 - N/A, no new public API requiring JSDoc.
- [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. Label application should be
confirmed when creating the GitHub PR.
#### Performance checks (if applicable)
- [x] I've tested on Android - N/A, no native or platform-specific
rendering change; automated JS coverage was added for the affected data
paths.
- Ideally on a mid-range device; emulator is acceptable
- [x] I've tested with a power user scenario - N/A, change is scoped to
Predict feed market filtering and does not touch wallet/account/token
scale paths.
- 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 - N/A, no new network operation or long-running
production operation was introduced.
- 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.
## **Verification**
```bash
yarn jest app/components/UI/Predict/utils/marketStaleness.test.ts app/components/UI/Predict/hooks/usePredictMarketData.test.tsx app/components/UI/Predict/hooks/usePredictSearchMarketData.test.tsx app/components/UI/Predict/hooks/useFeaturedCarouselData.test.ts app/components/UI/Predict/hooks/usePredictWorldCup.test.ts app/components/UI/Predict/components/PredictMarketMultiple/PredictMarketMultiple.test.tsx --runInBand --coverage=false
yarn jest app/components/UI/Predict/controllers/PredictController.test.ts -t "getMarkets with market highlights" --runInBand --coverage=false
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes Predict discovery data shaping by filtering/reshaping outcomes
and re-ranking markets (including a new `isHighlighted` flag), which can
affect what users see and pagination behavior across multiple surfaces.
>
> **Overview**
> Prevents *stale/settled* Predict markets from appearing in discovery
surfaces by introducing a shared `marketStaleness` policy
(`getVisiblePredictMarkets`) that drops closed/expired markets and
prunes “dead” outcomes (probability near 0/1), while re-ranking
remaining markets.
>
> Applies this visibility filter in `usePredictMarketData`,
`useFeaturedCarouselData`, `usePredictWorldCupMarkets`, and the
empty-query fallback path in `usePredictSearchMarketData`, with tests
asserting pagination/cursors remain unchanged.
>
> Updates `PredictController.getMarkets` to tag fetched highlight
markets with `isHighlighted` (kept pinned and exempt from staleness
filtering), and removes `PredictMarketMultiple`’s old price-based
outcome filtering so the UI renders outcomes as provided by the feed
model.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
443cd36. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
…g) (#30413) ## **Description** Mirrors the change from [MetaMask/core#8633](MetaMask/core#8633) into mobile ahead of the full migration to core, so the mobile copy of `PerpsController` stays in sync with the upstream controller. The single substantive change forwards `isInternal: true` in the `options` payload when `PerpsController` submits a transaction via the `TransactionController` messenger, matching the new core behavior that flags controller-originated transactions as internal. This keeps both codebases behaviorally identical until mobile retires its local `PerpsController` in favor of the core package. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Perps internal transaction flag parity Scenario: user submits a perps transaction routed through PerpsController Given the Perps feature is enabled and the wallet is unlocked When user initiates a perps action that triggers PerpsController to submit a transaction Then the resulting TransactionController entry is marked as an internal transaction (isInternal: true) And the transaction completes with the same UX as before this change ``` ## **Screenshots/Recordings** ### **Before** N/A — behavioral parity change, no user-visible UI difference. ### **After** N/A — behavioral parity change, no user-visible UI difference. ## **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 - [x] I've tested with a power user scenario - [x] I've instrumented key operations with Sentry traces for production performance metrics ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the options passed to `TransactionController:addTransaction` for perps flows, which could affect how transactions are classified/handled downstream (e.g., UI filtering or telemetry). Scope is small and covered by updated unit tests. > > **Overview** > Perps transactions submitted via `PerpsController` now always forward `isInternal: true` when calling `TransactionController:addTransaction`, aligning mobile behavior with the upstream core controller. > > Tests for `depositWithConfirmation` (including the `perpsDeposit` and `perpsDepositAndOrder` paths) are updated to assert the new `isInternal` flag in the messenger call options. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 93a877e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## **Description**
Updates the Predict World Cup main feed banner to match the latest Figma
card design. The banner now renders as a muted card with the World Cup
image, title, description, and a filled icon button in the footer.
Also updates the Predict World Cup feature flag image override from a
single URL to a structured `bannerImage` object containing `{ url,
width, height }`, so remote images can preserve their intended aspect
ratio while still falling back to a safe default aspect ratio when
dimensions are missing or invalid.
## **Changelog**
CHANGELOG entry: null
## **Related issues**
Fixes: PRED-895
## **Manual testing steps**
```gherkin
Feature: Predict World Cup main feed banner
Scenario: user views the Predict feed with World Cup enabled
Given the Predict World Cup feature flag is enabled
And showMainFeedBanner and showWorldCupScreen are enabled
When user opens the Predict feed
Then the World Cup banner is displayed as a card with an image, title, description, and arrow button
Scenario: user taps the World Cup banner
Given the World Cup banner is visible on the Predict feed
When user taps the banner
Then user is navigated to the World Cup Predictions screen
```
## **Screenshots/Recordings**
### **Before**
N/A
### **After**
<img width="350" alt="Simulator Screenshot - mm-blue - 2026-05-20 at 15
16 24"
src="https://github.com/user-attachments/assets/088e1035-373e-4f23-bd3e-736d6930ff7b"
/>
## **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)
- [ ] 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](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
- [ ] 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.
## Testing
```bash
yarn jest app/components/UI/Predict/components/PredictWorldCupMainFeedBanner/PredictWorldCupMainFeedBanner.test.tsx app/components/UI/Predict/schemas/flags.test.ts app/components/UI/Predict/utils/resolvePredictFeatureFlags.test.ts --runInBand
```
Result: 3 test suites passed, 53 tests passed.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Moderate risk because it changes the remote feature-flag schema/type
for the World Cup banner image (from `bannerImageUrl` to structured
`bannerImage`) and updates banner sizing logic based on provided
dimensions, which could impact rendering if remote config is malformed.
>
> **Overview**
> Updates the Predict World Cup main-feed banner to a card-style layout
with footer copy (title/description) and a filled arrow `ButtonIcon`,
while keeping the same navigation/tracking behavior.
>
> Reworks the World Cup feature-flag image override from
`bannerImageUrl` to a structured `bannerImage` object (`{ url, width,
height }`) and introduces `getPredictWorldCupBannerImageAspectRatio` to
compute banner height from configured dimensions with a safe default
fallback; associated schema/type and unit tests are updated accordingly,
and the English banner description string is revised.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
580875f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )