Skip to content

fix(perps-controller): keep watchlist stars across overlapping hydrations and failed writes - #10670

Merged
abretonc7s merged 5 commits into
mainfrom
fix/perps-watchlist-hydration-races
Oct 5, 2026
Merged

abretonc7s merged 5 commits into
mainfrom
fix/perps-watchlist-hydration-races

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

This is a follow-up to #10665. Two existing races could lose a watchlist star.

  • Overlapping hydrations: #performInitialization replaced #ausQueue with the AUS hydration instead of chaining onto it. Here is how a star got lost with full AUS:
    1. The user stars a market while hydration is in flight.
    2. toggleTestnet() or switchProvider() starts a second hydration at once. Its read doesn't include the star, because the star hasn't been written yet.
    3. That read overwrites local state, and the star's queued write then saves the list without it.
  • Failed write: a rejected PUT restored the list from before the toggle. That discarded markets hydrated since then and later toggles, so a later star could be lost locally and remotely.

The fix:

  • Hydration now runs through #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.
  • A failed write undoes only its own toggle. A removed market goes back to its position. The undo is skipped when a later toggle of the same market is pending, because that toggle's own sync decides the market's state.

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

  • 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
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

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 #ausQueue with 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:symbol pending 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.

…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.
@abretonc7s
abretonc7s requested a review from a team as a code owner October 3, 2026 13:22
@abretonc7s
abretonc7s deployed to default-branch October 3, 2026 13:22 — with GitHub Actions Active
@abretonc7s
abretonc7s requested a review from a team as a code owner October 3, 2026 13:22
…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.
@abretonc7s
abretonc7s added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit c8e3447 Oct 5, 2026
45 checks passed
@abretonc7s
abretonc7s deleted the fix/perps-watchlist-hydration-races branch October 5, 2026 10:13
@abretonc7s abretonc7s mentioned this pull request Oct 6, 2026
4 tasks done
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>
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.

2 participants