Skip to content

chore(runway): cherry-pick feat(predict): support series ids in predictMarketHighlights cp-7.81.0#31161

Merged
sleepytanya merged 1 commit into
release/7.81.0from
runway-cherry-pick-7.81.0-1780675324
Jun 7, 2026
Merged

chore(runway): cherry-pick feat(predict): support series ids in predictMarketHighlights cp-7.81.0#31161
sleepytanya merged 1 commit into
release/7.81.0from
runway-cherry-pick-7.81.0-1780675324

Conversation

@runway-github

@runway-github runway-github Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Description

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)

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

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).

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

Before

N/A

After

N/A

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
  • Use these power-user
    SRPs

    to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production
    performance metrics
  • See trace() for usage and
    addToken
    for an example

For performance guidelines and tooling, see the Performance
Guide
.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the
    app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described
    in the ticket it closes and includes the necessary testing evidence such
    as recordings and or screenshots.

Note

Medium Risk
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.

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

[c32e24f](https://github.com/MetaMask/metamask-mobile/commit/c32e24fbe741dd242b86ac2b627751b99da28be3)

…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 -->
@runway-github runway-github Bot requested a review from a team as a code owner June 5, 2026 16:02
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@mm-token-exchange-service mm-token-exchange-service Bot added the team-bots Bot team (for MetaMask Bot, Runway Bot, etc.) label Jun 5, 2026
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

⏭️ Smart E2E selection skipped - PR targets a release or stable branch (release/* or stable)

All E2E tests pre-selected.

View GitHub Actions results

@github-actions github-actions Bot added size-L risk:medium AI analysis: medium risk labels Jun 5, 2026
@sleepytanya sleepytanya merged commit 7eae1ca into release/7.81.0 Jun 7, 2026
395 of 400 checks passed
@sleepytanya sleepytanya deleted the runway-cherry-pick-7.81.0-1780675324 branch June 7, 2026 04:21
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

risk:medium AI analysis: medium risk size-L team-bots Bot team (for MetaMask Bot, Runway Bot, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants