Skip to content

chore(runway): cherry-pick fix(perps): reduce WebSocket subscription overhead and prevent leaks cp-7.63.0 cp-7.64.0#25503

Merged
joaoloureirop merged 1 commit intorelease/7.63.0from
runway-cherry-pick-7.63.0-1770025877
Feb 2, 2026
Merged

chore(runway): cherry-pick fix(perps): reduce WebSocket subscription overhead and prevent leaks cp-7.63.0 cp-7.64.0#25503
joaoloureirop merged 1 commit intorelease/7.63.0from
runway-cherry-pick-7.63.0-1770025877

Conversation

@runway-github
Copy link
Copy Markdown
Contributor

@runway-github runway-github bot commented Feb 2, 2026

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

Manual testing steps

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

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).


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 new MAINNET_HIP3_CONFIG and extractDexsFromAllowlist()), defaulting to main DEX-only when no HIP-3 DEXs are allowlisted.

Prevents duplicate clearinghouseState/openOrders subscriptions 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

…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>
@runway-github runway-github bot requested a review from a team as a code owner February 2, 2026 09:51
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 2, 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.

@metamaskbot metamaskbot added the team-bots Bot team (for MetaMask Bot, Runway Bot, etc.) label Feb 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 2, 2026

🔍 Smart E2E Test Selection

⏭️ Smart E2E selection skipped - base branch is not main (base: release/7.63.0)

All E2E tests pre-selected.

View GitHub Actions results

@github-actions github-actions bot added the size-M label Feb 2, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

if (colonIndex > 0) {
// Has colon - extract DEX prefix
const dex = pattern.substring(0, colonIndex);
dexNames.add(dex);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Feb 2, 2026

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@aganglada aganglada added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label Feb 2, 2026
@joaoloureirop joaoloureirop merged commit 7b1fcba into release/7.63.0 Feb 2, 2026
173 of 179 checks passed
@joaoloureirop joaoloureirop deleted the runway-cherry-pick-7.63.0-1770025877 branch February 2, 2026 14:47
@github-actions github-actions bot locked and limited conversation to collaborators Feb 2, 2026
@metamaskbot metamaskbot added the release-7.63.0 Issue or pull request that will be included in release 7.63.0 label Feb 6, 2026
@metamaskbot
Copy link
Copy Markdown
Collaborator

No release label on PR. Adding release label release-7.63.0 on PR, as PR was cherry-picked in branch 7.63.0.

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

Labels

release-7.63.0 Issue or pull request that will be included in release 7.63.0 size-M skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-bots Bot team (for MetaMask Bot, Runway Bot, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants