@metamask-previews/perps-controller 1.3.0-preview-a0caca0c0 → 1.3.0-preview-17c76be24

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 (158) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/PerpsController-method-action-types.cjs +3 -0
  3. package/dist/PerpsController-method-action-types.cjs.map +1 -0
  4. package/dist/PerpsController-method-action-types.d.cts +147 -0
  5. package/dist/PerpsController-method-action-types.d.cts.map +1 -0
  6. package/dist/PerpsController-method-action-types.d.mts +147 -0
  7. package/dist/PerpsController-method-action-types.d.mts.map +1 -0
  8. package/dist/PerpsController-method-action-types.mjs +2 -0
  9. package/dist/PerpsController-method-action-types.mjs.map +1 -0
  10. package/dist/PerpsController.cjs +488 -183
  11. package/dist/PerpsController.cjs.map +1 -1
  12. package/dist/PerpsController.d.cts +91 -131
  13. package/dist/PerpsController.d.cts.map +1 -1
  14. package/dist/PerpsController.d.mts +91 -131
  15. package/dist/PerpsController.d.mts.map +1 -1
  16. package/dist/PerpsController.mjs +485 -182
  17. package/dist/PerpsController.mjs.map +1 -1
  18. package/dist/constants/eventNames.cjs +26 -0
  19. package/dist/constants/eventNames.cjs.map +1 -1
  20. package/dist/constants/eventNames.d.cts +19 -0
  21. package/dist/constants/eventNames.d.cts.map +1 -1
  22. package/dist/constants/eventNames.d.mts +19 -0
  23. package/dist/constants/eventNames.d.mts.map +1 -1
  24. package/dist/constants/eventNames.mjs +26 -0
  25. package/dist/constants/eventNames.mjs.map +1 -1
  26. package/dist/constants/hyperLiquidConfig.cjs +24 -4
  27. package/dist/constants/hyperLiquidConfig.cjs.map +1 -1
  28. package/dist/constants/hyperLiquidConfig.d.cts +8 -3
  29. package/dist/constants/hyperLiquidConfig.d.cts.map +1 -1
  30. package/dist/constants/hyperLiquidConfig.d.mts +8 -3
  31. package/dist/constants/hyperLiquidConfig.d.mts.map +1 -1
  32. package/dist/constants/hyperLiquidConfig.mjs +23 -3
  33. package/dist/constants/hyperLiquidConfig.mjs.map +1 -1
  34. package/dist/constants/myxConfig.cjs +75 -51
  35. package/dist/constants/myxConfig.cjs.map +1 -1
  36. package/dist/constants/myxConfig.d.cts +58 -19
  37. package/dist/constants/myxConfig.d.cts.map +1 -1
  38. package/dist/constants/myxConfig.d.mts +58 -19
  39. package/dist/constants/myxConfig.d.mts.map +1 -1
  40. package/dist/constants/myxConfig.mjs +74 -50
  41. package/dist/constants/myxConfig.mjs.map +1 -1
  42. package/dist/constants/perpsConfig.cjs +6 -2
  43. package/dist/constants/perpsConfig.cjs.map +1 -1
  44. package/dist/constants/perpsConfig.d.cts +5 -1
  45. package/dist/constants/perpsConfig.d.cts.map +1 -1
  46. package/dist/constants/perpsConfig.d.mts +5 -1
  47. package/dist/constants/perpsConfig.d.mts.map +1 -1
  48. package/dist/constants/perpsConfig.mjs +5 -1
  49. package/dist/constants/perpsConfig.mjs.map +1 -1
  50. package/dist/index.cjs +243 -25
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +48 -5
  53. package/dist/index.d.cts.map +1 -1
  54. package/dist/index.d.mts +48 -5
  55. package/dist/index.d.mts.map +1 -1
  56. package/dist/index.mjs +36 -10
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/providers/AggregatedPerpsProvider.cjs +24 -0
  59. package/dist/providers/AggregatedPerpsProvider.cjs.map +1 -1
  60. package/dist/providers/AggregatedPerpsProvider.d.cts +4 -0
  61. package/dist/providers/AggregatedPerpsProvider.d.cts.map +1 -1
  62. package/dist/providers/AggregatedPerpsProvider.d.mts +4 -0
  63. package/dist/providers/AggregatedPerpsProvider.d.mts.map +1 -1
  64. package/dist/providers/AggregatedPerpsProvider.mjs +24 -0
  65. package/dist/providers/AggregatedPerpsProvider.mjs.map +1 -1
  66. package/dist/providers/HyperLiquidProvider.cjs +470 -154
  67. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  68. package/dist/providers/HyperLiquidProvider.d.cts +11 -2
  69. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  70. package/dist/providers/HyperLiquidProvider.d.mts +11 -2
  71. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  72. package/dist/providers/HyperLiquidProvider.mjs +471 -155
  73. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  74. package/dist/selectors.cjs +1 -1
  75. package/dist/selectors.cjs.map +1 -1
  76. package/dist/selectors.mjs +1 -1
  77. package/dist/selectors.mjs.map +1 -1
  78. package/dist/services/AccountService.cjs +1 -1
  79. package/dist/services/AccountService.cjs.map +1 -1
  80. package/dist/services/AccountService.d.cts.map +1 -1
  81. package/dist/services/AccountService.d.mts.map +1 -1
  82. package/dist/services/AccountService.mjs +2 -2
  83. package/dist/services/AccountService.mjs.map +1 -1
  84. package/dist/services/DepositService.cjs +2 -1
  85. package/dist/services/DepositService.cjs.map +1 -1
  86. package/dist/services/DepositService.d.cts.map +1 -1
  87. package/dist/services/DepositService.d.mts.map +1 -1
  88. package/dist/services/DepositService.mjs +2 -1
  89. package/dist/services/DepositService.mjs.map +1 -1
  90. package/dist/services/EligibilityService.cjs +8 -103
  91. package/dist/services/EligibilityService.cjs.map +1 -1
  92. package/dist/services/EligibilityService.d.cts +4 -15
  93. package/dist/services/EligibilityService.d.cts.map +1 -1
  94. package/dist/services/EligibilityService.d.mts +4 -15
  95. package/dist/services/EligibilityService.d.mts.map +1 -1
  96. package/dist/services/EligibilityService.mjs +8 -103
  97. package/dist/services/EligibilityService.mjs.map +1 -1
  98. package/dist/services/HyperLiquidSubscriptionService.cjs +252 -46
  99. package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
  100. package/dist/services/HyperLiquidSubscriptionService.d.cts +1 -0
  101. package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
  102. package/dist/services/HyperLiquidSubscriptionService.d.mts +1 -0
  103. package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
  104. package/dist/services/HyperLiquidSubscriptionService.mjs +252 -46
  105. package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
  106. package/dist/services/MarketDataService.cjs +2 -4
  107. package/dist/services/MarketDataService.cjs.map +1 -1
  108. package/dist/services/MarketDataService.d.cts.map +1 -1
  109. package/dist/services/MarketDataService.d.mts.map +1 -1
  110. package/dist/services/MarketDataService.mjs +2 -4
  111. package/dist/services/MarketDataService.mjs.map +1 -1
  112. package/dist/services/TradingService.cjs +72 -6
  113. package/dist/services/TradingService.cjs.map +1 -1
  114. package/dist/services/TradingService.d.cts.map +1 -1
  115. package/dist/services/TradingService.d.mts.map +1 -1
  116. package/dist/services/TradingService.mjs +72 -6
  117. package/dist/services/TradingService.mjs.map +1 -1
  118. package/dist/types/index.cjs.map +1 -1
  119. package/dist/types/index.d.cts +51 -3
  120. package/dist/types/index.d.cts.map +1 -1
  121. package/dist/types/index.d.mts +51 -3
  122. package/dist/types/index.d.mts.map +1 -1
  123. package/dist/types/index.mjs.map +1 -1
  124. package/dist/types/messenger.cjs.map +1 -1
  125. package/dist/types/messenger.d.cts +2 -1
  126. package/dist/types/messenger.d.cts.map +1 -1
  127. package/dist/types/messenger.d.mts +2 -1
  128. package/dist/types/messenger.d.mts.map +1 -1
  129. package/dist/types/messenger.mjs.map +1 -1
  130. package/dist/types/myx-types.cjs +19 -3
  131. package/dist/types/myx-types.cjs.map +1 -1
  132. package/dist/types/myx-types.d.cts +33 -2
  133. package/dist/types/myx-types.d.cts.map +1 -1
  134. package/dist/types/myx-types.d.mts +33 -2
  135. package/dist/types/myx-types.d.mts.map +1 -1
  136. package/dist/types/myx-types.mjs +6 -2
  137. package/dist/types/myx-types.mjs.map +1 -1
  138. package/dist/types/perps-types.cjs +3 -0
  139. package/dist/types/perps-types.cjs.map +1 -1
  140. package/dist/types/perps-types.d.cts +3 -3
  141. package/dist/types/perps-types.d.cts.map +1 -1
  142. package/dist/types/perps-types.d.mts +3 -3
  143. package/dist/types/perps-types.d.mts.map +1 -1
  144. package/dist/types/perps-types.mjs +3 -0
  145. package/dist/types/perps-types.mjs.map +1 -1
  146. package/dist/utils/accountUtils.cjs.map +1 -1
  147. package/dist/utils/accountUtils.d.cts.map +1 -1
  148. package/dist/utils/accountUtils.d.mts.map +1 -1
  149. package/dist/utils/accountUtils.mjs.map +1 -1
  150. package/dist/utils/myxAdapter.cjs +360 -15
  151. package/dist/utils/myxAdapter.cjs.map +1 -1
  152. package/dist/utils/myxAdapter.d.cts +94 -7
  153. package/dist/utils/myxAdapter.d.cts.map +1 -1
  154. package/dist/utils/myxAdapter.d.mts +94 -7
  155. package/dist/utils/myxAdapter.d.mts.map +1 -1
  156. package/dist/utils/myxAdapter.mjs +351 -16
  157. package/dist/utils/myxAdapter.mjs.map +1 -1
  158. package/package.json +5 -3
@@ -10,9 +10,9 @@ 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_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_eligibilityCheckDeferred, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_getOrCreateStandaloneProvider, _PerpsController_cleanupStandaloneProvider, _PerpsController_getMetrics, _PerpsController_findNetworkClientIdForChain, _PerpsController_submitTransaction, _PerpsController_migrateRequestsIfNeeded, _PerpsController_withStreamPause, _PerpsController_performInitialization, _PerpsController_getErrorContext, _PerpsController_getControllerState, _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_performMarketDataPreload, _PerpsController_performUserDataPreload;
13
+ var _PerpsController_instances, _a, _PerpsController_initializationPromise, _PerpsController_isReinitializing, _PerpsController_myxRegistrationPromise, _PerpsController_blockedRegionListVersion, _PerpsController_hip3Enabled, _PerpsController_hip3AllowlistMarkets, _PerpsController_hip3BlocklistMarkets, _PerpsController_hip3ConfigSource, _PerpsController_isMYXProviderEnabled, _PerpsController_standaloneProvider, _PerpsController_handlersRegistered, _PerpsController_standaloneProviderIsTestnet, _PerpsController_standaloneProviderHip3Version, _PerpsController_eligibilityCheckDeferred, _PerpsController_options, _PerpsController_tradingService, _PerpsController_marketDataService, _PerpsController_accountService, _PerpsController_eligibilityService, _PerpsController_dataLakeService, _PerpsController_depositService, _PerpsController_featureFlagConfigurationService, _PerpsController_rewardsIntegrationService, _PerpsController_logError, _PerpsController_debugLog, _PerpsController_marketCacheKey, _PerpsController_providerIsTestnet, _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_createServiceContext, _PerpsController_ensureTradingServiceDeps, _PerpsController_preloadWatchedPaths, _PerpsController_preloadTimer, _PerpsController_isPreloading, _PerpsController_isPreloadingUserData, _PerpsController_preloadStateUnsubscribe, _PerpsController_accountChangeUnsubscribe, _PerpsController_previousIsTestnet, _PerpsController_previousHip3ConfigVersion, _PerpsController_preloadRefreshMs, _PerpsController_preloadGuardMs, _PerpsController_performMarketDataPreload, _PerpsController_performUserDataPreload;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.PerpsController = exports.getDefaultPerpsControllerState = exports.InitializationState = exports.PERPS_ERROR_CODES = void 0;
15
+ exports.PerpsController = exports.getDefaultPerpsControllerState = exports.InitializationState = exports.PERPS_ERROR_CODES = exports.resolveMyxAuthConfig = exports.firstNonEmpty = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
17
17
  const controller_utils_1 = require("@metamask/controller-utils");
18
18
  const uuid_1 = require("uuid");
@@ -37,6 +37,39 @@ const types_1 = require("./types/index.cjs");
37
37
  const accountUtils_1 = require("./utils/accountUtils.cjs");
38
38
  const errorUtils_1 = require("./utils/errorUtils.cjs");
39
39
  const wait_1 = require("./utils/wait.cjs");
40
+ /**
41
+ * Returns the first non-empty string from the given values.
42
+ * Env vars default to '' (not null/undefined), so ?? wouldn't fall through.
43
+ *
44
+ * @param vals - String values to check in order.
45
+ * @returns The first non-empty string, or '' if all are empty/undefined.
46
+ */
47
+ function firstNonEmpty(...vals) {
48
+ return (vals.find((val) => val !== null && val !== undefined && val !== '') ?? '');
49
+ }
50
+ exports.firstNonEmpty = firstNonEmpty;
51
+ /**
52
+ * Resolves MYX auth config from provider credentials, handling
53
+ * testnet/mainnet fallback logic.
54
+ *
55
+ * @param myx - MYX provider credentials.
56
+ * @param isTestnet - Whether the controller is in testnet mode.
57
+ * @returns Resolved appId, apiSecret, and brokerAddress.
58
+ */
59
+ function resolveMyxAuthConfig(myx, isTestnet) {
60
+ return {
61
+ appId: isTestnet
62
+ ? (myx.appIdTestnet ?? '')
63
+ : firstNonEmpty(myx.appIdMainnet, myx.appIdTestnet),
64
+ apiSecret: isTestnet
65
+ ? (myx.apiSecretTestnet ?? '')
66
+ : firstNonEmpty(myx.apiSecretMainnet, myx.apiSecretTestnet),
67
+ brokerAddress: isTestnet
68
+ ? (myx.brokerAddressTestnet ?? '')
69
+ : firstNonEmpty(myx.brokerAddressMainnet, myx.brokerAddressTestnet),
70
+ };
71
+ }
72
+ exports.resolveMyxAuthConfig = resolveMyxAuthConfig;
40
73
  // Re-export error codes from separate file to avoid circular dependencies
41
74
  var perpsErrorCodes_2 = require("./perpsErrorCodes.cjs");
42
75
  Object.defineProperty(exports, "PERPS_ERROR_CODES", { enumerable: true, get: function () { return perpsErrorCodes_2.PERPS_ERROR_CODES; } });
@@ -105,13 +138,8 @@ const getDefaultPerpsControllerState = () => ({
105
138
  },
106
139
  hip3ConfigVersion: 0,
107
140
  selectedPaymentToken: null,
108
- cachedMarketData: null,
109
- cachedMarketDataTimestamp: 0,
110
- cachedPositions: null,
111
- cachedOrders: null,
112
- cachedAccountState: null,
113
- cachedUserDataTimestamp: 0,
114
- cachedUserDataAddress: null,
141
+ cachedMarketDataByProvider: {},
142
+ cachedUserDataByProvider: {},
115
143
  });
116
144
  exports.getDefaultPerpsControllerState = getDefaultPerpsControllerState;
117
145
  /**
@@ -268,48 +296,18 @@ const metadata = {
268
296
  includeInDebugSnapshot: false,
269
297
  usedInUi: true,
270
298
  },
271
- cachedMarketData: {
299
+ cachedMarketDataByProvider: {
272
300
  includeInStateLogs: false,
273
301
  persist: false,
274
302
  includeInDebugSnapshot: false,
275
303
  usedInUi: true,
276
304
  },
277
- cachedMarketDataTimestamp: {
278
- includeInStateLogs: false,
279
- persist: false,
280
- includeInDebugSnapshot: false,
281
- usedInUi: false,
282
- },
283
- cachedPositions: {
305
+ cachedUserDataByProvider: {
284
306
  includeInStateLogs: false,
285
307
  persist: false,
286
308
  includeInDebugSnapshot: false,
287
309
  usedInUi: true,
288
310
  },
289
- cachedOrders: {
290
- includeInStateLogs: false,
291
- persist: false,
292
- includeInDebugSnapshot: false,
293
- usedInUi: true,
294
- },
295
- cachedAccountState: {
296
- includeInStateLogs: false,
297
- persist: false,
298
- includeInDebugSnapshot: false,
299
- usedInUi: true,
300
- },
301
- cachedUserDataTimestamp: {
302
- includeInStateLogs: false,
303
- persist: false,
304
- includeInDebugSnapshot: false,
305
- usedInUi: false,
306
- },
307
- cachedUserDataAddress: {
308
- includeInStateLogs: false,
309
- persist: false,
310
- includeInDebugSnapshot: false,
311
- usedInUi: false,
312
- },
313
311
  };
314
312
  const MESSENGER_EXPOSED_METHODS = [
315
313
  'placeOrder',
@@ -369,6 +367,8 @@ class PerpsController extends base_controller_1.BaseController {
369
367
  this.isInitialized = false;
370
368
  _PerpsController_initializationPromise.set(this, null);
371
369
  _PerpsController_isReinitializing.set(this, false);
370
+ /** Tracks the async MYX dynamic import so performInitialization can await it. */
371
+ _PerpsController_myxRegistrationPromise.set(this, null);
372
372
  this.blockedRegionList = {
373
373
  list: [],
374
374
  source: 'fallback',
@@ -397,6 +397,7 @@ class PerpsController extends base_controller_1.BaseController {
397
397
  * connections) on every standalone call from the preload cycle.
398
398
  */
399
399
  _PerpsController_standaloneProvider.set(this, null);
400
+ _PerpsController_handlersRegistered.set(this, false);
400
401
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
401
402
  _PerpsController_standaloneProviderHip3Version.set(this, null);
402
403
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
@@ -472,7 +473,122 @@ class PerpsController extends base_controller_1.BaseController {
472
473
  this.providers = new Map();
473
474
  // Migrate old persisted data without accountAddress
474
475
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_migrateRequestsIfNeeded).call(this);
475
- this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
476
+ }
477
+ /**
478
+ * Read cached market data for the currently active provider (or aggregated).
479
+ * Returns null when no valid cache exists or when cache has expired.
480
+ *
481
+ * @returns The cached market data array, or null if no valid cache.
482
+ */
483
+ getCachedMarketDataForActiveProvider() {
484
+ const { activeProvider } = this.state;
485
+ const cache = this.state.cachedMarketDataByProvider;
486
+ if (activeProvider === 'aggregated') {
487
+ // Assemble from all registered provider entries
488
+ const assembled = [];
489
+ let oldestTimestamp = Infinity;
490
+ for (const [providerId] of this.providers) {
491
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, providerId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, providerId));
492
+ const entry = cache[key];
493
+ if (!entry || entry.data.length === 0) {
494
+ continue;
495
+ }
496
+ oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
497
+ assembled.push(...entry.data);
498
+ }
499
+ if (assembled.length === 0) {
500
+ return null;
501
+ }
502
+ // Check TTL against the oldest entry
503
+ if (Date.now() - oldestTimestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
504
+ return null;
505
+ }
506
+ return assembled;
507
+ }
508
+ // Single provider mode
509
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, activeProvider, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, activeProvider));
510
+ const entry = cache[key];
511
+ if (!entry || entry.data.length === 0) {
512
+ return null;
513
+ }
514
+ if (Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
515
+ return null;
516
+ }
517
+ return entry.data;
518
+ }
519
+ /**
520
+ * Read cached user data for the currently active provider (or aggregated).
521
+ * Returns null when no valid cache exists, cache has expired, or address
522
+ * does not match the currently selected EVM account.
523
+ *
524
+ * @returns The cached user data, or null if no valid cache.
525
+ */
526
+ getCachedUserDataForActiveProvider() {
527
+ const { activeProvider } = this.state;
528
+ const cache = this.state.cachedUserDataByProvider;
529
+ const staleCutoff = __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 2; // 60s
530
+ // Get current user address for validation
531
+ let currentAddress = null;
532
+ try {
533
+ const evmAccount = (0, accountUtils_1.getSelectedEvmAccount)(this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup'));
534
+ currentAddress = evmAccount?.address ?? null;
535
+ }
536
+ catch {
537
+ // Can't determine current account — trust the cache
538
+ }
539
+ const isValidEntry = (entry) => {
540
+ if (!entry) {
541
+ return false;
542
+ }
543
+ if (Date.now() - entry.timestamp >= staleCutoff) {
544
+ return false;
545
+ }
546
+ if (currentAddress &&
547
+ entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
548
+ return false;
549
+ }
550
+ return true;
551
+ };
552
+ if (activeProvider === 'aggregated') {
553
+ // Assemble from all registered provider entries
554
+ const allPositions = [];
555
+ const allOrders = [];
556
+ let defaultAccountState = null;
557
+ let hasValidEntry = false;
558
+ for (const [providerId] of this.providers) {
559
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, providerId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, providerId));
560
+ const entry = cache[key];
561
+ if (!isValidEntry(entry)) {
562
+ continue;
563
+ }
564
+ hasValidEntry = true;
565
+ allPositions.push(...entry.positions);
566
+ allOrders.push(...entry.orders);
567
+ // AccountState from default provider (hyperliquid)
568
+ if (providerId === 'hyperliquid') {
569
+ defaultAccountState = entry.accountState;
570
+ }
571
+ }
572
+ if (!hasValidEntry) {
573
+ return null;
574
+ }
575
+ return {
576
+ positions: allPositions,
577
+ orders: allOrders,
578
+ accountState: defaultAccountState,
579
+ };
580
+ }
581
+ // Single provider mode
582
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, activeProvider, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, activeProvider));
583
+ const entry = cache[key];
584
+ if (!entry || !isValidEntry(entry)) {
585
+ return null;
586
+ }
587
+ return {
588
+ positions: entry.positions,
589
+ orders: entry.orders,
590
+ accountState: entry.accountState,
591
+ };
476
592
  }
477
593
  /**
478
594
  * Test-observable accessor for whether a standalone provider is cached.
@@ -552,6 +668,10 @@ class PerpsController extends base_controller_1.BaseController {
552
668
  * @returns A promise that resolves when the operation completes.
553
669
  */
554
670
  async init() {
671
+ if (!__classPrivateFieldGet(this, _PerpsController_handlersRegistered, "f")) {
672
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
673
+ __classPrivateFieldSet(this, _PerpsController_handlersRegistered, true, "f");
674
+ }
555
675
  if (this.isInitialized) {
556
676
  return undefined;
557
677
  }
@@ -561,6 +681,46 @@ class PerpsController extends base_controller_1.BaseController {
561
681
  __classPrivateFieldSet(this, _PerpsController_initializationPromise, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performInitialization).call(this), "f");
562
682
  return __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f");
563
683
  }
684
+ /**
685
+ * Registers the MYX provider after dynamic import resolves.
686
+ *
687
+ * Extracted from the import().then() callback so it can be tested directly
688
+ * (Jest cannot resolve dynamic imports without --experimental-vm-modules).
689
+ *
690
+ * @param MYXProvider - Constructor class for the MYX provider.
691
+ */
692
+ registerMYXProvider(MYXProvider) {
693
+ const myxIsTestnet = perpsConfig_1.PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet;
694
+ const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx ?? {};
695
+ const myxAuthConfig = resolveMyxAuthConfig(myx, myxIsTestnet);
696
+ const myxProvider = new MYXProvider({
697
+ isTestnet: myxIsTestnet,
698
+ platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
699
+ messenger: this.messenger,
700
+ myxAuthConfig,
701
+ });
702
+ this.providers.set('myx', myxProvider);
703
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider registered', {
704
+ isTestnet: myxIsTestnet,
705
+ });
706
+ }
707
+ /**
708
+ * Handles errors from the MYX dynamic import.
709
+ *
710
+ * Module-not-found errors are expected (extension doesn't ship MYX) → debug log.
711
+ * Other errors indicate constructor/config problems → Sentry via logError.
712
+ *
713
+ * @param error - The caught error from the dynamic import or constructor.
714
+ */
715
+ handleMYXImportError(error) {
716
+ const isModuleError = error?.code === 'MODULE_NOT_FOUND';
717
+ if (isModuleError) {
718
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider module not available, skipping registration');
719
+ }
720
+ else {
721
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_logError).call(this, error instanceof Error ? error : new Error(String(error)), __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getErrorContext).call(this, 'createProviders.myx'));
722
+ }
723
+ }
564
724
  /**
565
725
  * Get the currently active provider.
566
726
  * In aggregated mode, returns AggregatedPerpsProvider which routes to underlying providers.
@@ -821,6 +981,14 @@ class PerpsController extends base_controller_1.BaseController {
821
981
  origin: controller_utils_1.ORIGIN_METAMASK,
822
982
  skipInitialGasEstimate: true,
823
983
  };
984
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.addBreadcrumb({
985
+ category: 'perps',
986
+ message: 'Deposit action started',
987
+ level: 'info',
988
+ data: {
989
+ place_order_after_deposit: placeOrder === true,
990
+ },
991
+ });
824
992
  if (placeOrder) {
825
993
  // Use addTransaction to create transaction without navigating to confirmation screen
826
994
  const addResult = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_submitTransaction).call(this, transaction, {
@@ -956,8 +1124,8 @@ class PerpsController extends base_controller_1.BaseController {
956
1124
  const errorMessage = (0, errorUtils_1.ensureError)(error, 'PerpsController.depositWithOrder').message;
957
1125
  const isCancellation = errorMessage.includes('User denied') ||
958
1126
  errorMessage.includes('User rejected') ||
959
- errorMessage.includes('cancelled') ||
960
- errorMessage.includes('canceled');
1127
+ errorMessage.includes('User cancelled') ||
1128
+ errorMessage.includes('User canceled');
961
1129
  this.update((state) => {
962
1130
  const requestToUpdate = state.depositRequests.find((req) => req.id === currentDepositId);
963
1131
  if (requestToUpdate) {
@@ -1321,7 +1489,7 @@ class PerpsController extends base_controller_1.BaseController {
1321
1489
  /* fire-and-forget */
1322
1490
  });
1323
1491
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1324
- // Watch for isTestnet / hip3ConfigVersion / cachedUserDataAddress changes
1492
+ // Watch for isTestnet / hip3ConfigVersion changes
1325
1493
  const handler = (_state, patches) => {
1326
1494
  // Early-return when no watched field changed (skips ~46 unrelated updates)
1327
1495
  const hasRelevantChange = patches.some((patch) => typeof patch.path[0] === 'string' &&
@@ -1342,16 +1510,9 @@ class PerpsController extends base_controller_1.BaseController {
1342
1510
  });
1343
1511
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, currentIsTestnet, "f");
1344
1512
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, currentHip3Version, "f");
1345
- // Clear stale cache (market + user data)
1346
- this.update((state) => {
1347
- state.cachedMarketData = null;
1348
- state.cachedMarketDataTimestamp = 0;
1349
- state.cachedPositions = null;
1350
- state.cachedOrders = null;
1351
- state.cachedAccountState = null;
1352
- state.cachedUserDataTimestamp = 0;
1353
- state.cachedUserDataAddress = null;
1354
- });
1513
+ // No need to clear user data cache per-provider keys include the
1514
+ // network, so different networks don't collide. Re-preload will
1515
+ // populate the new key.
1355
1516
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1356
1517
  /* fire-and-forget */
1357
1518
  });
@@ -1365,16 +1526,13 @@ class PerpsController extends base_controller_1.BaseController {
1365
1526
  const accountChangeHandler = () => {
1366
1527
  const evmAccount = (0, accountUtils_1.getSelectedEvmAccount)(this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup'));
1367
1528
  const currentAddress = evmAccount?.address ?? null;
1368
- // If there's cached data from a different account (or no EVM account now), clear it
1369
- if (this.state.cachedUserDataAddress !== null &&
1370
- currentAddress !== this.state.cachedUserDataAddress) {
1529
+ // If any cached entry belongs to a different account, clear all entries.
1530
+ // Max 4 entries (2 providers × 2 networks) — clearing all is simple and safe.
1531
+ const hasStaleEntries = Object.values(this.state.cachedUserDataByProvider).some((entry) => entry.address.toLowerCase() !== currentAddress?.toLowerCase());
1532
+ if (hasStaleEntries) {
1371
1533
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Account changed, clearing user data cache');
1372
1534
  this.update((state) => {
1373
- state.cachedPositions = null;
1374
- state.cachedOrders = null;
1375
- state.cachedAccountState = null;
1376
- state.cachedUserDataTimestamp = 0;
1377
- state.cachedUserDataAddress = null;
1535
+ state.cachedUserDataByProvider = {};
1378
1536
  });
1379
1537
  // Only preload if the new account is an EVM account
1380
1538
  if (currentAddress) {
@@ -1607,6 +1765,15 @@ class PerpsController extends base_controller_1.BaseController {
1607
1765
  }
1608
1766
  finally {
1609
1767
  __classPrivateFieldSet(this, _PerpsController_isReinitializing, false, "f");
1768
+ // Re-trigger preload now that reinit is complete and the
1769
+ // activeProviderInstance points to the correct network.
1770
+ // The state-change listener may have already fired during reinit
1771
+ // but was skipped due to the #isReinitializing guard.
1772
+ if (__classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
1773
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1774
+ /* fire-and-forget */
1775
+ });
1776
+ }
1610
1777
  }
1611
1778
  }
1612
1779
  /**
@@ -1653,7 +1820,7 @@ class PerpsController extends base_controller_1.BaseController {
1653
1820
  // Provider disconnect is handled by performInitialization() during
1654
1821
  // reinitialization. The disconnect() method skips provider teardown
1655
1822
  // when isReinitializing is true to prevent double-disconnect.
1656
- // Update state with new provider
1823
+ // Update state with new provider (market data cache preserved per-provider)
1657
1824
  this.update((state) => {
1658
1825
  state.activeProvider = providerId;
1659
1826
  state.accountState = null;
@@ -1710,6 +1877,12 @@ class PerpsController extends base_controller_1.BaseController {
1710
1877
  }
1711
1878
  finally {
1712
1879
  __classPrivateFieldSet(this, _PerpsController_isReinitializing, false, "f");
1880
+ // Re-trigger preload now that reinit is complete.
1881
+ if (__classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
1882
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1883
+ /* fire-and-forget */
1884
+ });
1885
+ }
1713
1886
  }
1714
1887
  }
1715
1888
  /**
@@ -2087,14 +2260,18 @@ class PerpsController extends base_controller_1.BaseController {
2087
2260
  * Eligibility (Geo-Blocking)
2088
2261
  */
2089
2262
  /**
2090
- * Activates geo-blocking eligibility monitoring.
2091
- * Call this after onboarding is complete when the controller was constructed
2092
- * with `deferEligibilityCheck: true`.
2263
+ * Fetch geo location
2093
2264
  *
2094
- * Reads the current RemoteFeatureFlagController state, performs the initial
2095
- * eligibility check, and unblocks the existing stateChange subscription so
2096
- * future feature-flag updates flow through normally.
2097
- * Safe to call multiple times; subsequent calls simply re-check eligibility.
2265
+ * Returned in Country or Country-Region format
2266
+ * Example: FR, DE, US-MI, CA-ON
2267
+ */
2268
+ /**
2269
+ * Refresh eligibility status
2270
+ */
2271
+ /**
2272
+ * Resume eligibility monitoring after onboarding completes.
2273
+ * Clears the deferred flag and triggers an immediate eligibility check
2274
+ * using the current remote feature flag state.
2098
2275
  */
2099
2276
  startEligibilityMonitoring() {
2100
2277
  __classPrivateFieldSet(this, _PerpsController_eligibilityCheckDeferred, false, "f");
@@ -2117,9 +2294,6 @@ class PerpsController extends base_controller_1.BaseController {
2117
2294
  stopEligibilityMonitoring() {
2118
2295
  __classPrivateFieldSet(this, _PerpsController_eligibilityCheckDeferred, true, "f");
2119
2296
  }
2120
- /**
2121
- * Refresh eligibility status
2122
- */
2123
2297
  async refreshEligibility() {
2124
2298
  if (__classPrivateFieldGet(this, _PerpsController_eligibilityCheckDeferred, "f")) {
2125
2299
  return;
@@ -2130,9 +2304,10 @@ class PerpsController extends base_controller_1.BaseController {
2130
2304
  // (started with remote config after it was fetched).
2131
2305
  const versionAtStart = __classPrivateFieldGet(this, _PerpsController_blockedRegionListVersion, "f");
2132
2306
  try {
2133
- // TODO: It would be good to have this location before we call this async function to avoid the race condition
2307
+ const geoLocation = await this.messenger.call('GeolocationController:getGeolocation');
2134
2308
  const isEligible = await __classPrivateFieldGet(this, _PerpsController_eligibilityService, "f").checkEligibility({
2135
2309
  blockedRegions: this.blockedRegionList.list,
2310
+ geoLocation,
2136
2311
  });
2137
2312
  // Only update state if the blocked region list hasn't changed while we were awaiting.
2138
2313
  // This prevents stale fallback-based eligibility checks from overwriting
@@ -2616,27 +2791,46 @@ class PerpsController extends base_controller_1.BaseController {
2616
2791
  }
2617
2792
  }
2618
2793
  exports.PerpsController = PerpsController;
2619
- _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _PerpsController_isReinitializing = new WeakMap(), _PerpsController_blockedRegionListVersion = new WeakMap(), _PerpsController_hip3Enabled = new WeakMap(), _PerpsController_hip3AllowlistMarkets = new WeakMap(), _PerpsController_hip3BlocklistMarkets = new WeakMap(), _PerpsController_hip3ConfigSource = new WeakMap(), _PerpsController_standaloneProvider = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_eligibilityCheckDeferred = 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() {
2620
- const getLocalFlag = () => typeof globalThis.process !== 'undefined' &&
2621
- globalThis.process.env?.MM_PERPS_MYX_PROVIDER_ENABLED === 'true';
2794
+ _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_standaloneProvider = new WeakMap(), _PerpsController_handlersRegistered = new WeakMap(), _PerpsController_standaloneProviderIsTestnet = new WeakMap(), _PerpsController_standaloneProviderHip3Version = new WeakMap(), _PerpsController_eligibilityCheckDeferred = 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() {
2795
+ const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
2796
+ // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
2797
+ // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
2798
+ // and UI agree on whether MYX is available.
2799
+ if (myx?.enabled) {
2800
+ return true;
2801
+ }
2802
+ // Credentials present → MYX is enabled regardless of remote flag.
2803
+ // Use || so empty-string env vars (default '') fall through.
2804
+ const hasCredentials = Boolean(
2805
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
2806
+ myx?.appIdTestnet || myx?.appIdMainnet);
2807
+ if (hasCredentials) {
2808
+ return true;
2809
+ }
2810
+ // No local override or credentials — check remote flag as fallback
2622
2811
  try {
2623
- const localFlag = getLocalFlag();
2624
2812
  const remoteState = this.messenger.call('RemoteFeatureFlagController:getState');
2625
2813
  const remoteFlag = remoteState.remoteFeatureFlags?.perpsMyxProviderEnabled;
2626
2814
  if ((0, types_1.isVersionGatedFeatureFlag)(remoteFlag)) {
2627
2815
  const validated = __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.featureFlags.validateVersionGated(remoteFlag);
2628
- return validated ?? localFlag;
2816
+ return validated ?? false;
2629
2817
  }
2630
- return localFlag;
2818
+ return false;
2631
2819
  }
2632
2820
  catch {
2633
- // If RemoteFeatureFlagController not ready, use fallback
2634
- return getLocalFlag();
2821
+ return false;
2635
2822
  }
2636
2823
  }, _PerpsController_logError = function _PerpsController_logError(error, options) {
2637
2824
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.logger.error(error, options);
2638
2825
  }, _PerpsController_debugLog = function _PerpsController_debugLog(...args) {
2639
2826
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.debugLogger.log(...args);
2827
+ }, _PerpsController_marketCacheKey = function _PerpsController_marketCacheKey(providerId, isTestnet) {
2828
+ return `${providerId}:${isTestnet ? 'testnet' : 'mainnet'}`;
2829
+ }, _PerpsController_providerIsTestnet = function _PerpsController_providerIsTestnet(providerId) {
2830
+ if (providerId === 'myx') {
2831
+ return perpsConfig_1.PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet;
2832
+ }
2833
+ return this.state.isTestnet;
2640
2834
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
2641
2835
  const currentIsTestnet = this.state.isTestnet;
2642
2836
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -2659,6 +2853,10 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
2659
2853
  blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2660
2854
  platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2661
2855
  messenger: this.messenger,
2856
+ builderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
2857
+ ?.builderAddressTestnet,
2858
+ builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
2859
+ ?.builderAddressMainnet,
2662
2860
  }), "f");
2663
2861
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
2664
2862
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
@@ -2816,89 +3014,16 @@ async function _PerpsController_performInitialization() {
2816
3014
  }
2817
3015
  this.providers.clear();
2818
3016
  await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this);
2819
- const { activeProvider } = this.state;
2820
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Creating provider with HIP-3 configuration', {
2821
- hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
2822
- hip3AllowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
2823
- hip3BlocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2824
- hip3ConfigSource: __classPrivateFieldGet(this, _PerpsController_hip3ConfigSource, "f"),
2825
- isTestnet: this.state.isTestnet,
2826
- activeProvider,
2827
- });
2828
- // Always create HyperLiquid provider as the base provider
2829
- const hyperLiquidProvider = new HyperLiquidProvider_1.HyperLiquidProvider({
2830
- isTestnet: this.state.isTestnet,
2831
- hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
2832
- allowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
2833
- blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2834
- platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2835
- messenger: this.messenger,
2836
- });
2837
- this.providers.set('hyperliquid', hyperLiquidProvider);
2838
- // Register MYX provider if enabled via feature flag.
2839
- // Uses dynamic import so @myx-trade/sdk is excluded from the bundle
2840
- // unless MM_PERPS_MYX_PROVIDER_ENABLED=true is set at build time.
2841
- // Wrapped in try/catch so a missing module (stripped at build time)
2842
- // only skips MYX registration instead of aborting initialization.
2843
- const isMYXEnabled = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMYXProviderEnabled).call(this);
2844
- if (isMYXEnabled) {
2845
- try {
2846
- const { MYXProvider } = await import("./providers/MYXProvider.cjs");
2847
- const myxProvider = new MYXProvider({
2848
- isTestnet: perpsConfig_1.PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet,
2849
- platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2850
- });
2851
- this.providers.set('myx', myxProvider);
2852
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider registered', {
2853
- isTestnet: perpsConfig_1.PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet,
2854
- });
2855
- }
2856
- catch {
2857
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider module not available (stripped from build), skipping registration');
2858
- }
2859
- }
2860
- // Set up active provider based on activeProvider value in state
2861
- // 'aggregated' is treated as just another provider that wraps others
2862
- if (activeProvider === 'aggregated') {
2863
- // Aggregated mode: wrap in AggregatedPerpsProvider for multi-provider support
2864
- this.activeProviderInstance = new AggregatedPerpsProvider_1.AggregatedPerpsProvider({
2865
- providers: this.providers,
2866
- defaultProvider: 'hyperliquid',
2867
- infrastructure: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2868
- });
2869
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Using aggregated provider (multi-provider)', { registeredProviders: Array.from(this.providers.keys()) });
2870
- }
2871
- else if (activeProvider === 'hyperliquid') {
2872
- // Direct provider mode: use HyperLiquid provider directly
2873
- this.activeProviderInstance = hyperLiquidProvider;
2874
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${activeProvider})`);
2875
- }
2876
- else if (activeProvider === 'myx') {
2877
- // MYX provider mode
2878
- const myxProvider = this.providers.get('myx');
2879
- if (myxProvider) {
2880
- this.activeProviderInstance = myxProvider;
2881
- }
2882
- else {
2883
- // MYX feature flag is disabled — fall back to HyperLiquid
2884
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider not available (feature flag disabled), falling back to hyperliquid');
2885
- this.activeProviderInstance = hyperLiquidProvider;
2886
- this.update((state) => {
2887
- state.activeProvider = 'hyperliquid';
2888
- });
2889
- }
2890
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${this.activeProviderInstance === hyperLiquidProvider ? 'hyperliquid' : activeProvider})`);
2891
- }
2892
- else {
2893
- // Unsupported provider - throw error to prevent silent misconfiguration
2894
- throw new Error(`Unsupported provider: ${String(activeProvider)}. Currently only 'hyperliquid', 'myx', and 'aggregated' are supported.`);
2895
- }
2896
- // Future providers can be added here with their own authentication patterns:
2897
- // - Some might use API keys: new BinanceProvider({ apiKey, apiSecret })
2898
- // - Some might use different wallet patterns: new GMXProvider({ signer })
2899
- // - Some might not need auth at all: new DydxProvider()
2900
- // Wait for WebSocket transport to be ready before marking as initialized
2901
- await (0, wait_1.wait)(perpsConfig_1.PERPS_CONSTANTS.ReconnectionCleanupDelayMs);
3017
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createProviders).call(this);
3018
+ // Await MYX dynamic import (if started) so MYX is in the providers
3019
+ // map before we assign the active provider. Runs concurrently with
3020
+ // the WebSocket readiness delay for zero additional latency.
3021
+ await Promise.all([
3022
+ (0, wait_1.wait)(perpsConfig_1.PERPS_CONSTANTS.ReconnectionCleanupDelayMs),
3023
+ __classPrivateFieldGet(this, _PerpsController_myxRegistrationPromise, "f"),
3024
+ ]);
3025
+ __classPrivateFieldSet(this, _PerpsController_myxRegistrationPromise, null, "f");
3026
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_assignActiveProvider).call(this);
2902
3027
  this.isInitialized = true;
2903
3028
  this.update((state) => {
2904
3029
  state.initializationState = InitializationState.Initialized;
@@ -2906,7 +3031,7 @@ async function _PerpsController_performInitialization() {
2906
3031
  });
2907
3032
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Providers initialized successfully', {
2908
3033
  providerCount: this.providers.size,
2909
- activeProvider,
3034
+ activeProvider: this.state.activeProvider,
2910
3035
  timestamp: new Date().toISOString(),
2911
3036
  attempts: attempt,
2912
3037
  });
@@ -2941,6 +3066,84 @@ async function _PerpsController_performInitialization() {
2941
3066
  attempts: maxAttempts,
2942
3067
  timestamp: new Date().toISOString(),
2943
3068
  });
3069
+ }, _PerpsController_createProviders = function _PerpsController_createProviders() {
3070
+ const { activeProvider } = this.state;
3071
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Creating provider with HIP-3 configuration', {
3072
+ hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
3073
+ hip3AllowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
3074
+ hip3BlocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
3075
+ hip3ConfigSource: __classPrivateFieldGet(this, _PerpsController_hip3ConfigSource, "f"),
3076
+ isTestnet: this.state.isTestnet,
3077
+ activeProvider,
3078
+ });
3079
+ // Always create HyperLiquid provider as the base provider
3080
+ const hyperLiquidProvider = new HyperLiquidProvider_1.HyperLiquidProvider({
3081
+ isTestnet: this.state.isTestnet,
3082
+ hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
3083
+ allowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
3084
+ blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
3085
+ platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
3086
+ messenger: this.messenger,
3087
+ builderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3088
+ ?.builderAddressTestnet,
3089
+ builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3090
+ ?.builderAddressMainnet,
3091
+ });
3092
+ this.providers.set('hyperliquid', hyperLiquidProvider);
3093
+ // Register MYX provider if enabled via feature flag.
3094
+ // Dynamic import because the MYX package pulls in heavy dependencies we
3095
+ // don't want bundled in extension. Until MYX fixes their package, extension
3096
+ // doesn't ship it — the catch branch silently skips registration.
3097
+ // Uses .then()/.catch() instead of await because #createProviders is not async;
3098
+ // MYX registration completing asynchronously is fine since it's only used when
3099
+ // explicitly enabled and selected.
3100
+ const isMYXEnabled = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMYXProviderEnabled).call(this);
3101
+ if (isMYXEnabled) {
3102
+ // IMPORTANT: Must use import() — NOT require() — for core/extension tree-shaking.
3103
+ // require() is synchronous and bundlers include it in the main bundle.
3104
+ // import() enables true code splitting so MYX is excluded when not enabled.
3105
+ __classPrivateFieldSet(this, _PerpsController_myxRegistrationPromise, import("./providers/MYXProvider.cjs")
3106
+ .then(({ MYXProvider }) => {
3107
+ this.registerMYXProvider(MYXProvider);
3108
+ return undefined;
3109
+ })
3110
+ .catch((error) => this.handleMYXImportError(error)), "f");
3111
+ }
3112
+ }, _PerpsController_assignActiveProvider = function _PerpsController_assignActiveProvider() {
3113
+ const { activeProvider } = this.state;
3114
+ const hyperLiquidProvider = this.providers.get('hyperliquid');
3115
+ if (!hyperLiquidProvider) {
3116
+ throw new Error('HyperLiquid provider not registered — cannot assign active provider');
3117
+ }
3118
+ if (activeProvider === 'aggregated') {
3119
+ this.activeProviderInstance = new AggregatedPerpsProvider_1.AggregatedPerpsProvider({
3120
+ providers: this.providers,
3121
+ defaultProvider: 'hyperliquid',
3122
+ infrastructure: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
3123
+ });
3124
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Using aggregated provider (multi-provider)', { registeredProviders: Array.from(this.providers.keys()) });
3125
+ }
3126
+ else if (activeProvider === 'hyperliquid') {
3127
+ this.activeProviderInstance = hyperLiquidProvider;
3128
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${activeProvider})`);
3129
+ }
3130
+ else if (activeProvider === 'myx') {
3131
+ const myxProvider = this.providers.get('myx');
3132
+ if (myxProvider) {
3133
+ this.activeProviderInstance = myxProvider;
3134
+ }
3135
+ else {
3136
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider not available, falling back to hyperliquid');
3137
+ this.activeProviderInstance = hyperLiquidProvider;
3138
+ this.update((state) => {
3139
+ state.activeProvider = 'hyperliquid';
3140
+ });
3141
+ }
3142
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${this.activeProviderInstance === hyperLiquidProvider ? 'hyperliquid' : activeProvider})`);
3143
+ }
3144
+ else {
3145
+ throw new Error(`Unsupported provider: ${String(activeProvider)}. Currently only 'hyperliquid', 'myx', and 'aggregated' are supported.`);
3146
+ }
2944
3147
  }, _PerpsController_getErrorContext = function _PerpsController_getErrorContext(method, extra) {
2945
3148
  return {
2946
3149
  tags: {
@@ -2970,7 +3173,7 @@ async function _PerpsController_performInitialization() {
2970
3173
  },
2971
3174
  stateManager: {
2972
3175
  update: (updater) =>
2973
- // @ts-expect-error TS2589 - excessively deep instantiation when inferring stateManager from BaseController
3176
+ // @ts-expect-error TS2589 - excessively deep instantiation from BaseController generic
2974
3177
  this.update(updater),
2975
3178
  getState: () => __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getControllerState).call(this),
2976
3179
  },
@@ -2988,9 +3191,22 @@ async function _PerpsController_performMarketDataPreload() {
2988
3191
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
2989
3192
  return;
2990
3193
  }
3194
+ // Skip preloading during provider/network reinitialisation.
3195
+ // The activeProviderInstance still points to the OLD network's provider
3196
+ // until init() completes, so fetching now would store stale data under
3197
+ // the NEW network's cache key.
3198
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3199
+ return;
3200
+ }
3201
+ // Determine actual provider and cache key for debounce
3202
+ const actualProviderId = this.activeProviderInstance
3203
+ ? this.state.activeProvider // includes 'aggregated'
3204
+ : 'hyperliquid';
3205
+ const cacheKey = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, actualProviderId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, actualProviderId));
2991
3206
  const now = Date.now();
2992
- if (now - this.state.cachedMarketDataTimestamp <
2993
- __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3207
+ const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3208
+ if (existingEntry &&
3209
+ now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
2994
3210
  return;
2995
3211
  }
2996
3212
  __classPrivateFieldSet(this, _PerpsController_isPreloading, true, "f");
@@ -3009,10 +3225,46 @@ async function _PerpsController_performMarketDataPreload() {
3009
3225
  });
3010
3226
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching market data in background');
3011
3227
  const data = await this.getMarketDataWithPrices({ standalone: true });
3012
- this.update((state) => {
3013
- state.cachedMarketData = data;
3014
- state.cachedMarketDataTimestamp = Date.now();
3015
- });
3228
+ // Store under per-provider key(s)
3229
+ const ts = Date.now();
3230
+ if (this.state.activeProvider === 'aggregated' &&
3231
+ this.activeProviderInstance) {
3232
+ // Split returned data by providerId and store each slice
3233
+ const fallbackProviderId = 'hyperliquid'; // default for items missing providerId
3234
+ const byProvider = new Map();
3235
+ for (const item of data) {
3236
+ const pid = item.providerId ?? fallbackProviderId;
3237
+ const existing = byProvider.get(pid);
3238
+ if (existing) {
3239
+ existing.push(item);
3240
+ }
3241
+ else {
3242
+ byProvider.set(pid, [item]);
3243
+ }
3244
+ }
3245
+ this.update((state) => {
3246
+ for (const [pid, slice] of byProvider) {
3247
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, pid, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, pid));
3248
+ state.cachedMarketDataByProvider[key] = {
3249
+ data: slice,
3250
+ timestamp: ts,
3251
+ };
3252
+ }
3253
+ // Write aggregated sentinel so the staleness guard sees it
3254
+ state.cachedMarketDataByProvider[cacheKey] = {
3255
+ data: [], // sentinel — real data is in per-provider keys
3256
+ timestamp: ts,
3257
+ };
3258
+ });
3259
+ }
3260
+ else {
3261
+ this.update((state) => {
3262
+ state.cachedMarketDataByProvider[cacheKey] = {
3263
+ data,
3264
+ timestamp: ts,
3265
+ };
3266
+ });
3267
+ }
3016
3268
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Market data preloaded', {
3017
3269
  marketCount: data.length,
3018
3270
  });
@@ -3056,10 +3308,16 @@ async function _PerpsController_performUserDataPreload() {
3056
3308
  return;
3057
3309
  }
3058
3310
  const userAddress = evmAccount.address;
3311
+ // Determine actual provider (same logic as market preload)
3312
+ const actualProviderId = this.activeProviderInstance
3313
+ ? this.state.activeProvider // includes 'aggregated'
3314
+ : 'hyperliquid';
3315
+ const userCacheKey = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, actualProviderId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, actualProviderId));
3059
3316
  // Skip if cache is fresh and for same account
3060
3317
  const now = Date.now();
3061
- if (this.state.cachedUserDataAddress === userAddress &&
3062
- now - this.state.cachedUserDataTimestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3318
+ const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3319
+ if (existingEntry?.address === userAddress &&
3320
+ now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3063
3321
  return;
3064
3322
  }
3065
3323
  // Skip standalone REST polling when WebSocket is connected — live data is streaming
@@ -3090,13 +3348,60 @@ async function _PerpsController_performUserDataPreload() {
3090
3348
  this.getOpenOrders({ standalone: true, userAddress }),
3091
3349
  this.getAccountState({ standalone: true, userAddress }),
3092
3350
  ]);
3093
- this.update((state) => {
3094
- state.cachedPositions = positions;
3095
- state.cachedOrders = orders;
3096
- state.cachedAccountState = accountState;
3097
- state.cachedUserDataTimestamp = Date.now();
3098
- state.cachedUserDataAddress = userAddress;
3099
- });
3351
+ if (this.state.activeProvider === 'aggregated' &&
3352
+ this.activeProviderInstance) {
3353
+ // Split by providerId and write one cache entry per provider key
3354
+ // (mirrors the market-data preload pattern at ~line 2976)
3355
+ const ts = Date.now();
3356
+ const fallbackProviderId = 'hyperliquid'; // default for items missing providerId
3357
+ const byProvider = new Map();
3358
+ const ensureBucket = (pid) => {
3359
+ let bucket = byProvider.get(pid);
3360
+ if (!bucket) {
3361
+ bucket = { positions: [], orders: [], accountState: null };
3362
+ byProvider.set(pid, bucket);
3363
+ }
3364
+ return bucket;
3365
+ };
3366
+ for (const pos of positions) {
3367
+ ensureBucket(pos.providerId ?? fallbackProviderId).positions.push(pos);
3368
+ }
3369
+ for (const order of orders) {
3370
+ ensureBucket(order.providerId ?? fallbackProviderId).orders.push(order);
3371
+ }
3372
+ // AccountState — assign to its provider bucket
3373
+ ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3374
+ this.update((state) => {
3375
+ for (const [pid, data] of byProvider) {
3376
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, pid, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, pid));
3377
+ state.cachedUserDataByProvider[key] = {
3378
+ ...data,
3379
+ timestamp: ts,
3380
+ address: userAddress,
3381
+ };
3382
+ }
3383
+ // Write aggregated sentinel so the staleness guard sees it
3384
+ state.cachedUserDataByProvider[userCacheKey] = {
3385
+ positions: [],
3386
+ orders: [],
3387
+ accountState: null,
3388
+ timestamp: ts,
3389
+ address: userAddress,
3390
+ };
3391
+ });
3392
+ }
3393
+ else {
3394
+ // Single provider — store directly under its key
3395
+ this.update((state) => {
3396
+ state.cachedUserDataByProvider[userCacheKey] = {
3397
+ positions,
3398
+ orders,
3399
+ accountState,
3400
+ timestamp: Date.now(),
3401
+ address: userAddress,
3402
+ };
3403
+ });
3404
+ }
3100
3405
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3101
3406
  positionCount: positions.length,
3102
3407
  orderCount: orders.length,