Repository navigation
fix(perps-controller): guard userFills subscribe against reconnect race - #11160
Merged
Merged
Conversation
Pending + generation guards (same pattern as TWAP) so an in-flight userFills open that resolves after restoreSubscriptions cannot rehydrate a dead listener and leave live fills silent (extension #47287). Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…xfmt Co-authored-by: Cursor <cursoragent@cursor.com>
…er stale-frame drops Waiters on an in-flight userFills open now swallow its rejection and open their own, re-check the pending map so only one waiter reopens, and skip opening when no subscribers remain. Adds tests for frame delivery across reconnect, rejection fallthrough, last-subscriber teardown and clearAll while an open is in flight. Co-Authored-By: Claude <noreply@anthropic.com>
michalconsensys
previously approved these changes
Nov 2, 2026
…econnect-race # Conflicts: # packages/perps-controller/CHANGELOG.md
Main released 22.1.0 while this PR was open, so the merge left the entry inside the already-released section. Co-Authored-By: Claude <noreply@anthropic.com>
…econnect-race # Conflicts: # packages/perps-controller/CHANGELOG.md
michalconsensys
approved these changes
Nov 3, 2026
javiergarciavera
marked this pull request as ready for review
November 3, 2026 10:29
javiergarciavera
temporarily deployed
to
default-branch
November 3, 2026 10:30 — with
GitHub Actions
Inactive
This was referenced Nov 3, 2026
Merged
Naz-Ovh
pushed a commit
to 0x-fork/metamask-core
that referenced
this pull request
Nov 4, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Explanation Releases `@metamask/perps-controller@22.1.1` (patch) so the fix from MetaMask#11160 can be consumed by `metamask-extension`. This version migrates the package's existing `Unreleased` entries into `22.1.1`: ### Changed - Bump `uuid` from `^14.0.0` to `^14.0.1` (MetaMask#11179) ### Fixed - Prevent HyperLiquid `userFills` from going silent after a WebSocket reconnect when an in-flight subscribe races with `restoreSubscriptions` (MetaMask#11160) ## Notes - Dependencies of `perps-controller` that have unreleased changes (`@metamask/base-controller`, `@metamask/controller-utils`, `@metamask/messenger`, `@metamask/utils`) were intentionally skipped — `22.1.1` does not rely on new versions of them. - `@metamask/transaction-pay-middleware`'s dependency range on `perps-controller` was bumped to `^22.1.1` automatically by the release tooling (its own version is unchanged). - Dropped two non-consumer-facing CI/test commits (MetaMask#11261, MetaMask#11199) that the tooling had added under `Uncategorized`, per the release changelog guidelines. - `yarn changelog:validate` passes for `@metamask/perps-controller`. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] 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_AGENT_PR_BODY_END --> [Slack Thread](https://consensys.slack.com/archives/D0AD7VDJ3U0/p1782396245326519?thread_ts=1782396245.326519&cid=D0AD7VDJ3U0) <div><a href="https://cursor.com/agents/bc-bc8c3875-2395-5248-9807-fdb8636d9cc3?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-bc8c3875-2395-5248-9807-fdb8636d9cc3&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Version and changelog-only release PR with dependency range updates; functional changes were already merged in prior PRs. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e397b02. 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: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Amélie <ameliejyc@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
n3ps
pushed a commit
to n3ps/metamask-extension
that referenced
this pull request
Nov 4, 2026
) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## **Description** Bumps `@metamask/perps-controller` from `^20.1.0` to `^22.1.1` so the extension picks up [MetaMask/core#11160](MetaMask/core#11160), which prevents HyperLiquid `userFills` from going silent after a WebSocket reconnect when an in-flight subscribe races with `restoreSubscriptions`. That fix was released in core release `1384.0.0`. Notes on the bump: - This spans two majors (`21.0.0`, `22.0.0`). `@metamask/[email protected]` was already resolved transitively via `@metamask/transaction-pay-middleware`, so the version bump itself compiles with no changes; the UI adaptations below (ported from the earlier, now-closed [MetaMask#47337](MetaMask#47337)) keep perps behavior correct on 22.x. - **UI changes (ported from MetaMask#47337):** - `ui/components/app/perps/hooks/usePerpsNetworkManagement.ts`: on testnet, ensure the Arbitrum **Sepolia** network (where HyperLiquid testnet deposits now settle USDC2 after the 22.0.0 change) instead of always Arbitrum One, gated on `selectPerpsIsTestnet`. - `ui/hooks/perps/usePerpsOrderFees.ts`: treat the new `'grant'` `PerpsFeeSource` as a fee-reducing source alongside `rewards`/`subscription`. - `ui/pages/perps/perps-withdraw-page.test.tsx`: update the testnet USDC asset-id mock to USDC2. - Plus colocated test updates for the hooks above. - `yarn.lock` now dedupes the direct dependency and `transaction-pay-middleware`'s `^22.0.0` range onto a single `22.1.1`, and perps-controller now pulls `uuid@^14` (already present in the tree). - LavaMoat policies were regenerated (`yarn lavamoat:auto`): the duplicate `transaction-pay-middleware>@metamask/perps-controller` entries collapse into the shared `@metamask/perps-controller`, and `@metamask/perps-controller>uuid` moves from `crypto` to the finer-grained `crypto.getRandomValues` / `crypto.randomUUID` that `uuid@14` uses. - `yarn lint:tsc` passes and the affected perps unit suites (72 tests across the three touched files) pass. ## **Changelog** CHANGELOG entry: Fixed HyperLiquid live fills going silent after a WebSocket reconnect by updating `@metamask/perps-controller` to `22.1.1`. ## **Related issues** Related to: MetaMask#47287 ## **Manual testing steps** 1. Build the extension and open a HyperLiquid perps market with an open position. 2. Place or close an order so fills are streaming on the position/activity view. 3. Force a WebSocket reconnect (e.g. drop/restore the network connection) while a subscribe is in flight. 4. Verify live fills keep updating after the reconnect instead of going silent. 5. In perps testnet mode, start a deposit and verify the preflight adds/uses the Arbitrum Sepolia network (not Arbitrum One) so the testnet deposit succeeds. <!-- ## **Screenshots/Recordings** ### **Before** ### **After** --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/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. <!-- CURSOR_AGENT_PR_BODY_END --> [Slack Thread](https://consensys.slack.com/archives/D0AD7VDJ3U0/p1782396245326519?thread_ts=1782396245.326519&cid=D0AD7VDJ3U0) <div><a href="https://cursor.com/agents/bc-cb6458b0-6c9e-54df-a32c-5b5c5c0131bb?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-cb6458b0-6c9e-54df-a32c-5b5c5c0131bb&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes deposit network preflight and fee UI for testnet/grant paths, plus dependency and LavaMoat policy updates around crypto globals used by perps flows. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a3f83e6. 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: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Amélie <ameliejyc@users.noreply.github.com>
LabinatorSolutions
pushed a commit
to LabinatorSolutions/metamask-extension
that referenced
this pull request
Nov 6, 2026
…taMask#47643) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Opening Perps Activity force-refreshes transaction history, but that refresh only cleared the UI-side coalesce cache. `perpsGetOrderFills`, `perpsGetOrders` and `perpsGetFunding` still reached `PerpsController` without `forceRefresh`, so the controller's 60s REST coalesce cache could return a result from before a just-closed trade. The trade then only appeared if the live `userFills` stream delivered it. That is why the TP/SL E2E specs fail intermittently on Firefox after the `@metamask/perps-controller@22.1.1` bump. 1. **Reason for the change:** Activity must not show a result cached before the fill it is meant to show. 2. **Solution:** `refetch()` (used by `forceFreshOnMount` on Activity and by pull-to-refresh) now passes `{ forceRefresh: true }` to the three REST reads. Passive fetches, such as the Recent activity preview on Perps home, keep the controller cache. The E2E flow `assertPerpsActivityShowsCloseFill` no longer pushes `userFills` a second time, because the forced REST read now returns the fill from the mock. Related: [MetaMask#47600](MetaMask#47600), [MetaMask/core#11160](MetaMask/core#11160). ## **Changelog** CHANGELOG entry: Fixed a bug where a just-closed Perps trade could be missing from Perps Activity (This helps the Release Engineer do their job more quickly and accurately) ## **Related issues** Fixes: ## **Manual testing steps** 1. Run the extension with `PERPS_ENABLED=true` and sign in with an account that has an open ETH long position. 2. Open the ETH position and set a take-profit at a price the market will reach, or a stop-loss that will trigger. 3. Wait for the position to close, then return to the Perps home. 4. Under "Recent activity", select "See all". 5. Verify the "Closed long" trade row for ETH appears in Perps Activity. 6. Open Perps Activity a second time, then use pull-to-refresh if available, and verify the row is still shown. ## **Screenshots/Recordings** <!-- ## **Screenshots/Recordings** ### **Before** ### **After** --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes Perps transaction history fetch semantics and HyperLiquid REST traffic on Activity open/refetch; behavior is covered by new unit tests and a simplified E2E flow. > > **Overview** > Fixes **missing just-closed trades on Perps Activity** when the UI cleared its own coalesce cache but **`PerpsController` still served 60s-cached** `perpsGetOrderFills` / orders / funding reads (often after the home “Recent activity” preview had already fetched). > > **`usePerpsTransactionHistory`** now treats **`refetch()`** (pull-to-refresh and **`forceFreshOnMount`**) as a true refresh: background calls include **`{ forceRefresh: true }`**, and the UI coalescer uses **`replaceInFlight`** so a forced read does not join an in-flight passive request that would omit `forceRefresh`. Passive previews keep cache-friendly reads. > > **`coalesceBackgroundRequest`** gains an optional **`replaceInFlight`** flag (with tests). The E2E close-fill flow drops the extra **`pushUserFills()`** retry now that the forced REST path is expected to return the fill from the mock. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit b2641ab. 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: Cursor <cursoragent@cursor.com>
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Explanation
After a HyperLiquid WebSocket reconnect,
restoreSubscriptionsclears and re-opensuserFills. An in-flight subscribe that started before reconnect can still resolve afterward and write a live socket into the map that no one is listening to — or overwrite the post-reconnect subscription. Live fills then go silent until something else re-subscribes (e.g. lock/unlock).This mirrors the existing TWAP pending + generation guards for order fills: share one in-flight open per account, bump a generation on teardown/reconnect/
clearAll, and discard stale opens / frames when the generation no longer matches.Verified locally in the extension by patching
@metamask/perps-controller@20.1.0with this change and runningperps-tpslE2E without the lock/unlock workaround — both TP and SL cases passed.References
Perps Take Profit / Stop Loss simulates...metamask-extension#47165, test: fix flakiness inPerps Take Profit / Stop Loss simulates take-profit fillmetamask-extension#47278Checklist
Note
Medium Risk
Changes live WebSocket subscription lifecycle for HyperLiquid order fills (reconnect/teardown races); behavior is well-tested but affects real-time trading UI data paths.
Overview
Fixes HyperLiquid
userFillsgoing silent after a WebSocket reconnect when an in-flight subscribe races withrestoreSubscriptions— stale sockets could land in the map or overwrite the live post-reconnect subscription, so live fills never reached listeners (MetaMask/metamask-extension#47287).Order-fill subscription setup now mirrors the existing TWAP guards: one shared in-flight open per account (
#pendingOrderFillSubscriptions), a per-account generation counter bumped on unsubscribe teardown, reconnect restore, andclearAll, plus a dedicated#createOrderFillSubscriptionpath that drops stale opens (unsubscribe + skip map registration) and ignores frames when the generation no longer matches.Reconnect and full teardown also clear pending order-fill promises and bump generations for every account touched by subscribers, active subs, or pending opens. Tests cover reconnect re-subscribe, stale-open discard, frame routing after reconnect, pending rejection, subscriber churn, and
clearAll.Reviewed by Cursor Bugbot for commit f7c85bc. Bugbot is set up for automated code reviews on this repo. Configure here.