@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
@@ -9,15 +9,15 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _PerpsController_instances, _a, _PerpsController_initializationPromise, _PerpsController_isReinitializing, _PerpsController_myxRegistrationPromise, _PerpsController_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_priceDeviationLimit, _PerpsController_attributionContext, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_handlersRegistered, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_eligibilityCheckDeferred, _PerpsController_ausQueue, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_getAggregatedCacheProviderIds, _PerpsController_getOrCreateStandaloneProvider, _PerpsController_cleanupStandaloneProvider, _PerpsController_getMetrics, _PerpsController_findNetworkClientIdForChain, _PerpsController_submitTransaction, _PerpsController_migrateRequestsIfNeeded, _PerpsController_withStreamPause, _PerpsController_performInitialization, _PerpsController_createProviders, _PerpsController_assignActiveProvider, _PerpsController_getErrorContext, _PerpsController_getControllerState, _PerpsController_buildMarketAllowedFilter, _PerpsController_compilePatternsSafely, _PerpsController_createServiceContext, _PerpsController_ensureTradingServiceDeps, _PerpsController_preloadWatchedPaths, _PerpsController_preloadTimer, _PerpsController_isPreloading, _PerpsController_isPreloadingUserData, _PerpsController_preloadStateUnsubscribe, _PerpsController_accountChangeUnsubscribe, _PerpsController_previousIsTestnet, _PerpsController_previousHip3ConfigVersion, _PerpsController_preloadRefreshMs, _PerpsController_preloadGuardMs, _PerpsController_hydrateCacheFromDiskSync, _PerpsController_performMarketDataPreload, _PerpsController_performUserDataPreload, _PerpsController_persistWatchlistToRemote, _PerpsController_syncWatchlistFromRemote;
12
+ var _PerpsController_instances, _a, _PerpsController_initializationPromise, _PerpsController_isReinitializing, _PerpsController_myxRegistrationPromise, _PerpsController_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_priceDeviationLimit, _PerpsController_attributionContext, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_handlersRegistered, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_standaloneProviderOperations, _PerpsController_eligibilityCheckDeferred, _PerpsController_ausQueue, _PerpsController_userDiskWrite, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_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;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { ORIGIN_METAMASK } from "@metamask/controller-utils";
15
15
  import { v4 as uuidv4 } from "uuid";
16
16
  import { CandlePeriod } from "./constants/chartConfig.mjs";
17
17
  import { PERPS_EVENT_PROPERTY, PERPS_EVENT_VALUE } from "./constants/eventNames.mjs";
18
- import { USDC_SYMBOL } from "./constants/hyperLiquidConfig.mjs";
18
+ import { canonicalizeHyperLiquidDexes, MAINNET_HIP3_CONFIG, TESTNET_HIP3_CONFIG, USDC_SYMBOL } from "./constants/hyperLiquidConfig.mjs";
19
19
  import { PerpsMeasurementName } from "./constants/performanceMetrics.mjs";
20
- import { PERPS_CONSTANTS, MARKET_SORTING_CONFIG, PROVIDER_CONFIG, PERPS_DISK_CACHE_USER_DATA, buildProviderCacheKey, MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES } from "./constants/perpsConfig.mjs";
20
+ import { PERPS_CONSTANTS, MARKET_SORTING_CONFIG, PROVIDER_CONFIG, buildProviderCacheKey, MAX_SLIPPAGE_BOUNDS, DEFAULT_PERPS_MODE, DEFAULT_PRO_LAYOUT_PREFERENCES } from "./constants/perpsConfig.mjs";
21
21
  import { PERPS_ERROR_CODES } from "./perpsErrorCodes.mjs";
22
22
  import { AggregatedPerpsProvider } from "./providers/AggregatedPerpsProvider.mjs";
23
23
  import { HyperLiquidProvider } from "./providers/HyperLiquidProvider.mjs";
@@ -35,9 +35,39 @@ import { WebSocketConnectionState, PerpsAnalyticsEvent, PerpsTraceNames, PerpsTr
35
35
  import { getSelectedEvmAccountFromMessenger } from "./utils/accountUtils.mjs";
36
36
  import { ensureError } from "./utils/errorUtils.mjs";
37
37
  import { parseAssetName } from "./utils/hyperLiquidAdapter.mjs";
38
- import { compileMarketPattern, shouldIncludeMarket } from "./utils/marketUtils.mjs";
38
+ import { clonePerpsMarketData, compileMarketPattern, shouldIncludeMarket } from "./utils/marketUtils.mjs";
39
39
  import { hydrateFromDiskSync, persistMarketEntriesToDisk, persistUserEntriesToDisk } from "./utils/perpsDiskPersistence.mjs";
40
40
  import { wait } from "./utils/wait.mjs";
41
+ function cloneUserDataSnapshot(snapshot) {
42
+ return {
43
+ positions: snapshot.positions.map((position) => ({
44
+ ...position,
45
+ leverage: { ...position.leverage },
46
+ cumulativeFunding: { ...position.cumulativeFunding },
47
+ ...(position.takeProfitOrders && {
48
+ takeProfitOrders: position.takeProfitOrders.map((order) => ({
49
+ ...order,
50
+ })),
51
+ }),
52
+ ...(position.stopLossOrders && {
53
+ stopLossOrders: position.stopLossOrders.map((order) => ({
54
+ ...order,
55
+ })),
56
+ }),
57
+ })),
58
+ orders: snapshot.orders.map((order) => ({ ...order })),
59
+ accountState: {
60
+ ...snapshot.accountState,
61
+ ...(snapshot.accountState.subAccountBreakdown && {
62
+ subAccountBreakdown: Object.fromEntries(Object.entries(snapshot.accountState.subAccountBreakdown).map(([dex, balances]) => [dex, { ...balances }])),
63
+ }),
64
+ },
65
+ identity: {
66
+ ...snapshot.identity,
67
+ dexes: [...snapshot.identity.dexes],
68
+ },
69
+ };
70
+ }
41
71
  /**
42
72
  * Returns the first non-empty string from the given values.
43
73
  * Env vars default to '' (not null/undefined), so ?? wouldn't fall through.
@@ -373,6 +403,7 @@ const metadata = {
373
403
  },
374
404
  };
375
405
  const MESSENGER_EXPOSED_METHODS = [
406
+ 'approveSubscriptionBuilderFee',
376
407
  'calculateFees',
377
408
  'calculateLiquidationPrice',
378
409
  'calculateMaintenanceMargin',
@@ -400,6 +431,7 @@ const MESSENGER_EXPOSED_METHODS = [
400
431
  'getBlockExplorerUrl',
401
432
  'getCachedMarketDataForActiveProvider',
402
433
  'getCachedUserDataForActiveProvider',
434
+ 'getUserDataSnapshot',
403
435
  'getCurrentNetwork',
404
436
  'getFunding',
405
437
  'getHistoricalPortfolio',
@@ -421,6 +453,7 @@ const MESSENGER_EXPOSED_METHODS = [
421
453
  'getWithdrawalProgress',
422
454
  'getWithdrawalRoutes',
423
455
  'init',
456
+ 'invalidateSubscriptionBenefits',
424
457
  'isCurrentlyReinitializing',
425
458
  'isFirstTimeUserOnCurrentNetwork',
426
459
  'isWatchlistMarket',
@@ -532,6 +565,7 @@ export class PerpsController extends BaseController {
532
565
  _PerpsController_handlersRegistered.set(this, false);
533
566
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
534
567
  _PerpsController_standaloneProviderHip3Version.set(this, null);
568
+ _PerpsController_standaloneProviderOperations.set(this, new Map());
535
569
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
536
570
  /**
537
571
  * Serial promise queue for all AUS watchlist operations (hydration and
@@ -547,6 +581,7 @@ export class PerpsController extends BaseController {
547
581
  * a failed operation does not stall subsequent ones.
548
582
  */
549
583
  _PerpsController_ausQueue.set(this, Promise.resolve());
584
+ _PerpsController_userDiskWrite.set(this, Promise.resolve());
550
585
  // Store options for dependency injection (allows core package to inject platform-specific services)
551
586
  _PerpsController_options.set(this, void 0);
552
587
  // Service instances (instantiated with platform dependencies)
@@ -560,7 +595,10 @@ export class PerpsController extends BaseController {
560
595
  _PerpsController_rewardsIntegrationService.set(this, void 0);
561
596
  _PerpsController_preloadTimer.set(this, null);
562
597
  _PerpsController_isPreloading.set(this, false);
598
+ _PerpsController_marketPreloadQueued.set(this, false);
563
599
  _PerpsController_isPreloadingUserData.set(this, false);
600
+ _PerpsController_userPreloadQueued.set(this, false);
601
+ _PerpsController_userSnapshotRequests.set(this, new Map());
564
602
  _PerpsController_preloadStateUnsubscribe.set(this, null);
565
603
  _PerpsController_accountChangeUnsubscribe.set(this, null);
566
604
  _PerpsController_previousIsTestnet.set(this, null);
@@ -579,7 +617,9 @@ export class PerpsController extends BaseController {
579
617
  __classPrivateFieldSet(this, _PerpsController_marketDataService, new MarketDataService({
580
618
  ...infrastructure,
581
619
  terminalMarketService: infrastructure.terminalMarketService ??
582
- (infrastructure.terminalApiUrl
620
+ (infrastructure.terminalApi?.marketDataUrl ||
621
+ infrastructure.terminalApiUrl ||
622
+ infrastructure.terminalApi?.globalSnapshotUrl
583
623
  ? new TerminalMarketService(infrastructure)
584
624
  : undefined),
585
625
  }), "f");
@@ -646,24 +686,20 @@ export class PerpsController extends BaseController {
646
686
  if (activeProvider === 'aggregated') {
647
687
  // Assemble from all registered provider entries
648
688
  const assembled = [];
649
- let oldestTimestamp = Infinity;
650
689
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
651
690
  const key = buildProviderCacheKey(providerId, this.state.isTestnet);
652
691
  const entry = cache[key];
653
692
  if (!entry || entry.data.length === 0) {
654
693
  continue;
655
694
  }
656
- oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
657
- assembled.push(...entry.data);
695
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, providerId, entry, options)) {
696
+ continue;
697
+ }
698
+ assembled.push(...clonePerpsMarketData(entry.data));
658
699
  }
659
700
  if (assembled.length === 0) {
660
701
  return null;
661
702
  }
662
- // Check TTL against the oldest entry
663
- if (!options?.skipTTL &&
664
- Date.now() - oldestTimestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
665
- return null;
666
- }
667
703
  return assembled;
668
704
  }
669
705
  // Single provider mode
@@ -672,11 +708,10 @@ export class PerpsController extends BaseController {
672
708
  if (!entry || entry.data.length === 0) {
673
709
  return null;
674
710
  }
675
- if (!options?.skipTTL &&
676
- Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
711
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, activeProvider, entry, options)) {
677
712
  return null;
678
713
  }
679
- return entry.data;
714
+ return clonePerpsMarketData(entry.data);
680
715
  }
681
716
  /**
682
717
  * Read cached user data for the currently active provider (or aggregated).
@@ -700,18 +735,21 @@ export class PerpsController extends BaseController {
700
735
  currentAddress = evmAccount?.address ?? null;
701
736
  }
702
737
  catch {
703
- // Can't determine current account — trust the cache
738
+ // Account identity is required before account-scoped data can be trusted.
704
739
  }
740
+ if (!currentAddress) {
741
+ return null;
742
+ }
743
+ const selectedAddress = currentAddress;
705
744
  const skipTTL = options?.skipTTL ?? false;
706
- const isValidEntry = (entry) => {
745
+ const isValidEntry = (providerId, entry) => {
707
746
  if (!entry) {
708
747
  return false;
709
748
  }
710
749
  if (!skipTTL && Date.now() - entry.timestamp >= staleCutoff) {
711
750
  return false;
712
751
  }
713
- if (currentAddress &&
714
- entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
752
+ if (!__classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, providerId, entry, selectedAddress)) {
715
753
  return false;
716
754
  }
717
755
  return true;
@@ -723,9 +761,9 @@ export class PerpsController extends BaseController {
723
761
  let defaultAccountState = null;
724
762
  let hasValidEntry = false;
725
763
  for (const providerId of __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getAggregatedCacheProviderIds).call(this, Object.keys(cache))) {
726
- const key = buildProviderCacheKey(providerId, this.state.isTestnet);
727
- const entry = cache[key];
728
- if (!isValidEntry(entry)) {
764
+ const providerNetworkKey = buildProviderCacheKey(providerId, this.state.isTestnet);
765
+ const entry = cache[providerNetworkKey];
766
+ if (!isValidEntry(providerId, entry)) {
729
767
  continue;
730
768
  }
731
769
  hasValidEntry = true;
@@ -746,9 +784,9 @@ export class PerpsController extends BaseController {
746
784
  };
747
785
  }
748
786
  // Single provider mode
749
- const key = buildProviderCacheKey(activeProvider, this.state.isTestnet);
750
- const entry = cache[key];
751
- if (!entry || !isValidEntry(entry)) {
787
+ const providerNetworkKey = buildProviderCacheKey(activeProvider, this.state.isTestnet);
788
+ const entry = cache[providerNetworkKey];
789
+ if (!entry || !isValidEntry(activeProvider, entry)) {
752
790
  return null;
753
791
  }
754
792
  return {
@@ -757,6 +795,82 @@ export class PerpsController extends BaseController {
757
795
  accountState: entry.accountState,
758
796
  };
759
797
  }
798
+ /**
799
+ * Fetch, validate, and atomically cache a complete user-data snapshot.
800
+ * This remains callable after mount so consumers can seed their live channel
801
+ * from one coherent positions/orders/account result.
802
+ *
803
+ * @returns The accepted user-data snapshot.
804
+ */
805
+ async getUserDataSnapshot() {
806
+ const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
807
+ if (!evmAccount?.address) {
808
+ throw new Error('Cannot fetch user data snapshot without an EVM account');
809
+ }
810
+ if (this.state.activeProvider !== 'hyperliquid') {
811
+ throw new Error('User data snapshots require Hyperliquid provider mode');
812
+ }
813
+ const capturedActiveProvider = this.activeProviderInstance;
814
+ const standaloneProvider = capturedActiveProvider
815
+ ? null
816
+ : __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
817
+ const provider = capturedActiveProvider ?? standaloneProvider;
818
+ if (!provider) {
819
+ throw new Error('Cannot create standalone Hyperliquid provider');
820
+ }
821
+ const { address } = evmAccount;
822
+ const { isTestnet, hip3ConfigVersion } = this.state;
823
+ const network = isTestnet ? 'testnet' : 'mainnet';
824
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
825
+ if (!expectedDexes) {
826
+ throw new Error('User data snapshot DEX identity is not static');
827
+ }
828
+ const isCurrent = () => {
829
+ let currentAddress;
830
+ try {
831
+ currentAddress = getSelectedEvmAccountFromMessenger(this.messenger)?.address;
832
+ }
833
+ catch {
834
+ return false;
835
+ }
836
+ return (this.state.activeProvider === 'hyperliquid' &&
837
+ (!capturedActiveProvider ||
838
+ this.activeProviderInstance === capturedActiveProvider) &&
839
+ this.state.isTestnet === isTestnet &&
840
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
841
+ currentAddress?.toLowerCase() === address.toLowerCase());
842
+ };
843
+ const context = {
844
+ provider,
845
+ standaloneProvider,
846
+ address,
847
+ isTestnet,
848
+ hip3ConfigVersion,
849
+ expectedDexes,
850
+ isCurrent,
851
+ };
852
+ const requestKey = [
853
+ 'hyperliquid',
854
+ network,
855
+ address.toLowerCase(),
856
+ hip3ConfigVersion,
857
+ ...expectedDexes,
858
+ ].join('|');
859
+ const existingRequest = __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey);
860
+ if (existingRequest?.provider === provider) {
861
+ return existingRequest.promise;
862
+ }
863
+ const request = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_fetchAndCacheUserDataSnapshot).call(this, context);
864
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").set(requestKey, { provider, promise: request });
865
+ try {
866
+ return await request;
867
+ }
868
+ finally {
869
+ if (__classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").get(requestKey)?.promise === request) {
870
+ __classPrivateFieldGet(this, _PerpsController_userSnapshotRequests, "f").delete(requestKey);
871
+ }
872
+ }
873
+ }
760
874
  /**
761
875
  * Test-observable accessor for whether a standalone provider is cached.
762
876
  *
@@ -908,14 +1022,11 @@ export class PerpsController extends BaseController {
908
1022
  // Check if not initialized
909
1023
  if (this.state.initializationState !== InitializationState.Initialized ||
910
1024
  !this.isInitialized) {
911
- const errorMessage = this.state.initializationState === InitializationState.Failed
912
- ? `${PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED}: ${this.state.initializationError ?? 'Initialization failed'}`
913
- : PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED;
914
1025
  this.update((state) => {
915
- state.lastError = errorMessage;
1026
+ state.lastError = PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED;
916
1027
  state.lastUpdateTimestamp = Date.now();
917
1028
  });
918
- throw new Error(errorMessage);
1029
+ throw new Error(PERPS_ERROR_CODES.CLIENT_NOT_INITIALIZED);
919
1030
  }
920
1031
  // Return the active provider instance (set during initialization based on providerMode)
921
1032
  if (!this.activeProviderInstance) {
@@ -955,7 +1066,7 @@ export class PerpsController extends BaseController {
955
1066
  * @returns The order result with order ID and status.
956
1067
  */
957
1068
  async placeOrder(params) {
958
- const provider = this.getActiveProvider();
1069
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
959
1070
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
960
1071
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").placeOrder({
961
1072
  provider,
@@ -974,7 +1085,7 @@ export class PerpsController extends BaseController {
974
1085
  * @returns The updated order result with order ID and status.
975
1086
  */
976
1087
  async editOrder(params) {
977
- const provider = this.getActiveProvider();
1088
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
978
1089
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
979
1090
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").editOrder({
980
1091
  provider,
@@ -989,7 +1100,7 @@ export class PerpsController extends BaseController {
989
1100
  * @returns The cancellation result with status.
990
1101
  */
991
1102
  async cancelOrder(params) {
992
- const provider = this.getActiveProvider();
1103
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
993
1104
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").cancelOrder({
994
1105
  provider,
995
1106
  params,
@@ -1004,7 +1115,7 @@ export class PerpsController extends BaseController {
1004
1115
  * @returns The batch cancellation results for each order.
1005
1116
  */
1006
1117
  async cancelOrders(params) {
1007
- const provider = this.getActiveProvider();
1118
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1008
1119
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").cancelOrders({
1009
1120
  provider,
1010
1121
  params,
@@ -1022,7 +1133,7 @@ export class PerpsController extends BaseController {
1022
1133
  * @returns The order result from the close position request.
1023
1134
  */
1024
1135
  async closePosition(params) {
1025
- const provider = this.getActiveProvider();
1136
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1026
1137
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1027
1138
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").closePosition({
1028
1139
  provider,
@@ -1041,7 +1152,7 @@ export class PerpsController extends BaseController {
1041
1152
  * @returns The batch close results for each position.
1042
1153
  */
1043
1154
  async closePositions(params) {
1044
- const provider = this.getActiveProvider();
1155
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1045
1156
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1046
1157
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").closePositions({
1047
1158
  provider,
@@ -1058,7 +1169,7 @@ export class PerpsController extends BaseController {
1058
1169
  * @returns The order result from the TP/SL update.
1059
1170
  */
1060
1171
  async updatePositionTPSL(params) {
1061
- const provider = this.getActiveProvider();
1172
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1062
1173
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1063
1174
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").updatePositionTPSL({
1064
1175
  provider,
@@ -1073,7 +1184,7 @@ export class PerpsController extends BaseController {
1073
1184
  * @returns The margin update result.
1074
1185
  */
1075
1186
  async updateMargin(params) {
1076
- const provider = this.getActiveProvider();
1187
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1077
1188
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1078
1189
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").updateMargin({
1079
1190
  provider,
@@ -1089,7 +1200,7 @@ export class PerpsController extends BaseController {
1089
1200
  * @returns The order result from the position flip.
1090
1201
  */
1091
1202
  async flipPosition(params) {
1092
- const provider = this.getActiveProvider();
1203
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1093
1204
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_ensureTradingServiceDeps).call(this);
1094
1205
  return __classPrivateFieldGet(this, _PerpsController_tradingService, "f").flipPosition({
1095
1206
  provider,
@@ -1111,10 +1222,7 @@ export class PerpsController extends BaseController {
1111
1222
  const { amount, placeOrder } = params;
1112
1223
  let currentDepositId;
1113
1224
  try {
1114
- // Clear any stale results when starting a new deposit flow
1115
- // Don't set depositInProgress yet - wait until user confirms
1116
- // Prepare deposit transaction using DepositService
1117
- const provider = this.getActiveProvider();
1225
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1118
1226
  const { transaction, assetChainId, currentDepositId: depositId, } = await __classPrivateFieldGet(this, _PerpsController_depositService, "f").prepareTransaction({ provider });
1119
1227
  currentDepositId = depositId;
1120
1228
  // Get current account address via messenger (outside of update() for proper typing)
@@ -1504,7 +1612,7 @@ export class PerpsController extends BaseController {
1504
1612
  * @returns WithdrawResult with withdrawal ID and tracking info
1505
1613
  */
1506
1614
  async withdraw(params) {
1507
- const provider = this.getActiveProvider();
1615
+ const provider = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getActiveProviderWhenReady).call(this);
1508
1616
  return __classPrivateFieldGet(this, _PerpsController_accountService, "f").withdraw({
1509
1617
  provider,
1510
1618
  params,
@@ -1532,7 +1640,10 @@ export class PerpsController extends BaseController {
1532
1640
  // Fallback to cached standalone provider for pre-initialization discovery
1533
1641
  // TODO: When adding new providers (MYX), consider a provider factory pattern
1534
1642
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1535
- return provider.getPositions(params);
1643
+ const operation = provider.getPositions(params);
1644
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1645
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1646
+ : operation;
1536
1647
  }
1537
1648
  const provider = this.getActiveProvider();
1538
1649
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getPositions({
@@ -1593,7 +1704,10 @@ export class PerpsController extends BaseController {
1593
1704
  // For standalone mode, access provider directly without initialization check
1594
1705
  if (params?.standalone && params.userAddress) {
1595
1706
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1596
- return provider.getOpenOrders(params);
1707
+ const operation = provider.getOpenOrders(params);
1708
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1709
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1710
+ : operation;
1597
1711
  }
1598
1712
  const provider = this.getActiveProvider();
1599
1713
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getOpenOrders({
@@ -1638,7 +1752,10 @@ export class PerpsController extends BaseController {
1638
1752
  // Use activeProviderInstance if available (respects provider abstraction)
1639
1753
  // Fallback to cached standalone provider for pre-initialization discovery
1640
1754
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1641
- return provider.getAccountState(params);
1755
+ const operation = provider.getAccountState(params);
1756
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1757
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1758
+ : operation;
1642
1759
  }
1643
1760
  const provider = this.getActiveProvider();
1644
1761
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getAccountState({
@@ -1676,12 +1793,15 @@ export class PerpsController extends BaseController {
1676
1793
  const isMarketAllowed = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this);
1677
1794
  if (params?.standalone) {
1678
1795
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1679
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1796
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
1680
1797
  provider,
1681
1798
  params,
1682
1799
  context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarkets'),
1683
1800
  isMarketAllowed,
1684
1801
  });
1802
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1803
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1804
+ : operation;
1685
1805
  }
1686
1806
  const provider = this.getActiveProvider();
1687
1807
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarkets({
@@ -1707,19 +1827,26 @@ export class PerpsController extends BaseController {
1707
1827
  * @returns A promise that resolves to the market data.
1708
1828
  */
1709
1829
  async getMarketDataWithPrices(params) {
1830
+ const globalSnapshot = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildGlobalSnapshotContext).call(this);
1831
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices', {
1832
+ ...(globalSnapshot && { globalSnapshot }),
1833
+ });
1710
1834
  if (params?.standalone) {
1711
1835
  const provider = this.activeProviderInstance ?? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getOrCreateStandaloneProvider).call(this);
1712
- return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1836
+ const operation = __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1713
1837
  provider,
1714
1838
  params,
1715
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1839
+ context,
1716
1840
  });
1841
+ return provider === __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")
1842
+ ? __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, provider, operation)
1843
+ : operation;
1717
1844
  }
1718
1845
  const provider = this.getActiveProvider();
1719
1846
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").getMarketDataWithPrices({
1720
1847
  provider,
1721
1848
  params,
1722
- context: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'getMarketDataWithPrices'),
1849
+ context,
1723
1850
  });
1724
1851
  }
1725
1852
  /**
@@ -1740,11 +1867,17 @@ export class PerpsController extends BaseController {
1740
1867
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1741
1868
  /* fire-and-forget */
1742
1869
  });
1870
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1871
+ /* fire-and-forget */
1872
+ });
1743
1873
  // Periodic refresh
1744
1874
  __classPrivateFieldSet(this, _PerpsController_preloadTimer, setInterval(() => {
1745
1875
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1746
1876
  /* fire-and-forget */
1747
1877
  });
1878
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1879
+ /* fire-and-forget */
1880
+ });
1748
1881
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1749
1882
  // Watch for isTestnet / hip3ConfigVersion changes
1750
1883
  const handler = (_state, patches) => {
@@ -1773,36 +1906,29 @@ export class PerpsController extends BaseController {
1773
1906
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1774
1907
  /* fire-and-forget */
1775
1908
  });
1909
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1910
+ /* fire-and-forget */
1911
+ });
1776
1912
  }
1777
1913
  };
1778
- this.messenger.subscribe('PerpsController:stateChange', handler);
1914
+ this.messenger.subscribe('PerpsController:stateChanged', handler);
1779
1915
  __classPrivateFieldSet(this, _PerpsController_preloadStateUnsubscribe, () => {
1780
- this.messenger.unsubscribe('PerpsController:stateChange', handler);
1916
+ this.messenger.unsubscribe('PerpsController:stateChanged', handler);
1781
1917
  }, "f");
1782
1918
  // Watch for selected account changes and selected account group changes.
1783
1919
  const accountChangeHandler = () => {
1784
1920
  const evmAccount = getSelectedEvmAccountFromMessenger(this.messenger);
1785
1921
  const currentAddress = evmAccount?.address ?? null;
1786
- // If any cached entry belongs to a different account, clear all entries.
1787
- // Max 4 entries (2 providers × 2 networks) — clearing all is simple and safe.
1788
- const hasStaleEntries = Object.values(this.state.cachedUserDataByProvider).some((entry) => entry.address.toLowerCase() !== currentAddress?.toLowerCase());
1789
- if (hasStaleEntries) {
1790
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Account changed, clearing user data cache');
1791
- this.update((state) => {
1792
- state.cachedUserDataByProvider = {};
1793
- });
1794
- // Invalidate disk-cached user data for the old account
1795
- __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache
1796
- .removeItem(PERPS_DISK_CACHE_USER_DATA)
1797
- .catch(() => {
1922
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: account cache selection', {
1923
+ address: currentAddress?.toLowerCase() ?? null,
1924
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
1925
+ });
1926
+ // The address guard makes the previous entry unreadable immediately;
1927
+ // refresh replaces it under the existing provider/network key.
1928
+ if (currentAddress) {
1929
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1798
1930
  /* fire-and-forget */
1799
1931
  });
1800
- // Only preload if the new account is an EVM account
1801
- if (currentAddress) {
1802
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
1803
- /* fire-and-forget */
1804
- });
1805
- }
1806
1932
  }
1807
1933
  };
1808
1934
  this.messenger.subscribe('AccountsController:selectedAccountChange', accountChangeHandler);
@@ -1831,6 +1957,8 @@ export class PerpsController extends BaseController {
1831
1957
  }
1832
1958
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, null, "f");
1833
1959
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, null, "f");
1960
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
1961
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
1834
1962
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this).catch(() => {
1835
1963
  /* fire-and-forget to preserve sync signature */
1836
1964
  });
@@ -2091,6 +2219,9 @@ export class PerpsController extends BaseController {
2091
2219
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2092
2220
  /* fire-and-forget */
2093
2221
  });
2222
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2223
+ /* fire-and-forget */
2224
+ });
2094
2225
  }
2095
2226
  }
2096
2227
  }
@@ -2200,6 +2331,9 @@ export class PerpsController extends BaseController {
2200
2331
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
2201
2332
  /* fire-and-forget */
2202
2333
  });
2334
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
2335
+ /* fire-and-forget */
2336
+ });
2203
2337
  }
2204
2338
  }
2205
2339
  }
@@ -2535,9 +2669,41 @@ export class PerpsController extends BaseController {
2535
2669
  */
2536
2670
  async calculateFees(params) {
2537
2671
  const provider = this.getActiveProvider();
2538
- const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'calculateFees');
2672
+ // Preview owns subscription hydration. The submit resolver remains a pure
2673
+ // cache read and can therefore never start a benefits request while an
2674
+ // order is being signed.
2675
+ await __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").refreshSubscriptionBenefits();
2676
+ const waiverStatus = __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").getSubscriptionFeeWaiverStatus();
2677
+ const context = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createServiceContext).call(this, 'calculateFees', {
2678
+ subscriptionFeeWaiver: waiverStatus.reason === 'no-source' ? undefined : waiverStatus,
2679
+ });
2539
2680
  return __classPrivateFieldGet(this, _PerpsController_marketDataService, "f").calculateFees({ provider, params, context });
2540
2681
  }
2682
+ /**
2683
+ * Approve the dedicated subscription builder outside order submission.
2684
+ * Until this succeeds, subscription waivers fall back to the ordinary
2685
+ * builder at the standard fee.
2686
+ *
2687
+ * @returns Whether the subscription builder is approved.
2688
+ */
2689
+ async approveSubscriptionBuilderFee() {
2690
+ const provider = this.getActiveProvider();
2691
+ return provider.approveSubscriptionBuilderFee
2692
+ ? provider.approveSubscriptionBuilderFee()
2693
+ : false;
2694
+ }
2695
+ /**
2696
+ * Drop the cached subscription benefits snapshot.
2697
+ *
2698
+ * Call this when the identity behind the benefits changes — sign-out, or a
2699
+ * profile switch. The snapshot carries no profile identity of its own, so
2700
+ * without this it keeps answering for the previous profile until the next
2701
+ * successful refresh. The next fee resolution reports the waiver as
2702
+ * unavailable, so it is withheld until preview or lifecycle hydration.
2703
+ */
2704
+ invalidateSubscriptionBenefits() {
2705
+ __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f").invalidateSubscriptionBenefits();
2706
+ }
2541
2707
  /**
2542
2708
  * Disconnect provider and cleanup subscriptions
2543
2709
  * Call this when navigating away from Perps screens to prevent battery drain
@@ -3262,7 +3428,7 @@ export class PerpsController extends BaseController {
3262
3428
  return __classPrivateFieldGet(this, _PerpsController_isReinitializing, "f");
3263
3429
  }
3264
3430
  }
3265
- _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _PerpsController_isReinitializing = new WeakMap(), _PerpsController_myxRegistrationPromise = new WeakMap(), _PerpsController_blockedRegionListVersion = new WeakMap(), _PerpsController_hip3Enabled = new WeakMap(), _PerpsController_hip3AllowlistMarkets = new WeakMap(), _PerpsController_hip3BlocklistMarkets = new WeakMap(), _PerpsController_hip3ConfigSource = new WeakMap(), _PerpsController_priceDeviationLimit = new WeakMap(), _PerpsController_attributionContext = new WeakMap(), _PerpsController_standaloneProvider = new WeakMap(), _PerpsController_handlersRegistered = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_eligibilityCheckDeferred = new WeakMap(), _PerpsController_ausQueue = new WeakMap(), _PerpsController_options = new WeakMap(), _PerpsController_tradingService = new WeakMap(), _PerpsController_marketDataService = new WeakMap(), _PerpsController_accountService = new WeakMap(), _PerpsController_eligibilityService = new WeakMap(), _PerpsController_dataLakeService = new WeakMap(), _PerpsController_depositService = new WeakMap(), _PerpsController_featureFlagConfigurationService = new WeakMap(), _PerpsController_rewardsIntegrationService = new WeakMap(), _PerpsController_preloadTimer = new WeakMap(), _PerpsController_isPreloading = new WeakMap(), _PerpsController_isPreloadingUserData = new WeakMap(), _PerpsController_preloadStateUnsubscribe = new WeakMap(), _PerpsController_accountChangeUnsubscribe = new WeakMap(), _PerpsController_previousIsTestnet = new WeakMap(), _PerpsController_previousHip3ConfigVersion = new WeakMap(), _PerpsController_instances = new WeakSet(), _PerpsController_isMYXProviderEnabled = function _PerpsController_isMYXProviderEnabled() {
3431
+ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _PerpsController_isReinitializing = new WeakMap(), _PerpsController_myxRegistrationPromise = new WeakMap(), _PerpsController_blockedRegionListVersion = new WeakMap(), _PerpsController_hip3Enabled = new WeakMap(), _PerpsController_hip3AllowlistMarkets = new WeakMap(), _PerpsController_hip3BlocklistMarkets = new WeakMap(), _PerpsController_hip3ConfigSource = new WeakMap(), _PerpsController_priceDeviationLimit = new WeakMap(), _PerpsController_attributionContext = new WeakMap(), _PerpsController_standaloneProvider = new WeakMap(), _PerpsController_handlersRegistered = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_standaloneProviderOperations = new WeakMap(), _PerpsController_eligibilityCheckDeferred = new WeakMap(), _PerpsController_ausQueue = new WeakMap(), _PerpsController_userDiskWrite = new WeakMap(), _PerpsController_options = new WeakMap(), _PerpsController_tradingService = new WeakMap(), _PerpsController_marketDataService = new WeakMap(), _PerpsController_accountService = new WeakMap(), _PerpsController_eligibilityService = new WeakMap(), _PerpsController_dataLakeService = new WeakMap(), _PerpsController_depositService = new WeakMap(), _PerpsController_featureFlagConfigurationService = new WeakMap(), _PerpsController_rewardsIntegrationService = new WeakMap(), _PerpsController_preloadTimer = new WeakMap(), _PerpsController_isPreloading = new WeakMap(), _PerpsController_marketPreloadQueued = new WeakMap(), _PerpsController_isPreloadingUserData = new WeakMap(), _PerpsController_userPreloadQueued = new WeakMap(), _PerpsController_userSnapshotRequests = new WeakMap(), _PerpsController_preloadStateUnsubscribe = new WeakMap(), _PerpsController_accountChangeUnsubscribe = new WeakMap(), _PerpsController_previousIsTestnet = new WeakMap(), _PerpsController_previousHip3ConfigVersion = new WeakMap(), _PerpsController_instances = new WeakSet(), _PerpsController_isMYXProviderEnabled = function _PerpsController_isMYXProviderEnabled() {
3266
3432
  const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
3267
3433
  // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
3268
3434
  // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
@@ -3295,6 +3461,16 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3295
3461
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.logger.error(error, options);
3296
3462
  }, _PerpsController_debugLog = function _PerpsController_debugLog(...args) {
3297
3463
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.debugLogger.log(...args);
3464
+ }, _PerpsController_awaitInitializationIfInProgress =
3465
+ /**
3466
+ * Awaits the in-flight initialization promise if init is currently running.
3467
+ * Called internally by #getActiveProviderWhenReady().
3468
+ */
3469
+ async function _PerpsController_awaitInitializationIfInProgress() {
3470
+ if (this.state.initializationState === InitializationState.Initializing &&
3471
+ __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f")) {
3472
+ await __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f");
3473
+ }
3298
3474
  }, _PerpsController_getAggregatedCacheProviderIds = function _PerpsController_getAggregatedCacheProviderIds(cacheKeys) {
3299
3475
  const providerIds = new Set();
3300
3476
  const currentNetwork = this.state.isTestnet ? 'testnet' : 'mainnet';
@@ -3315,6 +3491,96 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3315
3491
  }
3316
3492
  }
3317
3493
  return Array.from(providerIds);
3494
+ }, _PerpsController_isMarketCacheEntryCurrent = function _PerpsController_isMarketCacheEntryCurrent(providerId, entry, options) {
3495
+ if (entry.sourceExpiresAt !== undefined) {
3496
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3497
+ return (providerId === 'hyperliquid' &&
3498
+ Date.now() < entry.sourceExpiresAt &&
3499
+ entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3500
+ expectedDexes !== undefined &&
3501
+ Array.isArray(entry.dexes) &&
3502
+ entry.dexes.length === expectedDexes.length &&
3503
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3504
+ }
3505
+ return (options?.skipTTL === true ||
3506
+ Date.now() - entry.timestamp <= __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10);
3507
+ }, _PerpsController_isUserCacheIdentityCurrent = function _PerpsController_isUserCacheIdentityCurrent(providerId, entry, address) {
3508
+ if (entry.address.toLowerCase() !== address.toLowerCase()) {
3509
+ return false;
3510
+ }
3511
+ if (providerId !== 'hyperliquid') {
3512
+ return true;
3513
+ }
3514
+ const expectedDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3515
+ return (entry.hip3ConfigVersion === this.state.hip3ConfigVersion &&
3516
+ expectedDexes !== undefined &&
3517
+ Array.isArray(entry.dexes) &&
3518
+ entry.dexes.length === expectedDexes.length &&
3519
+ entry.dexes.every((dex, index) => dex === expectedDexes[index]));
3520
+ }, _PerpsController_fetchAndCacheUserDataSnapshot = async function _PerpsController_fetchAndCacheUserDataSnapshot(context) {
3521
+ const { provider, standaloneProvider, address, isTestnet, hip3ConfigVersion, expectedDexes, isCurrent, } = context;
3522
+ if (!isCurrent()) {
3523
+ throw new Error('User data snapshot context changed');
3524
+ }
3525
+ if (!provider.getUserDataSnapshot) {
3526
+ throw new Error('Provider has no atomic snapshot API');
3527
+ }
3528
+ const identity = {
3529
+ provider: 'hyperliquid',
3530
+ network: isTestnet ? 'testnet' : 'mainnet',
3531
+ hip3ConfigVersion,
3532
+ dexes: expectedDexes,
3533
+ };
3534
+ const snapshotRequest = provider.getUserDataSnapshot({
3535
+ userAddress: address,
3536
+ identity,
3537
+ });
3538
+ const snapshot = standaloneProvider
3539
+ ? await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_trackStandaloneProviderOperation).call(this, standaloneProvider, snapshotRequest)
3540
+ : await snapshotRequest;
3541
+ if (!isCurrent()) {
3542
+ throw new Error('User data snapshot context changed');
3543
+ }
3544
+ const snapshotIdentity = snapshot.identity;
3545
+ const hasCompleteBundle = Array.isArray(snapshot.positions) &&
3546
+ Array.isArray(snapshot.orders) &&
3547
+ snapshot.accountState !== null &&
3548
+ typeof snapshot.accountState === 'object';
3549
+ const hasExactIdentity = snapshotIdentity.provider === identity.provider &&
3550
+ snapshotIdentity.network === identity.network &&
3551
+ snapshotIdentity.hip3ConfigVersion === identity.hip3ConfigVersion &&
3552
+ snapshotIdentity.address.toLowerCase() === address.toLowerCase() &&
3553
+ snapshotIdentity.dexes.length === expectedDexes.length &&
3554
+ snapshotIdentity.dexes.every((dex, index) => dex === expectedDexes[index]);
3555
+ if (!hasCompleteBundle || !hasExactIdentity) {
3556
+ throw new Error('User data snapshot is incomplete or mismatched');
3557
+ }
3558
+ if (!isCurrent()) {
3559
+ throw new Error('User data snapshot context changed');
3560
+ }
3561
+ const cachedSnapshot = cloneUserDataSnapshot(snapshot);
3562
+ const result = cloneUserDataSnapshot(snapshot);
3563
+ const timestamp = Date.now();
3564
+ const providerNetworkKey = buildProviderCacheKey('hyperliquid', isTestnet);
3565
+ this.update((state) => {
3566
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3567
+ positions: cachedSnapshot.positions,
3568
+ orders: cachedSnapshot.orders,
3569
+ accountState: cachedSnapshot.accountState,
3570
+ timestamp,
3571
+ address,
3572
+ hip3ConfigVersion,
3573
+ dexes: expectedDexes,
3574
+ };
3575
+ });
3576
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3577
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache snapshot written', {
3578
+ writtenKey: providerNetworkKey,
3579
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
3580
+ positionCount: cachedSnapshot.positions.length,
3581
+ orderCount: cachedSnapshot.orders.length,
3582
+ });
3583
+ return result;
3318
3584
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
3319
3585
  const currentIsTestnet = this.state.isTestnet;
3320
3586
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -3323,10 +3589,13 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3323
3589
  __classPrivateFieldGet(this, _PerpsController_standaloneProviderHip3Version, "f") === currentHip3Version) {
3324
3590
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3325
3591
  }
3326
- // Stale or missing — tear down old one (fire-and-forget)
3592
+ // Stale or missing — retire the old provider after active operations finish.
3327
3593
  if (__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3328
3594
  const old = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3329
- Promise.resolve(old.disconnect()).catch(() => {
3595
+ __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3596
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3597
+ __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3598
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, old).catch(() => {
3330
3599
  /* best-effort */
3331
3600
  });
3332
3601
  }
@@ -3342,28 +3611,53 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
3342
3611
  ?.builderAddressTestnet,
3343
3612
  builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3344
3613
  ?.builderAddressMainnet,
3614
+ subscriptionBuilderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3615
+ ?.subscriptionBuilderAddressTestnet,
3616
+ subscriptionBuilderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3617
+ ?.subscriptionBuilderAddressMainnet,
3345
3618
  }), "f");
3346
3619
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
3347
3620
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
3348
3621
  return __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3622
+ }, _PerpsController_trackStandaloneProviderOperation = function _PerpsController_trackStandaloneProviderOperation(provider, operation) {
3623
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider) ?? new Set();
3624
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").set(provider, operations);
3625
+ const trackedOperation = operation.finally(() => {
3626
+ operations.delete(trackedOperation);
3627
+ if (operations.size === 0) {
3628
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3629
+ }
3630
+ });
3631
+ operations.add(trackedOperation);
3632
+ return trackedOperation;
3633
+ }, _PerpsController_retireStandaloneProvider = async function _PerpsController_retireStandaloneProvider(provider) {
3634
+ const operations = __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").get(provider);
3635
+ if (operations?.size) {
3636
+ await Promise.allSettled([...operations]);
3637
+ }
3638
+ try {
3639
+ await provider.disconnect();
3640
+ }
3641
+ catch {
3642
+ /* best-effort */
3643
+ }
3644
+ finally {
3645
+ __classPrivateFieldGet(this, _PerpsController_standaloneProviderOperations, "f").delete(provider);
3646
+ }
3349
3647
  }, _PerpsController_cleanupStandaloneProvider =
3350
3648
  /**
3351
3649
  * Disconnect and discard the cached standalone provider (if any).
3352
3650
  * Best-effort — errors are silently caught.
3353
3651
  */
3354
3652
  async function _PerpsController_cleanupStandaloneProvider() {
3355
- if (!__classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f")) {
3653
+ const provider = __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f");
3654
+ if (!provider) {
3356
3655
  return;
3357
3656
  }
3358
- try {
3359
- await __classPrivateFieldGet(this, _PerpsController_standaloneProvider, "f").disconnect();
3360
- }
3361
- catch {
3362
- /* best-effort */
3363
- }
3364
3657
  __classPrivateFieldSet(this, _PerpsController_standaloneProvider, null, "f");
3365
3658
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, null, "f");
3366
3659
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, null, "f");
3660
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_retireStandaloneProvider).call(this, provider);
3367
3661
  }, _PerpsController_getMetrics = function _PerpsController_getMetrics() {
3368
3662
  return __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.metrics;
3369
3663
  }, _PerpsController_findNetworkClientIdForChain = function _PerpsController_findNetworkClientIdForChain(chainId) {
@@ -3581,6 +3875,10 @@ async function _PerpsController_performInitialization() {
3581
3875
  ?.builderAddressTestnet,
3582
3876
  builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3583
3877
  ?.builderAddressMainnet,
3878
+ subscriptionBuilderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3879
+ ?.subscriptionBuilderAddressTestnet,
3880
+ subscriptionBuilderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3881
+ ?.subscriptionBuilderAddressMainnet,
3584
3882
  });
3585
3883
  this.providers.set('hyperliquid', hyperLiquidProvider);
3586
3884
  // Register MYX provider if enabled via feature flag.
@@ -3697,6 +3995,75 @@ async function _PerpsController_performInitialization() {
3697
3995
  __classPrivateFieldGet(this, _PerpsController_tradingService, "f").setControllerDependencies({
3698
3996
  rewardsIntegrationService: __classPrivateFieldGet(this, _PerpsController_rewardsIntegrationService, "f"),
3699
3997
  });
3998
+ }, _PerpsController_getActiveProviderWhenReady =
3999
+ /**
4000
+ * Await in-flight initialization, then return the active provider.
4001
+ * Use for async action methods (trading, deposits, withdrawals) that should
4002
+ * tolerate an in-progress cold-start or reconnection instead of failing
4003
+ * immediately with CLIENT_NOT_INITIALIZED.
4004
+ *
4005
+ * Synchronous callers that need fail-fast behaviour should keep using
4006
+ * getActiveProvider() directly.
4007
+ *
4008
+ * @returns The active provider once initialization completes.
4009
+ */
4010
+ async function _PerpsController_getActiveProviderWhenReady() {
4011
+ await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_awaitInitializationIfInProgress).call(this);
4012
+ return this.getActiveProvider();
4013
+ }, _PerpsController_buildGlobalSnapshotContext = function _PerpsController_buildGlobalSnapshotContext() {
4014
+ const snapshotConfigured = Boolean(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalApi?.globalSnapshotUrl) ||
4015
+ typeof __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.terminalMarketService
4016
+ ?.fetchGlobalSnapshot === 'function';
4017
+ if (!snapshotConfigured || this.state.activeProvider !== 'hyperliquid') {
4018
+ return undefined;
4019
+ }
4020
+ const enabledDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4021
+ if (!enabledDexes) {
4022
+ return undefined;
4023
+ }
4024
+ const { isTestnet, hip3ConfigVersion } = this.state;
4025
+ return {
4026
+ request: {
4027
+ provider: 'hyperliquid',
4028
+ network: isTestnet ? 'testnet' : 'mainnet',
4029
+ enabledDexes,
4030
+ },
4031
+ isCurrent: () => this.state.activeProvider === 'hyperliquid' &&
4032
+ this.state.isTestnet === isTestnet &&
4033
+ this.state.hip3ConfigVersion === hip3ConfigVersion,
4034
+ isMarketAllowed: __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_buildMarketAllowedFilter).call(this),
4035
+ };
4036
+ }, _PerpsController_getStaticSnapshotDexes = function _PerpsController_getStaticSnapshotDexes() {
4037
+ if (!__classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f")) {
4038
+ return ['main'];
4039
+ }
4040
+ if (this.state.isTestnet) {
4041
+ return TESTNET_HIP3_CONFIG.AutoDiscoverAll
4042
+ ? undefined
4043
+ : canonicalizeHyperLiquidDexes(TESTNET_HIP3_CONFIG.EnabledDexs);
4044
+ }
4045
+ if (MAINNET_HIP3_CONFIG.AutoDiscoverAll) {
4046
+ return undefined;
4047
+ }
4048
+ const dexes = new Set();
4049
+ for (const pattern of __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f")) {
4050
+ const colonIndex = pattern.indexOf(':');
4051
+ if (colonIndex <= 0) {
4052
+ if (/^[a-z][a-z0-9]*$/iu.test(pattern)) {
4053
+ dexes.add(pattern.toLowerCase());
4054
+ continue;
4055
+ }
4056
+ return undefined;
4057
+ }
4058
+ const dex = pattern.slice(0, colonIndex);
4059
+ if (dex && /^[a-z0-9][a-z0-9-]*$/u.test(dex)) {
4060
+ dexes.add(dex);
4061
+ }
4062
+ else {
4063
+ return undefined;
4064
+ }
4065
+ }
4066
+ return canonicalizeHyperLiquidDexes(dexes);
3700
4067
  }, _PerpsController_hydrateCacheFromDiskSync = function _PerpsController_hydrateCacheFromDiskSync() {
3701
4068
  const { marketUpdates, userUpdates, stats } = hydrateFromDiskSync(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, this.state.cachedMarketDataByProvider, this.state.cachedUserDataByProvider, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs));
3702
4069
  const hasMarketUpdates = Object.keys(marketUpdates).length > 0;
@@ -3717,12 +4084,40 @@ async function _PerpsController_performInitialization() {
3717
4084
  orders: stats.userOrders,
3718
4085
  duration_ms: stats.durationMs,
3719
4086
  });
4087
+ }, _PerpsController_persistUserCacheToDisk = function _PerpsController_persistUserCacheToDisk() {
4088
+ const entries = [];
4089
+ for (const [cacheKey, entry] of Object.entries(this.state.cachedUserDataByProvider)) {
4090
+ const [providerId, network] = cacheKey.split(':');
4091
+ if (!providerId ||
4092
+ (network !== 'mainnet' && network !== 'testnet') ||
4093
+ providerId === 'aggregated') {
4094
+ continue;
4095
+ }
4096
+ entries.push({
4097
+ providerNetworkKey: `${providerId}:${network}`,
4098
+ address: entry.address,
4099
+ positions: entry.positions,
4100
+ orders: entry.orders,
4101
+ accountState: entry.accountState,
4102
+ timestamp: entry.timestamp,
4103
+ ...(entry.hip3ConfigVersion !== undefined && {
4104
+ hip3ConfigVersion: entry.hip3ConfigVersion,
4105
+ }),
4106
+ ...(entry.dexes !== undefined && { dexes: entry.dexes }),
4107
+ });
4108
+ }
4109
+ __classPrivateFieldSet(this, _PerpsController_userDiskWrite, __classPrivateFieldGet(this, _PerpsController_userDiskWrite, "f")
4110
+ .then(() => persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, entries))
4111
+ .catch(() => {
4112
+ // Disk persistence is best-effort and must not block live data.
4113
+ }), "f");
3720
4114
  }, _PerpsController_performMarketDataPreload =
3721
4115
  /**
3722
4116
  * Perform a single market data preload (best-effort, no throw).
3723
4117
  */
3724
4118
  async function _PerpsController_performMarketDataPreload() {
3725
4119
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
4120
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, true, "f");
3726
4121
  return;
3727
4122
  }
3728
4123
  // Skip preloading during provider/network reinitialisation.
@@ -3737,9 +4132,16 @@ async function _PerpsController_performMarketDataPreload() {
3737
4132
  ? this.state.activeProvider // includes 'aggregated'
3738
4133
  : 'hyperliquid';
3739
4134
  const cacheKey = buildProviderCacheKey(actualProviderId, this.state.isTestnet);
4135
+ const preloadContext = {
4136
+ activeProvider: this.state.activeProvider,
4137
+ isTestnet: this.state.isTestnet,
4138
+ hip3ConfigVersion: this.state.hip3ConfigVersion,
4139
+ };
4140
+ const staticSnapshotDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
3740
4141
  const now = Date.now();
3741
4142
  const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3742
4143
  if (existingEntry &&
4144
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMarketCacheEntryCurrent).call(this, actualProviderId, existingEntry) &&
3743
4145
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3744
4146
  return;
3745
4147
  }
@@ -3764,8 +4166,32 @@ async function _PerpsController_performMarketDataPreload() {
3764
4166
  duration_ms: Math.round(performance.now() - preloadStart),
3765
4167
  markets: data.length,
3766
4168
  });
4169
+ if (this.state.activeProvider !== preloadContext.activeProvider ||
4170
+ this.state.isTestnet !== preloadContext.isTestnet ||
4171
+ this.state.hip3ConfigVersion !== preloadContext.hip3ConfigVersion) {
4172
+ traceData = {
4173
+ success: false,
4174
+ error: 'Global snapshot preload context changed',
4175
+ };
4176
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Discarding stale global snapshot preload');
4177
+ return;
4178
+ }
3767
4179
  // Store under per-provider key(s)
3768
4180
  const ts = Date.now();
4181
+ const sourceExpiries = data.flatMap((market) => market.dataSource === 'terminal-global-snapshot-mark' &&
4182
+ typeof market.sourceExpiresAt === 'number'
4183
+ ? [market.sourceExpiresAt]
4184
+ : []);
4185
+ const sourceExpiresAt = data.length > 0 && sourceExpiries.length === data.length
4186
+ ? Math.min(...sourceExpiries)
4187
+ : undefined;
4188
+ const snapshotCacheIdentity = sourceExpiresAt !== undefined && staticSnapshotDexes
4189
+ ? {
4190
+ sourceExpiresAt,
4191
+ hip3ConfigVersion: preloadContext.hip3ConfigVersion,
4192
+ dexes: staticSnapshotDexes,
4193
+ }
4194
+ : {};
3769
4195
  const marketDiskEntries = [];
3770
4196
  if (this.state.activeProvider === 'aggregated' &&
3771
4197
  this.activeProviderInstance) {
@@ -3812,6 +4238,7 @@ async function _PerpsController_performMarketDataPreload() {
3812
4238
  state.cachedMarketDataByProvider[cacheKey] = {
3813
4239
  data,
3814
4240
  timestamp: ts,
4241
+ ...snapshotCacheIdentity,
3815
4242
  };
3816
4243
  });
3817
4244
  }
@@ -3821,10 +4248,6 @@ async function _PerpsController_performMarketDataPreload() {
3821
4248
  });
3822
4249
  traceData = { success: true, marketCount: data.length };
3823
4250
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(PerpsMeasurementName.PerpsMarketDataPreload, performance.now() - preloadStart, 'millisecond');
3824
- // Also preload user data (fire-and-forget, non-blocking)
3825
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
3826
- /* fire-and-forget */
3827
- });
3828
4251
  }
3829
4252
  catch (error) {
3830
4253
  traceData = {
@@ -3843,6 +4266,12 @@ async function _PerpsController_performMarketDataPreload() {
3843
4266
  data: traceData,
3844
4267
  });
3845
4268
  __classPrivateFieldSet(this, _PerpsController_isPreloading, false, "f");
4269
+ if (__classPrivateFieldGet(this, _PerpsController_marketPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4270
+ __classPrivateFieldSet(this, _PerpsController_marketPreloadQueued, false, "f");
4271
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
4272
+ // Background preload is best-effort.
4273
+ });
4274
+ }
3846
4275
  }
3847
4276
  }, _PerpsController_performUserDataPreload =
3848
4277
  /**
@@ -3851,6 +4280,10 @@ async function _PerpsController_performMarketDataPreload() {
3851
4280
  */
3852
4281
  async function _PerpsController_performUserDataPreload() {
3853
4282
  if (__classPrivateFieldGet(this, _PerpsController_isPreloadingUserData, "f")) {
4283
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, true, "f");
4284
+ return;
4285
+ }
4286
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3854
4287
  return;
3855
4288
  }
3856
4289
  // Get current user address
@@ -3859,21 +4292,54 @@ async function _PerpsController_performUserDataPreload() {
3859
4292
  return;
3860
4293
  }
3861
4294
  const userAddress = evmAccount.address;
4295
+ const { activeProvider, isTestnet, hip3ConfigVersion } = this.state;
4296
+ const { activeProviderInstance } = this;
4297
+ const hyperliquidDexes = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getStaticSnapshotDexes).call(this);
4298
+ const isCurrent = () => {
4299
+ let currentAddress;
4300
+ try {
4301
+ currentAddress = getSelectedEvmAccountFromMessenger(this.messenger)?.address;
4302
+ }
4303
+ catch {
4304
+ return false;
4305
+ }
4306
+ return (this.state.activeProvider === activeProvider &&
4307
+ this.activeProviderInstance === activeProviderInstance &&
4308
+ this.state.isTestnet === isTestnet &&
4309
+ this.state.hip3ConfigVersion === hip3ConfigVersion &&
4310
+ currentAddress?.toLowerCase() === userAddress.toLowerCase());
4311
+ };
3862
4312
  // Determine actual provider (same logic as market preload)
3863
- const actualProviderId = this.activeProviderInstance
3864
- ? this.state.activeProvider // includes 'aggregated'
4313
+ const actualProviderId = activeProviderInstance
4314
+ ? activeProvider // includes 'aggregated'
3865
4315
  : 'hyperliquid';
3866
- const userCacheKey = buildProviderCacheKey(actualProviderId, this.state.isTestnet);
4316
+ const providerNetworkKey = buildProviderCacheKey(actualProviderId, isTestnet);
3867
4317
  // Skip if cache is fresh and for same account
3868
4318
  const now = Date.now();
3869
- const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3870
- if (existingEntry?.address === userAddress &&
4319
+ const existingEntry = this.state.cachedUserDataByProvider[providerNetworkKey];
4320
+ const hasMatchingCache = existingEntry !== undefined &&
4321
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isUserCacheIdentityCurrent).call(this, actualProviderId, existingEntry, userAddress);
4322
+ const cacheAgeMs = existingEntry ? now - existingEntry.timestamp : null;
4323
+ const websocketState = this.getWebSocketConnectionState();
4324
+ let selectedEntryKey = null;
4325
+ if (this.state.cachedUserDataByProvider[providerNetworkKey]) {
4326
+ selectedEntryKey = providerNetworkKey;
4327
+ }
4328
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: user cache preload decision', {
4329
+ requestedKey: providerNetworkKey,
4330
+ selectedEntryKey,
4331
+ availableKeys: Object.keys(this.state.cachedUserDataByProvider).sort(),
4332
+ hasMatchingCache,
4333
+ cacheAgeMs,
4334
+ websocketState,
4335
+ });
4336
+ if (existingEntry &&
4337
+ hasMatchingCache &&
3871
4338
  now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3872
4339
  return;
3873
4340
  }
3874
- // Skip standalone REST polling when WebSocket is connected — live data is streaming
3875
- if (this.getWebSocketConnectionState() === WebSocketConnectionState.Connected) {
3876
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Skipping user data preload — WebSocket connected');
4341
+ if (hasMatchingCache &&
4342
+ this.getWebSocketConnectionState() === WebSocketConnectionState.Connected) {
3877
4343
  return;
3878
4344
  }
3879
4345
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, true, "f");
@@ -3886,21 +4352,38 @@ async function _PerpsController_performUserDataPreload() {
3886
4352
  id: traceId,
3887
4353
  op: PerpsTraceOperations.Operation,
3888
4354
  tags: {
3889
- provider: this.state.activeProvider,
3890
- isTestnet: this.state.isTestnet,
4355
+ provider: activeProvider,
4356
+ isTestnet,
3891
4357
  },
3892
4358
  data: { userAddress },
3893
4359
  });
3894
4360
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching user data in background', {
3895
4361
  userAddress,
3896
4362
  });
4363
+ if (activeProvider === 'hyperliquid') {
4364
+ const snapshot = await this.getUserDataSnapshot();
4365
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
4366
+ positionCount: snapshot.positions.length,
4367
+ orderCount: snapshot.orders.length,
4368
+ totalBalance: snapshot.accountState.totalBalance,
4369
+ });
4370
+ traceData = {
4371
+ success: true,
4372
+ positionCount: snapshot.positions.length,
4373
+ orderCount: snapshot.orders.length,
4374
+ };
4375
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.setMeasurement(PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond');
4376
+ return;
4377
+ }
3897
4378
  const [positions, orders, accountState] = await Promise.all([
3898
4379
  this.getPositions({ standalone: true, userAddress }),
3899
4380
  this.getOpenOrders({ standalone: true, userAddress }),
3900
4381
  this.getAccountState({ standalone: true, userAddress }),
3901
4382
  ]);
3902
- if (this.state.activeProvider === 'aggregated' &&
3903
- this.activeProviderInstance) {
4383
+ if (!isCurrent()) {
4384
+ throw new Error('User data preload context changed');
4385
+ }
4386
+ if (activeProvider === 'aggregated' && activeProviderInstance) {
3904
4387
  // Split by providerId and write one cache entry per provider key
3905
4388
  // (mirrors the market-data preload pattern at ~line 2976)
3906
4389
  const ts = Date.now();
@@ -3922,26 +4405,22 @@ async function _PerpsController_performUserDataPreload() {
3922
4405
  }
3923
4406
  // AccountState — assign to its provider bucket
3924
4407
  ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3925
- const diskEntries = [];
3926
4408
  this.update((state) => {
3927
4409
  for (const [pid, data] of byProvider) {
3928
- const key = buildProviderCacheKey(pid, this.state.isTestnet);
3929
- diskEntries.push({
3930
- providerNetworkKey: key,
3931
- address: userAddress,
3932
- positions: data.positions,
3933
- orders: data.orders,
3934
- accountState: data.accountState,
3935
- timestamp: ts,
3936
- });
4410
+ const key = buildProviderCacheKey(pid, isTestnet);
3937
4411
  state.cachedUserDataByProvider[key] = {
3938
4412
  ...data,
3939
4413
  timestamp: ts,
3940
4414
  address: userAddress,
4415
+ ...(pid === 'hyperliquid' &&
4416
+ hyperliquidDexes && {
4417
+ hip3ConfigVersion,
4418
+ dexes: hyperliquidDexes,
4419
+ }),
3941
4420
  };
3942
4421
  }
3943
4422
  // Write aggregated sentinel so the staleness guard sees it
3944
- state.cachedUserDataByProvider[userCacheKey] = {
4423
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3945
4424
  positions: [],
3946
4425
  orders: [],
3947
4426
  accountState: null,
@@ -3949,30 +4428,26 @@ async function _PerpsController_performUserDataPreload() {
3949
4428
  address: userAddress,
3950
4429
  };
3951
4430
  });
3952
- persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, diskEntries);
4431
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3953
4432
  }
3954
4433
  else {
3955
4434
  // Single provider — store directly under its key
3956
4435
  const ts = Date.now();
3957
4436
  this.update((state) => {
3958
- state.cachedUserDataByProvider[userCacheKey] = {
4437
+ state.cachedUserDataByProvider[providerNetworkKey] = {
3959
4438
  positions,
3960
4439
  orders,
3961
4440
  accountState,
3962
4441
  timestamp: ts,
3963
4442
  address: userAddress,
4443
+ ...(actualProviderId === 'hyperliquid' &&
4444
+ hyperliquidDexes && {
4445
+ hip3ConfigVersion,
4446
+ dexes: hyperliquidDexes,
4447
+ }),
3964
4448
  };
3965
4449
  });
3966
- persistUserEntriesToDisk(__classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.diskCache, [
3967
- {
3968
- providerNetworkKey: userCacheKey,
3969
- address: userAddress,
3970
- positions,
3971
- orders,
3972
- accountState,
3973
- timestamp: ts,
3974
- },
3975
- ]);
4450
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_persistUserCacheToDisk).call(this);
3976
4451
  }
3977
4452
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3978
4453
  positionCount: positions.length,
@@ -4003,6 +4478,12 @@ async function _PerpsController_performUserDataPreload() {
4003
4478
  data: traceData,
4004
4479
  });
4005
4480
  __classPrivateFieldSet(this, _PerpsController_isPreloadingUserData, false, "f");
4481
+ if (__classPrivateFieldGet(this, _PerpsController_userPreloadQueued, "f") && __classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
4482
+ __classPrivateFieldSet(this, _PerpsController_userPreloadQueued, false, "f");
4483
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performUserDataPreload).call(this).catch(() => {
4484
+ // Background preload is best-effort.
4485
+ });
4486
+ }
4006
4487
  }
4007
4488
  }, _PerpsController_persistWatchlistToRemote =
4008
4489
  /**