@metamask-previews/perps-controller 22.1.0-preview-543acb9 → 22.1.0-preview-3a0f39d23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - Bump `uuid` from `^14.0.0` to `^14.0.1` ([#11179](https://github.com/MetaMask/core/pull/11179))
13
+
14
+ ### Fixed
15
+
16
+ - Prevent HyperLiquid `userFills` from going silent after a WebSocket reconnect when an in-flight subscribe races with `restoreSubscriptions` (same pending + generation pattern TWAP already uses) ([#11160](https://github.com/MetaMask/core/pull/11160))
17
+ - Without this, live fills can keep attaching to a dead pre-reconnect listener (MetaMask/metamask-extension#47287).
18
+
10
19
  ## [22.1.0]
11
20
 
12
21
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"HyperLiquidSubscriptionService.d.ts","sourceRoot":"","sources":["../../src/services/HyperLiquidSubscriptionService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"HyperLiquidSubscriptionService.d.ts","sourceRoot":"","sources":["../../src/services/HyperLiquidSubscriptionService.ts"],"names":[],"mappings":"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"}
@@ -92,6 +92,20 @@ export class HyperLiquidSubscriptionService {
92
92
  #pendingBboPromises = new Map();
93
93
  // Order fill subscriptions keyed by accountId (normalized: undefined -> 'default')
94
94
  #orderFillSubscriptions = new Map();
95
+ // In-flight userFills subscribe calls, so concurrent subscribers / reconnect
96
+ // share one socket open instead of racing two opens onto the same map key.
97
+ #pendingOrderFillSubscriptions = new Map();
98
+ // Bumped per account by teardown and reconnect so an in-flight open for
99
+ // that account discards its subscription instead of rehydrating a cleared
100
+ // map (same race TWAP already guards; without it live fills can stick to a
101
+ // dead pre-reconnect listener — MetaMask/metamask-extension#47287).
102
+ #orderFillSubscriptionGenerations = new Map();
103
+ #orderFillGeneration(normalizedAccountId) {
104
+ return this.#orderFillSubscriptionGenerations.get(normalizedAccountId) ?? 0;
105
+ }
106
+ #bumpOrderFillGeneration(normalizedAccountId) {
107
+ this.#orderFillSubscriptionGenerations.set(normalizedAccountId, this.#orderFillGeneration(normalizedAccountId) + 1);
108
+ }
95
109
  // TWAP subscriptions keyed by accountId (normalized: undefined -> 'default')
96
110
  #twapOrderSubscriptions = new Map();
97
111
  // In-flight TWAP subscribe calls, so concurrent subscribers share one socket
@@ -1954,6 +1968,10 @@ export class HyperLiquidSubscriptionService {
1954
1968
  });
1955
1969
  this.#orderFillSubscriptions.delete(normalizedAccountId);
1956
1970
  }
1971
+ // Invalidate any open still in flight for this account: without this
1972
+ // its continuation installs a live socket nobody is listening to.
1973
+ this.#bumpOrderFillGeneration(normalizedAccountId);
1974
+ this.#pendingOrderFillSubscriptions.delete(normalizedAccountId);
1957
1975
  }
1958
1976
  };
1959
1977
  }
@@ -1971,19 +1989,71 @@ export class HyperLiquidSubscriptionService {
1971
1989
  if (this.#orderFillSubscriptions.has(normalizedAccountId)) {
1972
1990
  return;
1973
1991
  }
1974
- const subscriptionClient = this.#clientService.getSubscriptionClient();
1992
+ // Another caller is already opening this account's socket. Await it so
1993
+ // subscribe + reconnect do not race two userFills opens onto one key.
1994
+ // The owner of the pending open logs its failure, so swallow it here and
1995
+ // fall through to open our own. Re-read after each wait: several waiters
1996
+ // resume together and only the first should start the replacement open.
1997
+ let pending = this.#pendingOrderFillSubscriptions.get(normalizedAccountId);
1998
+ while (pending) {
1999
+ await pending.catch(() => undefined);
2000
+ if (this.#orderFillSubscriptions.has(normalizedAccountId)) {
2001
+ return;
2002
+ }
2003
+ pending = this.#pendingOrderFillSubscriptions.get(normalizedAccountId);
2004
+ }
2005
+ // A discarded open resolves without registering; if the last subscriber
2006
+ // left meanwhile, opening now would cache frames nobody is listening to.
2007
+ const subscribers = this.#orderFillSubscribers.get(normalizedAccountId);
2008
+ if (!subscribers || subscribers.size === 0) {
2009
+ return;
2010
+ }
2011
+ // Captured once here, spanning the whole open including the client
2012
+ // handshake. Capturing inside the open would let a bump that lands during
2013
+ // the handshake be adopted as the baseline and silently forgotten.
2014
+ const startGeneration = this.#orderFillGeneration(normalizedAccountId);
2015
+ const subscriptionPromise = this.#createOrderFillSubscription(normalizedAccountId, accountId, startGeneration);
2016
+ this.#pendingOrderFillSubscriptions.set(normalizedAccountId, subscriptionPromise);
2017
+ try {
2018
+ await subscriptionPromise;
2019
+ }
2020
+ finally {
2021
+ // Delete by identity: a reconnect may have cleared the map and installed
2022
+ // a newer open behind this one, which must not be evicted here.
2023
+ if (this.#pendingOrderFillSubscriptions.get(normalizedAccountId) ===
2024
+ subscriptionPromise) {
2025
+ this.#pendingOrderFillSubscriptions.delete(normalizedAccountId);
2026
+ }
2027
+ }
2028
+ }
2029
+ /**
2030
+ * Open one userFills subscription and register it for teardown.
2031
+ *
2032
+ * @param normalizedAccountId - Map key for this account ('default' when unset).
2033
+ * @param accountId - Optional CAIP account ID to resolve the address from.
2034
+ * @param startGeneration - This account's generation when the open began.
2035
+ * @returns A promise that resolves once the subscription is registered.
2036
+ */
2037
+ async #createOrderFillSubscription(normalizedAccountId, accountId, startGeneration) {
2038
+ // Bootstrap the client inline rather than recursing: a recursive call
2039
+ // would re-read the generation after the handshake and adopt a bump that
2040
+ // landed during it as its own baseline.
2041
+ let subscriptionClient = this.#clientService.getSubscriptionClient();
1975
2042
  if (!subscriptionClient) {
1976
2043
  await this.#clientService.ensureSubscriptionClient(this.#walletService.createWalletAdapter());
1977
- const client = this.#clientService.getSubscriptionClient();
1978
- if (!client) {
2044
+ subscriptionClient = this.#clientService.getSubscriptionClient();
2045
+ if (!subscriptionClient) {
1979
2046
  throw new Error('SubscriptionClient not available');
1980
2047
  }
1981
- await this.#ensureOrderFillISubscription(accountId);
1982
- return;
1983
2048
  }
1984
2049
  const userAddress = await this.#walletService.getUserAddressWithDefault(accountId);
1985
2050
  // userFills returns a Promise<ISubscription>, need to await it
1986
2051
  const subscription = await subscriptionClient.userFills({ user: userAddress }, (data) => {
2052
+ // A frame queued before an unsubscribe or reconnect must not mutate
2053
+ // the replacement's cache or reach its subscribers.
2054
+ if (startGeneration !== this.#orderFillGeneration(normalizedAccountId)) {
2055
+ return;
2056
+ }
1987
2057
  // Build a Map for O(1) lookup instead of O(n) find per fill
1988
2058
  const orderMap = new Map();
1989
2059
  if (this.#cachedOrders) {
@@ -2046,6 +2116,17 @@ export class HyperLiquidSubscriptionService {
2046
2116
  });
2047
2117
  }
2048
2118
  });
2119
+ // Teardown, clearAll, or a reconnect may have run while this open was in
2120
+ // flight. Rehydrating the map here would leave a socket with no
2121
+ // subscribers, or clobber the live replacement — so drop ours instead.
2122
+ const subscribers = this.#orderFillSubscribers.get(normalizedAccountId);
2123
+ if (startGeneration !== this.#orderFillGeneration(normalizedAccountId) ||
2124
+ this.#orderFillSubscriptions.has(normalizedAccountId) ||
2125
+ !subscribers ||
2126
+ subscribers.size === 0) {
2127
+ subscription.unsubscribe().catch(() => undefined);
2128
+ return;
2129
+ }
2049
2130
  this.#orderFillSubscriptions.set(normalizedAccountId, subscription);
2050
2131
  }
2051
2132
  /**
@@ -3543,8 +3624,19 @@ export class HyperLiquidSubscriptionService {
3543
3624
  }
3544
3625
  // Re-establish order fill subscriptions if there are fill subscribers
3545
3626
  if (this.#orderFillSubscribers.size > 0) {
3546
- // Clear existing subscription references (they're dead after reconnection)
3627
+ // Existing subscription references are dead after reconnection. Bump
3628
+ // each account's generation first so a pre-reconnect open still in
3629
+ // flight discards itself instead of occupying the map ahead of its
3630
+ // replacement (MetaMask/metamask-extension#47287).
3631
+ for (const normalizedAccountId of new Set([
3632
+ ...this.#orderFillSubscribers.keys(),
3633
+ ...this.#orderFillSubscriptions.keys(),
3634
+ ...this.#pendingOrderFillSubscriptions.keys(),
3635
+ ])) {
3636
+ this.#bumpOrderFillGeneration(normalizedAccountId);
3637
+ }
3547
3638
  this.#orderFillSubscriptions.clear();
3639
+ this.#pendingOrderFillSubscriptions.clear();
3548
3640
  // Re-establish subscriptions for all accountIds with subscribers
3549
3641
  // Note: normalizedAccountId is 'default' for undefined, need to convert back
3550
3642
  const normalizedAccountIds = Array.from(this.#orderFillSubscribers.keys());
@@ -3716,6 +3808,15 @@ export class HyperLiquidSubscriptionService {
3716
3808
  ])) {
3717
3809
  this.#bumpTwapOrderGeneration(normalizedAccountId);
3718
3810
  }
3811
+ // Same for order fills: bump before subscriber keys are cleared so an
3812
+ // in-flight userFills open cannot rehydrate after clearAll.
3813
+ for (const normalizedAccountId of new Set([
3814
+ ...this.#orderFillSubscribers.keys(),
3815
+ ...this.#orderFillSubscriptions.keys(),
3816
+ ...this.#pendingOrderFillSubscriptions.keys(),
3817
+ ])) {
3818
+ this.#bumpOrderFillGeneration(normalizedAccountId);
3819
+ }
3719
3820
  // Clear all local subscriber collections
3720
3821
  this.#priceSubscribers.clear();
3721
3822
  this.#positionSubscribers.clear();
@@ -3726,13 +3827,14 @@ export class HyperLiquidSubscriptionService {
3726
3827
  this.#accountSubscribers.clear();
3727
3828
  this.#marketDataSubscribers.clear();
3728
3829
  this.#orderBookSubscribers.clear();
3729
- // Clear order fill subscriptions
3830
+ // Clear order fill subscriptions; generations were bumped above.
3730
3831
  this.#orderFillSubscriptions.forEach((subscription) => {
3731
3832
  subscription.unsubscribe().catch(() => {
3732
3833
  // Ignore errors during cleanup
3733
3834
  });
3734
3835
  });
3735
3836
  this.#orderFillSubscriptions.clear();
3837
+ this.#pendingOrderFillSubscriptions.clear();
3736
3838
  // Clear TWAP subscriptions; generations were bumped above, before the
3737
3839
  // subscriber keys naming those accounts were cleared.
3738
3840
  this.#cachedTwapOrders.clear();