feat(predict): add crypto up/down feed card#30342
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Fix two stale test assertions in PredictCryptoUpDownMarketCard that referenced the old 4-arg useCryptoUpDownChartData signature (market, chartRef, targetPrice, options) - the hook is now 3-arg (market, targetPrice, options) since 25d3f50, so drop the extra undefined arg and re-index the calls[0][2] options lookup. Address three SonarCloud smells flagged on this PR: remove the unused PredictMarket import in useCurrentCryptoUpDownMarketData, drop the unused isCarousel prop on PredictCryptoUpDownMarketCard, and extract the historyEndDate nested ternary in useCryptoUpDownChartData into a named intermediate (liveHistoryEndDate).
PredictMarket.tsx routes to all variant cards and forwards isCarousel uniformly. Removing the prop from PredictCryptoUpDownMarketCard broke the variant-card contract and produced a TS2322 in PredictMarket.tsx. Restore the prop with a JSDoc explaining it is accepted for contract parity with PredictMarketSingle / PredictMarketMultiple; the crypto card does not yet implement a carousel sizing variant. The earlier sonar smell ('PropType defined but never used') will be addressed properly by wiring the carousel sizing in a follow-up.
High-priority issuesH1. Chainlink-candles history endpoint silently returns empty for non-recent / expired marketsconst { CHAINLINK_CANDLES_ENDPOINT } = getPolymarketEndpoints();
const { interval, limit } =
CHAINLINK_CANDLE_CONFIG_BY_VARIANT[variant] ??
DEFAULT_CHAINLINK_CANDLE_CONFIG;
const searchParams = new URLSearchParams({
symbol: normalizedSymbol,
interval,
limit: String(limit),
});
// ...
return data.candles
.filter((entry): entry is { time: number; close: number } => ... )
.filter((entry) =>
isWithinWindow({
timestamp: entry.time,
startSeconds,
endSeconds,
}),
)
.map((entry) => ({
timestamp: entry.time,
value: entry.close,
}));The previous
For longer recurrences this is worse:
The "past time slot" path is exactly what Suggested fix (in priority order):
A direct regression test for this path: it('returns empty when the requested window is older than limit * interval', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({
candles: Array.from({ length: 15 }, (_, i) => ({
time: 10_000 + i * 60,
close: 100 + i,
})),
}),
});
const result = await createProvider().getCryptoPriceHistory({
symbol: 'BTC',
eventStartTime: '1000',
endDate: '1300',
variant: 'fiveminute',
});
expect(result).toEqual([]); // currently passes silently — this is the regression
});This isn't a hypothetical: it's exercised every time a user taps a non-live time slot in the new details screen the PR plugs the card into. Medium-priority issuesM1.
|
- Chainlink candles: size limit dynamically against the requested window when the recurrence's base coverage isn't enough; warn via DevLogger when every candle is filtered out so silent empty charts surface in QA. - OutcomeButtons: gate disabled state on marketStatus === OPEN and harden handleBuyPress so rollover-boundary closed markets cannot open the buy sheet. - useCryptoUpDownChartData history bucket: scale by display duration (displayMs / 12, floor at 60s) so daily/4h cards stop refetching history once a minute. - useCurrentPredictMarketFromSeries: replace 1Hz setInterval with a self-rescheduling setTimeout that fires at the next durationMs boundary. - getVariant: log a DevLogger warning when falling back to 'hourly' for an unknown recurrence and map 'hourly' through explicitly. - PredictCryptoUpDownDetails: drop the duplicate getCurrentWindowMs in favour of utils/series#getCurrentSeriesWindowMs and import findLiveMarket from the SSOT. - PredictCryptoUpDownMarketCard: replace the module-level sparkline gradient counter with useId and move the shared clock behind a testable singleton with __resetCardClockForTest. - PredictMarketDetails: render a market-unavailable empty state when a deep link arrives without a resolvable marketId or series id, instead of a perpetual skeleton followed by a blank screen.
…cker - PredictCryptoUpDownChart: force badge momentum from value vs target so the price badge colors by win/lose state instead of recent-tick momentum. Falls back to liveline auto-detection when no target. - TimeSlotPicker: delay scroll-to-selected so onLayout settles after rollover refetches, subtract a content-padding offset so the selected pill isn't flush left, and drop the lastScrolledId guard so the picker re-scrolls when the selection returns to a previously-visited pill. - PredictCryptoUpDownDetails: split formatted currency into whole and fraction so the 'Price to beat' and current-price headers render the dollars at DisplayMd and the cents inline at HeadingMd. Tighten chart top padding.
Drop LivelineChart top padding from 48 to 8 so the chart sits right under the price headers instead of leaving a large empty band above the first gridline.
MarioAslau
left a comment
There was a problem hiding this comment.
Thanks for addressing the issues! LGTM !
The previous guard required both startSeconds AND endSeconds to be defined before the dynamic limit kicked in. The feed card's trailing history window passes only startDate (no endDate), so it always short-circuited to baseLimit and silently under-fetched candles when the requested lookback exceeded the recurrence's base coverage — most visibly on daily charts where baseLimit=30 covers 30 hours but the card requests 7 days of history. Drop endSeconds from computeChainlinkCandleLimit entirely. It was never used in the computation body, and the existing lookbackSeconds <= baseLimitCoverageSeconds branch already handles future-startSeconds via a negative lookback. Removing it also addresses the misleading-contract finding from review (endSeconds in the signature but unused in the body). Add a regression test that exercises the open-ended trailing-window path so a future refactor doesn't re-introduce the short-circuit.
Direct probing of https://polymarket.com/api/chainlink-candles revealed a hard server-side allowlist on the limit parameter: `{"error":"limit must be one of 15, 30, or 60"}`. The dynamic expansion introduced for the H1 review finding was computing limits like 125 / 173 / 200 for deep historical windows; every one of those requests returned 400 and the sparkline failed silently. The reviewer's preferred fix for H1 was a server change (accept start/to in the query) — without that, mobile can only request {15, 30, 60} candles at one of four allowed intervals. There is no client-side fix for lookups older than baseLimit * interval, so the dynamic logic just broke working flows. Restore baseLimit usage, narrow ChainlinkCandleLimit back to 15 | 30 | 60, and document the contract inline so the next contributor doesn't repeat the mistake. Keep the DevLogger warning when client-side filtering drops every candle — that signal is still useful for QA on deep-history lookups. Drops the two dynamic-expansion regression tests; keeps the 'logs a development warning when every candle falls outside the requested window' test.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
Tag Selection Rationale:
No changes to shared navigation, Engine controllers, account management, swap/bridge, or other cross-cutting components. The changes are well-contained within the Predict feature. Performance Test Selection: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4bca5e7. Configure here.
|




Description
This PR adds the Predict crypto up/down feed card and its supporting plumbing. It introduces the inline live sparkline + dual Up/Down CTA card that renders for crypto up/down markets inside the Predict feed, including current-series resolution so the card always shows the next active window. It also wires live RTDS crypto prices through to the feed card and tightens the underlying live chart behavior so the chart stays continuous across market rollover and uses a fixed 30-second live window.
It also includes targeted fixes to the existing crypto details screen so the header, chart, and CTA prices line up with the new feed-card expectations: live-updating CTA prices via WebSocket, simplified details header, and bottom margin under the CTA buttons.
TODO
Changelog
CHANGELOG entry: Added a live crypto up/down feed card with inline sparkline and Up/Down actions in Predict
Related issues
Fixes: N/A - stacked Predict crypto up/down implementation split from #29436.
Manual testing steps
Screenshots/Recordings
Before
N/A
After
N/A
Pre-merge author checklist
Performance checks (if applicable)
trace()for usage andaddTokenfor an exampleFor performance guidelines and tooling, see the Performance Guide.
Pre-merge reviewer checklist
Note
Medium Risk
Adds a new crypto Up/Down feed card and refactors crypto price streaming/history fetching (including new RTDS topic and Chainlink candle history), which could affect live price display and chart continuity across rollovers.
Overview
Introduces a new crypto Up/Down feed card (
PredictCryptoUpDownMarketCard) with inline animated sparkline, live countdown/progress ring, and Up/Down buy CTAs, and wires it intoPredictMarketrendering viaisCryptoUpDown.Refactors crypto series/market resolution and data plumbing by adding
useCurrentPredictMarketFromSeries+useCurrentCryptoUpDownMarketData, centralizing series-window utilities (getCurrentSeriesWindowMs,findLiveMarket/findNearestMarket), and updatingTimeSlotPickerscrolling behavior.Improves live chart behavior (
useCryptoUpDownChartData+PredictCryptoUpDownChart) with explicitenabled/liveUpdatesEnabled/historicalWindowoptions, preserved continuity across market rollover, target-based momentum coloring, and updated chart padding; updates the crypto details screen formatting/layout and shifts open-outcome computation touseOpenOutcomes.Switches Polymarket crypto history fetching from the legacy endpoint to Chainlink candles with variant-based interval/limit mapping and window filtering, and updates crypto price streaming to the
crypto_prices_chainlinkRTDS topic withsymbol/usdformatting; tests updated/added accordingly.Reviewed by Cursor Bugbot for commit 4bca5e7. Bugbot is set up for automated code reviews on this repo. Configure here.