chore: add ohlcv websocket streaming#29739
Conversation
|
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. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #29739 +/- ##
==========================================
+ Coverage 81.54% 81.68% +0.13%
==========================================
Files 5343 5389 +46
Lines 142128 143476 +1348
Branches 32411 32756 +345
==========================================
+ Hits 115899 117192 +1293
+ Misses 18299 18266 -33
- Partials 7930 8018 +88 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| "@metamask/connectivity-controller": "^0.1.0", | ||
| "@metamask/controller-utils": "^11.18.0", | ||
| "@metamask/core-backend": "^6.2.0", | ||
| "@metamask/core-backend": "npm:@metamask-previews/core-backend@6.2.2-preview-094d56fb3", |
There was a problem hiding this comment.
TODO: to be removed after release
| }); | ||
|
|
||
| const wsInterval = WS_INTERVAL_BY_TIME_RANGE[timeRange]; | ||
| // TODO: Check if we want to add a feature flag to gate the WS OHLCV feature |
There was a problem hiding this comment.
Could be a followup
…MetaMask#8695) ## Explanation ### Architecture Overview ``` ┌─────────────────┐ messenger ┌──────────────────────────┐ │ OHLCVService │ ─── calls actions ────► │ BackendWebSocketService │ │ (domain logic) │ │ (raw WS connection) │ │ │ ◄── listens to events ── │ │ └────────┬────────┘ └──────────┬───────────────┘ │ │ publishes events actual WebSocket to UI consumers (connect, auth, reconnect, heartbeat, JSON framing) │ ▼ ┌──────────────────┐ │ Mobile UI │ │ (React hooks) │ │ useOHLCVRealtime │ └──────────────────┘ ``` ### What - Add `OHLCVService` for real-time OHLCV (candlestick) data streaming via the backend WebSocket gateway - Move all WebSocket-related files (`BackendWebSocketService`, `AccountActivityService`) into a new `src/ws/` directory per code review feedback ### Why - Enable real-time chart updates on the Token Details screen without polling - Reduce API load by replacing periodic HTTP calls with persistent WebSocket subscriptions - Organize WebSocket code into a dedicated `ws/` folder for better discoverability ### New files - `src/ws/ohlcv/OHLCVService.ts` — main service with subscribe/unsubscribe semantics, reference counting, grace-period unsubscribe, idempotency checks, chain-status forwarding, and automatic resubscription on reconnect - `src/ws/ohlcv/OHLCVService.test.ts` — 22 unit tests covering all paths (100% branch coverage) - `src/ws/ohlcv/OHLCVService-method-action-types.ts` — auto-generated messenger action types - `src/ws/ohlcv/types.ts` — `OHLCVBar` and `OHLCVSubscriptionOptions` types - `src/ws/ohlcv/index.ts` — barrel exports ### Modified files - `src/index.ts` — added exports for `OHLCVService`, its types, and allowed actions/events; updated import paths to `./ws/` - `eslint-suppressions.json` — updated paths for moved files, added suppressions for new test file - `CHANGELOG.md` — documented new service and exports ### Moved files (no logic changes) - `src/BackendWebSocketService.ts` → `src/ws/BackendWebSocketService.ts` - `src/BackendWebSocketService.test.ts` → `src/ws/BackendWebSocketService.test.ts` - `src/BackendWebSocketService-method-action-types.ts` → `src/ws/BackendWebSocketService-method-action-types.ts` - `src/AccountActivityService.ts` → `src/ws/AccountActivityService.ts` - `src/AccountActivityService.test.ts` → `src/ws/AccountActivityService.test.ts` - `src/AccountActivityService-method-action-types.ts` → `src/ws/AccountActivityService-method-action-types.ts` - Only import path updates (`./logger` → `../logger`, `./types` → `../types`, test helper paths) ### Key design decisions - **UI-driven lifecycle** — unlike `AccountActivityService` (auto-subscribes on account change), `OHLCVService` exposes `subscribe()`/`unsubscribe()` called by the UI when the chart mounts/unmounts - **Reference counting** — multiple UI consumers subscribing to the same assetId/interval/currency share one WebSocket subscription - **Grace period (3s)** — when all consumers unsubscribe, actual WS unsubscribe is delayed 3 seconds to absorb rapid navigation (Token A → Token B → Token A) - **Idempotency** — uses `channelHasSubscription` before subscribing; duplicate calls are no-ops (React Strict Mode safe) - **Chain status** — listens to `system-notifications.v1.market-data.v1` (auto-subscribed by server) and publishes `OHLCVService:chainStatusChanged` - **Disconnect handling** — on WebSocket disconnect, publishes `chainStatusChanged { status: 'down' }` for all tracked chains, triggering UI polling fallback - **Reconnect** — resubscribes all active channels when WebSocket reconnects (no `sessionId` needed for OHLCV; UI polling fallback covers the gap) - **`init()` method** — system notification callback registered in `init()` (not constructor) to comply with messenger-in-constructor lint rule ### Events published - `OHLCVService:barUpdated` — `{ channel, bar: OHLCVBar }` — new candle data from WebSocket - `OHLCVService:chainStatusChanged` — `{ chainIds, status, timestamp? }` — chain up/down (server notification or WS disconnect) - `OHLCVService:subscriptionError` — `{ channel, error, operation }` — subscribe or unsubscribe failure ## References * Related to https://www.notion.so/metamask-consensys/OHLCV-WebSocket-Integration-UI-Implementation-Guide-346f86d67d6880b6a70fc3be0f0c34b9 * Related to MetaMask/metamask-mobile#29739 * Fixes https://consensyssoftware.atlassian.net/browse/ASSETS-3195 ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new WebSocket-driven market-data service with reference counting, timers, and reconnect resubscription logic, which can affect subscription lifecycles and event delivery. Also moves existing WebSocket services into `src/ws/`, so consumers relying on internal paths (vs package exports) could break if any remain. > > **Overview** > Adds a new `OHLCVService` to stream real-time OHLCV bars over WebSocket, exposing `subscribe`/`unsubscribe` via messenger actions, publishing `barUpdated`/`chainStatusChanged`/`subscriptionError` events, and handling reconnect resubscription with ref-counting plus a grace-period unsubscribe (mutex-protected). > > Refactors `core-backend` by moving `BackendWebSocketService` and `AccountActivityService` (and their tests/action-type files) into `src/ws/`, updating imports/exports (`src/index.ts`), and updating lint suppressions; also adds `async-mutex` plus comprehensive unit tests for the new service and documents the addition in the changelog. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 730af62. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
|
Will wait for package bump before we can marge |
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 88ec55d. Configure here.
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> New minor release for core-backend. Preview with changes here: MetaMask/metamask-mobile#29739 ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping: updates package versions/changelogs and bumps `@metamask/core-backend` dependency ranges without changing runtime code in this PR. > > **Overview** > Bumps the monorepo version to `984.0.0` and publishes `@metamask/core-backend@6.3.0` (updating its changelog and compare links). > > Updates `@metamask/assets-controller`, `@metamask/assets-controllers`, and `@metamask/transaction-controller` to depend on `@metamask/core-backend@^6.3.0`, with corresponding changelog entries and `yarn.lock` refresh. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1b7bb1a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
SmokeWalletPlatform is selected to validate that:
No other tags are needed as the changes are isolated to the asset overview/price chart domain and the new service is stateless and feature-flag gated. Performance Test Selection: |
|




Description
Related to: https://www.notion.so/metamask-consensys/OHLCV-WebSocket-Integration-UI-Implementation-Guide-346f86d67d6880b6a70fc3be0f0c34b9
Wires
OHLCVServicefrom@metamask/core-backendinto the Engine and creates auseOHLCVRealtimehook that streams live candlestick updates to the advanced chart via the existingrealtimeBarprop onAdvancedChart.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 newuseOHLCVRealtimehook subscribes toOHLCVService:barUpdatedevents, filters by channel, and converts the WS bar format (timestamp in Unix seconds) to the chart's expected format (time in milliseconds).Manual Test Plan
Prerequisites
backendWebSocketConnectionfeature flag enabledAdding console.log statements to the mobile hook
1. Inside
handleBarUpdated— after the channel guard:2. Inside
handleSubscriptionError— first line of the callback:3. Inside
handleChainStatusChanged— after thechainIds.includesguard:4. Inside
pollLatest— first line of the function:5. Inside the staleness
setInterval— whenisStale || chainDown:6. Inside the debounce
setTimeout— first line:7. In the cleanup
returnfunction — first line:Enabling core logs in the debugger
By default, core
OHLCVServicelogs useprojectLogger(thedebugpackage) and won't appear in the React Native debugger. To make them visible, open:Find this line (near the top, around line 30):
Replace with:
Now all core logs will appear in the debugger with the
[OHLCV-WS]prefix, alongside the mobile hook logs. Revert withyarn installwhen done.Group A — No Code Changes (Just Tap and Observe)
Scenario 1: Basic WebSocket Subscription
Steps:
Expected logs:
Verify: Bars continue arriving every ~5s with updating
closeprices.Scenario 2: Navigate Away (Unsubscribe + Grace Period)
Steps:
Expected logs:
Verify: No more bar updates after grace period expires.
Scenario 3: Rapid Navigation (Grace Period Cancel)
Steps:
Expected logs:
Verify:
Cancelled grace-period unsubscribe, bumped refCountappears — subscription was reused without a server roundtrip.Scenario 4: Switch Between Tokens
Steps:
Expected logs:
Verify: Token A fully unsubscribes (grace period expires). Token B gets its own subscription and bars flow.
Scenario 5: Rapid Time Range Switching
Steps:
Expected logs (showing one switch cycle: 15m → 1h):
This pattern repeats for each switch (1h → 1d → 1h → 15m → 1m). Each time, the old channel is flushed immediately before the new subscribe — no accumulation, no server rejections.
Verify: Every subscribe succeeds (
Subscribe succeeded).Flushing grace-period channelappears before each new subscribe. Bars flow on the final time range.Scenario 6: App Background / Foreground
Steps:
Expected logs:
Verify:
Resubscribing active channels after reconnect {count: 1}appears after foregrounding. Bars resume automatically without user interaction.Scenario 7: Unsupported Token (No OHLCV Data)
Steps:
Expected: No WS subscription, falls back to legacy line chart.
Group B — Requires Changing DEV Constants in
useOHLCVRealtime.tsScenario 8: WebSocket Disconnect → REST Polling Fallback
What this tests: The WebSocket connection drops and stays disconnected. After the staleness threshold (30s) is exceeded, the hook falls back to polling REST.
Code to add
In
useOHLCVRealtime.ts, set the DEV constant:The simulation code in the hook must call
disconnect(clean shutdown, notforceReconnection):How it works
After 10s, calls
BackendWebSocketService:disconnect(clean shutdown, no auto-reconnect). The WS stays dead. After 30s with no bars, staleness triggers REST polling every 15s.Steps to test
Expected logs:
Verify: After the simulated disconnect, no more
Bar receivedlogs. REST polling kicks in every 15s once staleness threshold (30s) is exceeded.Group C — Requires Editing
.cjsin node_modulesScenario 10: Subscribe Failure / Error Recovery
What this tests:
OHLCVService.subscribe()fails. The service catches the error, publishesOHLCVService:subscriptionError, forces reconnection, and REST fallback keeps the chart alive.Code to add
1. Disable dev simulation constant in
useOHLCVRealtime.ts:2. Simulate subscribe failure — open
node_modules/@metamask/core-backend/dist/ws/ohlcv/OHLCVService.cjs.Find the subscribe call (look for
BackendWebSocketService:subscribe) and comment it out, then add a throw:Steps to test
Expected — look for these key logs:
Verify: Error is caught, reconnection attempted (
Forcing WebSocket reconnection), and REST fallback keeps chart alive after staleness is detected.Log Reference
All logs use the
OHLCV-WSprefix. Filter byOHLCV-WSin Flipper / debugger.Changelog
CHANGELOG entry: Adds websocket streaming integration for ohlcv data
Related issues
Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3194?atlOrigin=eyJpIjoiYmQ4N2E3MTlmZTFlNGYyNGFiODUxNzA2YThmM2FkYTkiLCJwIjoiaiJ9
Related: MetaMask/core#8695
Manual testing steps
Screenshots/Recordings
Before
After
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
Introduces a new Engine-integrated WebSocket service and a real-time data path for price charts, which could impact app lifecycle, network subscriptions, and chart correctness if misconfigured. Includes a REST polling fallback and feature-flag gating, reducing blast radius but still touching core infrastructure.
Overview
Adds real-time OHLCV candlestick streaming to the token details advanced chart by wiring
OHLCVService(from@metamask/core-backend) into the Engine/messenger layer and upgrading@metamask/core-backendto^6.3.0.Introduces
useOHLCVRealtime, which subscribes (debounced) toOHLCVServicebar updates and provides a staleness/chain-down HTTP/latestfallback, then feeds updates intoAdvancedChartvia its existingrealtimeBarprop.Gates the behavior behind a new remote, version-gated feature flag
tokenDetailsOhlcvWsIntegration(registry + selector + CI constant mapping) and updates related unit tests/mocks to account for the new hook and selector.Reviewed by Cursor Bugbot for commit fe6f560. Bugbot is set up for automated code reviews on this repo. Configure here.