Skip to content

perf(perps): replace REST candle polling with real-time WebSocket#22232

Merged
abretonc7s merged 27 commits into
mainfrom
feat/perps/tat-1867-candle-subscription
Nov 21, 2025
Merged

perf(perps): replace REST candle polling with real-time WebSocket#22232
abretonc7s merged 27 commits into
mainfrom
feat/perps/tat-1867-candle-subscription

Conversation

@abretonc7s
Copy link
Copy Markdown
Contributor

@abretonc7s abretonc7s commented Nov 6, 2025

Description

Problem

The PerpsMarketDetailsView was creating duplicate Sentry traces for "Perps Fetch Historical Candles" due to two concurrent REST API calls for the same coin:

  1. Chart Data: User-selected interval (e.g., 3m, 5m, 15m) via usePerpsPositionData
  2. 24h Stats: Fixed 1h interval for high/low calculation via usePerpsMarketStats

Both used setInterval polling, resulting in:

  • Unnecessary network traffic from repeated REST calls
  • No cache sharing between different intervals
  • Mixed architecture (prices use WebSocket, candles use REST)
  • Identical Sentry trace names making debugging difficult

Solution

Migrated candle data from REST polling to WebSocket streaming pattern, following the established architecture for prices, orders, and positions:

  1. Created CandleStreamChannel - Lazy-loaded streaming infrastructure (no prewarm)
  2. Added subscribeToCandles() to HyperLiquidClientService and PerpsController
  3. Created usePerpsLiveCandles hook - Drop-in replacement for usePerpsPositionData
  4. Migrated consumers - Updated usePerpsMarketStats and PerpsMarketDetailsView
  5. Removed old implementation - Deleted usePerpsPositionData and tests

Benefits

Eliminates duplicate Sentry traces - Each (coin, interval) combination gets distinct subscription
Real-time updates - WebSocket push instead of polling (no setInterval)
Cache sharing - Multiple components using same (coin, interval) = 1 WebSocket connection
Auto-cleanup - Unsubscribe when components unmount
Protocol-agnostic - Architecture supports future providers (Binance, dYdX, etc.)
Consistent pattern - Matches existing streaming channels (prices, orders, positions)

Trade-off

The system maintains 2 WebSocket subscriptions per coin as required by product:

  • Chart: User-selected interval (3m/5m/15m/etc) for visual detail
  • Stats: Fixed 1h interval for 24h high/low calculation

This is not a bug - different granularities are needed for different use cases. If product unifies to a single interval in the future, the architecture will automatically reduce to 1 subscription (no code changes needed).

Android Rendering Fix

During testing, we discovered that the WebSocket migration exposed a race condition on Android devices. The chart data often arrived before the WebView finished initializing (Android WebView is 2-3x slower than iOS), causing:

  • Black screens when switching markets or intervals
  • Skeleton loader staying visible indefinitely
  • Poor user experience on mid-range Android devices

Fix: Added data buffering to hold early-arriving data until the chart is ready, and kept the chart component mounted during interval switches to avoid expensive WebView reinitialization.

Impact: Chart now loads smoothly on Android with no black screens, even on slower devices. This was caught during testing of the WebSocket streaming feature.

Changelog

CHANGELOG entry: Improved candle data loading with real-time WebSocket updates

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1867

Manual testing steps

⚠️ Important: Test on Android device (especially mid-range) - chart should display smoothly when switching markets/intervals with no black screens.

Feature: Real-time Candle Data via WebSocket

  Scenario: User views market details with live chart updates
    Given user is on the Perps Market Details screen for BTC
    And the chart displays candle data with user-selected interval (e.g., 3m)

    When market price changes
    Then the chart updates in real-time without manual refresh
    And the 24h high/low stats update automatically
    And no duplicate Sentry traces appear in monitoring

  Scenario: User switches between different chart intervals
    Given user is viewing BTC market with 1h candles

    When user switches to 3m interval
    Then the chart loads 3m candles via WebSocket
    And the old 1h subscription is cleaned up
    And the new 3m subscription is established

  Scenario: Multiple components share candle cache
    Given two components both need BTC 1h candles

    When both components mount
    Then only 1 WebSocket subscription is created
    And both components receive the same cached data

  Scenario: User navigates away from market details
    Given user is viewing BTC market with active candle subscriptions

    When user navigates away
    Then WebSocket subscriptions are automatically cleaned up
    And no orphaned connections remain

  Scenario: User switches between markets
    Given user is viewing BTC market with candles loaded

    When user switches to ETH market
    Then BTC candle subscription is cleaned up
    And ETH candle subscription is established
    And each market has its own cached data

Screenshots/Recordings

Before

  • REST polling via setInterval every 1-4 hours depending on interval
  • Duplicate Sentry traces with identical names
  • No cache sharing between intervals
  • Manual refresh required for updates

After

  • Real-time WebSocket streaming
  • Distinct subscriptions per (coin, interval)
  • Automatic cache sharing across components
  • Live updates without manual refresh
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-11-05.at.11.57.28.mp4

Technical Details

Architecture

Component (usePerpsLiveCandles)
    � (React integration, throttling)
CandleStreamChannel
    � (cache sharing, lazy load/cleanup)
PerpsController.subscribeToCandles()
    � (protocol-agnostic abstraction)
HyperLiquidClientService.subscribeToCandles()
    � (REST initial + WebSocket append)
SDK: subscriptionClient.candle()
    � (individual CandleEvent objects)

Files Changed

Created (2):

  • providers/channels/CandleStreamChannel.ts - Core streaming infrastructure
  • hooks/stream/usePerpsLiveCandles.ts - React hook API

Modified (8):

  • services/HyperLiquidClientService.ts - Added subscribeToCandles() method
  • controllers/PerpsController.ts - Added protocol-agnostic delegation
  • providers/PerpsStreamManager.tsx - Integrated CandleStreamChannel
  • hooks/usePerpsMarketStats.ts - Migrated to new hook
  • Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx - Migrated to new hook, removed conditional chart rendering
  • components/TradingViewChart/TradingViewChart.tsx - Added data buffering and fixed skeleton logic for Android
  • app/util/trace.ts - Removed unused trace name

Deleted (2):

  • hooks/usePerpsPositionData.ts
  • hooks/usePerpsPositionData.test.ts

Performance Impact

Before (REST Polling):

  • 2 REST calls per market (different intervals)
  • Polling every 60s - 4h depending on interval
  • No cache sharing

After (WebSocket Streaming):

  • 2 WebSocket subscriptions per market
  • Real-time push updates (no polling)
  • Cache shared across components using same (coin, interval)
  • Auto-cleanup when unmounted � 0 connections

Pre-merge author checklist

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

Replaces REST/polling candles with WebSocket streaming across stack, updates UI chart to consume live data and fetch more history, and adds provider/controller support with tests.

  • Streaming architecture:
    • Add subscribeToCandles in provider (HyperLiquidClientService), controller (PerpsController), and types (SubscribeCandlesParams).
    • Introduce CandleStreamChannel in providers/channels and wire into PerpsStreamManager as candles.
    • Expose usePerpsLiveCandles hook (hooks/stream) for real-time candles (with fetchMoreHistory).
  • UI integration:
    • Migrate PerpsMarketDetailsView to usePerpsLiveCandles; remove manual refresh of candles.
    • Enhance TradingViewChart/template: data buffering, symbol validation, CLEAR_DATA, NEED_MORE_HISTORY edge-detection callback, skeleton logic, onNeedMoreHistory prop.
    • Update usePerpsMarketStats to derive 24h high/low from live 1h candles.
  • Historical candles API:
    • Support endTime in MarketDataService.fetchHistoricalCandles and provider client; use dynamic limits and merge older candles on demand.
  • Removals/cleanup:
    • Remove usePerpsPositionData and related tests; adjust existing tests to WebSocket behavior (no candle refresh expectations).
  • Tests:
    • Add comprehensive tests for usePerpsLiveCandles, CandleStreamChannel, HyperLiquidClientService.subscribeToCandles, and updated services; update MarketDataService tests for endTime.

Written by Cursor Bugbot for commit 59514c6. This will update automatically on new commits. Configure here.

@abretonc7s abretonc7s requested a review from a team as a code owner November 6, 2025 08:04
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Nov 6, 2025

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.

@abretonc7s abretonc7s added the team-perps Perps team label Nov 6, 2025
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
…ove candle data handling

- Added `symbol` prop to `TradingViewChart` for validation against incoming candle data, preventing stale data rendering when switching markets.
- Updated `usePerpsLiveCandles` to reset candle data and loading state immediately upon coin or interval changes, ensuring accurate data display.
- Enhanced `CandleStreamChannel` to filter subscribers by `cacheKey`, improving WebSocket management and logging for subscription events.
- Refactored candle data updates in `HyperLiquidClientService` to trigger React re-renders correctly when new candles are received.
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
- Added `subscribeToCandles` method in `HyperLiquidProvider` to facilitate live candle updates.
- Updated `PerpsController` to utilize the new method for subscribing to candle data.
- Introduced `SubscribeCandlesParams` interface to define parameters for candle subscriptions, enhancing type safety and clarity in the codebase.
…vice

- Enhanced the `subscribeToCandles` method to prevent processing of events if already unsubscribed, ensuring better resource management.
- Updated tests to cover scenarios where unsubscribe is called before WebSocket establishment, improving reliability and performance.
- Refactored CandleStreamChannel tests for better readability and consistency.
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
abretonc7s and others added 3 commits November 6, 2025 21:09
…Service

- Updated error handling in the `subscribeToCandles` method to avoid throwing errors after the function has already returned, preventing potential unhandled promise rejections.
- Improved logging for error scenarios to enhance debugging capabilities.
…for improved data handling

- Removed conditional rendering of the TradingViewChart in PerpsMarketDetailsView to ensure it always renders, preventing WebView remount issues on interval changes.
- Introduced a buffering mechanism in TradingViewChart to handle candle data that arrives before the chart is ready, particularly for Android, ensuring smoother data updates and symbol validation.
- Updated skeleton loading logic to display only when the chart is not ready and data is either absent or mismatched, enhancing user experience during loading states.
@nickewansmith nickewansmith added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label Nov 10, 2025
@nickewansmith nickewansmith force-pushed the feat/perps/tat-1867-candle-subscription branch from cb3a621 to 60e8910 Compare November 10, 2025 21:04
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
…andle-subscription

# Conflicts:
#	app/components/UI/Perps/hooks/usePerpsPositionData.ts
- Added functionality to fetch additional historical candles when the user scrolls to the left edge of the TradingView chart.
- Introduced edge detection logic to trigger data fetching based on the visible range of the chart.
- Updated `usePerpsLiveCandles` hook to include a `fetchMoreHistory` method for loading more data.
- Enhanced `TradingViewChart` component to handle the new `onNeedMoreHistory` callback.
- Updated `CandleStreamChannel` to fetch historical candles before the current oldest candle, ensuring a seamless user experience.

This change improves the user experience by providing continuous access to historical data without manual refreshes.
…data fetching

- Introduced a new optional `duration` parameter in the `subscribeToCandles` method across multiple components, including `PerpsController`, `CandleStreamChannel`, and `HyperLiquidClientService`.
- Updated the `usePerpsLiveCandles` hook to accept and utilize the `duration` parameter for fetching live candle data.
- Enhanced historical candle fetching logic to dynamically calculate the number of candles based on the provided `duration` and `interval`, ensuring efficient data retrieval.
- Improved error handling and logging for subscription and fetching processes to enhance debugging and monitoring capabilities.

This change enhances the flexibility and performance of candle data management in the application.
@abretonc7s abretonc7s force-pushed the feat/perps/tat-1867-candle-subscription branch from 60e8910 to b30747b Compare November 20, 2025 11:53
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/providers/channels/CandleStreamChannel.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/services/HyperLiquidClientService.ts
Comment thread app/components/UI/Perps/controllers/PerpsController.ts
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps
  • Risk Level: medium
  • AI Confidence: 95%
click to see 🤖 AI reasoning details

Analysis Summary

All 22 changed files are exclusively within the app/components/UI/Perps/ directory and relate to Perpetuals trading functionality. This is a focused feature change with clear boundaries.

Key Changes Identified

1. Critical File: PerpsController.ts

  • Added subscribeToCandles() method for live candle WebSocket subscriptions
  • Modified fetchHistoricalCandles() to accept an optional endTime parameter
  • New type import: SubscribeCandlesParams

2. New WebSocket Streaming Architecture

  • New file: usePerpsLiveCandles.ts - Hook for real-time candle updates via WebSocket (replaces old polling-based usePerpsPositionData)
  • New file: CandleStreamChannel.ts - Stream channel for candle data management with caching and subscription handling
  • Updated PerpsStreamManager.tsx to add candles channel

3. Service Layer Updates

  • HyperLiquidClientService.ts: Added subscribeToCandles() method with WebSocket support
  • MarketDataService.ts: Added endTime parameter to fetchHistoricalCandles()
  • HyperLiquidProvider.ts: Added subscribeToCandles() method

4. UI Component Changes

  • PerpsMarketDetailsView.tsx: Migrated from usePerpsPositionData to usePerpsLiveCandles, removed skeleton loading logic
  • TradingViewChart.tsx:
    • Added onNeedMoreHistory callback for infinite scrolling
    • Added symbol prop for validation
    • Added data buffering for Android WebView
    • Improved stale data prevention

5. Types & Test Files

  • Added SubscribeCandlesParams interface
  • Multiple test files updated (.test.tsx and .test.ts files)

Impact Assessment

Scope: This is a feature enhancement that replaces polling-based candle data fetching with WebSocket streaming for real-time chart updates in the Perps feature.

Risk Factors:

  1. Isolated to Perps: All changes are contained within the Perps module
  2. ⚠️ Controller Changes: Modified the critical PerpsController.ts (marked as critical file)
  3. ⚠️ Architecture Change: Moving from polling to WebSocket streaming could introduce timing/race condition issues
  4. ⚠️ Chart Rendering: Modified TradingViewChart component which directly impacts user-facing charts
  5. Well-tested: Multiple test files updated indicating good test coverage

User-Facing Impact:

  • Chart data display and real-time updates in Perps markets
  • Historical candle loading when scrolling charts
  • Market details view functionality

Test Tag Selection

Selected: SmokePerps

Rationale:

  1. All changes are exclusively within the Perps feature domain
  2. The SmokePerps tag exists specifically for "Perpetuals trading" functionality
  3. E2E tests in e2e/specs/perps/ directory use the SmokePerps tag
  4. Changes affect core Perps functionality: chart data streaming, market views, and position management
  5. The architectural change (polling → WebSocket) warrants testing to ensure stability

Why not other tags:

  • ❌ SmokeCore: Not core wallet/framework changes
  • ❌ SmokeWalletPlatform: Not wallet platform or account switching
  • ❌ SmokeWalletUX: Specific to Perps UI, not general wallet UX
  • ❌ SmokeAssets: Not related to asset management/display outside Perps context
  • ❌ SmokeTrade/SmokeSwaps: These are DEX trading and swaps, not perpetuals

Confidence Level

95% confidence - Very high confidence because:

  • Clear feature boundary (all Perps-related files)
  • Appropriate test tag exists and is used in existing E2E tests
  • Change scope is well-understood (WebSocket streaming for charts)
  • No cross-feature dependencies identified

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
76.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@abretonc7s abretonc7s added this pull request to the merge queue Nov 21, 2025
Merged via the queue into main with commit 6be0f01 Nov 21, 2025
87 of 89 checks passed
@abretonc7s abretonc7s deleted the feat/perps/tat-1867-candle-subscription branch November 21, 2025 02:31
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 21, 2025
@metamaskbot metamaskbot added the release-7.61.0 Issue or pull request that will be included in release 7.61.0 label Nov 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.61.0 Issue or pull request that will be included in release 7.61.0 size-XL skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants