@metamask-previews/perps-controller 11.0.0-preview-9efd0b754 → 11.0.0-preview-ea8763e0c

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/PerpsController-method-action-types.cjs.map +1 -1
  3. package/dist/PerpsController-method-action-types.d.cts +12 -1
  4. package/dist/PerpsController-method-action-types.d.cts.map +1 -1
  5. package/dist/PerpsController-method-action-types.d.mts +12 -1
  6. package/dist/PerpsController-method-action-types.d.mts.map +1 -1
  7. package/dist/PerpsController-method-action-types.mjs.map +1 -1
  8. package/dist/PerpsController.cjs +519 -99
  9. package/dist/PerpsController.cjs.map +1 -1
  10. package/dist/PerpsController.d.cts +17 -4
  11. package/dist/PerpsController.d.cts.map +1 -1
  12. package/dist/PerpsController.d.mts +17 -4
  13. package/dist/PerpsController.d.mts.map +1 -1
  14. package/dist/PerpsController.mjs +522 -102
  15. package/dist/PerpsController.mjs.map +1 -1
  16. package/dist/constants/eventNames.cjs +3 -0
  17. package/dist/constants/eventNames.cjs.map +1 -1
  18. package/dist/constants/eventNames.d.cts +1 -0
  19. package/dist/constants/eventNames.d.cts.map +1 -1
  20. package/dist/constants/eventNames.d.mts +1 -0
  21. package/dist/constants/eventNames.d.mts.map +1 -1
  22. package/dist/constants/eventNames.mjs +3 -0
  23. package/dist/constants/eventNames.mjs.map +1 -1
  24. package/dist/constants/hyperLiquidConfig.cjs +14 -2
  25. package/dist/constants/hyperLiquidConfig.cjs.map +1 -1
  26. package/dist/constants/hyperLiquidConfig.d.cts +7 -0
  27. package/dist/constants/hyperLiquidConfig.d.cts.map +1 -1
  28. package/dist/constants/hyperLiquidConfig.d.mts +7 -0
  29. package/dist/constants/hyperLiquidConfig.d.mts.map +1 -1
  30. package/dist/constants/hyperLiquidConfig.mjs +11 -0
  31. package/dist/constants/hyperLiquidConfig.mjs.map +1 -1
  32. package/dist/constants/perpsConfig.cjs +4 -1
  33. package/dist/constants/perpsConfig.cjs.map +1 -1
  34. package/dist/constants/perpsConfig.d.cts +25 -7
  35. package/dist/constants/perpsConfig.d.cts.map +1 -1
  36. package/dist/constants/perpsConfig.d.mts +25 -7
  37. package/dist/constants/perpsConfig.d.mts.map +1 -1
  38. package/dist/constants/perpsConfig.mjs +4 -1
  39. package/dist/constants/perpsConfig.mjs.map +1 -1
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +3 -3
  42. package/dist/index.d.cts.map +1 -1
  43. package/dist/index.d.mts +3 -3
  44. package/dist/index.d.mts.map +1 -1
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/providers/HyperLiquidProvider.cjs +148 -1
  47. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  48. package/dist/providers/HyperLiquidProvider.d.cts +11 -1
  49. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  50. package/dist/providers/HyperLiquidProvider.d.mts +11 -1
  51. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  52. package/dist/providers/HyperLiquidProvider.mjs +149 -2
  53. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  54. package/dist/services/HyperLiquidSubscriptionService.cjs +15 -4
  55. package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
  56. package/dist/services/HyperLiquidSubscriptionService.d.cts +1 -1
  57. package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
  58. package/dist/services/HyperLiquidSubscriptionService.d.mts +1 -1
  59. package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
  60. package/dist/services/HyperLiquidSubscriptionService.mjs +15 -4
  61. package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
  62. package/dist/services/MarketDataService.cjs +41 -3
  63. package/dist/services/MarketDataService.cjs.map +1 -1
  64. package/dist/services/MarketDataService.d.cts +2 -2
  65. package/dist/services/MarketDataService.d.cts.map +1 -1
  66. package/dist/services/MarketDataService.d.mts +2 -2
  67. package/dist/services/MarketDataService.d.mts.map +1 -1
  68. package/dist/services/MarketDataService.mjs +41 -3
  69. package/dist/services/MarketDataService.mjs.map +1 -1
  70. package/dist/services/ServiceContext.cjs.map +1 -1
  71. package/dist/services/ServiceContext.d.cts +10 -1
  72. package/dist/services/ServiceContext.d.cts.map +1 -1
  73. package/dist/services/ServiceContext.d.mts +10 -1
  74. package/dist/services/ServiceContext.d.mts.map +1 -1
  75. package/dist/services/ServiceContext.mjs.map +1 -1
  76. package/dist/services/TerminalMarketService.cjs +422 -7
  77. package/dist/services/TerminalMarketService.cjs.map +1 -1
  78. package/dist/services/TerminalMarketService.d.cts +10 -1
  79. package/dist/services/TerminalMarketService.d.cts.map +1 -1
  80. package/dist/services/TerminalMarketService.d.mts +10 -1
  81. package/dist/services/TerminalMarketService.d.mts.map +1 -1
  82. package/dist/services/TerminalMarketService.mjs +423 -8
  83. package/dist/services/TerminalMarketService.mjs.map +1 -1
  84. package/dist/types/index.cjs.map +1 -1
  85. package/dist/types/index.d.cts +43 -7
  86. package/dist/types/index.d.cts.map +1 -1
  87. package/dist/types/index.d.mts +43 -7
  88. package/dist/types/index.d.mts.map +1 -1
  89. package/dist/types/index.mjs.map +1 -1
  90. package/dist/utils/marketUtils.cjs +16 -1
  91. package/dist/utils/marketUtils.cjs.map +1 -1
  92. package/dist/utils/marketUtils.d.cts +1 -0
  93. package/dist/utils/marketUtils.d.cts.map +1 -1
  94. package/dist/utils/marketUtils.d.mts +1 -0
  95. package/dist/utils/marketUtils.d.mts.map +1 -1
  96. package/dist/utils/marketUtils.mjs +14 -0
  97. package/dist/utils/marketUtils.mjs.map +1 -1
  98. package/dist/utils/perpsDiskPersistence.cjs +19 -12
  99. package/dist/utils/perpsDiskPersistence.cjs.map +1 -1
  100. package/dist/utils/perpsDiskPersistence.d.cts +5 -1
  101. package/dist/utils/perpsDiskPersistence.d.cts.map +1 -1
  102. package/dist/utils/perpsDiskPersistence.d.mts +5 -1
  103. package/dist/utils/perpsDiskPersistence.d.mts.map +1 -1
  104. package/dist/utils/perpsDiskPersistence.mjs +19 -12
  105. package/dist/utils/perpsDiskPersistence.mjs.map +1 -1
  106. package/package.json +1 -1
@@ -9,15 +9,15 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _PerpsController_instances, _a, _PerpsController_initializationPromise, _PerpsController_isReinitializing, _PerpsController_myxRegistrationPromise, _PerpsController_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_priceDeviationLimit, _PerpsController_attributionContext, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_handlersRegistered, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_eligibilityCheckDeferred, _PerpsController_ausQueue, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_getAggregatedCacheProviderIds, _PerpsController_getOrCreateStandaloneProvider, _PerpsController_cleanupStandaloneProvider, _PerpsController_getMetrics, _PerpsController_findNetworkClientIdForChain, _PerpsController_submitTransaction, _PerpsController_migrateRequestsIfNeeded, _PerpsController_withStreamPause, _PerpsController_performInitialization, _PerpsController_createProviders, _PerpsController_assignActiveProvider, _PerpsController_getErrorContext, _PerpsController_getControllerState, _PerpsController_buildMarketAllowedFilter, _PerpsController_compilePatternsSafely, _PerpsController_createServiceContext, _PerpsController_ensureTradingServiceDeps, _PerpsController_preloadWatchedPaths, _PerpsController_preloadTimer, _PerpsController_isPreloading, _PerpsController_isPreloadingUserData, _PerpsController_preloadStateUnsubscribe, _PerpsController_accountChangeUnsubscribe, _PerpsController_previousIsTestnet, _PerpsController_previousHip3ConfigVersion, _PerpsController_preloadRefreshMs, _PerpsController_preloadGuardMs, _PerpsController_hydrateCacheFromDiskSync, _PerpsController_performMarketDataPreload, _PerpsController_performUserDataPreload, _PerpsController_persistWatchlistToRemote, _PerpsController_syncWatchlistFromRemote;
12
+ var _PerpsController_instances, _a, _PerpsController_initializationPromise, _PerpsController_isReinitializing, _PerpsController_myxRegistrationPromise, _PerpsController_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_priceDeviationLimit, _PerpsController_attributionContext, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_handlersRegistered, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_standaloneProviderOperations, _PerpsController_eligibilityCheckDeferred, _PerpsController_ausQueue, _PerpsController_userDiskWrite, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_getAggregatedCacheProviderIds, _PerpsController_isMarketCacheEntryCurrent, _PerpsController_isUserCacheIdentityCurrent, _PerpsController_fetchAndCacheUserDataSnapshot, _PerpsController_getOrCreateStandaloneProvider, _PerpsController_trackStandaloneProviderOperation, _PerpsController_retireStandaloneProvider, _PerpsController_cleanupStandaloneProvider, _PerpsController_getMetrics, _PerpsController_findNetworkClientIdForChain, _PerpsController_submitTransaction, _PerpsController_migrateRequestsIfNeeded, _PerpsController_withStreamPause, _PerpsController_performInitialization, _PerpsController_createProviders, _PerpsController_assignActiveProvider, _PerpsController_getErrorContext, _PerpsController_getControllerState, _PerpsController_buildMarketAllowedFilter, _PerpsController_compilePatternsSafely, _PerpsController_createServiceContext, _PerpsController_ensureTradingServiceDeps, _PerpsController_buildGlobalSnapshotContext, _PerpsController_getStaticSnapshotDexes, _PerpsController_preloadWatchedPaths, _PerpsController_preloadTimer, _PerpsController_isPreloading, _PerpsController_marketPreloadQueued, _PerpsController_isPreloadingUserData, _PerpsController_userPreloadQueued, _PerpsController_userSnapshotRequests, _PerpsController_preloadStateUnsubscribe, _PerpsController_accountChangeUnsubscribe, _PerpsController_previousIsTestnet, _PerpsController_previousHip3ConfigVersion, _PerpsController_preloadRefreshMs, _PerpsController_preloadGuardMs, _PerpsController_hydrateCacheFromDiskSync, _PerpsController_persistUserCacheToDisk, _PerpsController_performMarketDataPreload, _PerpsController_performUserDataPreload, _PerpsController_persistWatchlistToRemote, _PerpsController_syncWatchlistFromRemote;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { ORIGIN_METAMASK } from "@metamask/controller-utils";
15
15
  import { v4 as uuidv4 } from "uuid";
16
16
  import { CandlePeriod } from "./constants/chartConfig.mjs";
17
17
  import { PERPS_EVENT_PROPERTY, PERPS_EVENT_VALUE } from "./constants/eventNames.mjs";
18
- import { USDC_SYMBOL } from "./constants/hyperLiquidConfig.mjs";
18
+ import { canonicalizeHyperLiquidDexes, MAINNET_HIP3_CONFIG, TESTNET_HIP3_CONFIG, USDC_SYMBOL } from "./constants/hyperLiquidConfig.mjs";
19
19
  import { PerpsMeasurementName } from "./constants/performanceMetrics.mjs";
20
- import { PERPS_CONSTANTS, MARKET_SORTING_CONFIG, PROVIDER_CONFIG, PERPS_DISK_CACHE_USER_DATA, buildProviderCacheKey, MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES } from "./constants/perpsConfig.mjs";
20
+ import { PERPS_CONSTANTS, MARKET_SORTING_CONFIG, PROVIDER_CONFIG, buildProviderCacheKey, MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES } from "./constants/perpsConfig.mjs";
21
21
  import { PERPS_ERROR_CODES } from "./perpsErrorCodes.mjs";
22
22
  import { AggregatedPerpsProvider } from "./providers/AggregatedPerpsProvider.mjs";
23
23
  import { HyperLiquidProvider } from "./providers/HyperLiquidProvider.mjs";
@@ -35,9 +35,39 @@ import { WebSocketConnectionState, PerpsAnalyticsEvent, PerpsTraceNames, PerpsTr
35
35
  import { getSelectedEvmAccountFromMessenger } from "./utils/accountUtils.mjs";
36
36
  import { ensureError } from "./utils/errorUtils.mjs";
37
37
  import { parseAssetName } from "./utils/hyperLiquidAdapter.mjs";
38
- import { compileMarketPattern, shouldIncludeMarket } from "./utils/marketUtils.mjs";
38
+ import { clonePerpsMarketData, compileMarketPattern, shouldIncludeMarket } from "./utils/marketUtils.mjs";
39
39
  import { hydrateFromDiskSync, persistMarketEntriesToDisk, persistUserEntriesToDisk } from "./utils/perpsDiskPersistence.mjs";
40
40
  import { wait } from "./utils/wait.mjs";
41
+ function cloneUserDataSnapshot(snapshot) {
42
+ return {
43
+ positions: snapshot.positions.map((position) => ({
44
+ ...position,
45
+ leverage: { ...position.leverage },
46
+ cumulativeFunding: { ...position.cumulativeFunding },
47
+ ...(position.takeProfitOrders && {
48
+ takeProfitOrders: position.takeProfitOrders.map((order) => ({
49
+ ...order,
50
+ })),
51
+ }),
52
+ ...(position.stopLossOrders && {
53
+ stopLossOrders: position.stopLossOrders.map((order) => ({
54
+ ...order,
55
+ })),
56
+ }),
57
+ })),
58
+ orders: snapshot.orders.map((order) => ({ ...order })),
59
+ accountState: {
60
+ ...snapshot.accountState,
61
+ ...(snapshot.accountState.subAccountBreakdown && {
62
+ subAccountBreakdown: Object.fromEntries(Object.entries(snapshot.accountState.subAccountBreakdown).map(([dex, balances]) => [dex, { ...balances }])),
63
+ }),
64
+ },
65
+ identity: {
66
+ ...snapshot.identity,
67
+ dexes: [...snapshot.identity.dexes],
68
+ },
69
+ };
70
+ }
41
71
  /**
42
72
  * Returns the first non-empty string from the given values.
43
73
  * Env vars default to '' (not null/undefined), so ?? wouldn't fall through.
@@ -400,6 +430,7 @@ const MESSENGER_EXPOSED_METHODS = [
400
430
  'getBlockExplorerUrl',
401
431
  'getCachedMarketDataForActiveProvider',
402
432
  'getCachedUserDataForActiveProvider',
433
+ 'getUserDataSnapshot',
403
434
  'getCurrentNetwork',
404
435
  'getFunding',
405
436
  'getHistoricalPortfolio',
@@ -532,6 +563,7 @@ export class PerpsController extends BaseController {
532
563
  _PerpsController_handlersRegistered.set(this, false);
533
564
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
534
565
  _PerpsController_standaloneProviderHip3Version.set(this, null);
566
+ _PerpsController_standaloneProviderOperations.set(this, new Map());
535
567
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
536
568
  /**
537
569
  * Serial promise queue for all AUS watchlist operations (hydration and
@@ -547,6 +579,7 @@ export class PerpsController extends BaseController {
547
579
  * a failed operation does not stall subsequent ones.
548
580
  */
549
581
  _PerpsController_ausQueue.set(this, Promise.resolve());
582
+ _PerpsController_userDiskWrite.set(this, Promise.resolve());
550
583
  // Store options for dependency injection (allows core package to inject platform-specific services)
551
584
  _PerpsController_options.set(this, void 0);
552
585
  // Service instances (instantiated with platform dependencies)
@@ -560,7 +593,10 @@ export class PerpsController extends BaseController {
560
593
  _PerpsController_rewardsIntegrationService.set(this, void 0);
561
594
  _PerpsController_preloadTimer.set(this, null);
562
595
  _PerpsController_isPreloading.set(this, false);
596
+ _PerpsController_marketPreloadQueued.set(this, false);
563
597
  _PerpsController_isPreloadingUserData.set(this, false);
598
+ _PerpsController_userPreloadQueued.set(this, false);
599
+ _PerpsController_userSnapshotRequests.set(this, new Map());
564
600
  _PerpsController_preloadStateUnsubscribe.set(this, null);
565
601
  _PerpsController_accountChangeUnsubscribe.set(this, null);
566
602
  _PerpsController_previousIsTestnet.set(this, null);
@@ -579,7 +615,9 @@ export class PerpsController extends BaseController {
579
615
  __classPrivateFieldSet(this, _PerpsController_marketDataService, new MarketDataService({
580
616
  ...infrastructure,
581
617
  terminalMarketService: infrastructure.terminalMarketService ??
582
- (infrastructure.terminalApiUrl
618
+ (infrastructure.terminalApi?.marketDataUrl ||
619
+ infrastructure.terminalApiUrl ||
620
+ infrastructure.terminalApi?.globalSnapshotUrl
583
621
  ? new TerminalMarketService(infrastructure)
584
622
  : undefined),
585
623
  }), "f");
@@ -646,24 +684,20 @@ export class PerpsController extends BaseController {
646
684
  if (activeProvider === 'aggregated') {
647
685
  // Assemble from all registered provider entries
648
686
  const assembled = [];
649
- let oldestTimestamp = Infinity;
650
687
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
651
688
  const key = buildProviderCacheKey(providerId, this.state.isTestnet);
652
689
  const entry = cache[key];
653
690
  if (!entry || entry.data.length === 0) {
654
691
  continue;
655
692
  }
656
- oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
657
- assembled.push(...entry.data);
693
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, providerId, entry, options)) {
694
+ continue;
695
+ }
696
+ assembled.push(...clonePerpsMarketData(entry.data));
658
697
  }
659
698
  if (assembled.length === 0) {
660
699
  return null;
661
700
  }
662
- // Check TTL against the oldest entry
663
- if (!options?.skipTTL &&
664
- Date.now() - oldestTimestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
665
- return null;
666
- }
667
701
  return assembled;
668
702
  }
669
703
  // Single provider mode
@@ -672,11 +706,10 @@ export class PerpsController extends BaseController {
672
706
  if (!entry || entry.data.length === 0) {
673
707
  return null;
674
708
  }
675
- if (!options?.skipTTL &&
676
- Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
709
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, activeProvider, entry, options)) {
677
710
  return null;
678
711
  }
679
- return entry.data;
712
+ return clonePerpsMarketData(entry.data);
680
713
  }
681
714
  /**
682
715
  * Read cached user data for the currently active provider (or aggregated).
@@ -700,18 +733,21 @@ export class PerpsController extends BaseController {
700
733
  currentAddress = evmAccount?.address ?? null;
701
734
  }
702
735
  catch {
703
- // Can't determine current account — trust the cache
736
+ // Account identity is required before account-scoped data can be trusted.
704
737
  }
738
+ if (!currentAddress) {
739
+ return null;
740
+ }
741
+ const selectedAddress = currentAddress;
705
742
  const skipTTL = options?.skipTTL ?? false;
706
- const isValidEntry = (entry) => {
743
+ const isValidEntry = (providerId, entry) => {
707
744
  if (!entry) {
708
745
  return false;
709
746
  }
710
747
  if (!skipTTL && Date.now() - entry.timestamp >= staleCutoff) {
711
748
  return false;
712
749
  }
713
- if (currentAddress &&
714
- entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
750
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, providerId, entry, selectedAddress)) {
715
751
  return false;
716
752
  }
717
753
  return true;
@@ -723,9 +759,9 @@ export class PerpsController extends BaseController {
723
759
  let defaultAccountState = null;
724
760
  let hasValidEntry = false;
725
761
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
726
- const key = buildProviderCacheKey(providerId, this.state.isTestnet);
727
- const entry = cache[key];
728
- if (!isValidEntry(entry)) {
762
+ const providerNetworkKey = buildProviderCacheKey(providerId, this.state.isTestnet);
763
+ const entry = cache[providerNetworkKey];
764
+ if (!isValidEntry(providerId, entry)) {
729
765
  continue;
730
766
  }
731
767
  hasValidEntry = true;
@@ -746,9 +782,9 @@ export class PerpsController extends BaseController {
746
782
  };
747
783
  }
748
784
  // Single provider mode
749
- const key = buildProviderCacheKey(activeProvider, this.state.isTestnet);
750
- const entry = cache[key];
751
- if (!entry || !isValidEntry(entry)) {
785
+ const providerNetworkKey = buildProviderCacheKey(activeProvider, this.state.isTestnet);
786
+ const entry = cache[providerNetworkKey];
787
+ if (!entry || !isValidEntry(activeProvider, entry)) {
752
788
  return null;
753
789
  }
754
790
  return {
@@ -757,6 +793,82 @@ export class PerpsController extends BaseController {
757
793
  accountState: entry.accountState,
758
794
  };
759
795
  }
796
+ /**
797
+ * Fetch, validate, and atomically cache a complete user-data snapshot.
798
+ * This remains callable after mount so consumers can seed their live channel
799
+ * from one coherent positions/orders/account result.
800
+ *
801
+ * @returns The accepted user-data snapshot.
802
+ */
803
+ async getUserDataSnapshot() {
804
+ const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
805
+ if (!evmAccount?.address) {
806
+ throw new Error('Cannot fetch user data snapshot without an EVM account');
807
+ }
808
+ if (this.state.activeProvider !== 'hyperliquid') {
809
+ throw new Error('User data snapshots require Hyperliquid provider mode');
810
+ }
811
+ const capturedActiveProvider = this.activeProviderInstance;
812
+ const standaloneProvider = capturedActiveProvider
813
+ ? null
814
+ : __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
815
+ const provider = capturedActiveProvider ?? standaloneProvider;
816
+ if (!provider) {
817
+ throw new Error('Cannot create standalone Hyperliquid provider');
818
+ }
819
+ const { address } = evmAccount;
820
+ const { isTestnet, hip3ConfigVersion } = this.state;
821
+ const network = isTestnet ? 'testnet' : 'mainnet';
822
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
823
+ if (!expectedDexes) {
824
+ throw new Error('User data snapshot DEX identity is not static');
825
+ }
826
+ const isCurrent = () => {
827
+ let currentAddress;
828
+ try {
829
+ currentAddress = getSelectedEvmAccountFromMessenger(this.messenger)?.address;
830
+ }
831
+ catch {
832
+ return false;
833
+ }
834
+ return (this.state.activeProvider === 'hyperliquid' &&
835
+ (!capturedActiveProvider ||
836
+ this.activeProviderInstance === capturedActiveProvider) &&
837
+ this.state.isTestnet === isTestnet &&
838
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
839
+ currentAddress?.toLowerCase() === address.toLowerCase());
840
+ };
841
+ const context = {
842
+ provider,
843
+ standaloneProvider,
844
+ address,
845
+ isTestnet,
846
+ hip3ConfigVersion,
847
+ expectedDexes,
848
+ isCurrent,
849
+ };
850
+ const requestKey = [
851
+ 'hyperliquid',
852
+ network,
853
+ address.toLowerCase(),
854
+ hip3ConfigVersion,
855
+ ...expectedDexes,
856
+ ].join('|');
857
+ const existingRequest = __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey);
858
+ if (existingRequest?.provider === provider) {
859
+ return existingRequest.promise;
860
+ }
861
+ const request = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_fetchAndCacheUserDataSnapshot).call(this, context);
862
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").set(requestKey, { provider, promise: request });
863
+ try {
864
+ return await request;
865
+ }
866
+ finally {
867
+ if (__classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey)?.promise === request) {
868
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").delete(requestKey);
869
+ }
870
+ }
871
+ }
760
872
  /**
761
873
  * Test-observable accessor for whether a standalone provider is cached.
762
874
  *
@@ -1532,7 +1644,10 @@ export class PerpsController extends BaseController {
1532
1644
  // Fallback to cached standalone provider for pre-initialization discovery
1533
1645
  // TODO: When adding new providers (MYX), consider a provider factory pattern
1534
1646
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1535
- return provider.getPositions(params);
1647
+ const operation = provider.getPositions(params);
1648
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1649
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1650
+ : operation;
1536
1651
  }
1537
1652
  const provider = this.getActiveProvider();
1538
1653
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getPositions({
@@ -1593,7 +1708,10 @@ export class PerpsController extends BaseController {
1593
1708
  // For standalone mode, access provider directly without initialization check
1594
1709
  if (params?.standalone && params.userAddress) {
1595
1710
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1596
- return provider.getOpenOrders(params);
1711
+ const operation = provider.getOpenOrders(params);
1712
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1713
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1714
+ : operation;
1597
1715
  }
1598
1716
  const provider = this.getActiveProvider();
1599
1717
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getOpenOrders({
@@ -1638,7 +1756,10 @@ export class PerpsController extends BaseController {
1638
1756
  // Use activeProviderInstance if available (respects provider abstraction)
1639
1757
  // Fallback to cached standalone provider for pre-initialization discovery
1640
1758
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1641
- return provider.getAccountState(params);
1759
+ const operation = provider.getAccountState(params);
1760
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1761
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1762
+ : operation;
1642
1763
  }
1643
1764
  const provider = this.getActiveProvider();
1644
1765
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getAccountState({
@@ -1676,12 +1797,15 @@ export class PerpsController extends BaseController {
1676
1797
  const isMarketAllowed = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this);
1677
1798
  if (params?.standalone) {
1678
1799
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1679
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1800
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1680
1801
  provider,
1681
1802
  params,
1682
1803
  context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarkets'),
1683
1804
  isMarketAllowed,
1684
1805
  });
1806
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1807
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1808
+ : operation;
1685
1809
  }
1686
1810
  const provider = this.getActiveProvider();
1687
1811
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
@@ -1707,19 +1831,26 @@ export class PerpsController extends BaseController {
1707
1831
  * @returns A promise that resolves to the market data.
1708
1832
  */
1709
1833
  async getMarketDataWithPrices(params) {
1834
+ const globalSnapshot = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildGlobalSnapshotContext).call(this);
1835
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices', {
1836
+ ...(globalSnapshot && { globalSnapshot }),
1837
+ });
1710
1838
  if (params?.standalone) {
1711
1839
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1712
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1840
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1713
1841
  provider,
1714
1842
  params,
1715
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1843
+ context,
1716
1844
  });
1845
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1846
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1847
+ : operation;
1717
1848
  }
1718
1849
  const provider = this.getActiveProvider();
1719
1850
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1720
1851
  provider,
1721
1852
  params,
1722
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1853
+ context,
1723
1854
  });
1724
1855
  }
1725
1856
  /**
@@ -1740,11 +1871,17 @@ export class PerpsController extends BaseController {
1740
1871
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1741
1872
  /* fire-and-forget */
1742
1873
  });
1874
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1875
+ /* fire-and-forget */
1876
+ });
1743
1877
  // Periodic refresh
1744
1878
  __classPrivateFieldSet(this, _PerpsController_preloadTimer, setInterval(() => {
1745
1879
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1746
1880
  /* fire-and-forget */
1747
1881
  });
1882
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1883
+ /* fire-and-forget */
1884
+ });
1748
1885
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1749
1886
  // Watch for isTestnet / hip3ConfigVersion changes
1750
1887
  const handler = (_state, patches) => {
@@ -1773,36 +1910,29 @@ export class PerpsController extends BaseController {
1773
1910
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1774
1911
  /* fire-and-forget */
1775
1912
  });
1913
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1914
+ /* fire-and-forget */
1915
+ });
1776
1916
  }
1777
1917
  };
1778
- this.messenger.subscribe('PerpsController:stateChange', handler);
1918
+ this.messenger.subscribe('PerpsController:stateChanged', handler);
1779
1919
  __classPrivateFieldSet(this, _PerpsController_preloadStateUnsubscribe, () => {
1780
- this.messenger.unsubscribe('PerpsController:stateChange', handler);
1920
+ this.messenger.unsubscribe('PerpsController:stateChanged', handler);
1781
1921
  }, "f");
1782
1922
  // Watch for selected account changes and selected account group changes.
1783
1923
  const accountChangeHandler = () => {
1784
1924
  const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
1785
1925
  const currentAddress = evmAccount?.address ?? null;
1786
- // If any cached entry belongs to a different account, clear all entries.
1787
- // Max 4 entries (2 providers × 2 networks) — clearing all is simple and safe.
1788
- const hasStaleEntries = Object.values(this.state.cachedUserDataByProvider).some((entry) => entry.address.toLowerCase() !== currentAddress?.toLowerCase());
1789
- if (hasStaleEntries) {
1790
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Account changed, clearing user data cache');
1791
- this.update((state) => {
1792
- state.cachedUserDataByProvider = {};
1793
- });
1794
- // Invalidate disk-cached user data for the old account
1795
- __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache
1796
- .removeItem(PERPS_DISK_CACHE_USER_DATA)
1797
- .catch(() => {
1926
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: account cache selection', {
1927
+ address: currentAddress?.toLowerCase() ?? null,
1928
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
1929
+ });
1930
+ // The address guard makes the previous entry unreadable immediately;
1931
+ // refresh replaces it under the existing provider/network key.
1932
+ if (currentAddress) {
1933
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1798
1934
  /* fire-and-forget */
1799
1935
  });
1800
- // Only preload if the new account is an EVM account
1801
- if (currentAddress) {
1802
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1803
- /* fire-and-forget */
1804
- });
1805
- }
1806
1936
  }
1807
1937
  };
1808
1938
  this.messenger.subscribe('AccountsController:selectedAccountChange', accountChangeHandler);
@@ -1831,6 +1961,8 @@ export class PerpsController extends BaseController {
1831
1961
  }
1832
1962
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, null, "f");
1833
1963
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, null, "f");
1964
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
1965
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
1834
1966
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this).catch(() => {
1835
1967
  /* fire-and-forget to preserve sync signature */
1836
1968
  });
@@ -2091,6 +2223,9 @@ export class PerpsController extends BaseController {
2091
2223
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2092
2224
  /* fire-and-forget */
2093
2225
  });
2226
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2227
+ /* fire-and-forget */
2228
+ });
2094
2229
  }
2095
2230
  }
2096
2231
  }
@@ -2200,6 +2335,9 @@ export class PerpsController extends BaseController {
2200
2335
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2201
2336
  /* fire-and-forget */
2202
2337
  });
2338
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2339
+ /* fire-and-forget */
2340
+ });
2203
2341
  }
2204
2342
  }
2205
2343
  }
@@ -3262,7 +3400,7 @@ export class PerpsController extends BaseController {
3262
3400
  return __classPrivateFieldGet(this, _PerpsController_isReinitializing, "f");
3263
3401
  }
3264
3402
  }
3265
- _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _PerpsController_isReinitializing = new WeakMap(), _PerpsController_myxRegistrationPromise = new WeakMap(), _PerpsController_blockedRegionListVersion = new WeakMap(), _PerpsController_hip3Enabled = new WeakMap(), _PerpsController_hip3AllowlistMarkets = new WeakMap(), _PerpsController_hip3BlocklistMarkets = new WeakMap(), _PerpsController_hip3ConfigSource = new WeakMap(), _PerpsController_priceDeviationLimit = new WeakMap(), _PerpsController_attributionContext = new WeakMap(), _PerpsController_standaloneProvider = new WeakMap(), _PerpsController_handlersRegistered = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_eligibilityCheckDeferred = new WeakMap(), _PerpsController_ausQueue = new WeakMap(), _PerpsController_options = new WeakMap(), _PerpsController_tradingService = new WeakMap(), _PerpsController_marketDataService = new WeakMap(), _PerpsController_accountService = new WeakMap(), _PerpsController_eligibilityService = new WeakMap(), _PerpsController_dataLakeService = new WeakMap(), _PerpsController_depositService = new WeakMap(), _PerpsController_featureFlagConfigurationService = new WeakMap(), _PerpsController_rewardsIntegrationService = new WeakMap(), _PerpsController_preloadTimer = new WeakMap(), _PerpsController_isPreloading = new WeakMap(), _PerpsController_isPreloadingUserData = new WeakMap(), _PerpsController_preloadStateUnsubscribe = new WeakMap(), _PerpsController_accountChangeUnsubscribe = new WeakMap(), _PerpsController_previousIsTestnet = new WeakMap(), _PerpsController_previousHip3ConfigVersion = new WeakMap(), _PerpsController_instances = new WeakSet(), _PerpsController_isMYXProviderEnabled = function _PerpsController_isMYXProviderEnabled() {
3403
+ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _PerpsController_isReinitializing = new WeakMap(), _PerpsController_myxRegistrationPromise = new WeakMap(), _PerpsController_blockedRegionListVersion = new WeakMap(), _PerpsController_hip3Enabled = new WeakMap(), _PerpsController_hip3AllowlistMarkets = new WeakMap(), _PerpsController_hip3BlocklistMarkets = new WeakMap(), _PerpsController_hip3ConfigSource = new WeakMap(), _PerpsController_priceDeviationLimit = new WeakMap(), _PerpsController_attributionContext = new WeakMap(), _PerpsController_standaloneProvider = new WeakMap(), _PerpsController_handlersRegistered = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_standaloneProviderOperations = new WeakMap(), _PerpsController_eligibilityCheckDeferred = new WeakMap(), _PerpsController_ausQueue = new WeakMap(), _PerpsController_userDiskWrite = new WeakMap(), _PerpsController_options = new WeakMap(), _PerpsController_tradingService = new WeakMap(), _PerpsController_marketDataService = new WeakMap(), _PerpsController_accountService = new WeakMap(), _PerpsController_eligibilityService = new WeakMap(), _PerpsController_dataLakeService = new WeakMap(), _PerpsController_depositService = new WeakMap(), _PerpsController_featureFlagConfigurationService = new WeakMap(), _PerpsController_rewardsIntegrationService = new WeakMap(), _PerpsController_preloadTimer = new WeakMap(), _PerpsController_isPreloading = new WeakMap(), _PerpsController_marketPreloadQueued = new WeakMap(), _PerpsController_isPreloadingUserData = new WeakMap(), _PerpsController_userPreloadQueued = new WeakMap(), _PerpsController_userSnapshotRequests = new WeakMap(), _PerpsController_preloadStateUnsubscribe = new WeakMap(), _PerpsController_accountChangeUnsubscribe = new WeakMap(), _PerpsController_previousIsTestnet = new WeakMap(), _PerpsController_previousHip3ConfigVersion = new WeakMap(), _PerpsController_instances = new WeakSet(), _PerpsController_isMYXProviderEnabled = function _PerpsController_isMYXProviderEnabled() {
3266
3404
  const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
3267
3405
  // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
3268
3406
  // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
@@ -3315,6 +3453,96 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3315
3453
  }
3316
3454
  }
3317
3455
  return Array.from(providerIds);
3456
+ }, _PerpsController_isMarketCacheEntryCurrent = function _PerpsController_isMarketCacheEntryCurrent(providerId, entry, options) {
3457
+ if (entry.sourceExpiresAt !== undefined) {
3458
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3459
+ return (providerId === 'hyperliquid' &&
3460
+ Date.now() < entry.sourceExpiresAt &&
3461
+ entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3462
+ expectedDexes !== undefined &&
3463
+ Array.isArray(entry.dexes) &&
3464
+ entry.dexes.length === expectedDexes.length &&
3465
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3466
+ }
3467
+ return (options?.skipTTL === true ||
3468
+ Date.now() - entry.timestamp <= __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10);
3469
+ }, _PerpsController_isUserCacheIdentityCurrent = function _PerpsController_isUserCacheIdentityCurrent(providerId, entry, address) {
3470
+ if (entry.address.toLowerCase() !== address.toLowerCase()) {
3471
+ return false;
3472
+ }
3473
+ if (providerId !== 'hyperliquid') {
3474
+ return true;
3475
+ }
3476
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3477
+ return (entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3478
+ expectedDexes !== undefined &&
3479
+ Array.isArray(entry.dexes) &&
3480
+ entry.dexes.length === expectedDexes.length &&
3481
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3482
+ }, _PerpsController_fetchAndCacheUserDataSnapshot = async function _PerpsController_fetchAndCacheUserDataSnapshot(context) {
3483
+ const { provider, standaloneProvider, address, isTestnet, hip3ConfigVersion, expectedDexes, isCurrent, } = context;
3484
+ if (!isCurrent()) {
3485
+ throw new Error('User data snapshot context changed');
3486
+ }
3487
+ if (!provider.getUserDataSnapshot) {
3488
+ throw new Error('Provider has no atomic snapshot API');
3489
+ }
3490
+ const identity = {
3491
+ provider: 'hyperliquid',
3492
+ network: isTestnet ? 'testnet' : 'mainnet',
3493
+ hip3ConfigVersion,
3494
+ dexes: expectedDexes,
3495
+ };
3496
+ const snapshotRequest = provider.getUserDataSnapshot({
3497
+ userAddress: address,
3498
+ identity,
3499
+ });
3500
+ const snapshot = standaloneProvider
3501
+ ? await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, standaloneProvider, snapshotRequest)
3502
+ : await snapshotRequest;
3503
+ if (!isCurrent()) {
3504
+ throw new Error('User data snapshot context changed');
3505
+ }
3506
+ const snapshotIdentity = snapshot.identity;
3507
+ const hasCompleteBundle = Array.isArray(snapshot.positions) &&
3508
+ Array.isArray(snapshot.orders) &&
3509
+ snapshot.accountState !== null &&
3510
+ typeof snapshot.accountState === 'object';
3511
+ const hasExactIdentity = snapshotIdentity.provider === identity.provider &&
3512
+ snapshotIdentity.network === identity.network &&
3513
+ snapshotIdentity.hip3ConfigVersion === identity.hip3ConfigVersion &&
3514
+ snapshotIdentity.address.toLowerCase() === address.toLowerCase() &&
3515
+ snapshotIdentity.dexes.length === expectedDexes.length &&
3516
+ snapshotIdentity.dexes.every((dex, index) => dex === expectedDexes[index]);
3517
+ if (!hasCompleteBundle || !hasExactIdentity) {
3518
+ throw new Error('User data snapshot is incomplete or mismatched');
3519
+ }
3520
+ if (!isCurrent()) {
3521
+ throw new Error('User data snapshot context changed');
3522
+ }
3523
+ const cachedSnapshot = cloneUserDataSnapshot(snapshot);
3524
+ const result = cloneUserDataSnapshot(snapshot);
3525
+ const timestamp = Date.now();
3526
+ const providerNetworkKey = buildProviderCacheKey('hyperliquid', isTestnet);
3527
+ this.update((state) => {
3528
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3529
+ positions: cachedSnapshot.positions,
3530
+ orders: cachedSnapshot.orders,
3531
+ accountState: cachedSnapshot.accountState,
3532
+ timestamp,
3533
+ address,
3534
+ hip3ConfigVersion,
3535
+ dexes: expectedDexes,
3536
+ };
3537
+ });
3538
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3539
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache snapshot written', {
3540
+ writtenKey: providerNetworkKey,
3541
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
3542
+ positionCount: cachedSnapshot.positions.length,
3543
+ orderCount: cachedSnapshot.orders.length,
3544
+ });
3545
+ return result;
3318
3546
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
3319
3547
  const currentIsTestnet = this.state.isTestnet;
3320
3548
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -3323,10 +3551,13 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3323
3551
  __classPrivateFieldGet(this, _PerpsController_standaloneProviderHip3Version, "f") === currentHip3Version) {
3324
3552
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3325
3553
  }
3326
- // Stale or missing — tear down old one (fire-and-forget)
3554
+ // Stale or missing — retire the old provider after active operations finish.
3327
3555
  if (__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3328
3556
  const old = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3329
- Promise.resolve(old.disconnect()).catch(() => {
3557
+ __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3558
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3559
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3560
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, old).catch(() => {
3330
3561
  /* best-effort */
3331
3562
  });
3332
3563
  }
@@ -3346,24 +3577,45 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3346
3577
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
3347
3578
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
3348
3579
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3580
+ }, _PerpsController_trackStandaloneProviderOperation = function _PerpsController_trackStandaloneProviderOperation(provider, operation) {
3581
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider) ?? new Set();
3582
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").set(provider, operations);
3583
+ const trackedOperation = operation.finally(() => {
3584
+ operations.delete(trackedOperation);
3585
+ if (operations.size === 0) {
3586
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3587
+ }
3588
+ });
3589
+ operations.add(trackedOperation);
3590
+ return trackedOperation;
3591
+ }, _PerpsController_retireStandaloneProvider = async function _PerpsController_retireStandaloneProvider(provider) {
3592
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider);
3593
+ if (operations?.size) {
3594
+ await Promise.allSettled([...operations]);
3595
+ }
3596
+ try {
3597
+ await provider.disconnect();
3598
+ }
3599
+ catch {
3600
+ /* best-effort */
3601
+ }
3602
+ finally {
3603
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3604
+ }
3349
3605
  }, _PerpsController_cleanupStandaloneProvider =
3350
3606
  /**
3351
3607
  * Disconnect and discard the cached standalone provider (if any).
3352
3608
  * Best-effort — errors are silently caught.
3353
3609
  */
3354
3610
  async function _PerpsController_cleanupStandaloneProvider() {
3355
- if (!__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3611
+ const provider = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3612
+ if (!provider) {
3356
3613
  return;
3357
3614
  }
3358
- try {
3359
- await __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f").disconnect();
3360
- }
3361
- catch {
3362
- /* best-effort */
3363
- }
3364
3615
  __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3365
3616
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3366
3617
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3618
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, provider);
3367
3619
  }, _PerpsController_getMetrics = function _PerpsController_getMetrics() {
3368
3620
  return __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.metrics;
3369
3621
  }, _PerpsController_findNetworkClientIdForChain = function _PerpsController_findNetworkClientIdForChain(chainId) {
@@ -3697,6 +3949,60 @@ async function _PerpsController_performInitialization() {
3697
3949
  __classPrivateFieldGet(this, _PerpsController_tradingService, "f").setControllerDependencies({
3698
3950
  rewardsIntegrationService: __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f"),
3699
3951
  });
3952
+ }, _PerpsController_buildGlobalSnapshotContext = function _PerpsController_buildGlobalSnapshotContext() {
3953
+ const snapshotConfigured = Boolean(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalApi?.globalSnapshotUrl) ||
3954
+ typeof __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalMarketService
3955
+ ?.fetchGlobalSnapshot === 'function';
3956
+ if (!snapshotConfigured || this.state.activeProvider !== 'hyperliquid') {
3957
+ return undefined;
3958
+ }
3959
+ const enabledDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3960
+ if (!enabledDexes) {
3961
+ return undefined;
3962
+ }
3963
+ const { isTestnet, hip3ConfigVersion } = this.state;
3964
+ return {
3965
+ request: {
3966
+ provider: 'hyperliquid',
3967
+ network: isTestnet ? 'testnet' : 'mainnet',
3968
+ enabledDexes,
3969
+ },
3970
+ isCurrent: () => this.state.activeProvider === 'hyperliquid' &&
3971
+ this.state.isTestnet === isTestnet &&
3972
+ this.state.hip3ConfigVersion === hip3ConfigVersion,
3973
+ isMarketAllowed: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this),
3974
+ };
3975
+ }, _PerpsController_getStaticSnapshotDexes = function _PerpsController_getStaticSnapshotDexes() {
3976
+ if (!__classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f")) {
3977
+ return ['main'];
3978
+ }
3979
+ if (this.state.isTestnet) {
3980
+ return TESTNET_HIP3_CONFIG.AutoDiscoverAll
3981
+ ? undefined
3982
+ : canonicalizeHyperLiquidDexes(TESTNET_HIP3_CONFIG.EnabledDexs);
3983
+ }
3984
+ if (MAINNET_HIP3_CONFIG.AutoDiscoverAll) {
3985
+ return undefined;
3986
+ }
3987
+ const dexes = new Set();
3988
+ for (const pattern of __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f")) {
3989
+ const colonIndex = pattern.indexOf(':');
3990
+ if (colonIndex <= 0) {
3991
+ if (/^[a-z][a-z0-9]*$/iu.test(pattern)) {
3992
+ dexes.add(pattern.toLowerCase());
3993
+ continue;
3994
+ }
3995
+ return undefined;
3996
+ }
3997
+ const dex = pattern.slice(0, colonIndex);
3998
+ if (dex && /^[a-z0-9][a-z0-9-]*$/u.test(dex)) {
3999
+ dexes.add(dex);
4000
+ }
4001
+ else {
4002
+ return undefined;
4003
+ }
4004
+ }
4005
+ return canonicalizeHyperLiquidDexes(dexes);
3700
4006
  }, _PerpsController_hydrateCacheFromDiskSync = function _PerpsController_hydrateCacheFromDiskSync() {
3701
4007
  const { marketUpdates, userUpdates, stats } = hydrateFromDiskSync(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, this.state.cachedMarketDataByProvider, this.state.cachedUserDataByProvider, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs));
3702
4008
  const hasMarketUpdates = Object.keys(marketUpdates).length > 0;
@@ -3717,12 +4023,40 @@ async function _PerpsController_performInitialization() {
3717
4023
  orders: stats.userOrders,
3718
4024
  duration_ms: stats.durationMs,
3719
4025
  });
4026
+ }, _PerpsController_persistUserCacheToDisk = function _PerpsController_persistUserCacheToDisk() {
4027
+ const entries = [];
4028
+ for (const [cacheKey, entry] of Object.entries(this.state.cachedUserDataByProvider)) {
4029
+ const [providerId, network] = cacheKey.split(':');
4030
+ if (!providerId ||
4031
+ (network !== 'mainnet' && network !== 'testnet') ||
4032
+ providerId === 'aggregated') {
4033
+ continue;
4034
+ }
4035
+ entries.push({
4036
+ providerNetworkKey: `${providerId}:${network}`,
4037
+ address: entry.address,
4038
+ positions: entry.positions,
4039
+ orders: entry.orders,
4040
+ accountState: entry.accountState,
4041
+ timestamp: entry.timestamp,
4042
+ ...(entry.hip3ConfigVersion !== undefined && {
4043
+ hip3ConfigVersion: entry.hip3ConfigVersion,
4044
+ }),
4045
+ ...(entry.dexes !== undefined && { dexes: entry.dexes }),
4046
+ });
4047
+ }
4048
+ __classPrivateFieldSet(this, _PerpsController_userDiskWrite, __classPrivateFieldGet(this, _PerpsController_userDiskWrite, "f")
4049
+ .then(() => persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, entries))
4050
+ .catch(() => {
4051
+ // Disk persistence is best-effort and must not block live data.
4052
+ }), "f");
3720
4053
  }, _PerpsController_performMarketDataPreload =
3721
4054
  /**
3722
4055
  * Perform a single market data preload (best-effort, no throw).
3723
4056
  */
3724
4057
  async function _PerpsController_performMarketDataPreload() {
3725
4058
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
4059
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, true, "f");
3726
4060
  return;
3727
4061
  }
3728
4062
  // Skip preloading during provider/network reinitialisation.
@@ -3737,9 +4071,16 @@ async function _PerpsController_performMarketDataPreload() {
3737
4071
  ? this.state.activeProvider // includes 'aggregated'
3738
4072
  : 'hyperliquid';
3739
4073
  const cacheKey = buildProviderCacheKey(actualProviderId, this.state.isTestnet);
4074
+ const preloadContext = {
4075
+ activeProvider: this.state.activeProvider,
4076
+ isTestnet: this.state.isTestnet,
4077
+ hip3ConfigVersion: this.state.hip3ConfigVersion,
4078
+ };
4079
+ const staticSnapshotDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3740
4080
  const now = Date.now();
3741
4081
  const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3742
4082
  if (existingEntry &&
4083
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, actualProviderId, existingEntry) &&
3743
4084
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3744
4085
  return;
3745
4086
  }
@@ -3764,8 +4105,32 @@ async function _PerpsController_performMarketDataPreload() {
3764
4105
  duration_ms: Math.round(performance.now() - preloadStart),
3765
4106
  markets: data.length,
3766
4107
  });
4108
+ if (this.state.activeProvider !== preloadContext.activeProvider ||
4109
+ this.state.isTestnet !== preloadContext.isTestnet ||
4110
+ this.state.hip3ConfigVersion !== preloadContext.hip3ConfigVersion) {
4111
+ traceData = {
4112
+ success: false,
4113
+ error: 'Global snapshot preload context changed',
4114
+ };
4115
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Discarding stale global snapshot preload');
4116
+ return;
4117
+ }
3767
4118
  // Store under per-provider key(s)
3768
4119
  const ts = Date.now();
4120
+ const sourceExpiries = data.flatMap((market) => market.dataSource === 'terminal-global-snapshot-mark' &&
4121
+ typeof market.sourceExpiresAt === 'number'
4122
+ ? [market.sourceExpiresAt]
4123
+ : []);
4124
+ const sourceExpiresAt = data.length > 0 && sourceExpiries.length === data.length
4125
+ ? Math.min(...sourceExpiries)
4126
+ : undefined;
4127
+ const snapshotCacheIdentity = sourceExpiresAt !== undefined && staticSnapshotDexes
4128
+ ? {
4129
+ sourceExpiresAt,
4130
+ hip3ConfigVersion: preloadContext.hip3ConfigVersion,
4131
+ dexes: staticSnapshotDexes,
4132
+ }
4133
+ : {};
3769
4134
  const marketDiskEntries = [];
3770
4135
  if (this.state.activeProvider === 'aggregated' &&
3771
4136
  this.activeProviderInstance) {
@@ -3812,6 +4177,7 @@ async function _PerpsController_performMarketDataPreload() {
3812
4177
  state.cachedMarketDataByProvider[cacheKey] = {
3813
4178
  data,
3814
4179
  timestamp: ts,
4180
+ ...snapshotCacheIdentity,
3815
4181
  };
3816
4182
  });
3817
4183
  }
@@ -3821,10 +4187,6 @@ async function _PerpsController_performMarketDataPreload() {
3821
4187
  });
3822
4188
  traceData = { success: true, marketCount: data.length };
3823
4189
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(PerpsMeasurementName.PerpsMarketDataPreload, performance.now() - preloadStart, 'millisecond');
3824
- // Also preload user data (fire-and-forget, non-blocking)
3825
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
3826
- /* fire-and-forget */
3827
- });
3828
4190
  }
3829
4191
  catch (error) {
3830
4192
  traceData = {
@@ -3843,6 +4205,12 @@ async function _PerpsController_performMarketDataPreload() {
3843
4205
  data: traceData,
3844
4206
  });
3845
4207
  __classPrivateFieldSet(this, _PerpsController_isPreloading, false, "f");
4208
+ if (__classPrivateFieldGet(this, _PerpsController_marketPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4209
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
4210
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
4211
+ // Background preload is best-effort.
4212
+ });
4213
+ }
3846
4214
  }
3847
4215
  }, _PerpsController_performUserDataPreload =
3848
4216
  /**
@@ -3851,6 +4219,10 @@ async function _PerpsController_performMarketDataPreload() {
3851
4219
  */
3852
4220
  async function _PerpsController_performUserDataPreload() {
3853
4221
  if (__classPrivateFieldGet(this, _PerpsController_isPreloadingUserData, "f")) {
4222
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, true, "f");
4223
+ return;
4224
+ }
4225
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3854
4226
  return;
3855
4227
  }
3856
4228
  // Get current user address
@@ -3859,21 +4231,54 @@ async function _PerpsController_performUserDataPreload() {
3859
4231
  return;
3860
4232
  }
3861
4233
  const userAddress = evmAccount.address;
4234
+ const { activeProvider, isTestnet, hip3ConfigVersion } = this.state;
4235
+ const { activeProviderInstance } = this;
4236
+ const hyperliquidDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4237
+ const isCurrent = () => {
4238
+ let currentAddress;
4239
+ try {
4240
+ currentAddress = getSelectedEvmAccountFromMessenger(this.messenger)?.address;
4241
+ }
4242
+ catch {
4243
+ return false;
4244
+ }
4245
+ return (this.state.activeProvider === activeProvider &&
4246
+ this.activeProviderInstance === activeProviderInstance &&
4247
+ this.state.isTestnet === isTestnet &&
4248
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
4249
+ currentAddress?.toLowerCase() === userAddress.toLowerCase());
4250
+ };
3862
4251
  // Determine actual provider (same logic as market preload)
3863
- const actualProviderId = this.activeProviderInstance
3864
- ? this.state.activeProvider // includes 'aggregated'
4252
+ const actualProviderId = activeProviderInstance
4253
+ ? activeProvider // includes 'aggregated'
3865
4254
  : 'hyperliquid';
3866
- const userCacheKey = buildProviderCacheKey(actualProviderId, this.state.isTestnet);
4255
+ const providerNetworkKey = buildProviderCacheKey(actualProviderId, isTestnet);
3867
4256
  // Skip if cache is fresh and for same account
3868
4257
  const now = Date.now();
3869
- const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3870
- if (existingEntry?.address === userAddress &&
4258
+ const existingEntry = this.state.cachedUserDataByProvider[providerNetworkKey];
4259
+ const hasMatchingCache = existingEntry !== undefined &&
4260
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, actualProviderId, existingEntry, userAddress);
4261
+ const cacheAgeMs = existingEntry ? now - existingEntry.timestamp : null;
4262
+ const websocketState = this.getWebSocketConnectionState();
4263
+ let selectedEntryKey = null;
4264
+ if (this.state.cachedUserDataByProvider[providerNetworkKey]) {
4265
+ selectedEntryKey = providerNetworkKey;
4266
+ }
4267
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache preload decision', {
4268
+ requestedKey: providerNetworkKey,
4269
+ selectedEntryKey,
4270
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
4271
+ hasMatchingCache,
4272
+ cacheAgeMs,
4273
+ websocketState,
4274
+ });
4275
+ if (existingEntry &&
4276
+ hasMatchingCache &&
3871
4277
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3872
4278
  return;
3873
4279
  }
3874
- // Skip standalone REST polling when WebSocket is connected — live data is streaming
3875
- if (this.getWebSocketConnectionState() === WebSocketConnectionState.Connected) {
3876
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Skipping user data preload — WebSocket connected');
4280
+ if (hasMatchingCache &&
4281
+ this.getWebSocketConnectionState() === WebSocketConnectionState.Connected) {
3877
4282
  return;
3878
4283
  }
3879
4284
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, true, "f");
@@ -3886,21 +4291,38 @@ async function _PerpsController_performUserDataPreload() {
3886
4291
  id: traceId,
3887
4292
  op: PerpsTraceOperations.Operation,
3888
4293
  tags: {
3889
- provider: this.state.activeProvider,
3890
- isTestnet: this.state.isTestnet,
4294
+ provider: activeProvider,
4295
+ isTestnet,
3891
4296
  },
3892
4297
  data: { userAddress },
3893
4298
  });
3894
4299
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching user data in background', {
3895
4300
  userAddress,
3896
4301
  });
4302
+ if (activeProvider === 'hyperliquid') {
4303
+ const snapshot = await this.getUserDataSnapshot();
4304
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
4305
+ positionCount: snapshot.positions.length,
4306
+ orderCount: snapshot.orders.length,
4307
+ totalBalance: snapshot.accountState.totalBalance,
4308
+ });
4309
+ traceData = {
4310
+ success: true,
4311
+ positionCount: snapshot.positions.length,
4312
+ orderCount: snapshot.orders.length,
4313
+ };
4314
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond');
4315
+ return;
4316
+ }
3897
4317
  const [positions, orders, accountState] = await Promise.all([
3898
4318
  this.getPositions({ standalone: true, userAddress }),
3899
4319
  this.getOpenOrders({ standalone: true, userAddress }),
3900
4320
  this.getAccountState({ standalone: true, userAddress }),
3901
4321
  ]);
3902
- if (this.state.activeProvider === 'aggregated' &&
3903
- this.activeProviderInstance) {
4322
+ if (!isCurrent()) {
4323
+ throw new Error('User data preload context changed');
4324
+ }
4325
+ if (activeProvider === 'aggregated' && activeProviderInstance) {
3904
4326
  // Split by providerId and write one cache entry per provider key
3905
4327
  // (mirrors the market-data preload pattern at ~line 2976)
3906
4328
  const ts = Date.now();
@@ -3922,26 +4344,22 @@ async function _PerpsController_performUserDataPreload() {
3922
4344
  }
3923
4345
  // AccountState — assign to its provider bucket
3924
4346
  ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3925
- const diskEntries = [];
3926
4347
  this.update((state) => {
3927
4348
  for (const [pid, data] of byProvider) {
3928
- const key = buildProviderCacheKey(pid, this.state.isTestnet);
3929
- diskEntries.push({
3930
- providerNetworkKey: key,
3931
- address: userAddress,
3932
- positions: data.positions,
3933
- orders: data.orders,
3934
- accountState: data.accountState,
3935
- timestamp: ts,
3936
- });
4349
+ const key = buildProviderCacheKey(pid, isTestnet);
3937
4350
  state.cachedUserDataByProvider[key] = {
3938
4351
  ...data,
3939
4352
  timestamp: ts,
3940
4353
  address: userAddress,
4354
+ ...(pid === 'hyperliquid' &&
4355
+ hyperliquidDexes && {
4356
+ hip3ConfigVersion,
4357
+ dexes: hyperliquidDexes,
4358
+ }),
3941
4359
  };
3942
4360
  }
3943
4361
  // Write aggregated sentinel so the staleness guard sees it
3944
- state.cachedUserDataByProvider[userCacheKey] = {
4362
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3945
4363
  positions: [],
3946
4364
  orders: [],
3947
4365
  accountState: null,
@@ -3949,30 +4367,26 @@ async function _PerpsController_performUserDataPreload() {
3949
4367
  address: userAddress,
3950
4368
  };
3951
4369
  });
3952
- persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, diskEntries);
4370
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3953
4371
  }
3954
4372
  else {
3955
4373
  // Single provider — store directly under its key
3956
4374
  const ts = Date.now();
3957
4375
  this.update((state) => {
3958
- state.cachedUserDataByProvider[userCacheKey] = {
4376
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3959
4377
  positions,
3960
4378
  orders,
3961
4379
  accountState,
3962
4380
  timestamp: ts,
3963
4381
  address: userAddress,
4382
+ ...(actualProviderId === 'hyperliquid' &&
4383
+ hyperliquidDexes && {
4384
+ hip3ConfigVersion,
4385
+ dexes: hyperliquidDexes,
4386
+ }),
3964
4387
  };
3965
4388
  });
3966
- persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, [
3967
- {
3968
- providerNetworkKey: userCacheKey,
3969
- address: userAddress,
3970
- positions,
3971
- orders,
3972
- accountState,
3973
- timestamp: ts,
3974
- },
3975
- ]);
4389
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3976
4390
  }
3977
4391
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3978
4392
  positionCount: positions.length,
@@ -4003,6 +4417,12 @@ async function _PerpsController_performUserDataPreload() {
4003
4417
  data: traceData,
4004
4418
  });
4005
4419
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, false, "f");
4420
+ if (__classPrivateFieldGet(this, _PerpsController_userPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4421
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
4422
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
4423
+ // Background preload is best-effort.
4424
+ });
4425
+ }
4006
4426
  }
4007
4427
  }, _PerpsController_persistWatchlistToRemote =
4008
4428
  /**