@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
@@ -10,7 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
10
10
  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");
11
11
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
12
  };
13
- 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;
13
+ 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;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.PerpsController = exports.getDefaultPerpsControllerState = exports.DEFAULT_PRO_LAYOUT_PREFERENCES = exports.DEFAULT_PERPS_MODE = exports.PerpsMode = exports.InitializationState = exports.PERPS_ERROR_CODES = exports.resolveMyxAuthConfig = exports.resolveWatchlistExchangeKey = exports.firstNonEmpty = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
@@ -41,6 +41,36 @@ const hyperLiquidAdapter_js_1 = require("./utils/hyperLiquidAdapter.cjs");
41
41
  const marketUtils_js_1 = require("./utils/marketUtils.cjs");
42
42
  const perpsDiskPersistence_js_1 = require("./utils/perpsDiskPersistence.cjs");
43
43
  const wait_js_1 = require("./utils/wait.cjs");
44
+ function cloneUserDataSnapshot(snapshot) {
45
+ return {
46
+ positions: snapshot.positions.map((position) => ({
47
+ ...position,
48
+ leverage: { ...position.leverage },
49
+ cumulativeFunding: { ...position.cumulativeFunding },
50
+ ...(position.takeProfitOrders && {
51
+ takeProfitOrders: position.takeProfitOrders.map((order) => ({
52
+ ...order,
53
+ })),
54
+ }),
55
+ ...(position.stopLossOrders && {
56
+ stopLossOrders: position.stopLossOrders.map((order) => ({
57
+ ...order,
58
+ })),
59
+ }),
60
+ })),
61
+ orders: snapshot.orders.map((order) => ({ ...order })),
62
+ accountState: {
63
+ ...snapshot.accountState,
64
+ ...(snapshot.accountState.subAccountBreakdown && {
65
+ subAccountBreakdown: Object.fromEntries(Object.entries(snapshot.accountState.subAccountBreakdown).map(([dex, balances]) => [dex, { ...balances }])),
66
+ }),
67
+ },
68
+ identity: {
69
+ ...snapshot.identity,
70
+ dexes: [...snapshot.identity.dexes],
71
+ },
72
+ };
73
+ }
44
74
  /**
45
75
  * Returns the first non-empty string from the given values.
46
76
  * Env vars default to '' (not null/undefined), so ?? wouldn't fall through.
@@ -411,6 +441,7 @@ const MESSENGER_EXPOSED_METHODS = [
411
441
  'getBlockExplorerUrl',
412
442
  'getCachedMarketDataForActiveProvider',
413
443
  'getCachedUserDataForActiveProvider',
444
+ 'getUserDataSnapshot',
414
445
  'getCurrentNetwork',
415
446
  'getFunding',
416
447
  'getHistoricalPortfolio',
@@ -543,6 +574,7 @@ class PerpsController extends base_controller_1.BaseController {
543
574
  _PerpsController_handlersRegistered.set(this, false);
544
575
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
545
576
  _PerpsController_standaloneProviderHip3Version.set(this, null);
577
+ _PerpsController_standaloneProviderOperations.set(this, new Map());
546
578
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
547
579
  /**
548
580
  * Serial promise queue for all AUS watchlist operations (hydration and
@@ -558,6 +590,7 @@ class PerpsController extends base_controller_1.BaseController {
558
590
  * a failed operation does not stall subsequent ones.
559
591
  */
560
592
  _PerpsController_ausQueue.set(this, Promise.resolve());
593
+ _PerpsController_userDiskWrite.set(this, Promise.resolve());
561
594
  // Store options for dependency injection (allows core package to inject platform-specific services)
562
595
  _PerpsController_options.set(this, void 0);
563
596
  // Service instances (instantiated with platform dependencies)
@@ -571,7 +604,10 @@ class PerpsController extends base_controller_1.BaseController {
571
604
  _PerpsController_rewardsIntegrationService.set(this, void 0);
572
605
  _PerpsController_preloadTimer.set(this, null);
573
606
  _PerpsController_isPreloading.set(this, false);
607
+ _PerpsController_marketPreloadQueued.set(this, false);
574
608
  _PerpsController_isPreloadingUserData.set(this, false);
609
+ _PerpsController_userPreloadQueued.set(this, false);
610
+ _PerpsController_userSnapshotRequests.set(this, new Map());
575
611
  _PerpsController_preloadStateUnsubscribe.set(this, null);
576
612
  _PerpsController_accountChangeUnsubscribe.set(this, null);
577
613
  _PerpsController_previousIsTestnet.set(this, null);
@@ -590,7 +626,9 @@ class PerpsController extends base_controller_1.BaseController {
590
626
  __classPrivateFieldSet(this, _PerpsController_marketDataService, new MarketDataService_js_1.MarketDataService({
591
627
  ...infrastructure,
592
628
  terminalMarketService: infrastructure.terminalMarketService ??
593
- (infrastructure.terminalApiUrl
629
+ (infrastructure.terminalApi?.marketDataUrl ||
630
+ infrastructure.terminalApiUrl ||
631
+ infrastructure.terminalApi?.globalSnapshotUrl
594
632
  ? new TerminalMarketService_js_1.TerminalMarketService(infrastructure)
595
633
  : undefined),
596
634
  }), "f");
@@ -657,24 +695,20 @@ class PerpsController extends base_controller_1.BaseController {
657
695
  if (activeProvider === 'aggregated') {
658
696
  // Assemble from all registered provider entries
659
697
  const assembled = [];
660
- let oldestTimestamp = Infinity;
661
698
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
662
699
  const key = (0, perpsConfig_js_1.buildProviderCacheKey)(providerId, this.state.isTestnet);
663
700
  const entry = cache[key];
664
701
  if (!entry || entry.data.length === 0) {
665
702
  continue;
666
703
  }
667
- oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
668
- assembled.push(...entry.data);
704
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, providerId, entry, options)) {
705
+ continue;
706
+ }
707
+ assembled.push(...(0, marketUtils_js_1.clonePerpsMarketData)(entry.data));
669
708
  }
670
709
  if (assembled.length === 0) {
671
710
  return null;
672
711
  }
673
- // Check TTL against the oldest entry
674
- if (!options?.skipTTL &&
675
- Date.now() - oldestTimestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
676
- return null;
677
- }
678
712
  return assembled;
679
713
  }
680
714
  // Single provider mode
@@ -683,11 +717,10 @@ class PerpsController extends base_controller_1.BaseController {
683
717
  if (!entry || entry.data.length === 0) {
684
718
  return null;
685
719
  }
686
- if (!options?.skipTTL &&
687
- Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
720
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, activeProvider, entry, options)) {
688
721
  return null;
689
722
  }
690
- return entry.data;
723
+ return (0, marketUtils_js_1.clonePerpsMarketData)(entry.data);
691
724
  }
692
725
  /**
693
726
  * Read cached user data for the currently active provider (or aggregated).
@@ -711,18 +744,21 @@ class PerpsController extends base_controller_1.BaseController {
711
744
  currentAddress = evmAccount?.address ?? null;
712
745
  }
713
746
  catch {
714
- // Can't determine current account — trust the cache
747
+ // Account identity is required before account-scoped data can be trusted.
715
748
  }
749
+ if (!currentAddress) {
750
+ return null;
751
+ }
752
+ const selectedAddress = currentAddress;
716
753
  const skipTTL = options?.skipTTL ?? false;
717
- const isValidEntry = (entry) => {
754
+ const isValidEntry = (providerId, entry) => {
718
755
  if (!entry) {
719
756
  return false;
720
757
  }
721
758
  if (!skipTTL && Date.now() - entry.timestamp >= staleCutoff) {
722
759
  return false;
723
760
  }
724
- if (currentAddress &&
725
- entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
761
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, providerId, entry, selectedAddress)) {
726
762
  return false;
727
763
  }
728
764
  return true;
@@ -734,9 +770,9 @@ class PerpsController extends base_controller_1.BaseController {
734
770
  let defaultAccountState = null;
735
771
  let hasValidEntry = false;
736
772
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
737
- const key = (0, perpsConfig_js_1.buildProviderCacheKey)(providerId, this.state.isTestnet);
738
- const entry = cache[key];
739
- if (!isValidEntry(entry)) {
773
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(providerId, this.state.isTestnet);
774
+ const entry = cache[providerNetworkKey];
775
+ if (!isValidEntry(providerId, entry)) {
740
776
  continue;
741
777
  }
742
778
  hasValidEntry = true;
@@ -757,9 +793,9 @@ class PerpsController extends base_controller_1.BaseController {
757
793
  };
758
794
  }
759
795
  // Single provider mode
760
- const key = (0, perpsConfig_js_1.buildProviderCacheKey)(activeProvider, this.state.isTestnet);
761
- const entry = cache[key];
762
- if (!entry || !isValidEntry(entry)) {
796
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(activeProvider, this.state.isTestnet);
797
+ const entry = cache[providerNetworkKey];
798
+ if (!entry || !isValidEntry(activeProvider, entry)) {
763
799
  return null;
764
800
  }
765
801
  return {
@@ -768,6 +804,82 @@ class PerpsController extends base_controller_1.BaseController {
768
804
  accountState: entry.accountState,
769
805
  };
770
806
  }
807
+ /**
808
+ * Fetch, validate, and atomically cache a complete user-data snapshot.
809
+ * This remains callable after mount so consumers can seed their live channel
810
+ * from one coherent positions/orders/account result.
811
+ *
812
+ * @returns The accepted user-data snapshot.
813
+ */
814
+ async getUserDataSnapshot() {
815
+ const evmAccount = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger);
816
+ if (!evmAccount?.address) {
817
+ throw new Error('Cannot fetch user data snapshot without an EVM account');
818
+ }
819
+ if (this.state.activeProvider !== 'hyperliquid') {
820
+ throw new Error('User data snapshots require Hyperliquid provider mode');
821
+ }
822
+ const capturedActiveProvider = this.activeProviderInstance;
823
+ const standaloneProvider = capturedActiveProvider
824
+ ? null
825
+ : __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
826
+ const provider = capturedActiveProvider ?? standaloneProvider;
827
+ if (!provider) {
828
+ throw new Error('Cannot create standalone Hyperliquid provider');
829
+ }
830
+ const { address } = evmAccount;
831
+ const { isTestnet, hip3ConfigVersion } = this.state;
832
+ const network = isTestnet ? 'testnet' : 'mainnet';
833
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
834
+ if (!expectedDexes) {
835
+ throw new Error('User data snapshot DEX identity is not static');
836
+ }
837
+ const isCurrent = () => {
838
+ let currentAddress;
839
+ try {
840
+ currentAddress = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger)?.address;
841
+ }
842
+ catch {
843
+ return false;
844
+ }
845
+ return (this.state.activeProvider === 'hyperliquid' &&
846
+ (!capturedActiveProvider ||
847
+ this.activeProviderInstance === capturedActiveProvider) &&
848
+ this.state.isTestnet === isTestnet &&
849
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
850
+ currentAddress?.toLowerCase() === address.toLowerCase());
851
+ };
852
+ const context = {
853
+ provider,
854
+ standaloneProvider,
855
+ address,
856
+ isTestnet,
857
+ hip3ConfigVersion,
858
+ expectedDexes,
859
+ isCurrent,
860
+ };
861
+ const requestKey = [
862
+ 'hyperliquid',
863
+ network,
864
+ address.toLowerCase(),
865
+ hip3ConfigVersion,
866
+ ...expectedDexes,
867
+ ].join('|');
868
+ const existingRequest = __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey);
869
+ if (existingRequest?.provider === provider) {
870
+ return existingRequest.promise;
871
+ }
872
+ const request = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_fetchAndCacheUserDataSnapshot).call(this, context);
873
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").set(requestKey, { provider, promise: request });
874
+ try {
875
+ return await request;
876
+ }
877
+ finally {
878
+ if (__classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey)?.promise === request) {
879
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").delete(requestKey);
880
+ }
881
+ }
882
+ }
771
883
  /**
772
884
  * Test-observable accessor for whether a standalone provider is cached.
773
885
  *
@@ -1543,7 +1655,10 @@ class PerpsController extends base_controller_1.BaseController {
1543
1655
  // Fallback to cached standalone provider for pre-initialization discovery
1544
1656
  // TODO: When adding new providers (MYX), consider a provider factory pattern
1545
1657
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1546
- return provider.getPositions(params);
1658
+ const operation = provider.getPositions(params);
1659
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1660
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1661
+ : operation;
1547
1662
  }
1548
1663
  const provider = this.getActiveProvider();
1549
1664
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getPositions({
@@ -1604,7 +1719,10 @@ class PerpsController extends base_controller_1.BaseController {
1604
1719
  // For standalone mode, access provider directly without initialization check
1605
1720
  if (params?.standalone && params.userAddress) {
1606
1721
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1607
- return provider.getOpenOrders(params);
1722
+ const operation = provider.getOpenOrders(params);
1723
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1724
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1725
+ : operation;
1608
1726
  }
1609
1727
  const provider = this.getActiveProvider();
1610
1728
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getOpenOrders({
@@ -1649,7 +1767,10 @@ class PerpsController extends base_controller_1.BaseController {
1649
1767
  // Use activeProviderInstance if available (respects provider abstraction)
1650
1768
  // Fallback to cached standalone provider for pre-initialization discovery
1651
1769
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1652
- return provider.getAccountState(params);
1770
+ const operation = provider.getAccountState(params);
1771
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1772
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1773
+ : operation;
1653
1774
  }
1654
1775
  const provider = this.getActiveProvider();
1655
1776
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getAccountState({
@@ -1687,12 +1808,15 @@ class PerpsController extends base_controller_1.BaseController {
1687
1808
  const isMarketAllowed = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this);
1688
1809
  if (params?.standalone) {
1689
1810
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1690
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1811
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1691
1812
  provider,
1692
1813
  params,
1693
1814
  context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarkets'),
1694
1815
  isMarketAllowed,
1695
1816
  });
1817
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1818
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1819
+ : operation;
1696
1820
  }
1697
1821
  const provider = this.getActiveProvider();
1698
1822
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
@@ -1718,19 +1842,26 @@ class PerpsController extends base_controller_1.BaseController {
1718
1842
  * @returns A promise that resolves to the market data.
1719
1843
  */
1720
1844
  async getMarketDataWithPrices(params) {
1845
+ const globalSnapshot = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildGlobalSnapshotContext).call(this);
1846
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices', {
1847
+ ...(globalSnapshot && { globalSnapshot }),
1848
+ });
1721
1849
  if (params?.standalone) {
1722
1850
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1723
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1851
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1724
1852
  provider,
1725
1853
  params,
1726
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1854
+ context,
1727
1855
  });
1856
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1857
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1858
+ : operation;
1728
1859
  }
1729
1860
  const provider = this.getActiveProvider();
1730
1861
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1731
1862
  provider,
1732
1863
  params,
1733
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1864
+ context,
1734
1865
  });
1735
1866
  }
1736
1867
  /**
@@ -1751,11 +1882,17 @@ class PerpsController extends base_controller_1.BaseController {
1751
1882
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1752
1883
  /* fire-and-forget */
1753
1884
  });
1885
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1886
+ /* fire-and-forget */
1887
+ });
1754
1888
  // Periodic refresh
1755
1889
  __classPrivateFieldSet(this, _PerpsController_preloadTimer, setInterval(() => {
1756
1890
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1757
1891
  /* fire-and-forget */
1758
1892
  });
1893
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1894
+ /* fire-and-forget */
1895
+ });
1759
1896
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1760
1897
  // Watch for isTestnet / hip3ConfigVersion changes
1761
1898
  const handler = (_state, patches) => {
@@ -1784,36 +1921,29 @@ class PerpsController extends base_controller_1.BaseController {
1784
1921
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1785
1922
  /* fire-and-forget */
1786
1923
  });
1924
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1925
+ /* fire-and-forget */
1926
+ });
1787
1927
  }
1788
1928
  };
1789
- this.messenger.subscribe('PerpsController:stateChange', handler);
1929
+ this.messenger.subscribe('PerpsController:stateChanged', handler);
1790
1930
  __classPrivateFieldSet(this, _PerpsController_preloadStateUnsubscribe, () => {
1791
- this.messenger.unsubscribe('PerpsController:stateChange', handler);
1931
+ this.messenger.unsubscribe('PerpsController:stateChanged', handler);
1792
1932
  }, "f");
1793
1933
  // Watch for selected account changes and selected account group changes.
1794
1934
  const accountChangeHandler = () => {
1795
1935
  const evmAccount = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger);
1796
1936
  const currentAddress = evmAccount?.address ?? null;
1797
- // If any cached entry belongs to a different account, clear all entries.
1798
- // Max 4 entries (2 providers × 2 networks) — clearing all is simple and safe.
1799
- const hasStaleEntries = Object.values(this.state.cachedUserDataByProvider).some((entry) => entry.address.toLowerCase() !== currentAddress?.toLowerCase());
1800
- if (hasStaleEntries) {
1801
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Account changed, clearing user data cache');
1802
- this.update((state) => {
1803
- state.cachedUserDataByProvider = {};
1804
- });
1805
- // Invalidate disk-cached user data for the old account
1806
- __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache
1807
- .removeItem(perpsConfig_js_1.PERPS_DISK_CACHE_USER_DATA)
1808
- .catch(() => {
1937
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: account cache selection', {
1938
+ address: currentAddress?.toLowerCase() ?? null,
1939
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
1940
+ });
1941
+ // The address guard makes the previous entry unreadable immediately;
1942
+ // refresh replaces it under the existing provider/network key.
1943
+ if (currentAddress) {
1944
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1809
1945
  /* fire-and-forget */
1810
1946
  });
1811
- // Only preload if the new account is an EVM account
1812
- if (currentAddress) {
1813
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1814
- /* fire-and-forget */
1815
- });
1816
- }
1817
1947
  }
1818
1948
  };
1819
1949
  this.messenger.subscribe('AccountsController:selectedAccountChange', accountChangeHandler);
@@ -1842,6 +1972,8 @@ class PerpsController extends base_controller_1.BaseController {
1842
1972
  }
1843
1973
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, null, "f");
1844
1974
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, null, "f");
1975
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
1976
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
1845
1977
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this).catch(() => {
1846
1978
  /* fire-and-forget to preserve sync signature */
1847
1979
  });
@@ -2102,6 +2234,9 @@ class PerpsController extends base_controller_1.BaseController {
2102
2234
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2103
2235
  /* fire-and-forget */
2104
2236
  });
2237
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2238
+ /* fire-and-forget */
2239
+ });
2105
2240
  }
2106
2241
  }
2107
2242
  }
@@ -2211,6 +2346,9 @@ class PerpsController extends base_controller_1.BaseController {
2211
2346
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2212
2347
  /* fire-and-forget */
2213
2348
  });
2349
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2350
+ /* fire-and-forget */
2351
+ });
2214
2352
  }
2215
2353
  }
2216
2354
  }
@@ -3274,7 +3412,7 @@ class PerpsController extends base_controller_1.BaseController {
3274
3412
  }
3275
3413
  }
3276
3414
  exports.PerpsController = PerpsController;
3277
- _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() {
3415
+ _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() {
3278
3416
  const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
3279
3417
  // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
3280
3418
  // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
@@ -3327,6 +3465,96 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3327
3465
  }
3328
3466
  }
3329
3467
  return Array.from(providerIds);
3468
+ }, _PerpsController_isMarketCacheEntryCurrent = function _PerpsController_isMarketCacheEntryCurrent(providerId, entry, options) {
3469
+ if (entry.sourceExpiresAt !== undefined) {
3470
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3471
+ return (providerId === 'hyperliquid' &&
3472
+ Date.now() < entry.sourceExpiresAt &&
3473
+ entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3474
+ expectedDexes !== undefined &&
3475
+ Array.isArray(entry.dexes) &&
3476
+ entry.dexes.length === expectedDexes.length &&
3477
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3478
+ }
3479
+ return (options?.skipTTL === true ||
3480
+ Date.now() - entry.timestamp <= __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10);
3481
+ }, _PerpsController_isUserCacheIdentityCurrent = function _PerpsController_isUserCacheIdentityCurrent(providerId, entry, address) {
3482
+ if (entry.address.toLowerCase() !== address.toLowerCase()) {
3483
+ return false;
3484
+ }
3485
+ if (providerId !== 'hyperliquid') {
3486
+ return true;
3487
+ }
3488
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3489
+ return (entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3490
+ expectedDexes !== undefined &&
3491
+ Array.isArray(entry.dexes) &&
3492
+ entry.dexes.length === expectedDexes.length &&
3493
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3494
+ }, _PerpsController_fetchAndCacheUserDataSnapshot = async function _PerpsController_fetchAndCacheUserDataSnapshot(context) {
3495
+ const { provider, standaloneProvider, address, isTestnet, hip3ConfigVersion, expectedDexes, isCurrent, } = context;
3496
+ if (!isCurrent()) {
3497
+ throw new Error('User data snapshot context changed');
3498
+ }
3499
+ if (!provider.getUserDataSnapshot) {
3500
+ throw new Error('Provider has no atomic snapshot API');
3501
+ }
3502
+ const identity = {
3503
+ provider: 'hyperliquid',
3504
+ network: isTestnet ? 'testnet' : 'mainnet',
3505
+ hip3ConfigVersion,
3506
+ dexes: expectedDexes,
3507
+ };
3508
+ const snapshotRequest = provider.getUserDataSnapshot({
3509
+ userAddress: address,
3510
+ identity,
3511
+ });
3512
+ const snapshot = standaloneProvider
3513
+ ? await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, standaloneProvider, snapshotRequest)
3514
+ : await snapshotRequest;
3515
+ if (!isCurrent()) {
3516
+ throw new Error('User data snapshot context changed');
3517
+ }
3518
+ const snapshotIdentity = snapshot.identity;
3519
+ const hasCompleteBundle = Array.isArray(snapshot.positions) &&
3520
+ Array.isArray(snapshot.orders) &&
3521
+ snapshot.accountState !== null &&
3522
+ typeof snapshot.accountState === 'object';
3523
+ const hasExactIdentity = snapshotIdentity.provider === identity.provider &&
3524
+ snapshotIdentity.network === identity.network &&
3525
+ snapshotIdentity.hip3ConfigVersion === identity.hip3ConfigVersion &&
3526
+ snapshotIdentity.address.toLowerCase() === address.toLowerCase() &&
3527
+ snapshotIdentity.dexes.length === expectedDexes.length &&
3528
+ snapshotIdentity.dexes.every((dex, index) => dex === expectedDexes[index]);
3529
+ if (!hasCompleteBundle || !hasExactIdentity) {
3530
+ throw new Error('User data snapshot is incomplete or mismatched');
3531
+ }
3532
+ if (!isCurrent()) {
3533
+ throw new Error('User data snapshot context changed');
3534
+ }
3535
+ const cachedSnapshot = cloneUserDataSnapshot(snapshot);
3536
+ const result = cloneUserDataSnapshot(snapshot);
3537
+ const timestamp = Date.now();
3538
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)('hyperliquid', isTestnet);
3539
+ this.update((state) => {
3540
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3541
+ positions: cachedSnapshot.positions,
3542
+ orders: cachedSnapshot.orders,
3543
+ accountState: cachedSnapshot.accountState,
3544
+ timestamp,
3545
+ address,
3546
+ hip3ConfigVersion,
3547
+ dexes: expectedDexes,
3548
+ };
3549
+ });
3550
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3551
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache snapshot written', {
3552
+ writtenKey: providerNetworkKey,
3553
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
3554
+ positionCount: cachedSnapshot.positions.length,
3555
+ orderCount: cachedSnapshot.orders.length,
3556
+ });
3557
+ return result;
3330
3558
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
3331
3559
  const currentIsTestnet = this.state.isTestnet;
3332
3560
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -3335,10 +3563,13 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3335
3563
  __classPrivateFieldGet(this, _PerpsController_standaloneProviderHip3Version, "f") === currentHip3Version) {
3336
3564
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3337
3565
  }
3338
- // Stale or missing — tear down old one (fire-and-forget)
3566
+ // Stale or missing — retire the old provider after active operations finish.
3339
3567
  if (__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3340
3568
  const old = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3341
- Promise.resolve(old.disconnect()).catch(() => {
3569
+ __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3570
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3571
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3572
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, old).catch(() => {
3342
3573
  /* best-effort */
3343
3574
  });
3344
3575
  }
@@ -3358,24 +3589,45 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3358
3589
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
3359
3590
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
3360
3591
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3592
+ }, _PerpsController_trackStandaloneProviderOperation = function _PerpsController_trackStandaloneProviderOperation(provider, operation) {
3593
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider) ?? new Set();
3594
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").set(provider, operations);
3595
+ const trackedOperation = operation.finally(() => {
3596
+ operations.delete(trackedOperation);
3597
+ if (operations.size === 0) {
3598
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3599
+ }
3600
+ });
3601
+ operations.add(trackedOperation);
3602
+ return trackedOperation;
3603
+ }, _PerpsController_retireStandaloneProvider = async function _PerpsController_retireStandaloneProvider(provider) {
3604
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider);
3605
+ if (operations?.size) {
3606
+ await Promise.allSettled([...operations]);
3607
+ }
3608
+ try {
3609
+ await provider.disconnect();
3610
+ }
3611
+ catch {
3612
+ /* best-effort */
3613
+ }
3614
+ finally {
3615
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3616
+ }
3361
3617
  }, _PerpsController_cleanupStandaloneProvider =
3362
3618
  /**
3363
3619
  * Disconnect and discard the cached standalone provider (if any).
3364
3620
  * Best-effort — errors are silently caught.
3365
3621
  */
3366
3622
  async function _PerpsController_cleanupStandaloneProvider() {
3367
- if (!__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3623
+ const provider = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3624
+ if (!provider) {
3368
3625
  return;
3369
3626
  }
3370
- try {
3371
- await __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f").disconnect();
3372
- }
3373
- catch {
3374
- /* best-effort */
3375
- }
3376
3627
  __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3377
3628
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3378
3629
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3630
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, provider);
3379
3631
  }, _PerpsController_getMetrics = function _PerpsController_getMetrics() {
3380
3632
  return __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.metrics;
3381
3633
  }, _PerpsController_findNetworkClientIdForChain = function _PerpsController_findNetworkClientIdForChain(chainId) {
@@ -3709,6 +3961,60 @@ async function _PerpsController_performInitialization() {
3709
3961
  __classPrivateFieldGet(this, _PerpsController_tradingService, "f").setControllerDependencies({
3710
3962
  rewardsIntegrationService: __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f"),
3711
3963
  });
3964
+ }, _PerpsController_buildGlobalSnapshotContext = function _PerpsController_buildGlobalSnapshotContext() {
3965
+ const snapshotConfigured = Boolean(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalApi?.globalSnapshotUrl) ||
3966
+ typeof __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalMarketService
3967
+ ?.fetchGlobalSnapshot === 'function';
3968
+ if (!snapshotConfigured || this.state.activeProvider !== 'hyperliquid') {
3969
+ return undefined;
3970
+ }
3971
+ const enabledDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3972
+ if (!enabledDexes) {
3973
+ return undefined;
3974
+ }
3975
+ const { isTestnet, hip3ConfigVersion } = this.state;
3976
+ return {
3977
+ request: {
3978
+ provider: 'hyperliquid',
3979
+ network: isTestnet ? 'testnet' : 'mainnet',
3980
+ enabledDexes,
3981
+ },
3982
+ isCurrent: () => this.state.activeProvider === 'hyperliquid' &&
3983
+ this.state.isTestnet === isTestnet &&
3984
+ this.state.hip3ConfigVersion === hip3ConfigVersion,
3985
+ isMarketAllowed: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this),
3986
+ };
3987
+ }, _PerpsController_getStaticSnapshotDexes = function _PerpsController_getStaticSnapshotDexes() {
3988
+ if (!__classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f")) {
3989
+ return ['main'];
3990
+ }
3991
+ if (this.state.isTestnet) {
3992
+ return hyperLiquidConfig_js_1.TESTNET_HIP3_CONFIG.AutoDiscoverAll
3993
+ ? undefined
3994
+ : (0, hyperLiquidConfig_js_1.canonicalizeHyperLiquidDexes)(hyperLiquidConfig_js_1.TESTNET_HIP3_CONFIG.EnabledDexs);
3995
+ }
3996
+ if (hyperLiquidConfig_js_1.MAINNET_HIP3_CONFIG.AutoDiscoverAll) {
3997
+ return undefined;
3998
+ }
3999
+ const dexes = new Set();
4000
+ for (const pattern of __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f")) {
4001
+ const colonIndex = pattern.indexOf(':');
4002
+ if (colonIndex <= 0) {
4003
+ if (/^[a-z][a-z0-9]*$/iu.test(pattern)) {
4004
+ dexes.add(pattern.toLowerCase());
4005
+ continue;
4006
+ }
4007
+ return undefined;
4008
+ }
4009
+ const dex = pattern.slice(0, colonIndex);
4010
+ if (dex && /^[a-z0-9][a-z0-9-]*$/u.test(dex)) {
4011
+ dexes.add(dex);
4012
+ }
4013
+ else {
4014
+ return undefined;
4015
+ }
4016
+ }
4017
+ return (0, hyperLiquidConfig_js_1.canonicalizeHyperLiquidDexes)(dexes);
3712
4018
  }, _PerpsController_hydrateCacheFromDiskSync = function _PerpsController_hydrateCacheFromDiskSync() {
3713
4019
  const { marketUpdates, userUpdates, stats } = (0, perpsDiskPersistence_js_1.hydrateFromDiskSync)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, this.state.cachedMarketDataByProvider, this.state.cachedUserDataByProvider, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs));
3714
4020
  const hasMarketUpdates = Object.keys(marketUpdates).length > 0;
@@ -3729,12 +4035,40 @@ async function _PerpsController_performInitialization() {
3729
4035
  orders: stats.userOrders,
3730
4036
  duration_ms: stats.durationMs,
3731
4037
  });
4038
+ }, _PerpsController_persistUserCacheToDisk = function _PerpsController_persistUserCacheToDisk() {
4039
+ const entries = [];
4040
+ for (const [cacheKey, entry] of Object.entries(this.state.cachedUserDataByProvider)) {
4041
+ const [providerId, network] = cacheKey.split(':');
4042
+ if (!providerId ||
4043
+ (network !== 'mainnet' && network !== 'testnet') ||
4044
+ providerId === 'aggregated') {
4045
+ continue;
4046
+ }
4047
+ entries.push({
4048
+ providerNetworkKey: `${providerId}:${network}`,
4049
+ address: entry.address,
4050
+ positions: entry.positions,
4051
+ orders: entry.orders,
4052
+ accountState: entry.accountState,
4053
+ timestamp: entry.timestamp,
4054
+ ...(entry.hip3ConfigVersion !== undefined && {
4055
+ hip3ConfigVersion: entry.hip3ConfigVersion,
4056
+ }),
4057
+ ...(entry.dexes !== undefined && { dexes: entry.dexes }),
4058
+ });
4059
+ }
4060
+ __classPrivateFieldSet(this, _PerpsController_userDiskWrite, __classPrivateFieldGet(this, _PerpsController_userDiskWrite, "f")
4061
+ .then(() => (0, perpsDiskPersistence_js_1.persistUserEntriesToDisk)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, entries))
4062
+ .catch(() => {
4063
+ // Disk persistence is best-effort and must not block live data.
4064
+ }), "f");
3732
4065
  }, _PerpsController_performMarketDataPreload =
3733
4066
  /**
3734
4067
  * Perform a single market data preload (best-effort, no throw).
3735
4068
  */
3736
4069
  async function _PerpsController_performMarketDataPreload() {
3737
4070
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
4071
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, true, "f");
3738
4072
  return;
3739
4073
  }
3740
4074
  // Skip preloading during provider/network reinitialisation.
@@ -3749,9 +4083,16 @@ async function _PerpsController_performMarketDataPreload() {
3749
4083
  ? this.state.activeProvider // includes 'aggregated'
3750
4084
  : 'hyperliquid';
3751
4085
  const cacheKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, this.state.isTestnet);
4086
+ const preloadContext = {
4087
+ activeProvider: this.state.activeProvider,
4088
+ isTestnet: this.state.isTestnet,
4089
+ hip3ConfigVersion: this.state.hip3ConfigVersion,
4090
+ };
4091
+ const staticSnapshotDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3752
4092
  const now = Date.now();
3753
4093
  const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3754
4094
  if (existingEntry &&
4095
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, actualProviderId, existingEntry) &&
3755
4096
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3756
4097
  return;
3757
4098
  }
@@ -3776,8 +4117,32 @@ async function _PerpsController_performMarketDataPreload() {
3776
4117
  duration_ms: Math.round(performance.now() - preloadStart),
3777
4118
  markets: data.length,
3778
4119
  });
4120
+ if (this.state.activeProvider !== preloadContext.activeProvider ||
4121
+ this.state.isTestnet !== preloadContext.isTestnet ||
4122
+ this.state.hip3ConfigVersion !== preloadContext.hip3ConfigVersion) {
4123
+ traceData = {
4124
+ success: false,
4125
+ error: 'Global snapshot preload context changed',
4126
+ };
4127
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Discarding stale global snapshot preload');
4128
+ return;
4129
+ }
3779
4130
  // Store under per-provider key(s)
3780
4131
  const ts = Date.now();
4132
+ const sourceExpiries = data.flatMap((market) => market.dataSource === 'terminal-global-snapshot-mark' &&
4133
+ typeof market.sourceExpiresAt === 'number'
4134
+ ? [market.sourceExpiresAt]
4135
+ : []);
4136
+ const sourceExpiresAt = data.length > 0 && sourceExpiries.length === data.length
4137
+ ? Math.min(...sourceExpiries)
4138
+ : undefined;
4139
+ const snapshotCacheIdentity = sourceExpiresAt !== undefined && staticSnapshotDexes
4140
+ ? {
4141
+ sourceExpiresAt,
4142
+ hip3ConfigVersion: preloadContext.hip3ConfigVersion,
4143
+ dexes: staticSnapshotDexes,
4144
+ }
4145
+ : {};
3781
4146
  const marketDiskEntries = [];
3782
4147
  if (this.state.activeProvider === 'aggregated' &&
3783
4148
  this.activeProviderInstance) {
@@ -3824,6 +4189,7 @@ async function _PerpsController_performMarketDataPreload() {
3824
4189
  state.cachedMarketDataByProvider[cacheKey] = {
3825
4190
  data,
3826
4191
  timestamp: ts,
4192
+ ...snapshotCacheIdentity,
3827
4193
  };
3828
4194
  });
3829
4195
  }
@@ -3833,10 +4199,6 @@ async function _PerpsController_performMarketDataPreload() {
3833
4199
  });
3834
4200
  traceData = { success: true, marketCount: data.length };
3835
4201
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(performanceMetrics_js_1.PerpsMeasurementName.PerpsMarketDataPreload, performance.now() - preloadStart, 'millisecond');
3836
- // Also preload user data (fire-and-forget, non-blocking)
3837
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
3838
- /* fire-and-forget */
3839
- });
3840
4202
  }
3841
4203
  catch (error) {
3842
4204
  traceData = {
@@ -3855,6 +4217,12 @@ async function _PerpsController_performMarketDataPreload() {
3855
4217
  data: traceData,
3856
4218
  });
3857
4219
  __classPrivateFieldSet(this, _PerpsController_isPreloading, false, "f");
4220
+ if (__classPrivateFieldGet(this, _PerpsController_marketPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4221
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
4222
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
4223
+ // Background preload is best-effort.
4224
+ });
4225
+ }
3858
4226
  }
3859
4227
  }, _PerpsController_performUserDataPreload =
3860
4228
  /**
@@ -3863,6 +4231,10 @@ async function _PerpsController_performMarketDataPreload() {
3863
4231
  */
3864
4232
  async function _PerpsController_performUserDataPreload() {
3865
4233
  if (__classPrivateFieldGet(this, _PerpsController_isPreloadingUserData, "f")) {
4234
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, true, "f");
4235
+ return;
4236
+ }
4237
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3866
4238
  return;
3867
4239
  }
3868
4240
  // Get current user address
@@ -3871,21 +4243,54 @@ async function _PerpsController_performUserDataPreload() {
3871
4243
  return;
3872
4244
  }
3873
4245
  const userAddress = evmAccount.address;
4246
+ const { activeProvider, isTestnet, hip3ConfigVersion } = this.state;
4247
+ const { activeProviderInstance } = this;
4248
+ const hyperliquidDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4249
+ const isCurrent = () => {
4250
+ let currentAddress;
4251
+ try {
4252
+ currentAddress = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger)?.address;
4253
+ }
4254
+ catch {
4255
+ return false;
4256
+ }
4257
+ return (this.state.activeProvider === activeProvider &&
4258
+ this.activeProviderInstance === activeProviderInstance &&
4259
+ this.state.isTestnet === isTestnet &&
4260
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
4261
+ currentAddress?.toLowerCase() === userAddress.toLowerCase());
4262
+ };
3874
4263
  // Determine actual provider (same logic as market preload)
3875
- const actualProviderId = this.activeProviderInstance
3876
- ? this.state.activeProvider // includes 'aggregated'
4264
+ const actualProviderId = activeProviderInstance
4265
+ ? activeProvider // includes 'aggregated'
3877
4266
  : 'hyperliquid';
3878
- const userCacheKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, this.state.isTestnet);
4267
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, isTestnet);
3879
4268
  // Skip if cache is fresh and for same account
3880
4269
  const now = Date.now();
3881
- const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3882
- if (existingEntry?.address === userAddress &&
4270
+ const existingEntry = this.state.cachedUserDataByProvider[providerNetworkKey];
4271
+ const hasMatchingCache = existingEntry !== undefined &&
4272
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, actualProviderId, existingEntry, userAddress);
4273
+ const cacheAgeMs = existingEntry ? now - existingEntry.timestamp : null;
4274
+ const websocketState = this.getWebSocketConnectionState();
4275
+ let selectedEntryKey = null;
4276
+ if (this.state.cachedUserDataByProvider[providerNetworkKey]) {
4277
+ selectedEntryKey = providerNetworkKey;
4278
+ }
4279
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache preload decision', {
4280
+ requestedKey: providerNetworkKey,
4281
+ selectedEntryKey,
4282
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
4283
+ hasMatchingCache,
4284
+ cacheAgeMs,
4285
+ websocketState,
4286
+ });
4287
+ if (existingEntry &&
4288
+ hasMatchingCache &&
3883
4289
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3884
4290
  return;
3885
4291
  }
3886
- // Skip standalone REST polling when WebSocket is connected — live data is streaming
3887
- if (this.getWebSocketConnectionState() === index_js_1.WebSocketConnectionState.Connected) {
3888
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Skipping user data preload — WebSocket connected');
4292
+ if (hasMatchingCache &&
4293
+ this.getWebSocketConnectionState() === index_js_1.WebSocketConnectionState.Connected) {
3889
4294
  return;
3890
4295
  }
3891
4296
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, true, "f");
@@ -3898,21 +4303,38 @@ async function _PerpsController_performUserDataPreload() {
3898
4303
  id: traceId,
3899
4304
  op: index_js_1.PerpsTraceOperations.Operation,
3900
4305
  tags: {
3901
- provider: this.state.activeProvider,
3902
- isTestnet: this.state.isTestnet,
4306
+ provider: activeProvider,
4307
+ isTestnet,
3903
4308
  },
3904
4309
  data: { userAddress },
3905
4310
  });
3906
4311
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching user data in background', {
3907
4312
  userAddress,
3908
4313
  });
4314
+ if (activeProvider === 'hyperliquid') {
4315
+ const snapshot = await this.getUserDataSnapshot();
4316
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
4317
+ positionCount: snapshot.positions.length,
4318
+ orderCount: snapshot.orders.length,
4319
+ totalBalance: snapshot.accountState.totalBalance,
4320
+ });
4321
+ traceData = {
4322
+ success: true,
4323
+ positionCount: snapshot.positions.length,
4324
+ orderCount: snapshot.orders.length,
4325
+ };
4326
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(performanceMetrics_js_1.PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond');
4327
+ return;
4328
+ }
3909
4329
  const [positions, orders, accountState] = await Promise.all([
3910
4330
  this.getPositions({ standalone: true, userAddress }),
3911
4331
  this.getOpenOrders({ standalone: true, userAddress }),
3912
4332
  this.getAccountState({ standalone: true, userAddress }),
3913
4333
  ]);
3914
- if (this.state.activeProvider === 'aggregated' &&
3915
- this.activeProviderInstance) {
4334
+ if (!isCurrent()) {
4335
+ throw new Error('User data preload context changed');
4336
+ }
4337
+ if (activeProvider === 'aggregated' && activeProviderInstance) {
3916
4338
  // Split by providerId and write one cache entry per provider key
3917
4339
  // (mirrors the market-data preload pattern at ~line 2976)
3918
4340
  const ts = Date.now();
@@ -3934,26 +4356,22 @@ async function _PerpsController_performUserDataPreload() {
3934
4356
  }
3935
4357
  // AccountState — assign to its provider bucket
3936
4358
  ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3937
- const diskEntries = [];
3938
4359
  this.update((state) => {
3939
4360
  for (const [pid, data] of byProvider) {
3940
- const key = (0, perpsConfig_js_1.buildProviderCacheKey)(pid, this.state.isTestnet);
3941
- diskEntries.push({
3942
- providerNetworkKey: key,
3943
- address: userAddress,
3944
- positions: data.positions,
3945
- orders: data.orders,
3946
- accountState: data.accountState,
3947
- timestamp: ts,
3948
- });
4361
+ const key = (0, perpsConfig_js_1.buildProviderCacheKey)(pid, isTestnet);
3949
4362
  state.cachedUserDataByProvider[key] = {
3950
4363
  ...data,
3951
4364
  timestamp: ts,
3952
4365
  address: userAddress,
4366
+ ...(pid === 'hyperliquid' &&
4367
+ hyperliquidDexes && {
4368
+ hip3ConfigVersion,
4369
+ dexes: hyperliquidDexes,
4370
+ }),
3953
4371
  };
3954
4372
  }
3955
4373
  // Write aggregated sentinel so the staleness guard sees it
3956
- state.cachedUserDataByProvider[userCacheKey] = {
4374
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3957
4375
  positions: [],
3958
4376
  orders: [],
3959
4377
  accountState: null,
@@ -3961,30 +4379,26 @@ async function _PerpsController_performUserDataPreload() {
3961
4379
  address: userAddress,
3962
4380
  };
3963
4381
  });
3964
- (0, perpsDiskPersistence_js_1.persistUserEntriesToDisk)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, diskEntries);
4382
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3965
4383
  }
3966
4384
  else {
3967
4385
  // Single provider — store directly under its key
3968
4386
  const ts = Date.now();
3969
4387
  this.update((state) => {
3970
- state.cachedUserDataByProvider[userCacheKey] = {
4388
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3971
4389
  positions,
3972
4390
  orders,
3973
4391
  accountState,
3974
4392
  timestamp: ts,
3975
4393
  address: userAddress,
4394
+ ...(actualProviderId === 'hyperliquid' &&
4395
+ hyperliquidDexes && {
4396
+ hip3ConfigVersion,
4397
+ dexes: hyperliquidDexes,
4398
+ }),
3976
4399
  };
3977
4400
  });
3978
- (0, perpsDiskPersistence_js_1.persistUserEntriesToDisk)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, [
3979
- {
3980
- providerNetworkKey: userCacheKey,
3981
- address: userAddress,
3982
- positions,
3983
- orders,
3984
- accountState,
3985
- timestamp: ts,
3986
- },
3987
- ]);
4401
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3988
4402
  }
3989
4403
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3990
4404
  positionCount: positions.length,
@@ -4015,6 +4429,12 @@ async function _PerpsController_performUserDataPreload() {
4015
4429
  data: traceData,
4016
4430
  });
4017
4431
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, false, "f");
4432
+ if (__classPrivateFieldGet(this, _PerpsController_userPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4433
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
4434
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
4435
+ // Background preload is best-effort.
4436
+ });
4437
+ }
4018
4438
  }
4019
4439
  }, _PerpsController_persistWatchlistToRemote =
4020
4440
  /**