chore(runway): cherry-pick feat(predict): support series ids in predictMarketHighlights cp-7.81.0#31161
Merged
Conversation
…ctMarketHighlights cp-7.81.0 (#31044) <!-- 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? --> Extends the `predictMarketHighlights` remote feature flag to accept series IDs in addition to market IDs, and renders series-resolved crypto up/down highlights in horizontally-scrolling carousels via a new compact card variant. **Why** Recurring crypto up/down markets (e.g. "BTC Up or Down — 5 Minutes") expose a new market every N minutes. A market-ID pin in `predictMarketHighlights` therefore goes stale within a single time slot — once the market resolves, the pin disappears, and there is no way to express "always pin the currently-live BTC Up/Down 5m market." Resolving from a series at request time fixes this: the pin automatically rotates to whatever slot is live when the feed is fetched. **Schema change (additive, backward compatible)** ```ts export interface PredictMarketHighlight { category: string; markets?: string[]; // was required, now optional series?: string[]; // NEW } ``` - Existing LD flag values that only use `markets` continue to work unchanged. Both fields are now optional; entries with neither are skipped. - The controller's highlight block fetches `getMarketsByIds(markets)` and `getMarketSeries(seriesId)` in parallel via `Promise.all`, then resolves each series response to its currently-live market via the existing `findLiveMarket()` (with `findNearestMarket()` as a fallback when no future market is in the fetch window). - Resolved series markets pass through the same `status === 'open'` filter and are marked with `isHighlighted: true`, identical to the market-ID path. - Order: market-ID highlights are prepended first, then series-resolved highlights, then the regular feed. Existing flag values that only use `markets` keep their original ordering. Dedupe is unified across both paths so a market reachable via both `markets` and `series` appears exactly once. **Compact carousel variant** `PredictCryptoUpDownMarketCard` now has two render variants, keyed off the existing `isCarousel` prop that `PredictMarket` forwards to its sibling cards (`PredictMarketSingle`, `PredictMarketMultiple`, `PredictMarketSportCard`): - **Full** (`isCarousel=false`, the default) — the existing sparkline + target-line / target-price treatment used on `PredictFeed`, `PredictHome`, the wallet home carousel, and search. - **Compact** (`isCarousel=true`) — drops the sparkline and target labels and switches to a `height: 100%` + flex-column + `justifyContent: 'space-between'` layout that mirrors `PredictMarketSingle` / `PredictMarketMultiple` so the card sits flush with its neighbours in `HorizontalCarousel`. This is the variant the 5 TrendingView carousel tabs (Now, Macro, RWAs, Crypto, Sports) render for series-resolved highlights. The compact path also gates `useCryptoUpDownChartData` and `useCryptoTargetPrice` with `enabled: !isCompact`, so the chart historical fetch and live-price websocket subscription don't run for cards that never display them. **Resilience** - Per-series fetch failures are caught locally with a `DevLogger.log`, so one unhealthy series entry can't take down a healthy batch. - The series fetch reuses the canonical `SERIES_MAX_EVENTS` (50) for `limit`. The provider returns markets in `endDate ASC` order, and a fast recurrence (5m) can produce ~12 past events inside the past buffer alone — too low a limit would clip the live slot. - `getMarketsByIds` keeps its existing fail-hard contract; the previously misleading test `"handles getMarketsByIds failure gracefully"` (which actually only tested an empty-array result) was renamed accordingly, and a real rejection-propagation test was added. **Test coverage** - 8 new tests in `PredictController.test.ts` (live-market resolution, mixed markets+series ordering, nearest-market fallback, empty-series skip, closed-market status filter, throw-silent on single series, partial-batch failure, dedupe across paths) - 1 shape-passthrough test in `resolvePredictFeatureFlags.test.ts` - 4 new tests in `PredictCryptoUpDownMarketCard.test.tsx` covering the compact variant (sparkline not rendered, both chart queries gated with `enabled: false`, compact skeleton renders, buy sheet still opens) - All tests in the touched suites pass. ## **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 support for pinning the currently-live market of a recurring series (e.g. BTC Up/Down 5m) via the `predictMarketHighlights` remote feature flag. ## **Related issues** Fixes: PRED-950 ## **Manual testing steps** Both scenarios assume the `predictMarketHighlights` LaunchDarkly variation is updated to the new shape. The series ID `10684` corresponds to the Polymarket Gamma `BTC Up/Down 5m` series (also hardcoded as `BTC_UP_DOWN_5M_SERIES_ID` in `app/components/UI/Predict/constants/btcUpDown5mSeries.ts`). ```gherkin Feature: Series-id highlights in predictMarketHighlights Scenario: A series-id highlight resolves to the currently-live market on PredictFeed Given the predictMarketHighlights flag is set to """ { "enabled": true, "minimumVersion": "7.63.0", "highlights": [ { "category": "crypto", "series": ["10684"] } ] } """ When the user opens the Predict tab and switches to the "Crypto" feed tab Then the currently-live BTC Up/Down 5m market is pinned at the top of the feed And the pin auto-rotates to the next live slot when the current one resolves And the card renders in the full variant (with sparkline + target labels) Scenario: A series-id highlight renders the compact variant in TrendingView carousels Given the same flag configuration as above When the user opens the Explore page and views the "Crypto" or "Now" tab carousel Then the BTC Up/Down series card IS pinned in the Explore carousel And it renders as the compact variant (no sparkline, same height as its neighbours) And no chart-history fetch or live-price websocket subscription is opened for that card Scenario: Existing market-id highlights are unaffected Given the predictMarketHighlights flag is set to """ { "enabled": true, "highlights": [ { "category": "trending", "markets": ["451614"] } ] } """ When the user opens PredictFeed → Trending and Explore → Now Then market 451614 is pinned at the top on both surfaces (market-id path is unchanged) ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A <!-- [screenshots/recordings] --> ### **After** N/A <!-- [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) - [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** > Feed ordering and highlight resolution now depend on series API behavior and remote flag shape; carousel gating changes which data loads on Explore surfaces. > > **Overview** > Adds optional **`series`** IDs to **`predictMarketHighlights`** so recurring crypto up/down pins resolve to the **currently live** market via **`getMarketSeries`** + **`findLiveMarket`** / **`findNearestMarket`**, merged with existing market-ID highlights (parallel fetch, dedupe, open-only, market IDs first). > > **`PredictCryptoUpDownMarketCard`** now honors **`isCarousel`**: a **compact** layout (no sparkline/target UI, full-height flex) and **`enabled: false`** on chart/target hooks to avoid unused network work in Explore carousels; full feed cards unchanged with **`enabled: true`** on chart data. > > Controller tests cover series resolution edge cases; **`getMarketsByIds`** failures now **reject** instead of being mislabeled as graceful handling. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 83f5c75. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Contributor
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
Contributor
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - PR targets a release or stable branch (release/* or stable) All E2E tests pre-selected. |
sleepytanya
approved these changes
Jun 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Extends the
predictMarketHighlightsremote feature flag to acceptseries IDs in addition to market IDs, and renders series-resolved crypto
up/down highlights in horizontally-scrolling carousels via a new compact
card variant.
Why
Recurring crypto up/down markets (e.g. "BTC Up or Down — 5 Minutes")
expose a new market every N minutes. A market-ID pin in
predictMarketHighlightstherefore goes stale within a single time slot— once the market resolves, the pin disappears, and there is no way to
express "always pin the currently-live BTC Up/Down 5m market." Resolving
from a series at request time fixes this: the pin automatically rotates
to whatever slot is live when the feed is fetched.
Schema change (additive, backward compatible)
marketscontinue to workunchanged. Both fields are now optional; entries with neither are
skipped.
getMarketsByIds(markets)and
getMarketSeries(seriesId)in parallel viaPromise.all, thenresolves each series response to its currently-live market via the
existing
findLiveMarket()(withfindNearestMarket()as a fallbackwhen no future market is in the fetch window).
status === 'open'filter and are marked with
isHighlighted: true, identical to themarket-ID path.
highlights, then the regular feed. Existing flag values that only use
marketskeep their original ordering. Dedupe is unified across bothpaths so a market reachable via both
marketsandseriesappearsexactly once.
Compact carousel variant
PredictCryptoUpDownMarketCardnow has two render variants, keyed offthe existing
isCarouselprop thatPredictMarketforwards to itssibling cards (
PredictMarketSingle,PredictMarketMultiple,PredictMarketSportCard):isCarousel=false, the default) — the existing sparkline +target-line / target-price treatment used on
PredictFeed,PredictHome, the wallet home carousel, and search.isCarousel=true) — drops the sparkline and targetlabels and switches to a
height: 100%+ flex-column +justifyContent: 'space-between'layout that mirrorsPredictMarketSingle/PredictMarketMultipleso the card sits flush with its neighbours inHorizontalCarousel. This is the variant the 5 TrendingView carouseltabs (Now, Macro, RWAs, Crypto, Sports) render for series-resolved
highlights.
The compact path also gates
useCryptoUpDownChartDataanduseCryptoTargetPricewithenabled: !isCompact, so the charthistorical fetch and live-price websocket subscription don't run for
cards that never display them.
Resilience
DevLogger.log,so one unhealthy series entry can't take down a healthy batch.
SERIES_MAX_EVENTS(50) forlimit. The provider returns markets inendDate ASCorder, and a fastrecurrence (5m) can produce ~12 past events inside the past buffer alone
— too low a limit would clip the live slot.
getMarketsByIdskeeps its existing fail-hard contract; thepreviously misleading test
"handles getMarketsByIds failure gracefully"(which actually only tested an empty-array result) wasrenamed accordingly, and a real rejection-propagation test was added.
Test coverage
PredictController.test.ts(live-market resolution,mixed markets+series ordering, nearest-market fallback, empty-series
skip, closed-market status filter, throw-silent on single series,
partial-batch failure, dedupe across paths)
resolvePredictFeatureFlags.test.tsPredictCryptoUpDownMarketCard.test.tsxcovering thecompact variant (sparkline not rendered, both chart queries gated with
enabled: false, compact skeleton renders, buy sheet still opens)Changelog
CHANGELOG entry: Added support for pinning the currently-live market of
a recurring series (e.g. BTC Up/Down 5m) via the
predictMarketHighlightsremote feature flag.Related issues
Fixes: PRED-950
Manual testing steps
Both scenarios assume the
predictMarketHighlightsLaunchDarklyvariation is updated to the new shape. The series ID
10684correspondsto the Polymarket Gamma
BTC Up/Down 5mseries (also hardcoded asBTC_UP_DOWN_5M_SERIES_IDinapp/components/UI/Predict/constants/btcUpDown5mSeries.ts).Screenshots/Recordings
Before
N/A
After
N/A
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Performance checks (if applicable)
SRPs
to import wallets with many accounts and tokens
performance metrics
trace()for usage andaddTokenfor an example
For performance guidelines and tooling, see the Performance
Guide.
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Note
Medium Risk
Feed ordering and highlight resolution now depend on series API
behavior and remote flag shape; carousel gating changes which data loads
on Explore surfaces.
Overview
Adds optional
seriesIDs topredictMarketHighlightssorecurring crypto up/down pins resolve to the currently live market
via
getMarketSeries+findLiveMarket/findNearestMarket, merged with existing market-ID highlights(parallel fetch, dedupe, open-only, market IDs first).
PredictCryptoUpDownMarketCardnow honorsisCarousel: acompact layout (no sparkline/target UI, full-height flex) and
enabled: falseon chart/target hooks to avoid unused network workin Explore carousels; full feed cards unchanged with
enabled: trueon chart data.
Controller tests cover series resolution edge cases;
getMarketsByIdsfailures now reject instead of beingmislabeled as graceful handling.
Reviewed by Cursor Bugbot for commit
83f5c75. Bugbot is set up for automated
code reviews on this repo. Configure
here.