Repository navigation
fix(perps-controller): keep watchlist stars across overlapping hydrations and failed writes - #10670
Merged
Merged
Conversation
…ions and failed writes Queue AUS hydration behind earlier watchlist writes and record toggles from the moment it is queued, so a re-initialization started by toggleTestnet or switchProvider no longer overwrites a star that has not been written yet. When a watchlist write fails, undo only that toggle instead of restoring the list from before it, which discarded hydrated markets and later toggles.
…me market superseded Track the latest pending toggle per network and market, so a failed write is undone only when no later toggle of that market will sync its state. Move the stateful AUS store tests beside the undelegated-AUS ones and clarify the changelog.
Cover every write of a toggle burst failing, the same market toggled on both networks, and a failed toggle after a completed burst. Reword the changelog's re-initialization trade-off.
aganglada
approved these changes
Oct 5, 2026
Naz-Ovh
pushed a commit
to 0x-fork/metamask-core
that referenced
this pull request
Oct 7, 2026
## Explanation Releases `@metamask/perps-controller` **19.0.0 → 20.0.0**. The monorepo version goes **1314.0.0 → 1315.0.0**. No other package is being published. Perps-controller has no in-monorepo dependents that need a workspace range bump. The bump is **major**. Public unions and Lighter order validation change: - **BREAKING:** Lighter `placeOrder` / `validateOrder` accept supported native attached TP/SL instead of refusing all attachments. Gate forwarding on `attachedTpsl`. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** Grouped Lighter signer calls require grouping/count `1/2` or `2/2` with two orders, or `3/3` with three orders. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** `ScaleOrderChild.state` adds `canceled`. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** `DirectProviderOrderCapabilitiesUnavailableReason` and `OrderCapabilitiesUnavailableReason` add `order_market_unsupported`. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** Stray `triggerPrice` on Lighter basic market/limit orders is refused with `ORDER_TRIGGER_PRICE_NOT_SUPPORTED`. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** Lighter position TP/SL replacement and removal preserve independent partial triggers. ([MetaMask#10638](MetaMask#10638)) - **BREAKING:** `OrderFill.pnl` is optional when the venue omits realized PnL. Treat missing as unknown, not zero. ([MetaMask#10605](MetaMask#10605)) Also ships Lighter Scale/Chase/TWAP probe work, fee quote attribution (`feeSource`, `metamaskFeeDiscountBips`), HyperLiquid agent/signing fixes, quieter optional-messenger failures, and watchlist hydration/write races. ## Changelog Moved Unreleased entries in `packages/perps-controller/CHANGELOG.md` under `[20.0.0]`. Removed Uncategorized monorepo release markers (1311–1314) that do not affect package consumers. Dropped a duplicate Chase probe summary already covered by more specific entries. ## References - Source PRs: [MetaMask#10638](MetaMask#10638), [MetaMask#10618](MetaMask#10618), [MetaMask#10605](MetaMask#10605), [MetaMask#10643](MetaMask#10643), [MetaMask#10650](MetaMask#10650), [MetaMask#10651](MetaMask#10651), [MetaMask#10665](MetaMask#10665), [MetaMask#10670](MetaMask#10670), [MetaMask#10683](MetaMask#10683) - No in-monorepo consumer packages to bump. Mobile and Extension exhaustive union matches need the new members on upgrade. Lighter attached TP/SL stays gated on `attachedTpsl` and client rollout. ## 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 - [x] 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) - [x] 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 Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
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
This is a follow-up to #10665. Two existing races could lose a watchlist star.
#performInitializationreplaced#ausQueuewith the AUS hydration instead of chaining onto it. Here is how a star got lost with full AUS:toggleTestnet()orswitchProvider()starts a second hydration at once. Its read doesn't include the star, because the star hasn't been written yet.The fix:
#ausQueue, so its read waits for earlier writes. Its edit list is registered when it is queued, so a toggle made while it waits is replayed onto the remote list too.No in-flight hydration can still hold the failed edit. Any hydration that recorded it was queued before the edit's write, so it has finished by the time the write fails.
With only the AUS read delegated, a hydration queued after a star still replaces it, as #10665 documents. A test pins this. Trade-off: re-initialization no longer replaces the AUS queue, so a hydration read that never settles now also holds later watchlist writes. Stars still apply locally, and init stays non-blocking.
References
Checklist
Note
Medium Risk
Changes serialized AUS watchlist sync and optimistic rollback in user-facing favorites; wrong ordering could still lose stars or block writes if hydration hangs, but scope is isolated to perps watchlist persistence.
Overview
Fixes watchlist favorite races when syncing with Authenticated User Storage (AUS), following #10665.
Overlapping hydrations: Re-init (
toggleTestnet,switchProvider) no longer replaces#ausQueuewith a new hydration. Hydration is chained so its read runs after pending toggle writes, and toggles made while hydration is queued are recorded and replayed on the remote list (not only in-flight reads).Failed AUS writes: On PUT failure, the controller reverts only the failed toggle (re-insert removed symbols at their prior index) instead of restoring a pre-toggle snapshot, so hydrated markets and other toggles are not wiped. A per-
network:symbolpending toggle counter skips rollback when a later toggle of the same market superseded the failed one.Changelog and state tests (stateful AUS store) document the trade-off: a hydration read that never settles can block later writes on the queue.
Reviewed by Cursor Bugbot for commit 5431555. Bugbot is set up for automated code reviews on this repo. Configure here.