@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
@@ -9,7 +9,7 @@ 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_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;
12
+ 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;
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";
@@ -34,6 +34,37 @@ import { WebSocketConnectionState, PerpsAnalyticsEvent, PerpsTraceNames, PerpsTr
34
34
  import { getSelectedEvmAccount } from "./utils/accountUtils.mjs";
35
35
  import { ensureError } from "./utils/errorUtils.mjs";
36
36
  import { wait } from "./utils/wait.mjs";
37
+ /**
38
+ * Returns the first non-empty string from the given values.
39
+ * Env vars default to '' (not null/undefined), so ?? wouldn't fall through.
40
+ *
41
+ * @param vals - String values to check in order.
42
+ * @returns The first non-empty string, or '' if all are empty/undefined.
43
+ */
44
+ export function firstNonEmpty(...vals) {
45
+ return (vals.find((val) => val !== null && val !== undefined && val !== '') ?? '');
46
+ }
47
+ /**
48
+ * Resolves MYX auth config from provider credentials, handling
49
+ * testnet/mainnet fallback logic.
50
+ *
51
+ * @param myx - MYX provider credentials.
52
+ * @param isTestnet - Whether the controller is in testnet mode.
53
+ * @returns Resolved appId, apiSecret, and brokerAddress.
54
+ */
55
+ export function resolveMyxAuthConfig(myx, isTestnet) {
56
+ return {
57
+ appId: isTestnet
58
+ ? (myx.appIdTestnet ?? '')
59
+ : firstNonEmpty(myx.appIdMainnet, myx.appIdTestnet),
60
+ apiSecret: isTestnet
61
+ ? (myx.apiSecretTestnet ?? '')
62
+ : firstNonEmpty(myx.apiSecretMainnet, myx.apiSecretTestnet),
63
+ brokerAddress: isTestnet
64
+ ? (myx.brokerAddressTestnet ?? '')
65
+ : firstNonEmpty(myx.brokerAddressMainnet, myx.brokerAddressTestnet),
66
+ };
67
+ }
37
68
  // Re-export error codes from separate file to avoid circular dependencies
38
69
  export { PERPS_ERROR_CODES } from "./perpsErrorCodes.mjs";
39
70
  /**
@@ -101,13 +132,8 @@ export const getDefaultPerpsControllerState = () => ({
101
132
  },
102
133
  hip3ConfigVersion: 0,
103
134
  selectedPaymentToken: null,
104
- cachedMarketData: null,
105
- cachedMarketDataTimestamp: 0,
106
- cachedPositions: null,
107
- cachedOrders: null,
108
- cachedAccountState: null,
109
- cachedUserDataTimestamp: 0,
110
- cachedUserDataAddress: null,
135
+ cachedMarketDataByProvider: {},
136
+ cachedUserDataByProvider: {},
111
137
  });
112
138
  /**
113
139
  * State metadata for the PerpsController
@@ -263,48 +289,18 @@ const metadata = {
263
289
  includeInDebugSnapshot: false,
264
290
  usedInUi: true,
265
291
  },
266
- cachedMarketData: {
292
+ cachedMarketDataByProvider: {
267
293
  includeInStateLogs: false,
268
294
  persist: false,
269
295
  includeInDebugSnapshot: false,
270
296
  usedInUi: true,
271
297
  },
272
- cachedMarketDataTimestamp: {
273
- includeInStateLogs: false,
274
- persist: false,
275
- includeInDebugSnapshot: false,
276
- usedInUi: false,
277
- },
278
- cachedPositions: {
298
+ cachedUserDataByProvider: {
279
299
  includeInStateLogs: false,
280
300
  persist: false,
281
301
  includeInDebugSnapshot: false,
282
302
  usedInUi: true,
283
303
  },
284
- cachedOrders: {
285
- includeInStateLogs: false,
286
- persist: false,
287
- includeInDebugSnapshot: false,
288
- usedInUi: true,
289
- },
290
- cachedAccountState: {
291
- includeInStateLogs: false,
292
- persist: false,
293
- includeInDebugSnapshot: false,
294
- usedInUi: true,
295
- },
296
- cachedUserDataTimestamp: {
297
- includeInStateLogs: false,
298
- persist: false,
299
- includeInDebugSnapshot: false,
300
- usedInUi: false,
301
- },
302
- cachedUserDataAddress: {
303
- includeInStateLogs: false,
304
- persist: false,
305
- includeInDebugSnapshot: false,
306
- usedInUi: false,
307
- },
308
304
  };
309
305
  const MESSENGER_EXPOSED_METHODS = [
310
306
  'placeOrder',
@@ -364,6 +360,8 @@ export class PerpsController extends BaseController {
364
360
  this.isInitialized = false;
365
361
  _PerpsController_initializationPromise.set(this, null);
366
362
  _PerpsController_isReinitializing.set(this, false);
363
+ /** Tracks the async MYX dynamic import so performInitialization can await it. */
364
+ _PerpsController_myxRegistrationPromise.set(this, null);
367
365
  this.blockedRegionList = {
368
366
  list: [],
369
367
  source: 'fallback',
@@ -392,6 +390,7 @@ export class PerpsController extends BaseController {
392
390
  * connections) on every standalone call from the preload cycle.
393
391
  */
394
392
  _PerpsController_standaloneProvider.set(this, null);
393
+ _PerpsController_handlersRegistered.set(this, false);
395
394
  _PerpsController_standaloneProviderIsTestnet.set(this, null);
396
395
  _PerpsController_standaloneProviderHip3Version.set(this, null);
397
396
  _PerpsController_eligibilityCheckDeferred.set(this, void 0);
@@ -467,7 +466,122 @@ export class PerpsController extends BaseController {
467
466
  this.providers = new Map();
468
467
  // Migrate old persisted data without accountAddress
469
468
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_migrateRequestsIfNeeded).call(this);
470
- this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
469
+ }
470
+ /**
471
+ * Read cached market data for the currently active provider (or aggregated).
472
+ * Returns null when no valid cache exists or when cache has expired.
473
+ *
474
+ * @returns The cached market data array, or null if no valid cache.
475
+ */
476
+ getCachedMarketDataForActiveProvider() {
477
+ const { activeProvider } = this.state;
478
+ const cache = this.state.cachedMarketDataByProvider;
479
+ if (activeProvider === 'aggregated') {
480
+ // Assemble from all registered provider entries
481
+ const assembled = [];
482
+ let oldestTimestamp = Infinity;
483
+ for (const [providerId] of this.providers) {
484
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, providerId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, providerId));
485
+ const entry = cache[key];
486
+ if (!entry || entry.data.length === 0) {
487
+ continue;
488
+ }
489
+ oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
490
+ assembled.push(...entry.data);
491
+ }
492
+ if (assembled.length === 0) {
493
+ return null;
494
+ }
495
+ // Check TTL against the oldest entry
496
+ if (Date.now() - oldestTimestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
497
+ return null;
498
+ }
499
+ return assembled;
500
+ }
501
+ // Single provider mode
502
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, activeProvider, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, activeProvider));
503
+ const entry = cache[key];
504
+ if (!entry || entry.data.length === 0) {
505
+ return null;
506
+ }
507
+ if (Date.now() - entry.timestamp > __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 10) {
508
+ return null;
509
+ }
510
+ return entry.data;
511
+ }
512
+ /**
513
+ * Read cached user data for the currently active provider (or aggregated).
514
+ * Returns null when no valid cache exists, cache has expired, or address
515
+ * does not match the currently selected EVM account.
516
+ *
517
+ * @returns The cached user data, or null if no valid cache.
518
+ */
519
+ getCachedUserDataForActiveProvider() {
520
+ const { activeProvider } = this.state;
521
+ const cache = this.state.cachedUserDataByProvider;
522
+ const staleCutoff = __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs) * 2; // 60s
523
+ // Get current user address for validation
524
+ let currentAddress = null;
525
+ try {
526
+ const evmAccount = getSelectedEvmAccount(this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup'));
527
+ currentAddress = evmAccount?.address ?? null;
528
+ }
529
+ catch {
530
+ // Can't determine current account — trust the cache
531
+ }
532
+ const isValidEntry = (entry) => {
533
+ if (!entry) {
534
+ return false;
535
+ }
536
+ if (Date.now() - entry.timestamp >= staleCutoff) {
537
+ return false;
538
+ }
539
+ if (currentAddress &&
540
+ entry.address.toLowerCase() !== currentAddress.toLowerCase()) {
541
+ return false;
542
+ }
543
+ return true;
544
+ };
545
+ if (activeProvider === 'aggregated') {
546
+ // Assemble from all registered provider entries
547
+ const allPositions = [];
548
+ const allOrders = [];
549
+ let defaultAccountState = null;
550
+ let hasValidEntry = false;
551
+ for (const [providerId] of this.providers) {
552
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, providerId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, providerId));
553
+ const entry = cache[key];
554
+ if (!isValidEntry(entry)) {
555
+ continue;
556
+ }
557
+ hasValidEntry = true;
558
+ allPositions.push(...entry.positions);
559
+ allOrders.push(...entry.orders);
560
+ // AccountState from default provider (hyperliquid)
561
+ if (providerId === 'hyperliquid') {
562
+ defaultAccountState = entry.accountState;
563
+ }
564
+ }
565
+ if (!hasValidEntry) {
566
+ return null;
567
+ }
568
+ return {
569
+ positions: allPositions,
570
+ orders: allOrders,
571
+ accountState: defaultAccountState,
572
+ };
573
+ }
574
+ // Single provider mode
575
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, activeProvider, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, activeProvider));
576
+ const entry = cache[key];
577
+ if (!entry || !isValidEntry(entry)) {
578
+ return null;
579
+ }
580
+ return {
581
+ positions: entry.positions,
582
+ orders: entry.orders,
583
+ accountState: entry.accountState,
584
+ };
471
585
  }
472
586
  /**
473
587
  * Test-observable accessor for whether a standalone provider is cached.
@@ -547,6 +661,10 @@ export class PerpsController extends BaseController {
547
661
  * @returns A promise that resolves when the operation completes.
548
662
  */
549
663
  async init() {
664
+ if (!__classPrivateFieldGet(this, _PerpsController_handlersRegistered, "f")) {
665
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
666
+ __classPrivateFieldSet(this, _PerpsController_handlersRegistered, true, "f");
667
+ }
550
668
  if (this.isInitialized) {
551
669
  return undefined;
552
670
  }
@@ -556,6 +674,46 @@ export class PerpsController extends BaseController {
556
674
  __classPrivateFieldSet(this, _PerpsController_initializationPromise, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performInitialization).call(this), "f");
557
675
  return __classPrivateFieldGet(this, _PerpsController_initializationPromise, "f");
558
676
  }
677
+ /**
678
+ * Registers the MYX provider after dynamic import resolves.
679
+ *
680
+ * Extracted from the import().then() callback so it can be tested directly
681
+ * (Jest cannot resolve dynamic imports without --experimental-vm-modules).
682
+ *
683
+ * @param MYXProvider - Constructor class for the MYX provider.
684
+ */
685
+ registerMYXProvider(MYXProvider) {
686
+ const myxIsTestnet = PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet;
687
+ const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx ?? {};
688
+ const myxAuthConfig = resolveMyxAuthConfig(myx, myxIsTestnet);
689
+ const myxProvider = new MYXProvider({
690
+ isTestnet: myxIsTestnet,
691
+ platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
692
+ messenger: this.messenger,
693
+ myxAuthConfig,
694
+ });
695
+ this.providers.set('myx', myxProvider);
696
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider registered', {
697
+ isTestnet: myxIsTestnet,
698
+ });
699
+ }
700
+ /**
701
+ * Handles errors from the MYX dynamic import.
702
+ *
703
+ * Module-not-found errors are expected (extension doesn't ship MYX) → debug log.
704
+ * Other errors indicate constructor/config problems → Sentry via logError.
705
+ *
706
+ * @param error - The caught error from the dynamic import or constructor.
707
+ */
708
+ handleMYXImportError(error) {
709
+ const isModuleError = error?.code === 'MODULE_NOT_FOUND';
710
+ if (isModuleError) {
711
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider module not available, skipping registration');
712
+ }
713
+ else {
714
+ __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'));
715
+ }
716
+ }
559
717
  /**
560
718
  * Get the currently active provider.
561
719
  * In aggregated mode, returns AggregatedPerpsProvider which routes to underlying providers.
@@ -816,6 +974,14 @@ export class PerpsController extends BaseController {
816
974
  origin: ORIGIN_METAMASK,
817
975
  skipInitialGasEstimate: true,
818
976
  };
977
+ __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.tracer.addBreadcrumb({
978
+ category: 'perps',
979
+ message: 'Deposit action started',
980
+ level: 'info',
981
+ data: {
982
+ place_order_after_deposit: placeOrder === true,
983
+ },
984
+ });
819
985
  if (placeOrder) {
820
986
  // Use addTransaction to create transaction without navigating to confirmation screen
821
987
  const addResult = await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_submitTransaction).call(this, transaction, {
@@ -951,8 +1117,8 @@ export class PerpsController extends BaseController {
951
1117
  const errorMessage = ensureError(error, 'PerpsController.depositWithOrder').message;
952
1118
  const isCancellation = errorMessage.includes('User denied') ||
953
1119
  errorMessage.includes('User rejected') ||
954
- errorMessage.includes('cancelled') ||
955
- errorMessage.includes('canceled');
1120
+ errorMessage.includes('User cancelled') ||
1121
+ errorMessage.includes('User canceled');
956
1122
  this.update((state) => {
957
1123
  const requestToUpdate = state.depositRequests.find((req) => req.id === currentDepositId);
958
1124
  if (requestToUpdate) {
@@ -1316,7 +1482,7 @@ export class PerpsController extends BaseController {
1316
1482
  /* fire-and-forget */
1317
1483
  });
1318
1484
  }, __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadRefreshMs)), "f");
1319
- // Watch for isTestnet / hip3ConfigVersion / cachedUserDataAddress changes
1485
+ // Watch for isTestnet / hip3ConfigVersion changes
1320
1486
  const handler = (_state, patches) => {
1321
1487
  // Early-return when no watched field changed (skips ~46 unrelated updates)
1322
1488
  const hasRelevantChange = patches.some((patch) => typeof patch.path[0] === 'string' &&
@@ -1337,16 +1503,9 @@ export class PerpsController extends BaseController {
1337
1503
  });
1338
1504
  __classPrivateFieldSet(this, _PerpsController_previousIsTestnet, currentIsTestnet, "f");
1339
1505
  __classPrivateFieldSet(this, _PerpsController_previousHip3ConfigVersion, currentHip3Version, "f");
1340
- // Clear stale cache (market + user data)
1341
- this.update((state) => {
1342
- state.cachedMarketData = null;
1343
- state.cachedMarketDataTimestamp = 0;
1344
- state.cachedPositions = null;
1345
- state.cachedOrders = null;
1346
- state.cachedAccountState = null;
1347
- state.cachedUserDataTimestamp = 0;
1348
- state.cachedUserDataAddress = null;
1349
- });
1506
+ // No need to clear user data cache per-provider keys include the
1507
+ // network, so different networks don't collide. Re-preload will
1508
+ // populate the new key.
1350
1509
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1351
1510
  /* fire-and-forget */
1352
1511
  });
@@ -1360,16 +1519,13 @@ export class PerpsController extends BaseController {
1360
1519
  const accountChangeHandler = () => {
1361
1520
  const evmAccount = getSelectedEvmAccount(this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup'));
1362
1521
  const currentAddress = evmAccount?.address ?? null;
1363
- // If there's cached data from a different account (or no EVM account now), clear it
1364
- if (this.state.cachedUserDataAddress !== null &&
1365
- currentAddress !== this.state.cachedUserDataAddress) {
1522
+ // If any cached entry belongs to a different account, clear all entries.
1523
+ // Max 4 entries (2 providers × 2 networks) — clearing all is simple and safe.
1524
+ const hasStaleEntries = Object.values(this.state.cachedUserDataByProvider).some((entry) => entry.address.toLowerCase() !== currentAddress?.toLowerCase());
1525
+ if (hasStaleEntries) {
1366
1526
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Account changed, clearing user data cache');
1367
1527
  this.update((state) => {
1368
- state.cachedPositions = null;
1369
- state.cachedOrders = null;
1370
- state.cachedAccountState = null;
1371
- state.cachedUserDataTimestamp = 0;
1372
- state.cachedUserDataAddress = null;
1528
+ state.cachedUserDataByProvider = {};
1373
1529
  });
1374
1530
  // Only preload if the new account is an EVM account
1375
1531
  if (currentAddress) {
@@ -1602,6 +1758,15 @@ export class PerpsController extends BaseController {
1602
1758
  }
1603
1759
  finally {
1604
1760
  __classPrivateFieldSet(this, _PerpsController_isReinitializing, false, "f");
1761
+ // Re-trigger preload now that reinit is complete and the
1762
+ // activeProviderInstance points to the correct network.
1763
+ // The state-change listener may have already fired during reinit
1764
+ // but was skipped due to the #isReinitializing guard.
1765
+ if (__classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
1766
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1767
+ /* fire-and-forget */
1768
+ });
1769
+ }
1605
1770
  }
1606
1771
  }
1607
1772
  /**
@@ -1648,7 +1813,7 @@ export class PerpsController extends BaseController {
1648
1813
  // Provider disconnect is handled by performInitialization() during
1649
1814
  // reinitialization. The disconnect() method skips provider teardown
1650
1815
  // when isReinitializing is true to prevent double-disconnect.
1651
- // Update state with new provider
1816
+ // Update state with new provider (market data cache preserved per-provider)
1652
1817
  this.update((state) => {
1653
1818
  state.activeProvider = providerId;
1654
1819
  state.accountState = null;
@@ -1705,6 +1870,12 @@ export class PerpsController extends BaseController {
1705
1870
  }
1706
1871
  finally {
1707
1872
  __classPrivateFieldSet(this, _PerpsController_isReinitializing, false, "f");
1873
+ // Re-trigger preload now that reinit is complete.
1874
+ if (__classPrivateFieldGet(this, _PerpsController_preloadTimer, "f")) {
1875
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_performMarketDataPreload).call(this).catch(() => {
1876
+ /* fire-and-forget */
1877
+ });
1878
+ }
1708
1879
  }
1709
1880
  }
1710
1881
  /**
@@ -2082,14 +2253,18 @@ export class PerpsController extends BaseController {
2082
2253
  * Eligibility (Geo-Blocking)
2083
2254
  */
2084
2255
  /**
2085
- * Activates geo-blocking eligibility monitoring.
2086
- * Call this after onboarding is complete when the controller was constructed
2087
- * with `deferEligibilityCheck: true`.
2256
+ * Fetch geo location
2088
2257
  *
2089
- * Reads the current RemoteFeatureFlagController state, performs the initial
2090
- * eligibility check, and unblocks the existing stateChange subscription so
2091
- * future feature-flag updates flow through normally.
2092
- * Safe to call multiple times; subsequent calls simply re-check eligibility.
2258
+ * Returned in Country or Country-Region format
2259
+ * Example: FR, DE, US-MI, CA-ON
2260
+ */
2261
+ /**
2262
+ * Refresh eligibility status
2263
+ */
2264
+ /**
2265
+ * Resume eligibility monitoring after onboarding completes.
2266
+ * Clears the deferred flag and triggers an immediate eligibility check
2267
+ * using the current remote feature flag state.
2093
2268
  */
2094
2269
  startEligibilityMonitoring() {
2095
2270
  __classPrivateFieldSet(this, _PerpsController_eligibilityCheckDeferred, false, "f");
@@ -2112,9 +2287,6 @@ export class PerpsController extends BaseController {
2112
2287
  stopEligibilityMonitoring() {
2113
2288
  __classPrivateFieldSet(this, _PerpsController_eligibilityCheckDeferred, true, "f");
2114
2289
  }
2115
- /**
2116
- * Refresh eligibility status
2117
- */
2118
2290
  async refreshEligibility() {
2119
2291
  if (__classPrivateFieldGet(this, _PerpsController_eligibilityCheckDeferred, "f")) {
2120
2292
  return;
@@ -2125,9 +2297,10 @@ export class PerpsController extends BaseController {
2125
2297
  // (started with remote config after it was fetched).
2126
2298
  const versionAtStart = __classPrivateFieldGet(this, _PerpsController_blockedRegionListVersion, "f");
2127
2299
  try {
2128
- // TODO: It would be good to have this location before we call this async function to avoid the race condition
2300
+ const geoLocation = await this.messenger.call('GeolocationController:getGeolocation');
2129
2301
  const isEligible = await __classPrivateFieldGet(this, _PerpsController_eligibilityService, "f").checkEligibility({
2130
2302
  blockedRegions: this.blockedRegionList.list,
2303
+ geoLocation,
2131
2304
  });
2132
2305
  // Only update state if the blocked region list hasn't changed while we were awaiting.
2133
2306
  // This prevents stale fallback-based eligibility checks from overwriting
@@ -2610,27 +2783,46 @@ export class PerpsController extends BaseController {
2610
2783
  return __classPrivateFieldGet(this, _PerpsController_isReinitializing, "f");
2611
2784
  }
2612
2785
  }
2613
- _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() {
2614
- const getLocalFlag = () => typeof globalThis.process !== 'undefined' &&
2615
- globalThis.process.env?.MM_PERPS_MYX_PROVIDER_ENABLED === 'true';
2786
+ _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() {
2787
+ const myx = __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.myx;
2788
+ // Local env-var override (MM_PERPS_MYX_PROVIDER_ENABLED) always wins —
2789
+ // matches the UI selector (resolvePerpsMyxProviderEnabled) so controller
2790
+ // and UI agree on whether MYX is available.
2791
+ if (myx?.enabled) {
2792
+ return true;
2793
+ }
2794
+ // Credentials present → MYX is enabled regardless of remote flag.
2795
+ // Use || so empty-string env vars (default '') fall through.
2796
+ const hasCredentials = Boolean(
2797
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
2798
+ myx?.appIdTestnet || myx?.appIdMainnet);
2799
+ if (hasCredentials) {
2800
+ return true;
2801
+ }
2802
+ // No local override or credentials — check remote flag as fallback
2616
2803
  try {
2617
- const localFlag = getLocalFlag();
2618
2804
  const remoteState = this.messenger.call('RemoteFeatureFlagController:getState');
2619
2805
  const remoteFlag = remoteState.remoteFeatureFlags?.perpsMyxProviderEnabled;
2620
2806
  if (isVersionGatedFeatureFlag(remoteFlag)) {
2621
2807
  const validated = __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.featureFlags.validateVersionGated(remoteFlag);
2622
- return validated ?? localFlag;
2808
+ return validated ?? false;
2623
2809
  }
2624
- return localFlag;
2810
+ return false;
2625
2811
  }
2626
2812
  catch {
2627
- // If RemoteFeatureFlagController not ready, use fallback
2628
- return getLocalFlag();
2813
+ return false;
2629
2814
  }
2630
2815
  }, _PerpsController_logError = function _PerpsController_logError(error, options) {
2631
2816
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.logger.error(error, options);
2632
2817
  }, _PerpsController_debugLog = function _PerpsController_debugLog(...args) {
2633
2818
  __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure.debugLogger.log(...args);
2819
+ }, _PerpsController_marketCacheKey = function _PerpsController_marketCacheKey(providerId, isTestnet) {
2820
+ return `${providerId}:${isTestnet ? 'testnet' : 'mainnet'}`;
2821
+ }, _PerpsController_providerIsTestnet = function _PerpsController_providerIsTestnet(providerId) {
2822
+ if (providerId === 'myx') {
2823
+ return PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet;
2824
+ }
2825
+ return this.state.isTestnet;
2634
2826
  }, _PerpsController_getOrCreateStandaloneProvider = function _PerpsController_getOrCreateStandaloneProvider() {
2635
2827
  const currentIsTestnet = this.state.isTestnet;
2636
2828
  const currentHip3Version = this.state.hip3ConfigVersion ?? 0;
@@ -2653,6 +2845,10 @@ _a = PerpsController, _PerpsController_initializationPromise = new WeakMap(), _P
2653
2845
  blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2654
2846
  platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2655
2847
  messenger: this.messenger,
2848
+ builderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
2849
+ ?.builderAddressTestnet,
2850
+ builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
2851
+ ?.builderAddressMainnet,
2656
2852
  }), "f");
2657
2853
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderIsTestnet, currentIsTestnet, "f");
2658
2854
  __classPrivateFieldSet(this, _PerpsController_standaloneProviderHip3Version, currentHip3Version, "f");
@@ -2810,89 +3006,16 @@ async function _PerpsController_performInitialization() {
2810
3006
  }
2811
3007
  this.providers.clear();
2812
3008
  await __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_cleanupStandaloneProvider).call(this);
2813
- const { activeProvider } = this.state;
2814
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Creating provider with HIP-3 configuration', {
2815
- hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
2816
- hip3AllowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
2817
- hip3BlocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2818
- hip3ConfigSource: __classPrivateFieldGet(this, _PerpsController_hip3ConfigSource, "f"),
2819
- isTestnet: this.state.isTestnet,
2820
- activeProvider,
2821
- });
2822
- // Always create HyperLiquid provider as the base provider
2823
- const hyperLiquidProvider = new HyperLiquidProvider({
2824
- isTestnet: this.state.isTestnet,
2825
- hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
2826
- allowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
2827
- blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
2828
- platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2829
- messenger: this.messenger,
2830
- });
2831
- this.providers.set('hyperliquid', hyperLiquidProvider);
2832
- // Register MYX provider if enabled via feature flag.
2833
- // Uses dynamic import so @myx-trade/sdk is excluded from the bundle
2834
- // unless MM_PERPS_MYX_PROVIDER_ENABLED=true is set at build time.
2835
- // Wrapped in try/catch so a missing module (stripped at build time)
2836
- // only skips MYX registration instead of aborting initialization.
2837
- const isMYXEnabled = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMYXProviderEnabled).call(this);
2838
- if (isMYXEnabled) {
2839
- try {
2840
- const { MYXProvider } = await import("./providers/MYXProvider.mjs");
2841
- const myxProvider = new MYXProvider({
2842
- isTestnet: PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet,
2843
- platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2844
- });
2845
- this.providers.set('myx', myxProvider);
2846
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider registered', {
2847
- isTestnet: PROVIDER_CONFIG.MYX_TESTNET_ONLY || this.state.isTestnet,
2848
- });
2849
- }
2850
- catch {
2851
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider module not available (stripped from build), skipping registration');
2852
- }
2853
- }
2854
- // Set up active provider based on activeProvider value in state
2855
- // 'aggregated' is treated as just another provider that wraps others
2856
- if (activeProvider === 'aggregated') {
2857
- // Aggregated mode: wrap in AggregatedPerpsProvider for multi-provider support
2858
- this.activeProviderInstance = new AggregatedPerpsProvider({
2859
- providers: this.providers,
2860
- defaultProvider: 'hyperliquid',
2861
- infrastructure: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
2862
- });
2863
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Using aggregated provider (multi-provider)', { registeredProviders: Array.from(this.providers.keys()) });
2864
- }
2865
- else if (activeProvider === 'hyperliquid') {
2866
- // Direct provider mode: use HyperLiquid provider directly
2867
- this.activeProviderInstance = hyperLiquidProvider;
2868
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${activeProvider})`);
2869
- }
2870
- else if (activeProvider === 'myx') {
2871
- // MYX provider mode
2872
- const myxProvider = this.providers.get('myx');
2873
- if (myxProvider) {
2874
- this.activeProviderInstance = myxProvider;
2875
- }
2876
- else {
2877
- // MYX feature flag is disabled — fall back to HyperLiquid
2878
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider not available (feature flag disabled), falling back to hyperliquid');
2879
- this.activeProviderInstance = hyperLiquidProvider;
2880
- this.update((state) => {
2881
- state.activeProvider = 'hyperliquid';
2882
- });
2883
- }
2884
- __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${this.activeProviderInstance === hyperLiquidProvider ? 'hyperliquid' : activeProvider})`);
2885
- }
2886
- else {
2887
- // Unsupported provider - throw error to prevent silent misconfiguration
2888
- throw new Error(`Unsupported provider: ${String(activeProvider)}. Currently only 'hyperliquid', 'myx', and 'aggregated' are supported.`);
2889
- }
2890
- // Future providers can be added here with their own authentication patterns:
2891
- // - Some might use API keys: new BinanceProvider({ apiKey, apiSecret })
2892
- // - Some might use different wallet patterns: new GMXProvider({ signer })
2893
- // - Some might not need auth at all: new DydxProvider()
2894
- // Wait for WebSocket transport to be ready before marking as initialized
2895
- await wait(PERPS_CONSTANTS.ReconnectionCleanupDelayMs);
3009
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_createProviders).call(this);
3010
+ // Await MYX dynamic import (if started) so MYX is in the providers
3011
+ // map before we assign the active provider. Runs concurrently with
3012
+ // the WebSocket readiness delay for zero additional latency.
3013
+ await Promise.all([
3014
+ wait(PERPS_CONSTANTS.ReconnectionCleanupDelayMs),
3015
+ __classPrivateFieldGet(this, _PerpsController_myxRegistrationPromise, "f"),
3016
+ ]);
3017
+ __classPrivateFieldSet(this, _PerpsController_myxRegistrationPromise, null, "f");
3018
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_assignActiveProvider).call(this);
2896
3019
  this.isInitialized = true;
2897
3020
  this.update((state) => {
2898
3021
  state.initializationState = InitializationState.Initialized;
@@ -2900,7 +3023,7 @@ async function _PerpsController_performInitialization() {
2900
3023
  });
2901
3024
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Providers initialized successfully', {
2902
3025
  providerCount: this.providers.size,
2903
- activeProvider,
3026
+ activeProvider: this.state.activeProvider,
2904
3027
  timestamp: new Date().toISOString(),
2905
3028
  attempts: attempt,
2906
3029
  });
@@ -2935,6 +3058,84 @@ async function _PerpsController_performInitialization() {
2935
3058
  attempts: maxAttempts,
2936
3059
  timestamp: new Date().toISOString(),
2937
3060
  });
3061
+ }, _PerpsController_createProviders = function _PerpsController_createProviders() {
3062
+ const { activeProvider } = this.state;
3063
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Creating provider with HIP-3 configuration', {
3064
+ hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
3065
+ hip3AllowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
3066
+ hip3BlocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
3067
+ hip3ConfigSource: __classPrivateFieldGet(this, _PerpsController_hip3ConfigSource, "f"),
3068
+ isTestnet: this.state.isTestnet,
3069
+ activeProvider,
3070
+ });
3071
+ // Always create HyperLiquid provider as the base provider
3072
+ const hyperLiquidProvider = new HyperLiquidProvider({
3073
+ isTestnet: this.state.isTestnet,
3074
+ hip3Enabled: __classPrivateFieldGet(this, _PerpsController_hip3Enabled, "f"),
3075
+ allowlistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3AllowlistMarkets, "f"),
3076
+ blocklistMarkets: __classPrivateFieldGet(this, _PerpsController_hip3BlocklistMarkets, "f"),
3077
+ platformDependencies: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
3078
+ messenger: this.messenger,
3079
+ builderAddressTestnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3080
+ ?.builderAddressTestnet,
3081
+ builderAddressMainnet: __classPrivateFieldGet(this, _PerpsController_options, "f").clientConfig?.providerCredentials?.hyperliquid
3082
+ ?.builderAddressMainnet,
3083
+ });
3084
+ this.providers.set('hyperliquid', hyperLiquidProvider);
3085
+ // Register MYX provider if enabled via feature flag.
3086
+ // Dynamic import because the MYX package pulls in heavy dependencies we
3087
+ // don't want bundled in extension. Until MYX fixes their package, extension
3088
+ // doesn't ship it — the catch branch silently skips registration.
3089
+ // Uses .then()/.catch() instead of await because #createProviders is not async;
3090
+ // MYX registration completing asynchronously is fine since it's only used when
3091
+ // explicitly enabled and selected.
3092
+ const isMYXEnabled = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_isMYXProviderEnabled).call(this);
3093
+ if (isMYXEnabled) {
3094
+ // IMPORTANT: Must use import() — NOT require() — for core/extension tree-shaking.
3095
+ // require() is synchronous and bundlers include it in the main bundle.
3096
+ // import() enables true code splitting so MYX is excluded when not enabled.
3097
+ __classPrivateFieldSet(this, _PerpsController_myxRegistrationPromise, import("./providers/MYXProvider.mjs")
3098
+ .then(({ MYXProvider }) => {
3099
+ this.registerMYXProvider(MYXProvider);
3100
+ return undefined;
3101
+ })
3102
+ .catch((error) => this.handleMYXImportError(error)), "f");
3103
+ }
3104
+ }, _PerpsController_assignActiveProvider = function _PerpsController_assignActiveProvider() {
3105
+ const { activeProvider } = this.state;
3106
+ const hyperLiquidProvider = this.providers.get('hyperliquid');
3107
+ if (!hyperLiquidProvider) {
3108
+ throw new Error('HyperLiquid provider not registered — cannot assign active provider');
3109
+ }
3110
+ if (activeProvider === 'aggregated') {
3111
+ this.activeProviderInstance = new AggregatedPerpsProvider({
3112
+ providers: this.providers,
3113
+ defaultProvider: 'hyperliquid',
3114
+ infrastructure: __classPrivateFieldGet(this, _PerpsController_options, "f").infrastructure,
3115
+ });
3116
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Using aggregated provider (multi-provider)', { registeredProviders: Array.from(this.providers.keys()) });
3117
+ }
3118
+ else if (activeProvider === 'hyperliquid') {
3119
+ this.activeProviderInstance = hyperLiquidProvider;
3120
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${activeProvider})`);
3121
+ }
3122
+ else if (activeProvider === 'myx') {
3123
+ const myxProvider = this.providers.get('myx');
3124
+ if (myxProvider) {
3125
+ this.activeProviderInstance = myxProvider;
3126
+ }
3127
+ else {
3128
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: MYX provider not available, falling back to hyperliquid');
3129
+ this.activeProviderInstance = hyperLiquidProvider;
3130
+ this.update((state) => {
3131
+ state.activeProvider = 'hyperliquid';
3132
+ });
3133
+ }
3134
+ __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, `PerpsController: Using direct provider (${this.activeProviderInstance === hyperLiquidProvider ? 'hyperliquid' : activeProvider})`);
3135
+ }
3136
+ else {
3137
+ throw new Error(`Unsupported provider: ${String(activeProvider)}. Currently only 'hyperliquid', 'myx', and 'aggregated' are supported.`);
3138
+ }
2938
3139
  }, _PerpsController_getErrorContext = function _PerpsController_getErrorContext(method, extra) {
2939
3140
  return {
2940
3141
  tags: {
@@ -2964,7 +3165,7 @@ async function _PerpsController_performInitialization() {
2964
3165
  },
2965
3166
  stateManager: {
2966
3167
  update: (updater) =>
2967
- // @ts-expect-error TS2589 - excessively deep instantiation when inferring stateManager from BaseController
3168
+ // @ts-expect-error TS2589 - excessively deep instantiation from BaseController generic
2968
3169
  this.update(updater),
2969
3170
  getState: () => __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_getControllerState).call(this),
2970
3171
  },
@@ -2982,9 +3183,22 @@ async function _PerpsController_performMarketDataPreload() {
2982
3183
  if (__classPrivateFieldGet(this, _PerpsController_isPreloading, "f")) {
2983
3184
  return;
2984
3185
  }
3186
+ // Skip preloading during provider/network reinitialisation.
3187
+ // The activeProviderInstance still points to the OLD network's provider
3188
+ // until init() completes, so fetching now would store stale data under
3189
+ // the NEW network's cache key.
3190
+ if (__classPrivateFieldGet(this, _PerpsController_isReinitializing, "f")) {
3191
+ return;
3192
+ }
3193
+ // Determine actual provider and cache key for debounce
3194
+ const actualProviderId = this.activeProviderInstance
3195
+ ? this.state.activeProvider // includes 'aggregated'
3196
+ : 'hyperliquid';
3197
+ const cacheKey = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, actualProviderId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, actualProviderId));
2985
3198
  const now = Date.now();
2986
- if (now - this.state.cachedMarketDataTimestamp <
2987
- __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3199
+ const existingEntry = this.state.cachedMarketDataByProvider[cacheKey];
3200
+ if (existingEntry &&
3201
+ now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
2988
3202
  return;
2989
3203
  }
2990
3204
  __classPrivateFieldSet(this, _PerpsController_isPreloading, true, "f");
@@ -3003,10 +3217,46 @@ async function _PerpsController_performMarketDataPreload() {
3003
3217
  });
3004
3218
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Fetching market data in background');
3005
3219
  const data = await this.getMarketDataWithPrices({ standalone: true });
3006
- this.update((state) => {
3007
- state.cachedMarketData = data;
3008
- state.cachedMarketDataTimestamp = Date.now();
3009
- });
3220
+ // Store under per-provider key(s)
3221
+ const ts = Date.now();
3222
+ if (this.state.activeProvider === 'aggregated' &&
3223
+ this.activeProviderInstance) {
3224
+ // Split returned data by providerId and store each slice
3225
+ const fallbackProviderId = 'hyperliquid'; // default for items missing providerId
3226
+ const byProvider = new Map();
3227
+ for (const item of data) {
3228
+ const pid = item.providerId ?? fallbackProviderId;
3229
+ const existing = byProvider.get(pid);
3230
+ if (existing) {
3231
+ existing.push(item);
3232
+ }
3233
+ else {
3234
+ byProvider.set(pid, [item]);
3235
+ }
3236
+ }
3237
+ this.update((state) => {
3238
+ for (const [pid, slice] of byProvider) {
3239
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, pid, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, pid));
3240
+ state.cachedMarketDataByProvider[key] = {
3241
+ data: slice,
3242
+ timestamp: ts,
3243
+ };
3244
+ }
3245
+ // Write aggregated sentinel so the staleness guard sees it
3246
+ state.cachedMarketDataByProvider[cacheKey] = {
3247
+ data: [], // sentinel — real data is in per-provider keys
3248
+ timestamp: ts,
3249
+ };
3250
+ });
3251
+ }
3252
+ else {
3253
+ this.update((state) => {
3254
+ state.cachedMarketDataByProvider[cacheKey] = {
3255
+ data,
3256
+ timestamp: ts,
3257
+ };
3258
+ });
3259
+ }
3010
3260
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: Market data preloaded', {
3011
3261
  marketCount: data.length,
3012
3262
  });
@@ -3050,10 +3300,16 @@ async function _PerpsController_performUserDataPreload() {
3050
3300
  return;
3051
3301
  }
3052
3302
  const userAddress = evmAccount.address;
3303
+ // Determine actual provider (same logic as market preload)
3304
+ const actualProviderId = this.activeProviderInstance
3305
+ ? this.state.activeProvider // includes 'aggregated'
3306
+ : 'hyperliquid';
3307
+ const userCacheKey = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, actualProviderId, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, actualProviderId));
3053
3308
  // Skip if cache is fresh and for same account
3054
3309
  const now = Date.now();
3055
- if (this.state.cachedUserDataAddress === userAddress &&
3056
- now - this.state.cachedUserDataTimestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3310
+ const existingEntry = this.state.cachedUserDataByProvider[userCacheKey];
3311
+ if (existingEntry?.address === userAddress &&
3312
+ now - existingEntry.timestamp < __classPrivateFieldGet(_a, _a, "f", _PerpsController_preloadGuardMs)) {
3057
3313
  return;
3058
3314
  }
3059
3315
  // Skip standalone REST polling when WebSocket is connected — live data is streaming
@@ -3084,13 +3340,60 @@ async function _PerpsController_performUserDataPreload() {
3084
3340
  this.getOpenOrders({ standalone: true, userAddress }),
3085
3341
  this.getAccountState({ standalone: true, userAddress }),
3086
3342
  ]);
3087
- this.update((state) => {
3088
- state.cachedPositions = positions;
3089
- state.cachedOrders = orders;
3090
- state.cachedAccountState = accountState;
3091
- state.cachedUserDataTimestamp = Date.now();
3092
- state.cachedUserDataAddress = userAddress;
3093
- });
3343
+ if (this.state.activeProvider === 'aggregated' &&
3344
+ this.activeProviderInstance) {
3345
+ // Split by providerId and write one cache entry per provider key
3346
+ // (mirrors the market-data preload pattern at ~line 2976)
3347
+ const ts = Date.now();
3348
+ const fallbackProviderId = 'hyperliquid'; // default for items missing providerId
3349
+ const byProvider = new Map();
3350
+ const ensureBucket = (pid) => {
3351
+ let bucket = byProvider.get(pid);
3352
+ if (!bucket) {
3353
+ bucket = { positions: [], orders: [], accountState: null };
3354
+ byProvider.set(pid, bucket);
3355
+ }
3356
+ return bucket;
3357
+ };
3358
+ for (const pos of positions) {
3359
+ ensureBucket(pos.providerId ?? fallbackProviderId).positions.push(pos);
3360
+ }
3361
+ for (const order of orders) {
3362
+ ensureBucket(order.providerId ?? fallbackProviderId).orders.push(order);
3363
+ }
3364
+ // AccountState — assign to its provider bucket
3365
+ ensureBucket(accountState.providerId ?? fallbackProviderId).accountState = accountState;
3366
+ this.update((state) => {
3367
+ for (const [pid, data] of byProvider) {
3368
+ const key = __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_marketCacheKey).call(this, pid, __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_providerIsTestnet).call(this, pid));
3369
+ state.cachedUserDataByProvider[key] = {
3370
+ ...data,
3371
+ timestamp: ts,
3372
+ address: userAddress,
3373
+ };
3374
+ }
3375
+ // Write aggregated sentinel so the staleness guard sees it
3376
+ state.cachedUserDataByProvider[userCacheKey] = {
3377
+ positions: [],
3378
+ orders: [],
3379
+ accountState: null,
3380
+ timestamp: ts,
3381
+ address: userAddress,
3382
+ };
3383
+ });
3384
+ }
3385
+ else {
3386
+ // Single provider — store directly under its key
3387
+ this.update((state) => {
3388
+ state.cachedUserDataByProvider[userCacheKey] = {
3389
+ positions,
3390
+ orders,
3391
+ accountState,
3392
+ timestamp: Date.now(),
3393
+ address: userAddress,
3394
+ };
3395
+ });
3396
+ }
3094
3397
  __classPrivateFieldGet(this, _PerpsController_instances, "m", _PerpsController_debugLog).call(this, 'PerpsController: User data preloaded', {
3095
3398
  positionCount: positions.length,
3096
3399
  orderCount: orders.length,