Skip to content

chore: add ohlcv websocket streaming#29739

Draft
sahar-fehri wants to merge 2 commits intomainfrom
chore/ohlcv-ws-streaming-integration
Draft

chore: add ohlcv websocket streaming#29739
sahar-fehri wants to merge 2 commits intomainfrom
chore/ohlcv-ws-streaming-integration

Conversation

@sahar-fehri
Copy link
Copy Markdown
Contributor

@sahar-fehri sahar-fehri commented May 5, 2026

🚧

Description

Wires OHLCVService from @metamask/core-backend into the Engine and creates a useOHLCVRealtime hook that streams live candlestick updates to the advanced chart via the existing realtimeBar prop on AdvancedChart.

Why: The advanced chart currently only renders historical data fetched via the REST OHLCV API. Users see stale candles until they navigate away and back. Real-time streaming via WebSocket keeps the chart live with 5-second heartbeat updates.

How: Follows the exact same Engine wiring pattern as AccountActivityService — messenger, init function, Engine registration. The new useOHLCVRealtime hook subscribes to OHLCVService:barUpdated events, filters by channel, and converts the WS bar format (timestamp in Unix seconds) to the chart's expected format (time in milliseconds).

Local Testing Guide

Where to see logs

The WebSocket connection is managed by BackendWebSocketService in the RN JS Engine layer — not in a WebView. Chrome DevTools' Network > WS tab will be empty because it only captures WebView-originated connections.

Instead, look for logs in:

  • Metro terminal (the terminal running npx react-native start)
  • Chrome DevTools > Console tab (when connected via Hermes debugger)

All logs are prefixed with [OHLCVRealtime].

What the logs mean

Log Meaning
Skipping — enabled=false Chart is still loading or fell back to legacy; hook won't subscribe
Setting up subscription for channel: ... Hook effect ran, event listeners registered, debounce timer started
Debounce fired — calling OHLCVService:subscribe 300ms elapsed without cleanup; actual WS subscribe sent to gateway
Subscribe SUCCESS Gateway acknowledged the subscription
Bar received — channel=..., close=..., ts=... A heartbeat arrived (every ~5s); ts is candle open time in seconds
Cleanup — wasSubscribed=true/false Effect cleanup ran (navigation away or param change)
Calling OHLCVService:unsubscribe Actual WS unsubscribe sent (only if wasSubscribed=true)
Unsubscribe SUCCESS Gateway acknowledged the unsubscription
Subscription error on ... Gateway or service-level error for that channel

What to look for

  1. Happy path: Navigate to Token Details > see Subscribe SUCCESS > see repeated Bar received logs every ~5s > navigate back > see Unsubscribe SUCCESS

  2. Debounce working: Navigate quickly in and out of Token Details. You should see Setting up followed immediately by Cleanup — wasSubscribed=false (no actual WS call was made). This confirms the 300ms debounce absorbed the rapid navigation.

  3. Timestamp behavior: The ts value stays the same within a candle bucket (e.g. 15 minutes for the 1D view). It only changes when the next candle opens. Identical bars with the same ts and close are normal — it means no trades occurred since the last heartbeat.

  4. Time range switch: Change from 1D to 1H — you should see Unsubscribe SUCCESS for the 15m channel followed by Subscribe SUCCESS for a 1m channel.

  5. No leaks: Every Subscribe SUCCESS should eventually have a matching Unsubscribe SUCCESS. If you see subscribes without unsubscribes, there's a leak.

Where the current debug logs live

All [OHLCVRealtime] logs are in a single file:

app/components/UI/Charts/AdvancedChart/useOHLCVRealtime.ts

The logs are placed at these points in the hook's useEffect. To re-add them after removal, place console.log calls at these locations:

useEffect(() => {
  if (!enabled || !assetId || !interval || !currency) {
    // LOG HERE: `[OHLCVRealtime] Skipping — enabled=${enabled}, assetId=${assetId}, interval=${interval}, currency=${currency}`
    return;
  }

  const channel = buildChannel();
  // LOG HERE: `[OHLCVRealtime] Setting up subscription for channel: ${channel}`

  const handleBarUpdated = (payload) => {
    if (payload.channel === channelRef.current) {
      // LOG HERE: `[OHLCVRealtime] Bar received — channel=${payload.channel}, close=${payload.bar.close}, ts=${payload.bar.timestamp}`
      setLatestBar(payload.bar);
    }
  };

  const handleSubscriptionError = (payload) => {
    // LOG HERE: `[OHLCVRealtime] Subscription error on ${payload.channel}: ${payload.error} (${payload.operation})`
  };

  // ... event subscriptions ...

  debounceTimerRef.current = setTimeout(async () => {
    // LOG HERE: `[OHLCVRealtime] Debounce fired — calling OHLCVService:subscribe for ${channel}`
    try {
      await Engine.controllerMessenger.call('OHLCVService:subscribe', { ... });
      // LOG HERE: `[OHLCVRealtime] Subscribe SUCCESS for ${channel}`
    } catch (err) {
      // LOG HERE: `[OHLCVRealtime] Failed to subscribe: ${err.message}`
    }
  }, DEBOUNCE_MS);

  return () => {
    // LOG HERE: `[OHLCVRealtime] Cleanup — channel=${channel}, wasSubscribed=${subscribedRef.current}`

    // ... event unsubscriptions ...

    if (subscribedRef.current) {
      // LOG HERE: `[OHLCVRealtime] Calling OHLCVService:unsubscribe for ${channel}`
      Engine.controllerMessenger
        .call('OHLCVService:unsubscribe', { ... })
        .then(() => {
          // LOG HERE: `[OHLCVRealtime] Unsubscribe SUCCESS for ${channel}`
        })
        .catch((err) => {
          // LOG HERE: `[OHLCVRealtime] Failed to unsubscribe: ${err.message}`
        });
    }
  };
}, [assetId, interval, currency, enabled, buildChannel]);

Changelog

CHANGELOG entry:

Related issues

Fixes:
Related: MetaMask/core#8695

Manual testing steps

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

Before

After

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

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant