@metamask-previews/perps-controller 11.0.0-preview-9efd0b754 → 12.0.0-preview-4e64b66e8

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 (134) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/PerpsController-method-action-types.cjs.map +1 -1
  3. package/dist/PerpsController-method-action-types.d.cts +36 -1
  4. package/dist/PerpsController-method-action-types.d.cts.map +1 -1
  5. package/dist/PerpsController-method-action-types.d.mts +36 -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 +600 -119
  9. package/dist/PerpsController.cjs.map +1 -1
  10. package/dist/PerpsController.d.cts +35 -4
  11. package/dist/PerpsController.d.cts.map +1 -1
  12. package/dist/PerpsController.d.mts +35 -4
  13. package/dist/PerpsController.d.mts.map +1 -1
  14. package/dist/PerpsController.mjs +603 -122
  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 +19 -2
  33. package/dist/constants/perpsConfig.cjs.map +1 -1
  34. package/dist/constants/perpsConfig.d.cts +39 -7
  35. package/dist/constants/perpsConfig.d.cts.map +1 -1
  36. package/dist/constants/perpsConfig.d.mts +39 -7
  37. package/dist/constants/perpsConfig.d.mts.map +1 -1
  38. package/dist/constants/perpsConfig.mjs +18 -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/AggregatedPerpsProvider.cjs +16 -0
  47. package/dist/providers/AggregatedPerpsProvider.cjs.map +1 -1
  48. package/dist/providers/AggregatedPerpsProvider.d.cts +3 -1
  49. package/dist/providers/AggregatedPerpsProvider.d.cts.map +1 -1
  50. package/dist/providers/AggregatedPerpsProvider.d.mts +3 -1
  51. package/dist/providers/AggregatedPerpsProvider.d.mts.map +1 -1
  52. package/dist/providers/AggregatedPerpsProvider.mjs +16 -0
  53. package/dist/providers/AggregatedPerpsProvider.mjs.map +1 -1
  54. package/dist/providers/HyperLiquidProvider.cjs +325 -59
  55. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  56. package/dist/providers/HyperLiquidProvider.d.cts +27 -1
  57. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  58. package/dist/providers/HyperLiquidProvider.d.mts +27 -1
  59. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  60. package/dist/providers/HyperLiquidProvider.mjs +326 -60
  61. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  62. package/dist/services/HyperLiquidClientService.cjs +74 -25
  63. package/dist/services/HyperLiquidClientService.cjs.map +1 -1
  64. package/dist/services/HyperLiquidClientService.d.cts.map +1 -1
  65. package/dist/services/HyperLiquidClientService.d.mts.map +1 -1
  66. package/dist/services/HyperLiquidClientService.mjs +74 -25
  67. package/dist/services/HyperLiquidClientService.mjs.map +1 -1
  68. package/dist/services/HyperLiquidSubscriptionService.cjs +16 -5
  69. package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
  70. package/dist/services/HyperLiquidSubscriptionService.d.cts +1 -1
  71. package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
  72. package/dist/services/HyperLiquidSubscriptionService.d.mts +1 -1
  73. package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
  74. package/dist/services/HyperLiquidSubscriptionService.mjs +16 -5
  75. package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
  76. package/dist/services/MarketDataService.cjs +49 -5
  77. package/dist/services/MarketDataService.cjs.map +1 -1
  78. package/dist/services/MarketDataService.d.cts +2 -2
  79. package/dist/services/MarketDataService.d.cts.map +1 -1
  80. package/dist/services/MarketDataService.d.mts +2 -2
  81. package/dist/services/MarketDataService.d.mts.map +1 -1
  82. package/dist/services/MarketDataService.mjs +49 -5
  83. package/dist/services/MarketDataService.mjs.map +1 -1
  84. package/dist/services/RewardsIntegrationService.cjs +330 -68
  85. package/dist/services/RewardsIntegrationService.cjs.map +1 -1
  86. package/dist/services/RewardsIntegrationService.d.cts +61 -5
  87. package/dist/services/RewardsIntegrationService.d.cts.map +1 -1
  88. package/dist/services/RewardsIntegrationService.d.mts +61 -5
  89. package/dist/services/RewardsIntegrationService.d.mts.map +1 -1
  90. package/dist/services/RewardsIntegrationService.mjs +330 -68
  91. package/dist/services/RewardsIntegrationService.mjs.map +1 -1
  92. package/dist/services/ServiceContext.cjs.map +1 -1
  93. package/dist/services/ServiceContext.d.cts +17 -1
  94. package/dist/services/ServiceContext.d.cts.map +1 -1
  95. package/dist/services/ServiceContext.d.mts +17 -1
  96. package/dist/services/ServiceContext.d.mts.map +1 -1
  97. package/dist/services/ServiceContext.mjs.map +1 -1
  98. package/dist/services/TerminalMarketService.cjs +422 -7
  99. package/dist/services/TerminalMarketService.cjs.map +1 -1
  100. package/dist/services/TerminalMarketService.d.cts +10 -1
  101. package/dist/services/TerminalMarketService.d.cts.map +1 -1
  102. package/dist/services/TerminalMarketService.d.mts +10 -1
  103. package/dist/services/TerminalMarketService.d.mts.map +1 -1
  104. package/dist/services/TerminalMarketService.mjs +423 -8
  105. package/dist/services/TerminalMarketService.mjs.map +1 -1
  106. package/dist/services/TradingService.cjs +52 -28
  107. package/dist/services/TradingService.cjs.map +1 -1
  108. package/dist/services/TradingService.d.cts.map +1 -1
  109. package/dist/services/TradingService.d.mts.map +1 -1
  110. package/dist/services/TradingService.mjs +52 -28
  111. package/dist/services/TradingService.mjs.map +1 -1
  112. package/dist/types/index.cjs.map +1 -1
  113. package/dist/types/index.d.cts +156 -7
  114. package/dist/types/index.d.cts.map +1 -1
  115. package/dist/types/index.d.mts +156 -7
  116. package/dist/types/index.d.mts.map +1 -1
  117. package/dist/types/index.mjs.map +1 -1
  118. package/dist/utils/marketUtils.cjs +16 -1
  119. package/dist/utils/marketUtils.cjs.map +1 -1
  120. package/dist/utils/marketUtils.d.cts +1 -0
  121. package/dist/utils/marketUtils.d.cts.map +1 -1
  122. package/dist/utils/marketUtils.d.mts +1 -0
  123. package/dist/utils/marketUtils.d.mts.map +1 -1
  124. package/dist/utils/marketUtils.mjs +14 -0
  125. package/dist/utils/marketUtils.mjs.map +1 -1
  126. package/dist/utils/perpsDiskPersistence.cjs +19 -12
  127. package/dist/utils/perpsDiskPersistence.cjs.map +1 -1
  128. package/dist/utils/perpsDiskPersistence.d.cts +5 -1
  129. package/dist/utils/perpsDiskPersistence.d.cts.map +1 -1
  130. package/dist/utils/perpsDiskPersistence.d.mts +5 -1
  131. package/dist/utils/perpsDiskPersistence.d.mts.map +1 -1
  132. package/dist/utils/perpsDiskPersistence.mjs +19 -12
  133. package/dist/utils/perpsDiskPersistence.mjs.map +1 -1
  134. 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_awaitInitializationIfInProgress, _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_getActiveProviderWhenReady, _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.
@@ -384,6 +414,7 @@ const metadata = {
384
414
  },
385
415
  };
386
416
  const MESSENGER_EXPOSED_METHODS = [
417
+ 'approveSubscriptionBuilderFee',
387
418
  'calculateFees',
388
419
  'calculateLiquidationPrice',
389
420
  'calculateMaintenanceMargin',
@@ -411,6 +442,7 @@ const MESSENGER_EXPOSED_METHODS = [
411
442
  'getBlockExplorerUrl',
412
443
  'getCachedMarketDataForActiveProvider',
413
444
  'getCachedUserDataForActiveProvider',
445
+ 'getUserDataSnapshot',
414
446
  'getCurrentNetwork',
415
447
  'getFunding',
416
448
  'getHistoricalPortfolio',
@@ -432,6 +464,7 @@ const MESSENGER_EXPOSED_METHODS = [
432
464
  'getWithdrawalProgress',
433
465
  'getWithdrawalRoutes',
434
466
  'init',
467
+ 'invalidateSubscriptionBenefits',
435
468
  'isCurrentlyReinitializing',
436
469
  'isFirstTimeUserOnCurrentNetwork',
437
470
  'isWatchlistMarket',
@@ -543,6 +576,7 @@ class PerpsController extends base_controller_1.BaseController {
543
576
  _PerpsController_handlersRegistered.set(this, false);
544
577
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
545
578
  _PerpsController_standaloneProviderHip3Version.set(this, null);
579
+ _PerpsController_standaloneProviderOperations.set(this, new Map());
546
580
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
547
581
  /**
548
582
  * Serial promise queue for all AUS watchlist operations (hydration and
@@ -558,6 +592,7 @@ class PerpsController extends base_controller_1.BaseController {
558
592
  * a failed operation does not stall subsequent ones.
559
593
  */
560
594
  _PerpsController_ausQueue.set(this, Promise.resolve());
595
+ _PerpsController_userDiskWrite.set(this, Promise.resolve());
561
596
  // Store options for dependency injection (allows core package to inject platform-specific services)
562
597
  _PerpsController_options.set(this, void 0);
563
598
  // Service instances (instantiated with platform dependencies)
@@ -571,7 +606,10 @@ class PerpsController extends base_controller_1.BaseController {
571
606
  _PerpsController_rewardsIntegrationService.set(this, void 0);
572
607
  _PerpsController_preloadTimer.set(this, null);
573
608
  _PerpsController_isPreloading.set(this, false);
609
+ _PerpsController_marketPreloadQueued.set(this, false);
574
610
  _PerpsController_isPreloadingUserData.set(this, false);
611
+ _PerpsController_userPreloadQueued.set(this, false);
612
+ _PerpsController_userSnapshotRequests.set(this, new Map());
575
613
  _PerpsController_preloadStateUnsubscribe.set(this, null);
576
614
  _PerpsController_accountChangeUnsubscribe.set(this, null);
577
615
  _PerpsController_previousIsTestnet.set(this, null);
@@ -590,7 +628,9 @@ class PerpsController extends base_controller_1.BaseController {
590
628
  __classPrivateFieldSet(this, _PerpsController_marketDataService, new MarketDataService_js_1.MarketDataService({
591
629
  ...infrastructure,
592
630
  terminalMarketService: infrastructure.terminalMarketService ??
593
- (infrastructure.terminalApiUrl
631
+ (infrastructure.terminalApi?.marketDataUrl ||
632
+ infrastructure.terminalApiUrl ||
633
+ infrastructure.terminalApi?.globalSnapshotUrl
594
634
  ? new TerminalMarketService_js_1.TerminalMarketService(infrastructure)
595
635
  : undefined),
596
636
  }), "f");
@@ -657,24 +697,20 @@ class PerpsController extends base_controller_1.BaseController {
657
697
  if (activeProvider === 'aggregated') {
658
698
  // Assemble from all registered provider entries
659
699
  const assembled = [];
660
- let oldestTimestamp = Infinity;
661
700
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
662
701
  const key = (0, perpsConfig_js_1.buildProviderCacheKey)(providerId, this.state.isTestnet);
663
702
  const entry = cache[key];
664
703
  if (!entry || entry.data.length === 0) {
665
704
  continue;
666
705
  }
667
- oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
668
- assembled.push(...entry.data);
706
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, providerId, entry, options)) {
707
+ continue;
708
+ }
709
+ assembled.push(...(0, marketUtils_js_1.clonePerpsMarketData)(entry.data));
669
710
  }
670
711
  if (assembled.length === 0) {
671
712
  return null;
672
713
  }
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
714
  return assembled;
679
715
  }
680
716
  // Single provider mode
@@ -683,11 +719,10 @@ class PerpsController extends base_controller_1.BaseController {
683
719
  if (!entry || entry.data.length === 0) {
684
720
  return null;
685
721
  }
686
- if (!options?.skipTTL &&
687
- Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
722
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, activeProvider, entry, options)) {
688
723
  return null;
689
724
  }
690
- return entry.data;
725
+ return (0, marketUtils_js_1.clonePerpsMarketData)(entry.data);
691
726
  }
692
727
  /**
693
728
  * Read cached user data for the currently active provider (or aggregated).
@@ -711,18 +746,21 @@ class PerpsController extends base_controller_1.BaseController {
711
746
  currentAddress = evmAccount?.address ?? null;
712
747
  }
713
748
  catch {
714
- // Can't determine current account — trust the cache
749
+ // Account identity is required before account-scoped data can be trusted.
715
750
  }
751
+ if (!currentAddress) {
752
+ return null;
753
+ }
754
+ const selectedAddress = currentAddress;
716
755
  const skipTTL = options?.skipTTL ?? false;
717
- const isValidEntry = (entry) => {
756
+ const isValidEntry = (providerId, entry) => {
718
757
  if (!entry) {
719
758
  return false;
720
759
  }
721
760
  if (!skipTTL && Date.now() - entry.timestamp >= staleCutoff) {
722
761
  return false;
723
762
  }
724
- if (currentAddress &&
725
- entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
763
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, providerId, entry, selectedAddress)) {
726
764
  return false;
727
765
  }
728
766
  return true;
@@ -734,9 +772,9 @@ class PerpsController extends base_controller_1.BaseController {
734
772
  let defaultAccountState = null;
735
773
  let hasValidEntry = false;
736
774
  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)) {
775
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(providerId, this.state.isTestnet);
776
+ const entry = cache[providerNetworkKey];
777
+ if (!isValidEntry(providerId, entry)) {
740
778
  continue;
741
779
  }
742
780
  hasValidEntry = true;
@@ -757,9 +795,9 @@ class PerpsController extends base_controller_1.BaseController {
757
795
  };
758
796
  }
759
797
  // 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)) {
798
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(activeProvider, this.state.isTestnet);
799
+ const entry = cache[providerNetworkKey];
800
+ if (!entry || !isValidEntry(activeProvider, entry)) {
763
801
  return null;
764
802
  }
765
803
  return {
@@ -768,6 +806,82 @@ class PerpsController extends base_controller_1.BaseController {
768
806
  accountState: entry.accountState,
769
807
  };
770
808
  }
809
+ /**
810
+ * Fetch, validate, and atomically cache a complete user-data snapshot.
811
+ * This remains callable after mount so consumers can seed their live channel
812
+ * from one coherent positions/orders/account result.
813
+ *
814
+ * @returns The accepted user-data snapshot.
815
+ */
816
+ async getUserDataSnapshot() {
817
+ const evmAccount = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger);
818
+ if (!evmAccount?.address) {
819
+ throw new Error('Cannot fetch user data snapshot without an EVM account');
820
+ }
821
+ if (this.state.activeProvider !== 'hyperliquid') {
822
+ throw new Error('User data snapshots require Hyperliquid provider mode');
823
+ }
824
+ const capturedActiveProvider = this.activeProviderInstance;
825
+ const standaloneProvider = capturedActiveProvider
826
+ ? null
827
+ : __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
828
+ const provider = capturedActiveProvider ?? standaloneProvider;
829
+ if (!provider) {
830
+ throw new Error('Cannot create standalone Hyperliquid provider');
831
+ }
832
+ const { address } = evmAccount;
833
+ const { isTestnet, hip3ConfigVersion } = this.state;
834
+ const network = isTestnet ? 'testnet' : 'mainnet';
835
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
836
+ if (!expectedDexes) {
837
+ throw new Error('User data snapshot DEX identity is not static');
838
+ }
839
+ const isCurrent = () => {
840
+ let currentAddress;
841
+ try {
842
+ currentAddress = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger)?.address;
843
+ }
844
+ catch {
845
+ return false;
846
+ }
847
+ return (this.state.activeProvider === 'hyperliquid' &&
848
+ (!capturedActiveProvider ||
849
+ this.activeProviderInstance === capturedActiveProvider) &&
850
+ this.state.isTestnet === isTestnet &&
851
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
852
+ currentAddress?.toLowerCase() === address.toLowerCase());
853
+ };
854
+ const context = {
855
+ provider,
856
+ standaloneProvider,
857
+ address,
858
+ isTestnet,
859
+ hip3ConfigVersion,
860
+ expectedDexes,
861
+ isCurrent,
862
+ };
863
+ const requestKey = [
864
+ 'hyperliquid',
865
+ network,
866
+ address.toLowerCase(),
867
+ hip3ConfigVersion,
868
+ ...expectedDexes,
869
+ ].join('|');
870
+ const existingRequest = __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey);
871
+ if (existingRequest?.provider === provider) {
872
+ return existingRequest.promise;
873
+ }
874
+ const request = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_fetchAndCacheUserDataSnapshot).call(this, context);
875
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").set(requestKey, { provider, promise: request });
876
+ try {
877
+ return await request;
878
+ }
879
+ finally {
880
+ if (__classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey)?.promise === request) {
881
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").delete(requestKey);
882
+ }
883
+ }
884
+ }
771
885
  /**
772
886
  * Test-observable accessor for whether a standalone provider is cached.
773
887
  *
@@ -919,14 +1033,11 @@ class PerpsController extends base_controller_1.BaseController {
919
1033
  // Check if not initialized
920
1034
  if (this.state.initializationState !== InitializationState.Initialized ||
921
1035
  !this.isInitialized) {
922
- const errorMessage = this.state.initializationState === InitializationState.Failed
923
- ? `${perpsErrorCodes_js_1.PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED}: ${this.state.initializationError ?? 'Initialization failed'}`
924
- : perpsErrorCodes_js_1.PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED;
925
1036
  this.update((state) => {
926
- state.lastError = errorMessage;
1037
+ state.lastError = perpsErrorCodes_js_1.PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED;
927
1038
  state.lastUpdateTimestamp = Date.now();
928
1039
  });
929
- throw new Error(errorMessage);
1040
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED);
930
1041
  }
931
1042
  // Return the active provider instance (set during initialization based on providerMode)
932
1043
  if (!this.activeProviderInstance) {
@@ -966,7 +1077,7 @@ class PerpsController extends base_controller_1.BaseController {
966
1077
  * @returns The order result with order ID and status.
967
1078
  */
968
1079
  async placeOrder(params) {
969
- const provider = this.getActiveProvider();
1080
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
970
1081
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
971
1082
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").placeOrder({
972
1083
  provider,
@@ -985,7 +1096,7 @@ class PerpsController extends base_controller_1.BaseController {
985
1096
  * @returns The updated order result with order ID and status.
986
1097
  */
987
1098
  async editOrder(params) {
988
- const provider = this.getActiveProvider();
1099
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
989
1100
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
990
1101
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").editOrder({
991
1102
  provider,
@@ -1000,7 +1111,7 @@ class PerpsController extends base_controller_1.BaseController {
1000
1111
  * @returns The cancellation result with status.
1001
1112
  */
1002
1113
  async cancelOrder(params) {
1003
- const provider = this.getActiveProvider();
1114
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1004
1115
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").cancelOrder({
1005
1116
  provider,
1006
1117
  params,
@@ -1015,7 +1126,7 @@ class PerpsController extends base_controller_1.BaseController {
1015
1126
  * @returns The batch cancellation results for each order.
1016
1127
  */
1017
1128
  async cancelOrders(params) {
1018
- const provider = this.getActiveProvider();
1129
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1019
1130
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").cancelOrders({
1020
1131
  provider,
1021
1132
  params,
@@ -1033,7 +1144,7 @@ class PerpsController extends base_controller_1.BaseController {
1033
1144
  * @returns The order result from the close position request.
1034
1145
  */
1035
1146
  async closePosition(params) {
1036
- const provider = this.getActiveProvider();
1147
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1037
1148
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1038
1149
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").closePosition({
1039
1150
  provider,
@@ -1052,7 +1163,7 @@ class PerpsController extends base_controller_1.BaseController {
1052
1163
  * @returns The batch close results for each position.
1053
1164
  */
1054
1165
  async closePositions(params) {
1055
- const provider = this.getActiveProvider();
1166
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1056
1167
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1057
1168
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").closePositions({
1058
1169
  provider,
@@ -1069,7 +1180,7 @@ class PerpsController extends base_controller_1.BaseController {
1069
1180
  * @returns The order result from the TP/SL update.
1070
1181
  */
1071
1182
  async updatePositionTPSL(params) {
1072
- const provider = this.getActiveProvider();
1183
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1073
1184
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1074
1185
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").updatePositionTPSL({
1075
1186
  provider,
@@ -1084,7 +1195,7 @@ class PerpsController extends base_controller_1.BaseController {
1084
1195
  * @returns The margin update result.
1085
1196
  */
1086
1197
  async updateMargin(params) {
1087
- const provider = this.getActiveProvider();
1198
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1088
1199
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1089
1200
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").updateMargin({
1090
1201
  provider,
@@ -1100,7 +1211,7 @@ class PerpsController extends base_controller_1.BaseController {
1100
1211
  * @returns The order result from the position flip.
1101
1212
  */
1102
1213
  async flipPosition(params) {
1103
- const provider = this.getActiveProvider();
1214
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1104
1215
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1105
1216
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").flipPosition({
1106
1217
  provider,
@@ -1122,10 +1233,7 @@ class PerpsController extends base_controller_1.BaseController {
1122
1233
  const { amount, placeOrder } = params;
1123
1234
  let currentDepositId;
1124
1235
  try {
1125
- // Clear any stale results when starting a new deposit flow
1126
- // Don't set depositInProgress yet - wait until user confirms
1127
- // Prepare deposit transaction using DepositService
1128
- const provider = this.getActiveProvider();
1236
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1129
1237
  const { transaction, assetChainId, currentDepositId: depositId, } = await __classPrivateFieldGet(this, _PerpsController_depositService, "f").prepareTransaction({ provider });
1130
1238
  currentDepositId = depositId;
1131
1239
  // Get current account address via messenger (outside of update() for proper typing)
@@ -1515,7 +1623,7 @@ class PerpsController extends base_controller_1.BaseController {
1515
1623
  * @returns WithdrawResult with withdrawal ID and tracking info
1516
1624
  */
1517
1625
  async withdraw(params) {
1518
- const provider = this.getActiveProvider();
1626
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1519
1627
  return __classPrivateFieldGet(this, _PerpsController_accountService, "f").withdraw({
1520
1628
  provider,
1521
1629
  params,
@@ -1543,7 +1651,10 @@ class PerpsController extends base_controller_1.BaseController {
1543
1651
  // Fallback to cached standalone provider for pre-initialization discovery
1544
1652
  // TODO: When adding new providers (MYX), consider a provider factory pattern
1545
1653
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1546
- return provider.getPositions(params);
1654
+ const operation = provider.getPositions(params);
1655
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1656
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1657
+ : operation;
1547
1658
  }
1548
1659
  const provider = this.getActiveProvider();
1549
1660
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getPositions({
@@ -1604,7 +1715,10 @@ class PerpsController extends base_controller_1.BaseController {
1604
1715
  // For standalone mode, access provider directly without initialization check
1605
1716
  if (params?.standalone && params.userAddress) {
1606
1717
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1607
- return provider.getOpenOrders(params);
1718
+ const operation = provider.getOpenOrders(params);
1719
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1720
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1721
+ : operation;
1608
1722
  }
1609
1723
  const provider = this.getActiveProvider();
1610
1724
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getOpenOrders({
@@ -1649,7 +1763,10 @@ class PerpsController extends base_controller_1.BaseController {
1649
1763
  // Use activeProviderInstance if available (respects provider abstraction)
1650
1764
  // Fallback to cached standalone provider for pre-initialization discovery
1651
1765
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1652
- return provider.getAccountState(params);
1766
+ const operation = provider.getAccountState(params);
1767
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1768
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1769
+ : operation;
1653
1770
  }
1654
1771
  const provider = this.getActiveProvider();
1655
1772
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getAccountState({
@@ -1687,12 +1804,15 @@ class PerpsController extends base_controller_1.BaseController {
1687
1804
  const isMarketAllowed = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this);
1688
1805
  if (params?.standalone) {
1689
1806
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1690
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1807
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1691
1808
  provider,
1692
1809
  params,
1693
1810
  context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarkets'),
1694
1811
  isMarketAllowed,
1695
1812
  });
1813
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1814
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1815
+ : operation;
1696
1816
  }
1697
1817
  const provider = this.getActiveProvider();
1698
1818
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
@@ -1718,19 +1838,26 @@ class PerpsController extends base_controller_1.BaseController {
1718
1838
  * @returns A promise that resolves to the market data.
1719
1839
  */
1720
1840
  async getMarketDataWithPrices(params) {
1841
+ const globalSnapshot = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildGlobalSnapshotContext).call(this);
1842
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices', {
1843
+ ...(globalSnapshot && { globalSnapshot }),
1844
+ });
1721
1845
  if (params?.standalone) {
1722
1846
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1723
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1847
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1724
1848
  provider,
1725
1849
  params,
1726
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1850
+ context,
1727
1851
  });
1852
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1853
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1854
+ : operation;
1728
1855
  }
1729
1856
  const provider = this.getActiveProvider();
1730
1857
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1731
1858
  provider,
1732
1859
  params,
1733
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1860
+ context,
1734
1861
  });
1735
1862
  }
1736
1863
  /**
@@ -1751,11 +1878,17 @@ class PerpsController extends base_controller_1.BaseController {
1751
1878
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1752
1879
  /* fire-and-forget */
1753
1880
  });
1881
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1882
+ /* fire-and-forget */
1883
+ });
1754
1884
  // Periodic refresh
1755
1885
  __classPrivateFieldSet(this, _PerpsController_preloadTimer, setInterval(() => {
1756
1886
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1757
1887
  /* fire-and-forget */
1758
1888
  });
1889
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1890
+ /* fire-and-forget */
1891
+ });
1759
1892
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1760
1893
  // Watch for isTestnet / hip3ConfigVersion changes
1761
1894
  const handler = (_state, patches) => {
@@ -1784,36 +1917,29 @@ class PerpsController extends base_controller_1.BaseController {
1784
1917
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1785
1918
  /* fire-and-forget */
1786
1919
  });
1920
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1921
+ /* fire-and-forget */
1922
+ });
1787
1923
  }
1788
1924
  };
1789
- this.messenger.subscribe('PerpsController:stateChange', handler);
1925
+ this.messenger.subscribe('PerpsController:stateChanged', handler);
1790
1926
  __classPrivateFieldSet(this, _PerpsController_preloadStateUnsubscribe, () => {
1791
- this.messenger.unsubscribe('PerpsController:stateChange', handler);
1927
+ this.messenger.unsubscribe('PerpsController:stateChanged', handler);
1792
1928
  }, "f");
1793
1929
  // Watch for selected account changes and selected account group changes.
1794
1930
  const accountChangeHandler = () => {
1795
1931
  const evmAccount = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger);
1796
1932
  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(() => {
1933
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: account cache selection', {
1934
+ address: currentAddress?.toLowerCase() ?? null,
1935
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
1936
+ });
1937
+ // The address guard makes the previous entry unreadable immediately;
1938
+ // refresh replaces it under the existing provider/network key.
1939
+ if (currentAddress) {
1940
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1809
1941
  /* fire-and-forget */
1810
1942
  });
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
1943
  }
1818
1944
  };
1819
1945
  this.messenger.subscribe('AccountsController:selectedAccountChange', accountChangeHandler);
@@ -1842,6 +1968,8 @@ class PerpsController extends base_controller_1.BaseController {
1842
1968
  }
1843
1969
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, null, "f");
1844
1970
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, null, "f");
1971
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
1972
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
1845
1973
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this).catch(() => {
1846
1974
  /* fire-and-forget to preserve sync signature */
1847
1975
  });
@@ -2102,6 +2230,9 @@ class PerpsController extends base_controller_1.BaseController {
2102
2230
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2103
2231
  /* fire-and-forget */
2104
2232
  });
2233
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2234
+ /* fire-and-forget */
2235
+ });
2105
2236
  }
2106
2237
  }
2107
2238
  }
@@ -2211,6 +2342,9 @@ class PerpsController extends base_controller_1.BaseController {
2211
2342
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2212
2343
  /* fire-and-forget */
2213
2344
  });
2345
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2346
+ /* fire-and-forget */
2347
+ });
2214
2348
  }
2215
2349
  }
2216
2350
  }
@@ -2546,9 +2680,41 @@ class PerpsController extends base_controller_1.BaseController {
2546
2680
  */
2547
2681
  async calculateFees(params) {
2548
2682
  const provider = this.getActiveProvider();
2549
- const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'calculateFees');
2683
+ // Preview owns subscription hydration. The submit resolver remains a pure
2684
+ // cache read and can therefore never start a benefits request while an
2685
+ // order is being signed.
2686
+ await __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").refreshSubscriptionBenefits();
2687
+ const waiverStatus = __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").getSubscriptionFeeWaiverStatus();
2688
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'calculateFees', {
2689
+ subscriptionFeeWaiver: waiverStatus.reason === 'no-source' ? undefined : waiverStatus,
2690
+ });
2550
2691
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").calculateFees({ provider, params, context });
2551
2692
  }
2693
+ /**
2694
+ * Approve the dedicated subscription builder outside order submission.
2695
+ * Until this succeeds, subscription waivers fall back to the ordinary
2696
+ * builder at the standard fee.
2697
+ *
2698
+ * @returns Whether the subscription builder is approved.
2699
+ */
2700
+ async approveSubscriptionBuilderFee() {
2701
+ const provider = this.getActiveProvider();
2702
+ return provider.approveSubscriptionBuilderFee
2703
+ ? provider.approveSubscriptionBuilderFee()
2704
+ : false;
2705
+ }
2706
+ /**
2707
+ * Drop the cached subscription benefits snapshot.
2708
+ *
2709
+ * Call this when the identity behind the benefits changes — sign-out, or a
2710
+ * profile switch. The snapshot carries no profile identity of its own, so
2711
+ * without this it keeps answering for the previous profile until the next
2712
+ * successful refresh. The next fee resolution reports the waiver as
2713
+ * unavailable, so it is withheld until preview or lifecycle hydration.
2714
+ */
2715
+ invalidateSubscriptionBenefits() {
2716
+ __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").invalidateSubscriptionBenefits();
2717
+ }
2552
2718
  /**
2553
2719
  * Disconnect provider and cleanup subscriptions
2554
2720
  * Call this when navigating away from Perps screens to prevent battery drain
@@ -3274,7 +3440,7 @@ class PerpsController extends base_controller_1.BaseController {
3274
3440
  }
3275
3441
  }
3276
3442
  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() {
3443
+ _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
3444
  const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
3279
3445
  // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
3280
3446
  // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
@@ -3307,6 +3473,16 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3307
3473
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.logger.error(error, options);
3308
3474
  }, _PerpsController_debugLog = function _PerpsController_debugLog(...args) {
3309
3475
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.debugLogger.log(...args);
3476
+ }, _PerpsController_awaitInitializationIfInProgress =
3477
+ /**
3478
+ * Awaits the in-flight initialization promise if init is currently running.
3479
+ * Called internally by #getActiveProviderWhenReady().
3480
+ */
3481
+ async function _PerpsController_awaitInitializationIfInProgress() {
3482
+ if (this.state.initializationState === InitializationState.Initializing &&
3483
+ __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f")) {
3484
+ await __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f");
3485
+ }
3310
3486
  }, _PerpsController_getAggregatedCacheProviderIds = function _PerpsController_getAggregatedCacheProviderIds(cacheKeys) {
3311
3487
  const providerIds = new Set();
3312
3488
  const currentNetwork = this.state.isTestnet ? 'testnet' : 'mainnet';
@@ -3327,6 +3503,96 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3327
3503
  }
3328
3504
  }
3329
3505
  return Array.from(providerIds);
3506
+ }, _PerpsController_isMarketCacheEntryCurrent = function _PerpsController_isMarketCacheEntryCurrent(providerId, entry, options) {
3507
+ if (entry.sourceExpiresAt !== undefined) {
3508
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3509
+ return (providerId === 'hyperliquid' &&
3510
+ Date.now() < entry.sourceExpiresAt &&
3511
+ entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3512
+ expectedDexes !== undefined &&
3513
+ Array.isArray(entry.dexes) &&
3514
+ entry.dexes.length === expectedDexes.length &&
3515
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3516
+ }
3517
+ return (options?.skipTTL === true ||
3518
+ Date.now() - entry.timestamp <= __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10);
3519
+ }, _PerpsController_isUserCacheIdentityCurrent = function _PerpsController_isUserCacheIdentityCurrent(providerId, entry, address) {
3520
+ if (entry.address.toLowerCase() !== address.toLowerCase()) {
3521
+ return false;
3522
+ }
3523
+ if (providerId !== 'hyperliquid') {
3524
+ return true;
3525
+ }
3526
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3527
+ return (entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3528
+ expectedDexes !== undefined &&
3529
+ Array.isArray(entry.dexes) &&
3530
+ entry.dexes.length === expectedDexes.length &&
3531
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3532
+ }, _PerpsController_fetchAndCacheUserDataSnapshot = async function _PerpsController_fetchAndCacheUserDataSnapshot(context) {
3533
+ const { provider, standaloneProvider, address, isTestnet, hip3ConfigVersion, expectedDexes, isCurrent, } = context;
3534
+ if (!isCurrent()) {
3535
+ throw new Error('User data snapshot context changed');
3536
+ }
3537
+ if (!provider.getUserDataSnapshot) {
3538
+ throw new Error('Provider has no atomic snapshot API');
3539
+ }
3540
+ const identity = {
3541
+ provider: 'hyperliquid',
3542
+ network: isTestnet ? 'testnet' : 'mainnet',
3543
+ hip3ConfigVersion,
3544
+ dexes: expectedDexes,
3545
+ };
3546
+ const snapshotRequest = provider.getUserDataSnapshot({
3547
+ userAddress: address,
3548
+ identity,
3549
+ });
3550
+ const snapshot = standaloneProvider
3551
+ ? await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, standaloneProvider, snapshotRequest)
3552
+ : await snapshotRequest;
3553
+ if (!isCurrent()) {
3554
+ throw new Error('User data snapshot context changed');
3555
+ }
3556
+ const snapshotIdentity = snapshot.identity;
3557
+ const hasCompleteBundle = Array.isArray(snapshot.positions) &&
3558
+ Array.isArray(snapshot.orders) &&
3559
+ snapshot.accountState !== null &&
3560
+ typeof snapshot.accountState === 'object';
3561
+ const hasExactIdentity = snapshotIdentity.provider === identity.provider &&
3562
+ snapshotIdentity.network === identity.network &&
3563
+ snapshotIdentity.hip3ConfigVersion === identity.hip3ConfigVersion &&
3564
+ snapshotIdentity.address.toLowerCase() === address.toLowerCase() &&
3565
+ snapshotIdentity.dexes.length === expectedDexes.length &&
3566
+ snapshotIdentity.dexes.every((dex, index) => dex === expectedDexes[index]);
3567
+ if (!hasCompleteBundle || !hasExactIdentity) {
3568
+ throw new Error('User data snapshot is incomplete or mismatched');
3569
+ }
3570
+ if (!isCurrent()) {
3571
+ throw new Error('User data snapshot context changed');
3572
+ }
3573
+ const cachedSnapshot = cloneUserDataSnapshot(snapshot);
3574
+ const result = cloneUserDataSnapshot(snapshot);
3575
+ const timestamp = Date.now();
3576
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)('hyperliquid', isTestnet);
3577
+ this.update((state) => {
3578
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3579
+ positions: cachedSnapshot.positions,
3580
+ orders: cachedSnapshot.orders,
3581
+ accountState: cachedSnapshot.accountState,
3582
+ timestamp,
3583
+ address,
3584
+ hip3ConfigVersion,
3585
+ dexes: expectedDexes,
3586
+ };
3587
+ });
3588
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3589
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache snapshot written', {
3590
+ writtenKey: providerNetworkKey,
3591
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
3592
+ positionCount: cachedSnapshot.positions.length,
3593
+ orderCount: cachedSnapshot.orders.length,
3594
+ });
3595
+ return result;
3330
3596
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
3331
3597
  const currentIsTestnet = this.state.isTestnet;
3332
3598
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -3335,10 +3601,13 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3335
3601
  __classPrivateFieldGet(this, _PerpsController_standaloneProviderHip3Version, "f") === currentHip3Version) {
3336
3602
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3337
3603
  }
3338
- // Stale or missing — tear down old one (fire-and-forget)
3604
+ // Stale or missing — retire the old provider after active operations finish.
3339
3605
  if (__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3340
3606
  const old = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3341
- Promise.resolve(old.disconnect()).catch(() => {
3607
+ __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3608
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3609
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3610
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, old).catch(() => {
3342
3611
  /* best-effort */
3343
3612
  });
3344
3613
  }
@@ -3354,28 +3623,53 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3354
3623
  ?.builderAddressTestnet,
3355
3624
  builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3356
3625
  ?.builderAddressMainnet,
3626
+ subscriptionBuilderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3627
+ ?.subscriptionBuilderAddressTestnet,
3628
+ subscriptionBuilderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3629
+ ?.subscriptionBuilderAddressMainnet,
3357
3630
  }), "f");
3358
3631
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
3359
3632
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
3360
3633
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3634
+ }, _PerpsController_trackStandaloneProviderOperation = function _PerpsController_trackStandaloneProviderOperation(provider, operation) {
3635
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider) ?? new Set();
3636
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").set(provider, operations);
3637
+ const trackedOperation = operation.finally(() => {
3638
+ operations.delete(trackedOperation);
3639
+ if (operations.size === 0) {
3640
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3641
+ }
3642
+ });
3643
+ operations.add(trackedOperation);
3644
+ return trackedOperation;
3645
+ }, _PerpsController_retireStandaloneProvider = async function _PerpsController_retireStandaloneProvider(provider) {
3646
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider);
3647
+ if (operations?.size) {
3648
+ await Promise.allSettled([...operations]);
3649
+ }
3650
+ try {
3651
+ await provider.disconnect();
3652
+ }
3653
+ catch {
3654
+ /* best-effort */
3655
+ }
3656
+ finally {
3657
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3658
+ }
3361
3659
  }, _PerpsController_cleanupStandaloneProvider =
3362
3660
  /**
3363
3661
  * Disconnect and discard the cached standalone provider (if any).
3364
3662
  * Best-effort — errors are silently caught.
3365
3663
  */
3366
3664
  async function _PerpsController_cleanupStandaloneProvider() {
3367
- if (!__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3665
+ const provider = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3666
+ if (!provider) {
3368
3667
  return;
3369
3668
  }
3370
- try {
3371
- await __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f").disconnect();
3372
- }
3373
- catch {
3374
- /* best-effort */
3375
- }
3376
3669
  __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3377
3670
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3378
3671
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3672
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, provider);
3379
3673
  }, _PerpsController_getMetrics = function _PerpsController_getMetrics() {
3380
3674
  return __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.metrics;
3381
3675
  }, _PerpsController_findNetworkClientIdForChain = function _PerpsController_findNetworkClientIdForChain(chainId) {
@@ -3593,6 +3887,10 @@ async function _PerpsController_performInitialization() {
3593
3887
  ?.builderAddressTestnet,
3594
3888
  builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3595
3889
  ?.builderAddressMainnet,
3890
+ subscriptionBuilderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3891
+ ?.subscriptionBuilderAddressTestnet,
3892
+ subscriptionBuilderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3893
+ ?.subscriptionBuilderAddressMainnet,
3596
3894
  });
3597
3895
  this.providers.set('hyperliquid', hyperLiquidProvider);
3598
3896
  // Register MYX provider if enabled via feature flag.
@@ -3709,6 +4007,75 @@ async function _PerpsController_performInitialization() {
3709
4007
  __classPrivateFieldGet(this, _PerpsController_tradingService, "f").setControllerDependencies({
3710
4008
  rewardsIntegrationService: __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f"),
3711
4009
  });
4010
+ }, _PerpsController_getActiveProviderWhenReady =
4011
+ /**
4012
+ * Await in-flight initialization, then return the active provider.
4013
+ * Use for async action methods (trading, deposits, withdrawals) that should
4014
+ * tolerate an in-progress cold-start or reconnection instead of failing
4015
+ * immediately with CLIENT_NOT_INITIALIZED.
4016
+ *
4017
+ * Synchronous callers that need fail-fast behaviour should keep using
4018
+ * getActiveProvider() directly.
4019
+ *
4020
+ * @returns The active provider once initialization completes.
4021
+ */
4022
+ async function _PerpsController_getActiveProviderWhenReady() {
4023
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_awaitInitializationIfInProgress).call(this);
4024
+ return this.getActiveProvider();
4025
+ }, _PerpsController_buildGlobalSnapshotContext = function _PerpsController_buildGlobalSnapshotContext() {
4026
+ const snapshotConfigured = Boolean(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalApi?.globalSnapshotUrl) ||
4027
+ typeof __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalMarketService
4028
+ ?.fetchGlobalSnapshot === 'function';
4029
+ if (!snapshotConfigured || this.state.activeProvider !== 'hyperliquid') {
4030
+ return undefined;
4031
+ }
4032
+ const enabledDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4033
+ if (!enabledDexes) {
4034
+ return undefined;
4035
+ }
4036
+ const { isTestnet, hip3ConfigVersion } = this.state;
4037
+ return {
4038
+ request: {
4039
+ provider: 'hyperliquid',
4040
+ network: isTestnet ? 'testnet' : 'mainnet',
4041
+ enabledDexes,
4042
+ },
4043
+ isCurrent: () => this.state.activeProvider === 'hyperliquid' &&
4044
+ this.state.isTestnet === isTestnet &&
4045
+ this.state.hip3ConfigVersion === hip3ConfigVersion,
4046
+ isMarketAllowed: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this),
4047
+ };
4048
+ }, _PerpsController_getStaticSnapshotDexes = function _PerpsController_getStaticSnapshotDexes() {
4049
+ if (!__classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f")) {
4050
+ return ['main'];
4051
+ }
4052
+ if (this.state.isTestnet) {
4053
+ return hyperLiquidConfig_js_1.TESTNET_HIP3_CONFIG.AutoDiscoverAll
4054
+ ? undefined
4055
+ : (0, hyperLiquidConfig_js_1.canonicalizeHyperLiquidDexes)(hyperLiquidConfig_js_1.TESTNET_HIP3_CONFIG.EnabledDexs);
4056
+ }
4057
+ if (hyperLiquidConfig_js_1.MAINNET_HIP3_CONFIG.AutoDiscoverAll) {
4058
+ return undefined;
4059
+ }
4060
+ const dexes = new Set();
4061
+ for (const pattern of __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f")) {
4062
+ const colonIndex = pattern.indexOf(':');
4063
+ if (colonIndex <= 0) {
4064
+ if (/^[a-z][a-z0-9]*$/iu.test(pattern)) {
4065
+ dexes.add(pattern.toLowerCase());
4066
+ continue;
4067
+ }
4068
+ return undefined;
4069
+ }
4070
+ const dex = pattern.slice(0, colonIndex);
4071
+ if (dex && /^[a-z0-9][a-z0-9-]*$/u.test(dex)) {
4072
+ dexes.add(dex);
4073
+ }
4074
+ else {
4075
+ return undefined;
4076
+ }
4077
+ }
4078
+ return (0, hyperLiquidConfig_js_1.canonicalizeHyperLiquidDexes)(dexes);
3712
4079
  }, _PerpsController_hydrateCacheFromDiskSync = function _PerpsController_hydrateCacheFromDiskSync() {
3713
4080
  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
4081
  const hasMarketUpdates = Object.keys(marketUpdates).length > 0;
@@ -3729,12 +4096,40 @@ async function _PerpsController_performInitialization() {
3729
4096
  orders: stats.userOrders,
3730
4097
  duration_ms: stats.durationMs,
3731
4098
  });
4099
+ }, _PerpsController_persistUserCacheToDisk = function _PerpsController_persistUserCacheToDisk() {
4100
+ const entries = [];
4101
+ for (const [cacheKey, entry] of Object.entries(this.state.cachedUserDataByProvider)) {
4102
+ const [providerId, network] = cacheKey.split(':');
4103
+ if (!providerId ||
4104
+ (network !== 'mainnet' && network !== 'testnet') ||
4105
+ providerId === 'aggregated') {
4106
+ continue;
4107
+ }
4108
+ entries.push({
4109
+ providerNetworkKey: `${providerId}:${network}`,
4110
+ address: entry.address,
4111
+ positions: entry.positions,
4112
+ orders: entry.orders,
4113
+ accountState: entry.accountState,
4114
+ timestamp: entry.timestamp,
4115
+ ...(entry.hip3ConfigVersion !== undefined && {
4116
+ hip3ConfigVersion: entry.hip3ConfigVersion,
4117
+ }),
4118
+ ...(entry.dexes !== undefined && { dexes: entry.dexes }),
4119
+ });
4120
+ }
4121
+ __classPrivateFieldSet(this, _PerpsController_userDiskWrite, __classPrivateFieldGet(this, _PerpsController_userDiskWrite, "f")
4122
+ .then(() => (0, perpsDiskPersistence_js_1.persistUserEntriesToDisk)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, entries))
4123
+ .catch(() => {
4124
+ // Disk persistence is best-effort and must not block live data.
4125
+ }), "f");
3732
4126
  }, _PerpsController_performMarketDataPreload =
3733
4127
  /**
3734
4128
  * Perform a single market data preload (best-effort, no throw).
3735
4129
  */
3736
4130
  async function _PerpsController_performMarketDataPreload() {
3737
4131
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
4132
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, true, "f");
3738
4133
  return;
3739
4134
  }
3740
4135
  // Skip preloading during provider/network reinitialisation.
@@ -3749,9 +4144,16 @@ async function _PerpsController_performMarketDataPreload() {
3749
4144
  ? this.state.activeProvider // includes 'aggregated'
3750
4145
  : 'hyperliquid';
3751
4146
  const cacheKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, this.state.isTestnet);
4147
+ const preloadContext = {
4148
+ activeProvider: this.state.activeProvider,
4149
+ isTestnet: this.state.isTestnet,
4150
+ hip3ConfigVersion: this.state.hip3ConfigVersion,
4151
+ };
4152
+ const staticSnapshotDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3752
4153
  const now = Date.now();
3753
4154
  const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3754
4155
  if (existingEntry &&
4156
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, actualProviderId, existingEntry) &&
3755
4157
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3756
4158
  return;
3757
4159
  }
@@ -3776,8 +4178,32 @@ async function _PerpsController_performMarketDataPreload() {
3776
4178
  duration_ms: Math.round(performance.now() - preloadStart),
3777
4179
  markets: data.length,
3778
4180
  });
4181
+ if (this.state.activeProvider !== preloadContext.activeProvider ||
4182
+ this.state.isTestnet !== preloadContext.isTestnet ||
4183
+ this.state.hip3ConfigVersion !== preloadContext.hip3ConfigVersion) {
4184
+ traceData = {
4185
+ success: false,
4186
+ error: 'Global snapshot preload context changed',
4187
+ };
4188
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Discarding stale global snapshot preload');
4189
+ return;
4190
+ }
3779
4191
  // Store under per-provider key(s)
3780
4192
  const ts = Date.now();
4193
+ const sourceExpiries = data.flatMap((market) => market.dataSource === 'terminal-global-snapshot-mark' &&
4194
+ typeof market.sourceExpiresAt === 'number'
4195
+ ? [market.sourceExpiresAt]
4196
+ : []);
4197
+ const sourceExpiresAt = data.length > 0 && sourceExpiries.length === data.length
4198
+ ? Math.min(...sourceExpiries)
4199
+ : undefined;
4200
+ const snapshotCacheIdentity = sourceExpiresAt !== undefined && staticSnapshotDexes
4201
+ ? {
4202
+ sourceExpiresAt,
4203
+ hip3ConfigVersion: preloadContext.hip3ConfigVersion,
4204
+ dexes: staticSnapshotDexes,
4205
+ }
4206
+ : {};
3781
4207
  const marketDiskEntries = [];
3782
4208
  if (this.state.activeProvider === 'aggregated' &&
3783
4209
  this.activeProviderInstance) {
@@ -3824,6 +4250,7 @@ async function _PerpsController_performMarketDataPreload() {
3824
4250
  state.cachedMarketDataByProvider[cacheKey] = {
3825
4251
  data,
3826
4252
  timestamp: ts,
4253
+ ...snapshotCacheIdentity,
3827
4254
  };
3828
4255
  });
3829
4256
  }
@@ -3833,10 +4260,6 @@ async function _PerpsController_performMarketDataPreload() {
3833
4260
  });
3834
4261
  traceData = { success: true, marketCount: data.length };
3835
4262
  __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
4263
  }
3841
4264
  catch (error) {
3842
4265
  traceData = {
@@ -3855,6 +4278,12 @@ async function _PerpsController_performMarketDataPreload() {
3855
4278
  data: traceData,
3856
4279
  });
3857
4280
  __classPrivateFieldSet(this, _PerpsController_isPreloading, false, "f");
4281
+ if (__classPrivateFieldGet(this, _PerpsController_marketPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4282
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
4283
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
4284
+ // Background preload is best-effort.
4285
+ });
4286
+ }
3858
4287
  }
3859
4288
  }, _PerpsController_performUserDataPreload =
3860
4289
  /**
@@ -3863,6 +4292,10 @@ async function _PerpsController_performMarketDataPreload() {
3863
4292
  */
3864
4293
  async function _PerpsController_performUserDataPreload() {
3865
4294
  if (__classPrivateFieldGet(this, _PerpsController_isPreloadingUserData, "f")) {
4295
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, true, "f");
4296
+ return;
4297
+ }
4298
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3866
4299
  return;
3867
4300
  }
3868
4301
  // Get current user address
@@ -3871,21 +4304,54 @@ async function _PerpsController_performUserDataPreload() {
3871
4304
  return;
3872
4305
  }
3873
4306
  const userAddress = evmAccount.address;
4307
+ const { activeProvider, isTestnet, hip3ConfigVersion } = this.state;
4308
+ const { activeProviderInstance } = this;
4309
+ const hyperliquidDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4310
+ const isCurrent = () => {
4311
+ let currentAddress;
4312
+ try {
4313
+ currentAddress = (0, accountUtils_js_1.getSelectedEvmAccountFromMessenger)(this.messenger)?.address;
4314
+ }
4315
+ catch {
4316
+ return false;
4317
+ }
4318
+ return (this.state.activeProvider === activeProvider &&
4319
+ this.activeProviderInstance === activeProviderInstance &&
4320
+ this.state.isTestnet === isTestnet &&
4321
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
4322
+ currentAddress?.toLowerCase() === userAddress.toLowerCase());
4323
+ };
3874
4324
  // Determine actual provider (same logic as market preload)
3875
- const actualProviderId = this.activeProviderInstance
3876
- ? this.state.activeProvider // includes 'aggregated'
4325
+ const actualProviderId = activeProviderInstance
4326
+ ? activeProvider // includes 'aggregated'
3877
4327
  : 'hyperliquid';
3878
- const userCacheKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, this.state.isTestnet);
4328
+ const providerNetworkKey = (0, perpsConfig_js_1.buildProviderCacheKey)(actualProviderId, isTestnet);
3879
4329
  // Skip if cache is fresh and for same account
3880
4330
  const now = Date.now();
3881
- const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3882
- if (existingEntry?.address === userAddress &&
4331
+ const existingEntry = this.state.cachedUserDataByProvider[providerNetworkKey];
4332
+ const hasMatchingCache = existingEntry !== undefined &&
4333
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, actualProviderId, existingEntry, userAddress);
4334
+ const cacheAgeMs = existingEntry ? now - existingEntry.timestamp : null;
4335
+ const websocketState = this.getWebSocketConnectionState();
4336
+ let selectedEntryKey = null;
4337
+ if (this.state.cachedUserDataByProvider[providerNetworkKey]) {
4338
+ selectedEntryKey = providerNetworkKey;
4339
+ }
4340
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache preload decision', {
4341
+ requestedKey: providerNetworkKey,
4342
+ selectedEntryKey,
4343
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
4344
+ hasMatchingCache,
4345
+ cacheAgeMs,
4346
+ websocketState,
4347
+ });
4348
+ if (existingEntry &&
4349
+ hasMatchingCache &&
3883
4350
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3884
4351
  return;
3885
4352
  }
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');
4353
+ if (hasMatchingCache &&
4354
+ this.getWebSocketConnectionState() === index_js_1.WebSocketConnectionState.Connected) {
3889
4355
  return;
3890
4356
  }
3891
4357
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, true, "f");
@@ -3898,21 +4364,38 @@ async function _PerpsController_performUserDataPreload() {
3898
4364
  id: traceId,
3899
4365
  op: index_js_1.PerpsTraceOperations.Operation,
3900
4366
  tags: {
3901
- provider: this.state.activeProvider,
3902
- isTestnet: this.state.isTestnet,
4367
+ provider: activeProvider,
4368
+ isTestnet,
3903
4369
  },
3904
4370
  data: { userAddress },
3905
4371
  });
3906
4372
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching user data in background', {
3907
4373
  userAddress,
3908
4374
  });
4375
+ if (activeProvider === 'hyperliquid') {
4376
+ const snapshot = await this.getUserDataSnapshot();
4377
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
4378
+ positionCount: snapshot.positions.length,
4379
+ orderCount: snapshot.orders.length,
4380
+ totalBalance: snapshot.accountState.totalBalance,
4381
+ });
4382
+ traceData = {
4383
+ success: true,
4384
+ positionCount: snapshot.positions.length,
4385
+ orderCount: snapshot.orders.length,
4386
+ };
4387
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(performanceMetrics_js_1.PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond');
4388
+ return;
4389
+ }
3909
4390
  const [positions, orders, accountState] = await Promise.all([
3910
4391
  this.getPositions({ standalone: true, userAddress }),
3911
4392
  this.getOpenOrders({ standalone: true, userAddress }),
3912
4393
  this.getAccountState({ standalone: true, userAddress }),
3913
4394
  ]);
3914
- if (this.state.activeProvider === 'aggregated' &&
3915
- this.activeProviderInstance) {
4395
+ if (!isCurrent()) {
4396
+ throw new Error('User data preload context changed');
4397
+ }
4398
+ if (activeProvider === 'aggregated' && activeProviderInstance) {
3916
4399
  // Split by providerId and write one cache entry per provider key
3917
4400
  // (mirrors the market-data preload pattern at ~line 2976)
3918
4401
  const ts = Date.now();
@@ -3934,26 +4417,22 @@ async function _PerpsController_performUserDataPreload() {
3934
4417
  }
3935
4418
  // AccountState — assign to its provider bucket
3936
4419
  ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3937
- const diskEntries = [];
3938
4420
  this.update((state) => {
3939
4421
  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
- });
4422
+ const key = (0, perpsConfig_js_1.buildProviderCacheKey)(pid, isTestnet);
3949
4423
  state.cachedUserDataByProvider[key] = {
3950
4424
  ...data,
3951
4425
  timestamp: ts,
3952
4426
  address: userAddress,
4427
+ ...(pid === 'hyperliquid' &&
4428
+ hyperliquidDexes && {
4429
+ hip3ConfigVersion,
4430
+ dexes: hyperliquidDexes,
4431
+ }),
3953
4432
  };
3954
4433
  }
3955
4434
  // Write aggregated sentinel so the staleness guard sees it
3956
- state.cachedUserDataByProvider[userCacheKey] = {
4435
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3957
4436
  positions: [],
3958
4437
  orders: [],
3959
4438
  accountState: null,
@@ -3961,30 +4440,26 @@ async function _PerpsController_performUserDataPreload() {
3961
4440
  address: userAddress,
3962
4441
  };
3963
4442
  });
3964
- (0, perpsDiskPersistence_js_1.persistUserEntriesToDisk)(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, diskEntries);
4443
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3965
4444
  }
3966
4445
  else {
3967
4446
  // Single provider — store directly under its key
3968
4447
  const ts = Date.now();
3969
4448
  this.update((state) => {
3970
- state.cachedUserDataByProvider[userCacheKey] = {
4449
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3971
4450
  positions,
3972
4451
  orders,
3973
4452
  accountState,
3974
4453
  timestamp: ts,
3975
4454
  address: userAddress,
4455
+ ...(actualProviderId === 'hyperliquid' &&
4456
+ hyperliquidDexes && {
4457
+ hip3ConfigVersion,
4458
+ dexes: hyperliquidDexes,
4459
+ }),
3976
4460
  };
3977
4461
  });
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
- ]);
4462
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3988
4463
  }
3989
4464
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3990
4465
  positionCount: positions.length,
@@ -4015,6 +4490,12 @@ async function _PerpsController_performUserDataPreload() {
4015
4490
  data: traceData,
4016
4491
  });
4017
4492
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, false, "f");
4493
+ if (__classPrivateFieldGet(this, _PerpsController_userPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4494
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
4495
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
4496
+ // Background preload is best-effort.
4497
+ });
4498
+ }
4018
4499
  }
4019
4500
  }, _PerpsController_persistWatchlistToRemote =
4020
4501
  /**