chore(runway): cherry-pick fix(perps): reduce WebSocket subscription overhead and prevent leaks cp-7.63.0 cp-7.64.0#25503
Conversation
…overhead and prevent leaks cp-7.63.0 cp-7.64.0 (#25496) ## **Description** This PR addresses WebSocket subscription issues identified during the rate limiting incident investigation. The fixes reduce subscription message volume by ~75% and prevent subscription leaks. ### Root Causes Identified 1. **Subscriptions to unregistered DEXs** - System subscribed to ALL 8 DEXs from API instead of only the 2 in our allowlist (main + xyz) 2. **Duplicate DEX subscriptions from race conditions** - Concurrent calls created 2× subscriptions per DEX 3. **Candle subscription leaks** - Cleanup failed when component unmounted before async subscription resolved ### Fixes Implemented #### 1. Filter DEXs by Allowlist on Mainnet (HIGH PRIORITY) **Files:** `hyperLiquidConfig.ts`, `HyperLiquidProvider.ts` - Added `MAINNET_HIP3_CONFIG` with `AutoDiscoverAll: false` - DEX filtering is now dynamic - extracts DEX names from the `allowlistMarkets` feature flag patterns - Added `extractDexsFromAllowlist()` method that parses patterns like `xyz:*`, `xyz:TSLA`, or `xyz` - **Impact:** Reduces from 8 DEXs to 2 (main + xyz), ~75% reduction in subscription messages #### 2. Prevent Duplicate DEX Subscriptions (HIGH PRIORITY) **File:** `HyperLiquidSubscriptionService.ts` - Added `pendingClearinghouseSubscriptions` and `pendingOpenOrdersSubscriptions` Maps - Refactored `ensureClearinghouseStateSubscription()` and `ensureOpenOrdersSubscription()` to check for pending promises - Concurrent calls now wait for the pending promise instead of creating duplicate subscriptions - **Impact:** Prevents 50% redundant subscriptions from race conditions #### 3. Fix Candle Subscription Cleanup (HIGH PRIORITY) **File:** `HyperLiquidClientService.ts` - Store subscription promise to enable cleanup even when pending - Updated cleanup function to wait for pending promise and unsubscribe - **Impact:** Prevents WebSocket subscription leaks when component unmounts before subscription resolves ### Test Results | Metric | Before | After | Improvement | |--------|--------|-------|-------------| | DEXs subscribed | 8 | 2 | 75% reduction | | clearinghouseState subscriptions | 16 | 2 | 87% reduction | | openOrders subscriptions | 16 | 2 | 87% reduction | | Candle subscription leaks | Yes | No | Fixed | ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: Rate limiting incident from WebSocket over-subscription Related: [WebSocket Subscription Investigation Report](docs/perps/perps-websocket-subscription-investigation.md) ## **Manual testing steps** ```gherkin Feature: WebSocket subscription optimization Scenario: User connects to perps and only allowlisted DEXs are subscribed Given user has the app installed with perps feature enabled And WebSocket logging is enabled in dev mode When user navigates to Perps home screen Then WebSocket logs show subscriptions only for main and xyz DEXs And no subscriptions for flx, vntl, hyna, km, abcd, cash DEXs Scenario: User navigates between markets without duplicate subscriptions Given user is connected to perps And WebSocket logging is enabled When user navigates from Home to xyz:XYZ100 market details And user returns to Home And user navigates back to xyz:XYZ100 Then WebSocket logs show no duplicate clearinghouseState subscriptions And WebSocket logs show no duplicate openOrders subscriptions Scenario: User views candle chart and subscriptions are properly cleaned up Given user is on a market details screen with chart visible When user quickly navigates away from the screen And user waits for 2 seconds Then candle subscriptions are properly unsubscribed And no orphaned candle subscriptions exist ``` ## **Screenshots/Recordings** ### **Before** WebSocket subscription breakdown (full trading flow): - clearinghouseState: 16 subscriptions (8 DEXs × 2 duplicates) - openOrders: 16 subscriptions (8 DEXs × 2 duplicates) - candle: 4 subscriptions, 0 unsubscriptions (leak) ### **After** WebSocket subscription breakdown (full trading flow): - clearinghouseState: 2 subscriptions (main + xyz only) - openOrders: 2 subscriptions (main + xyz only) - candle: 2 subscriptions, 2 unsubscriptions (balanced) **Total outgoing messages:** 44 **Total incoming messages:** 402 (down from ~750) ## **Pre-merge author checklist** - [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. ## **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. --- ### Subscription Breakdown - `clearinghouseState`: 2 (main + xyz) - `openOrders`: 2 (main + xyz) - `userFills`: 1 - `webData3`: 1 - `allMids`: 1 - `assetCtxs`: 2 (xyz only, subscribed/unsubscribed on navigation) - `activeAssetCtx`: 2 (xyz:XYZ100, subscribed/unsubscribed on navigation) - `candle`: 2 (1h + 15m intervals) - `bbo`: 2 (subscribed/unsubscribed during order flow) ### Future Optimization Opportunity Trading operations (order placement, cancellation, modification) currently use **HTTP transport**: - `ExchangeClient` is configured with `httpTransport` to avoid 429 rate limiting - `InfoClient` uses `wsTransport` for info queries (multiplexed over single WS connection) Now that subscription volume is reduced by 75%, we could consider moving `ExchangeClient` to WebSocket transport (see follow-up investigation). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core perps WebSocket subscription and DEX-discovery behavior; bugs could lead to missing market data or stale positions/orders, though changes are localized and defensive. > > **Overview** > Reduces perps WebSocket load by **filtering mainnet HIP-3 DEX discovery/subscription based on the `allowlistMarkets` feature flag**, falling back to main DEX only when no HIP-3 patterns are allowlisted (via new `MAINNET_HIP3_CONFIG` and allowlist DEX parsing in `HyperLiquidProvider`). > > Prevents subscription churn/leaks by **deduplicating concurrent `clearinghouseState`/`openOrders` subscriptions** with pending-promise tracking in `HyperLiquidSubscriptionService`, and by **fixing candle unsubscription when the async subscribe promise hasn’t resolved yet** in `HyperLiquidClientService`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 73e62ff. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude <noreply@anthropic.com>
|
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. |
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - base branch is not main (base: release/7.63.0) All E2E tests pre-selected. |
| if (colonIndex > 0) { | ||
| // Has colon - extract DEX prefix | ||
| const dex = pattern.substring(0, colonIndex); | ||
| dexNames.add(dex); |
There was a problem hiding this comment.
Inconsistent case handling causes DEX filtering failures
High Severity
The extractDexsFromAllowlist() method has inconsistent case normalization between its two branches. When a pattern contains a colon (e.g., "XYZ:TSLA"), the DEX name is extracted without lowercasing. When a pattern has no colon (e.g., "xyz"), the pattern is lowercased. This inconsistency causes the subsequent filter availableHip3Dexs.filter((dex) => allowedDexsFromAllowlist.includes(dex)) to fail when the API returns lowercase DEX names (like "xyz") but the allowlist patterns use different case (like "XYZ:TSLA"). This could result in zero HIP-3 DEXs being matched on mainnet, defeating the purpose of the allowlist filtering.
Additional Locations (1)
|
|
No release label on PR. Adding release label release-7.63.0 on PR, as PR was cherry-picked in branch 7.63.0. |




Description
This PR addresses WebSocket subscription issues identified during the
rate limiting incident investigation. The fixes reduce subscription
message volume by ~75% and prevent subscription leaks.
Root Causes Identified
DEXs from API instead of only the 2 in our allowlist (main + xyz)
calls created 2× subscriptions per DEX
unmounted before async subscription resolved
Fixes Implemented
1. Filter DEXs by Allowlist on Mainnet (HIGH PRIORITY)
Files:
hyperLiquidConfig.ts,HyperLiquidProvider.tsMAINNET_HIP3_CONFIGwithAutoDiscoverAll: falseallowlistMarketsfeature flag patternsextractDexsFromAllowlist()method that parses patterns likexyz:*,xyz:TSLA, orxyzsubscription messages
2. Prevent Duplicate DEX Subscriptions (HIGH PRIORITY)
File:
HyperLiquidSubscriptionService.tspendingClearinghouseSubscriptionsandpendingOpenOrdersSubscriptionsMapsensureClearinghouseStateSubscription()andensureOpenOrdersSubscription()to check for pending promisesduplicate subscriptions
3. Fix Candle Subscription Cleanup (HIGH PRIORITY)
File:
HyperLiquidClientService.tsunmounts before subscription resolves
Test Results
Changelog
CHANGELOG entry: null
Related issues
Fixes: Rate limiting incident from WebSocket over-subscription
Related: WebSocket Subscription Investigation
Report
Manual testing steps
Screenshots/Recordings
Before
WebSocket subscription breakdown (full trading flow):
After
WebSocket subscription breakdown (full trading flow):
Total outgoing messages: 44
Total incoming messages: 402 (down from ~750)
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Subscription Breakdown
clearinghouseState: 2 (main + xyz)openOrders: 2 (main + xyz)userFills: 1webData3: 1allMids: 1assetCtxs: 2 (xyz only, subscribed/unsubscribed on navigation)activeAssetCtx: 2 (xyz:XYZ100, subscribed/unsubscribed onnavigation)
candle: 2 (1h + 15m intervals)bbo: 2 (subscribed/unsubscribed during order flow)Future Optimization Opportunity
Trading operations (order placement, cancellation, modification)
currently use HTTP transport:
ExchangeClientis configured withhttpTransportto avoid 429 ratelimiting
InfoClientuseswsTransportfor info queries (multiplexed oversingle WS connection)
Now that subscription volume is reduced by 75%, we could consider moving
ExchangeClientto WebSocket transport (see follow-up investigation).Note
Medium Risk
Changes core perps WebSocket subscription selection and lifecycle; incorrect allowlist parsing or promise-dedup logic could lead to missing updates or stale subscriptions.
Overview
Reduces HyperLiquid perps WebSocket subscription volume by filtering mainnet HIP-3 DEX discovery based on
allowlistMarkets(via newMAINNET_HIP3_CONFIGandextractDexsFromAllowlist()), defaulting to main DEX-only when no HIP-3 DEXs are allowlisted.Prevents duplicate
clearinghouseState/openOrderssubscriptions by deduplicating concurrent subscription attempts with per-DEX pending promise tracking, and fixes a candle chart race where unmounting before async subscription resolution could leak a WS subscription by awaiting/unsubscribing the pending promise during cleanup.Written by Cursor Bugbot for commit 1ec82f9. This will update automatically on new commits. Configure here.
Co-authored-by: Claude noreply@anthropic.com 7a0c2a1