Skip to content

test(perps-controller): cover userFills delivery over a shared SDK subscription - #11316

Open
javiergarciavera wants to merge 1 commit into
mainfrom
mmqa/perps-userfills-delivery-tests-809c
Open

javiergarciavera wants to merge 1 commit into
mainfrom
mmqa/perps-userfills-delivery-tests-809c

Conversation

@javiergarciavera

@javiergarciavera javiergarciavera commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

Since the extension moved to @metamask/perps-controller@22.1.1 (which includes #11160), perps-tpsl.spec.ts still fails intermittently on Firefox. The symptom matches MetaMask/metamask-extension#47287: after a mocked userFills push, Perps Activity shows "No transactions yet". This PR checks whether HyperLiquidSubscriptionService can still drop a live userFills frame. It adds tests only and makes no runtime changes. In every scenario covered, core delivers the frame correctly.

The existing userFills tests use a separate fake subscription per userFills() call. That fake can't show what happens when several opens share one server subscription, which is how @nktkas/hyperliquid actually behaves. The new describe block uses a fake that mirrors the SDK's WebSocketSubscriptionManager:

  • one server subscription per payload
  • every listener receives every frame on the channel
  • the server unsubscribe is sent only when the last listener leaves

Scenarios covered:

  • Restore on the same transport: restoreSubscriptions stops referencing the original ISubscription without unsubscribing it, so the first open's listener stays attached next to the replacement. Its generation is stale, so each frame reaches subscribers exactly once and nothing is lost. In production a reconnect replaces the transport, so the leftover listener dies with the old socket.
  • Restore onto a replacement transport: frames on the new transport are delivered, and frames on the old one are ignored.
  • Snapshot-shaped live frames (isSnapshot: true) after the initial snapshot, which is the shape the E2E mock pushes. Each push reaches subscribers as (fills, true), and the cache holds the fill without duplicating it.
  • Stale empty snapshot: when a stale listener receives an empty snapshot, the live fill stays cached. A snapshot from a real re-subscribe still replaces the cache, because that snapshot is the venue's full fill history.
  • Subscriber churn:
    • The last subscriber leaves and a new one arrives before the old teardown settles.
    • A new subscriber joins the in-flight open the previous subscriber abandoned.
    • A new subscriber arrives after the abandoned open has confirmed and torn itself down.

To check that the tests can actually catch a failure, I removed the frame generation guard. Three of the new tests failed, then passed again once the guard was restored.

CI logs from the failing runs (MetaMask/metamask-extension runs 41977292884 and 41979580026) also point away from core. Passing and failing attempts have identical perps WebSocket traffic:

  • one userFills subscribe, sent on the second connection
  • the mock's snapshot and follow-up replies
  • no unsubscribe, extra subscribe, or reconnect during the test

With stable wire traffic, core has nothing that would change the generation, so the frame callback stays live. The loss is therefore downstream, in the extension's PerpsStreamBridge or PerpsStreamManager.

References

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 (test-only, no consumer-facing change)
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
Open in Web Open in Cursor 

Note

Low Risk
Test-only additions to the perps-controller suite; no runtime or consumer-facing behavior changes.

Reviewed by Cursor Bugbot for commit 9bf9231. Bugbot is set up for automated code reviews on this repo. Configure here.

…bscription

Model the SDK's shared per-payload subscription (all listeners receive
every frame, server unsubscribe only when the last listener leaves) and
cover: restore on the transport that still holds the original listener,
restore onto a replacement transport, snapshot-shaped live frames after
the initial snapshot, stale empty snapshots, and subscriber churn while
an open or a teardown is still in flight.

Co-authored-by: javiergarciavera <javiergarciavera@users.noreply.github.com>
@javiergarciavera
javiergarciavera requested a review from a team as a code owner November 5, 2026 10:35

This branch was previously deployed

1 inactive deployment
default-branch — 9bf9231e Deployed Nov 5, 2026 by javiergarciavera via Determine whether this PR is a release PR #6547
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.

3 participants