@metamask-previews/assets-controllers 104.0.0-preview-d363b2d74 → 104.0.0-preview-eb46ce4de

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.
package/CHANGELOG.md CHANGED
@@ -14,12 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
14
  ### Changed
15
15
 
16
16
  - Bump `@metamask/account-tree-controller` from `^7.0.0` to `^7.1.0` ([#8472](https://github.com/MetaMask/core/pull/8472))
17
- - Bump `@metamask/transaction-controller` from `^64.2.0` to `^64.3.0` ([#8482](https://github.com/MetaMask/core/pull/8482))
18
- - Bump `@metamask/keyring-api` from `^21.6.0` to `^23.0.1` ([#8464](https://github.com/MetaMask/core/pull/8464))
19
17
 
20
18
  ### Fixed
21
19
 
22
- - Used the `removed` property coming from `MultichainAssetsController:accountAssetListUpdated` event body to manage stale balances ([#8461](https://github.com/MetaMask/core/pull/8461))
23
20
  - `MultichainAssetsController`: fungible `token:` assets from automatic detection are no longer added when Blockaid bulk scan fails, returns empty, or omits that address (previously fail open); an explicit non-malicious per-token result from `PhishingController:bulkScanTokens` is now required before add. ([#8400](https://github.com/MetaMask/core/pull/8400))
24
21
 
25
22
  ## [104.0.0]
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _MultichainAssetsRatesController_instances, _MultichainAssetsRatesController_mutex, _MultichainAssetsRatesController_currentCurrency, _MultichainAssetsRatesController_isUnlocked, _MultichainAssetsRatesController_isNonEvmAccount, _MultichainAssetsRatesController_listMultichainAccounts, _MultichainAssetsRatesController_listAccounts, _MultichainAssetsRatesController_addAssetsToSnapIdMap, _MultichainAssetsRatesController_getCaipCurrentCurrency, _MultichainAssetsRatesController_getConversionRates, _MultichainAssetsRatesController_getMarketData, _MultichainAssetsRatesController_getUpdatedRatesFor, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets, _MultichainAssetsRatesController_getAccount, _MultichainAssetsRatesController_getAssetsForAccount, _MultichainAssetsRatesController_applyUpdatedRates, _MultichainAssetsRatesController_isAssetTracked, _MultichainAssetsRatesController_handleSnapRequest;
13
+ var _MultichainAssetsRatesController_instances, _MultichainAssetsRatesController_mutex, _MultichainAssetsRatesController_currentCurrency, _MultichainAssetsRatesController_isUnlocked, _MultichainAssetsRatesController_isNonEvmAccount, _MultichainAssetsRatesController_listMultichainAccounts, _MultichainAssetsRatesController_listAccounts, _MultichainAssetsRatesController_addAssetsToSnapIdMap, _MultichainAssetsRatesController_getCaipCurrentCurrency, _MultichainAssetsRatesController_getConversionRates, _MultichainAssetsRatesController_getMarketData, _MultichainAssetsRatesController_getUpdatedRatesFor, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets, _MultichainAssetsRatesController_getAccount, _MultichainAssetsRatesController_getAssetsForAccount, _MultichainAssetsRatesController_applyUpdatedRates, _MultichainAssetsRatesController_handleSnapRequest;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.MultichainAssetsRatesController = exports.getDefaultMultichainAssetsRatesControllerState = void 0;
16
16
  const keyring_api_1 = require("@metamask/keyring-api");
@@ -100,14 +100,12 @@ class MultichainAssetsRatesController extends (0, polling_controller_1.StaticInt
100
100
  this.messenger.subscribe('MultichainAssetsController:accountAssetListUpdated',
101
101
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
102
102
  async ({ assets }) => {
103
- // Treat the event payload as a per-account delta so we can fetch only
104
- // newly added assets and independently clean up removed ones.
105
- const updatedAccountAssets = Object.entries(assets).map(([accountId, { added, removed }]) => ({
103
+ const newAccountAssets = Object.entries(assets).map(([accountId, { added }]) => ({
106
104
  accountId,
107
- added: [...added],
108
- removed: [...removed],
105
+ assets: [...added],
109
106
  }));
110
- await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_updateAssetsRatesForNewAssets).call(this, updatedAccountAssets);
107
+ // TODO; removed can be used in future for further cleanup
108
+ await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_updateAssetsRatesForNewAssets).call(this, newAccountAssets);
111
109
  });
112
110
  }
113
111
  /**
@@ -344,16 +342,9 @@ async function _MultichainAssetsRatesController_getUpdatedRatesFor(snapIdToAsset
344
342
  return updatedRates;
345
343
  }, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets =
346
344
  /**
347
- * Reconciles cached rates after an account asset-list update event.
345
+ * Updates the conversion rates for new assets.
348
346
  *
349
- * The event payload is treated as a delta:
350
- * - `added` assets are batched by Snap and fetched for fresh rates
351
- * - `removed` assets are deleted from cached state only if they are no longer tracked by any account
352
- *
353
- * This global check is required because rate state is keyed by asset rather
354
- * than by account, so the same asset may still be shared by another account.
355
- *
356
- * @param accounts - The per-account asset deltas from the asset-list update event.
347
+ * @param accounts - The accounts to update the conversion rates for.
357
348
  * @returns A promise that resolves when the rates are updated.
358
349
  */
359
350
  async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(accounts) {
@@ -365,23 +356,10 @@ async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(ac
365
356
  // First build a map containing all assets that need to be updated per
366
357
  // Snap ID, this will be used to batch the requests.
367
358
  const snapIdToAssets = new Map();
368
- const removedAssets = new Set();
369
- for (const { accountId, added, removed } of accounts) {
370
- if (added.length !== 0) {
371
- // Only newly added assets need fresh rate requests.
372
- __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_addAssetsToSnapIdMap).call(this, snapIdToAssets, __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getAccount).call(this, accountId), added);
373
- }
374
- // Collect removed assets separately so we can decide later whether they
375
- // are truly stale or still referenced by another account.
376
- for (const asset of removed) {
377
- removedAssets.add(asset);
378
- }
359
+ for (const { accountId, assets } of accounts) {
360
+ __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_addAssetsToSnapIdMap).call(this, snapIdToAssets, __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getAccount).call(this, accountId), assets);
379
361
  }
380
- const updatedRates = await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getUpdatedRatesFor).call(this, snapIdToAssets);
381
- // Rates are stored globally by asset, so delete only assets that no
382
- // longer exist in any account asset list.
383
- const assetsToDelete = Array.from(removedAssets).filter((asset) => !__classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_isAssetTracked).call(this, asset));
384
- __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_applyUpdatedRates).call(this, updatedRates, assetsToDelete);
362
+ __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_applyUpdatedRates).call(this, await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getUpdatedRatesFor).call(this, snapIdToAssets));
385
363
  })().finally(() => {
386
364
  releaseLock();
387
365
  });
@@ -396,28 +374,16 @@ async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(ac
396
374
  // new object references on every update, so caching would become stale.
397
375
  const { accountsAssets } = this.messenger.call('MultichainAssetsController:getState');
398
376
  return accountsAssets?.[accountId] ?? [];
399
- }, _MultichainAssetsRatesController_applyUpdatedRates = function _MultichainAssetsRatesController_applyUpdatedRates(updatedRates, removedAssets = []) {
400
- if (Object.keys(updatedRates).length === 0 && removedAssets.length === 0) {
377
+ }, _MultichainAssetsRatesController_applyUpdatedRates = function _MultichainAssetsRatesController_applyUpdatedRates(updatedRates) {
378
+ if (Object.keys(updatedRates).length === 0) {
401
379
  return;
402
380
  }
403
381
  this.update((state) => {
404
- // Drop both current rates and historical prices for assets that are no
405
- // longer referenced anywhere in MultichainAssetsController state.
406
- for (const asset of removedAssets) {
407
- delete state.conversionRates[asset];
408
- delete state.historicalPrices[asset];
409
- }
410
- // Merge the freshly fetched rates after cleanup.
411
382
  state.conversionRates = {
412
383
  ...state.conversionRates,
413
384
  ...updatedRates,
414
385
  };
415
386
  });
416
- }, _MultichainAssetsRatesController_isAssetTracked = function _MultichainAssetsRatesController_isAssetTracked(asset) {
417
- const { accountsAssets } = this.messenger.call('MultichainAssetsController:getState');
418
- // Rate state is global per asset, so inspect all account asset lists before
419
- // deciding whether a removed asset should be purged from cache.
420
- return Object.values(accountsAssets ?? {}).some((accountAssets) => accountAssets.includes(asset));
421
387
  }, _MultichainAssetsRatesController_handleSnapRequest = async function _MultichainAssetsRatesController_handleSnapRequest(args) {
422
388
  const { snapId, handler, params } = args;
423
389
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"MultichainAssetsRatesController.cjs","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerListMultichainAccountsAction,\n AccountsControllerAccountAddedEvent,\n AccountsControllerGetSelectedMultichainAccountAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerStateChangeEvent,\n ControllerGetStateAction,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { CaipAssetType } from '@metamask/keyring-api';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { SnapControllerHandleRequestAction } from '@metamask/snaps-controllers';\nimport type {\n SnapId,\n AssetConversion,\n OnAssetsConversionArguments,\n OnAssetHistoricalPriceArguments,\n OnAssetHistoricalPriceResponse,\n HistoricalPriceIntervals,\n OnAssetsMarketDataArguments,\n OnAssetsMarketDataResponse,\n FungibleAssetMarketData,\n OnAssetsConversionResponse,\n} from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { Mutex } from 'async-mutex';\nimport type { Draft } from 'immer';\n\nimport type {\n CurrencyRateState,\n CurrencyRateStateChange,\n GetCurrencyRateState,\n} from '../CurrencyRateController';\nimport type {\n MultichainAssetsControllerGetStateAction,\n MultichainAssetsControllerAccountAssetListUpdatedEvent,\n} from '../MultichainAssetsController';\nimport { MAP_CAIP_CURRENCIES } from './constant';\nimport type { MultichainAssetsRatesControllerMethodActions } from './MultichainAssetsRatesController-method-action-types';\n\n/**\n * The name of the MultichainAssetsRatesController.\n */\nconst controllerName = 'MultichainAssetsRatesController';\n\n// This is temporary until its exported from snap\ntype HistoricalPrice = {\n intervals: HistoricalPriceIntervals;\n // The UNIX timestamp of when the historical price was last updated.\n updateTime: number;\n // The UNIX timestamp of when the historical price will expire.\n expirationTime?: number;\n};\n\n/**\n * State used by the MultichainAssetsRatesController to cache token conversion rates.\n */\nexport type MultichainAssetsRatesControllerState = {\n conversionRates: Record<CaipAssetType, UnifiedAssetConversion>;\n historicalPrices: Record<CaipAssetType, Record<string, HistoricalPrice>>; // string being the current currency we fetched historical prices for\n};\n\n/**\n * Returns the state of the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsRatesControllerState\n >;\n\ntype UnifiedAssetConversion = AssetConversion & {\n marketData?: FungibleAssetMarketData;\n};\n\n/**\n * Constructs the default {@link MultichainAssetsRatesController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsRatesController} state.\n */\nexport function getDefaultMultichainAssetsRatesControllerState(): MultichainAssetsRatesControllerState {\n return { conversionRates: {}, historicalPrices: {} };\n}\n\n/**\n * Event emitted when the state of the MultichainAssetsRatesController changes.\n */\nexport type MultichainAssetsRatesControllerStateChange =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsRatesControllerState\n >;\n\n/**\n * Actions exposed by the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerActions =\n | MultichainAssetsRatesControllerGetStateAction\n | MultichainAssetsRatesControllerMethodActions;\n\n/**\n * Events emitted by MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerEvents =\n MultichainAssetsRatesControllerStateChange;\n\n/**\n * Actions that this controller is allowed to call.\n */\nexport type AllowedActions =\n | SnapControllerHandleRequestAction\n | AccountsControllerListMultichainAccountsAction\n | GetCurrencyRateState\n | MultichainAssetsControllerGetStateAction\n | AccountsControllerGetSelectedMultichainAccountAction;\n\n/**\n * Events that this controller is allowed to subscribe to.\n */\nexport type AllowedEvents =\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n | AccountsControllerAccountAddedEvent\n | CurrencyRateStateChange\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n/**\n * Messenger type for the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsRatesControllerActions | AllowedActions,\n MultichainAssetsRatesControllerEvents | AllowedEvents\n>;\n\n/**\n * The input for starting polling in MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesPollingInput = {\n accountId: string;\n};\n\nconst metadata: StateMetadata<MultichainAssetsRatesControllerState> = {\n conversionRates: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n historicalPrices: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nexport type ConversionRatesWithMarketData = {\n conversionRates: Record<\n CaipAssetType,\n Record<CaipAssetType, UnifiedAssetConversion | null>\n >;\n};\n\n/**\n * Arguments for a Snap request.\n */\ntype SnapRequestArgs<T> = {\n snapId: SnapId;\n handler: HandlerType;\n params: T;\n};\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'updateAssetsRates',\n 'fetchHistoricalPricesForAsset',\n] as const;\n\n/**\n * Controller that manages multichain token conversion rates.\n *\n * This controller polls for token conversion rates and updates its state.\n */\nexport class MultichainAssetsRatesController extends StaticIntervalPollingController<MultichainAssetsRatesPollingInput>()<\n typeof controllerName,\n MultichainAssetsRatesControllerState,\n MultichainAssetsRatesControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n #currentCurrency: CurrencyRateState['currentCurrency'];\n\n #isUnlocked = true;\n\n /**\n * Creates an instance of MultichainAssetsRatesController.\n *\n * @param options - Constructor options.\n * @param options.interval - The polling interval in milliseconds.\n * @param options.state - The initial state.\n * @param options.messenger - A reference to the messenger.\n */\n constructor({\n interval = 18000,\n state = {},\n messenger,\n }: {\n interval?: number;\n state?: Partial<MultichainAssetsRatesControllerState>;\n messenger: MultichainAssetsRatesControllerMessenger;\n }) {\n super({\n name: controllerName,\n messenger,\n state: {\n ...getDefaultMultichainAssetsRatesControllerState(),\n ...state,\n },\n metadata,\n });\n\n this.setIntervalLength(interval);\n\n messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);\n\n // Subscribe to keyring lock/unlock events.\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n });\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#isUnlocked = true;\n });\n\n ({ currentCurrency: this.#currentCurrency } = this.messenger.call(\n 'CurrencyRateController:getState',\n ));\n\n this.messenger.subscribe(\n 'CurrencyRateController:stateChange',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (currentCurrency: string) => {\n this.#currentCurrency = currentCurrency;\n await this.updateAssetsRates();\n },\n (currencyRateControllerState) =>\n currencyRateControllerState.currentCurrency,\n );\n\n this.messenger.subscribe(\n 'MultichainAssetsController:accountAssetListUpdated',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async ({ assets }) => {\n // Treat the event payload as a per-account delta so we can fetch only\n // newly added assets and independently clean up removed ones.\n const updatedAccountAssets = Object.entries(assets).map(\n ([accountId, { added, removed }]) => ({\n accountId,\n added: [...added],\n removed: [...removed],\n }),\n );\n await this.#updateAssetsRatesForNewAssets(updatedAccountAssets);\n },\n );\n }\n\n /**\n * Executes a poll by updating token conversion rates for the current account.\n *\n * @returns A promise that resolves when the polling completes.\n */\n async _executePoll(): Promise<void> {\n await this.updateAssetsRates();\n }\n\n /**\n * Determines whether the controller is active.\n *\n * @returns True if the keyring is unlocked; otherwise, false.\n */\n get isActive(): boolean {\n return this.#isUnlocked;\n }\n\n /**\n * Checks if an account is a non-EVM account with a Snap.\n *\n * @param account - The account to check.\n * @returns True if the account is non-EVM and has Snap metadata; otherwise, false.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) && account.metadata.snap !== undefined\n );\n }\n\n /**\n * Retrieves all multichain accounts from the AccountsController.\n *\n * @returns An array of internal accounts.\n */\n #listMultichainAccounts(): InternalAccount[] {\n return this.messenger.call('AccountsController:listMultichainAccounts');\n }\n\n /**\n * Filters and returns non-EVM accounts that should have balances.\n *\n * @returns An array of non-EVM internal accounts.\n */\n #listAccounts(): InternalAccount[] {\n const accounts = this.#listMultichainAccounts();\n return accounts.filter((account) => this.#isNonEvmAccount(account));\n }\n\n /**\n * Adds the assets to a map of Snap ID to assets.\n *\n * @param snapIdToAssets - The map of Snap ID to assets.\n * @param account - The account to add the assets for.\n * @param assets - The assets to add.\n */\n #addAssetsToSnapIdMap(\n snapIdToAssets: Map<SnapId, Set<CaipAssetType>>,\n account: InternalAccount,\n assets: CaipAssetType[],\n ): void {\n // Prevent creating a new set if there are no assets to add.\n if (assets.length === 0) {\n return;\n }\n\n // FIXME: Instead of using the Snap ID from the account, we should\n // select the Snap based on the supported scopes defined in the Snaps'\n // manifest.\n const snapId = account.metadata.snap?.id as SnapId | undefined;\n if (!snapId) {\n return;\n }\n\n let snapAssets = snapIdToAssets.get(snapId);\n if (!snapAssets) {\n snapAssets = new Set();\n snapIdToAssets.set(snapId, snapAssets);\n }\n\n for (const asset of assets) {\n snapAssets.add(asset);\n }\n }\n\n /**\n * Updates token conversion rates for each non-EVM account.\n *\n * @returns A promise that resolves when the rates are updated.\n */\n async updateAssetsRates(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n\n return (async (): Promise<void> => {\n if (!this.isActive) {\n return;\n }\n\n // Compute the set of unique assets from all accounts. It's important to\n // deduplicate assets here to avoid duplicate requests to the Snap.\n const accounts = this.#listAccounts();\n const snapIdToAssets = new Map<SnapId, Set<CaipAssetType>>();\n for (const account of accounts) {\n this.#addAssetsToSnapIdMap(\n snapIdToAssets,\n account,\n this.#getAssetsForAccount(account.id),\n );\n }\n\n this.#applyUpdatedRates(await this.#getUpdatedRatesFor(snapIdToAssets));\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Returns the CAIP-19 asset type for the current selected currency. Defaults\n * to USD if the current selected currency is not supported.\n *\n * @returns The CAIP-19 asset type for the current selected currency.\n */\n #getCaipCurrentCurrency(): CaipAssetType {\n return (\n MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? MAP_CAIP_CURRENCIES.usd\n );\n }\n\n /**\n * Fetches the conversion rates for the given assets from the given Snap.\n *\n * @param snapId - The ID of the Snap.\n * @param assets - The assets to fetch the conversion rates for.\n * @param currency - The currency to fetch the conversion rates for.\n * @returns A record of CAIP-19 asset types to conversion rates.\n */\n async #getConversionRates(\n snapId: SnapId,\n assets: Set<CaipAssetType>,\n currency: CaipAssetType,\n ): Promise<Record<CaipAssetType, AssetConversion | undefined>> {\n // Prevent making a Snap call if there are no assets to fetch.\n if (assets.size === 0) {\n return {};\n }\n\n const response = await this.#handleSnapRequest({\n snapId,\n handler: HandlerType.OnAssetsConversion,\n params: {\n conversions: Array.from(assets).map((asset) => ({\n from: asset,\n to: currency,\n })),\n },\n });\n\n if (!response) {\n return {};\n }\n\n const assetToConversionRate: Record<\n CaipAssetType,\n AssetConversion | undefined\n > = {};\n\n for (const asset of assets) {\n assetToConversionRate[asset] =\n response.conversionRates?.[asset]?.[currency] ?? undefined;\n }\n\n return assetToConversionRate;\n }\n\n /**\n * Fetches the market data for the given assets from the given Snap.\n *\n * @param snapId - The ID of the Snap.\n * @param assets - The assets to fetch the market data for.\n * @param currency - The currency to fetch the market data for.\n * @returns A record of CAIP-19 asset types to market data.\n */\n async #getMarketData(\n snapId: SnapId,\n assets: Set<CaipAssetType>,\n currency: CaipAssetType,\n ): Promise<Record<CaipAssetType, FungibleAssetMarketData | undefined>> {\n // Prevent making a Snap call if there are no assets to fetch.\n if (assets.size === 0) {\n return {};\n }\n\n const response = await this.#handleSnapRequest({\n snapId,\n handler: HandlerType.OnAssetsMarketData,\n params: {\n assets: Array.from(assets).map((asset) => ({\n asset,\n unit: currency,\n })),\n },\n });\n\n if (!response) {\n return {};\n }\n\n const assetToMarketData: Record<\n CaipAssetType,\n FungibleAssetMarketData | undefined\n > = {};\n\n for (const asset of assets) {\n const assetMarketData = response.marketData?.[asset]?.[currency];\n\n // We do not consider NFTs here, so `fungible` must be `true`.\n if (assetMarketData?.fungible) {\n assetToMarketData[asset] = assetMarketData;\n } else {\n assetToMarketData[asset] = undefined;\n }\n }\n\n return assetToMarketData;\n }\n\n /**\n * Fetches the updated rates for the given assets from the given Snaps.\n *\n * @param snapIdToAssets - A map of Snap ID to CAIP-19 asset types.\n * @returns A record of CAIP-19 asset types to unified asset conversions.\n */\n async #getUpdatedRatesFor(\n snapIdToAssets: Map<SnapId, Set<CaipAssetType>>,\n ): Promise<\n Record<CaipAssetType, UnifiedAssetConversion & { currency: CaipAssetType }>\n > {\n const updatedRates: Record<\n CaipAssetType,\n UnifiedAssetConversion & { currency: CaipAssetType }\n > = {};\n\n // Keep a local copy to ensure that the currency is always the same for the\n // entire loop.\n const currency = this.#getCaipCurrentCurrency();\n\n // Note: Since the assets come from a 1-to-1 mapping with Snap IDs, we know\n // that a given asset will not appear under multiple Snap IDs.\n for (const [snapId, assets] of snapIdToAssets.entries()) {\n const [rates, marketData] = await Promise.all([\n this.#getConversionRates(snapId, assets, currency),\n this.#getMarketData(snapId, assets, currency),\n ]);\n\n for (const asset of assets) {\n const assetRate = rates[asset];\n const assetMarketData = marketData[asset];\n\n // Rates are mandatory, so skip the asset if not available.\n if (!assetRate) {\n continue;\n }\n\n updatedRates[asset] = {\n currency,\n ...assetRate,\n ...(assetMarketData && { marketData: assetMarketData }),\n };\n }\n }\n\n return updatedRates;\n }\n\n /**\n * Fetches historical prices for the current account\n *\n * @param asset - The asset to fetch historical prices for.\n * @param account - optional account to fetch historical prices for\n * @returns The historical prices.\n */\n async fetchHistoricalPricesForAsset(\n asset: CaipAssetType,\n account?: InternalAccount,\n ): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n return (async () => {\n const currentCaipCurrency =\n MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? MAP_CAIP_CURRENCIES.usd;\n // Check if we already have historical prices for this asset and currency\n const historicalPriceExpirationTime =\n this.state.historicalPrices[asset]?.[this.#currentCurrency]\n ?.expirationTime;\n\n const historicalPriceHasExpired =\n historicalPriceExpirationTime &&\n historicalPriceExpirationTime < Date.now();\n\n if (historicalPriceHasExpired === false) {\n return;\n }\n\n const selectedAccount =\n account ??\n this.messenger.call('AccountsController:getSelectedMultichainAccount');\n try {\n const historicalPricesResponse = await this.messenger.call(\n 'SnapController:handleRequest',\n {\n snapId: selectedAccount?.metadata.snap?.id as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetHistoricalPrice,\n request: {\n jsonrpc: '2.0',\n method: HandlerType.OnAssetHistoricalPrice,\n params: {\n from: asset,\n to: currentCaipCurrency,\n },\n },\n },\n );\n\n // skip state update if no historical prices are returned\n if (!historicalPricesResponse) {\n return;\n }\n\n this.update((state) => {\n state.historicalPrices = {\n ...state.historicalPrices,\n [asset]: {\n ...state.historicalPrices[asset],\n [this.#currentCurrency]: (\n historicalPricesResponse as OnAssetHistoricalPriceResponse\n )?.historicalPrice,\n },\n };\n });\n } catch {\n throw new Error(\n `Failed to fetch historical prices for asset: ${asset}`,\n );\n }\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Reconciles cached rates after an account asset-list update event.\n *\n * The event payload is treated as a delta:\n * - `added` assets are batched by Snap and fetched for fresh rates\n * - `removed` assets are deleted from cached state only if they are no longer tracked by any account\n *\n * This global check is required because rate state is keyed by asset rather\n * than by account, so the same asset may still be shared by another account.\n *\n * @param accounts - The per-account asset deltas from the asset-list update event.\n * @returns A promise that resolves when the rates are updated.\n */\n async #updateAssetsRatesForNewAssets(\n accounts: {\n accountId: string;\n added: CaipAssetType[];\n removed: CaipAssetType[];\n }[],\n ): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n\n return (async () => {\n if (!this.isActive) {\n return;\n }\n\n // First build a map containing all assets that need to be updated per\n // Snap ID, this will be used to batch the requests.\n const snapIdToAssets = new Map<SnapId, Set<CaipAssetType>>();\n const removedAssets = new Set<CaipAssetType>();\n\n for (const { accountId, added, removed } of accounts) {\n if (added.length !== 0) {\n // Only newly added assets need fresh rate requests.\n this.#addAssetsToSnapIdMap(\n snapIdToAssets,\n this.#getAccount(accountId),\n added,\n );\n }\n\n // Collect removed assets separately so we can decide later whether they\n // are truly stale or still referenced by another account.\n for (const asset of removed) {\n removedAssets.add(asset);\n }\n }\n\n const updatedRates = await this.#getUpdatedRatesFor(snapIdToAssets);\n // Rates are stored globally by asset, so delete only assets that no\n // longer exist in any account asset list.\n const assetsToDelete = Array.from(removedAssets).filter(\n (asset) => !this.#isAssetTracked(asset),\n );\n\n this.#applyUpdatedRates(updatedRates, assetsToDelete);\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Get a non-EVM account from its ID.\n *\n * @param accountId - The account ID.\n * @returns The non-EVM account.\n */\n #getAccount(accountId: string): InternalAccount {\n const account: InternalAccount | undefined = this.#listAccounts().find(\n (multichainAccount) => multichainAccount.id === accountId,\n );\n\n if (!account) {\n throw new Error(`Unknown account: ${accountId}`);\n }\n\n return account;\n }\n\n /**\n * Returns the array of CAIP-19 assets for the given account ID.\n * If none are found, returns an empty array.\n *\n * @param accountId - The account ID to get the assets for.\n * @returns An array of CAIP-19 assets.\n */\n #getAssetsForAccount(accountId: string): CaipAssetType[] {\n // Always fetch fresh state - MultichainAssetsController uses Immer which creates\n // new object references on every update, so caching would become stale.\n const { accountsAssets } = this.messenger.call(\n 'MultichainAssetsController:getState',\n );\n return accountsAssets?.[accountId] ?? [];\n }\n\n /**\n * Applies fresh rates and removes stale asset-rate state in one update.\n *\n * @param updatedRates - The latest conversion rates fetched for added assets.\n * @param removedAssets - Assets that should be purged because they are no longer tracked by any account.\n */\n #applyUpdatedRates(\n updatedRates: Record<\n CaipAssetType,\n UnifiedAssetConversion & { currency: CaipAssetType }\n >,\n removedAssets: CaipAssetType[] = [],\n ): void {\n if (Object.keys(updatedRates).length === 0 && removedAssets.length === 0) {\n return;\n }\n this.update((state: Draft<MultichainAssetsRatesControllerState>) => {\n // Drop both current rates and historical prices for assets that are no\n // longer referenced anywhere in MultichainAssetsController state.\n for (const asset of removedAssets) {\n delete state.conversionRates[asset];\n delete state.historicalPrices[asset];\n }\n\n // Merge the freshly fetched rates after cleanup.\n state.conversionRates = {\n ...state.conversionRates,\n ...updatedRates,\n };\n });\n }\n\n /**\n * Checks whether an asset is still tracked by any account in\n * MultichainAssetsController state.\n *\n * @param asset - The asset to check.\n * @returns True if the asset still exists in any account asset list.\n */\n #isAssetTracked(asset: CaipAssetType): boolean {\n const { accountsAssets } = this.messenger.call(\n 'MultichainAssetsController:getState',\n );\n\n // Rate state is global per asset, so inspect all account asset lists before\n // deciding whether a removed asset should be purged from cache.\n return Object.values(accountsAssets ?? {}).some((accountAssets) =>\n accountAssets.includes(asset),\n );\n }\n\n /**\n * Forwards a Snap request to the SnapController.\n *\n * @param args - The request parameters.\n * @param args.snapId - The ID of the Snap.\n * @param args.handler - The handler type.\n * @param args.params - The asset conversions.\n * @returns A promise that resolves with the account rates.\n */\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetsConversionArguments>,\n ): Promise<OnAssetsConversionResponse | undefined>;\n\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetHistoricalPriceArguments>,\n ): Promise<OnAssetHistoricalPriceResponse | undefined>;\n\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetsMarketDataArguments>,\n ): Promise<OnAssetsMarketDataResponse | undefined>;\n\n async #handleSnapRequest(args: SnapRequestArgs<unknown>): Promise<unknown> {\n const { snapId, handler, params } = args;\n try {\n return await this.messenger.call('SnapController:handleRequest', {\n snapId,\n origin: 'metamask',\n handler,\n request: {\n jsonrpc: '2.0',\n method: handler,\n params,\n },\n });\n } catch (error) {\n console.error(`Snap request failed for ${handler}:`, {\n snapId,\n handler,\n message: (error as Error).message,\n params,\n });\n return undefined;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"MultichainAssetsRatesController.cjs","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerListMultichainAccountsAction,\n AccountsControllerAccountAddedEvent,\n AccountsControllerGetSelectedMultichainAccountAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerStateChangeEvent,\n ControllerGetStateAction,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { CaipAssetType } from '@metamask/keyring-api';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { SnapControllerHandleRequestAction } from '@metamask/snaps-controllers';\nimport type {\n SnapId,\n AssetConversion,\n OnAssetsConversionArguments,\n OnAssetHistoricalPriceArguments,\n OnAssetHistoricalPriceResponse,\n HistoricalPriceIntervals,\n OnAssetsMarketDataArguments,\n OnAssetsMarketDataResponse,\n FungibleAssetMarketData,\n OnAssetsConversionResponse,\n} from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { Mutex } from 'async-mutex';\nimport type { Draft } from 'immer';\n\nimport type {\n CurrencyRateState,\n CurrencyRateStateChange,\n GetCurrencyRateState,\n} from '../CurrencyRateController';\nimport type {\n MultichainAssetsControllerGetStateAction,\n MultichainAssetsControllerAccountAssetListUpdatedEvent,\n} from '../MultichainAssetsController';\nimport { MAP_CAIP_CURRENCIES } from './constant';\nimport type { MultichainAssetsRatesControllerMethodActions } from './MultichainAssetsRatesController-method-action-types';\n\n/**\n * The name of the MultichainAssetsRatesController.\n */\nconst controllerName = 'MultichainAssetsRatesController';\n\n// This is temporary until its exported from snap\ntype HistoricalPrice = {\n intervals: HistoricalPriceIntervals;\n // The UNIX timestamp of when the historical price was last updated.\n updateTime: number;\n // The UNIX timestamp of when the historical price will expire.\n expirationTime?: number;\n};\n\n/**\n * State used by the MultichainAssetsRatesController to cache token conversion rates.\n */\nexport type MultichainAssetsRatesControllerState = {\n conversionRates: Record<CaipAssetType, UnifiedAssetConversion>;\n historicalPrices: Record<CaipAssetType, Record<string, HistoricalPrice>>; // string being the current currency we fetched historical prices for\n};\n\n/**\n * Returns the state of the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsRatesControllerState\n >;\n\ntype UnifiedAssetConversion = AssetConversion & {\n marketData?: FungibleAssetMarketData;\n};\n\n/**\n * Constructs the default {@link MultichainAssetsRatesController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsRatesController} state.\n */\nexport function getDefaultMultichainAssetsRatesControllerState(): MultichainAssetsRatesControllerState {\n return { conversionRates: {}, historicalPrices: {} };\n}\n\n/**\n * Event emitted when the state of the MultichainAssetsRatesController changes.\n */\nexport type MultichainAssetsRatesControllerStateChange =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsRatesControllerState\n >;\n\n/**\n * Actions exposed by the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerActions =\n | MultichainAssetsRatesControllerGetStateAction\n | MultichainAssetsRatesControllerMethodActions;\n\n/**\n * Events emitted by MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerEvents =\n MultichainAssetsRatesControllerStateChange;\n\n/**\n * Actions that this controller is allowed to call.\n */\nexport type AllowedActions =\n | SnapControllerHandleRequestAction\n | AccountsControllerListMultichainAccountsAction\n | GetCurrencyRateState\n | MultichainAssetsControllerGetStateAction\n | AccountsControllerGetSelectedMultichainAccountAction;\n\n/**\n * Events that this controller is allowed to subscribe to.\n */\nexport type AllowedEvents =\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n | AccountsControllerAccountAddedEvent\n | CurrencyRateStateChange\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n/**\n * Messenger type for the MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsRatesControllerActions | AllowedActions,\n MultichainAssetsRatesControllerEvents | AllowedEvents\n>;\n\n/**\n * The input for starting polling in MultichainAssetsRatesController.\n */\nexport type MultichainAssetsRatesPollingInput = {\n accountId: string;\n};\n\nconst metadata: StateMetadata<MultichainAssetsRatesControllerState> = {\n conversionRates: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n historicalPrices: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nexport type ConversionRatesWithMarketData = {\n conversionRates: Record<\n CaipAssetType,\n Record<CaipAssetType, UnifiedAssetConversion | null>\n >;\n};\n\n/**\n * Arguments for a Snap request.\n */\ntype SnapRequestArgs<T> = {\n snapId: SnapId;\n handler: HandlerType;\n params: T;\n};\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'updateAssetsRates',\n 'fetchHistoricalPricesForAsset',\n] as const;\n\n/**\n * Controller that manages multichain token conversion rates.\n *\n * This controller polls for token conversion rates and updates its state.\n */\nexport class MultichainAssetsRatesController extends StaticIntervalPollingController<MultichainAssetsRatesPollingInput>()<\n typeof controllerName,\n MultichainAssetsRatesControllerState,\n MultichainAssetsRatesControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n #currentCurrency: CurrencyRateState['currentCurrency'];\n\n #isUnlocked = true;\n\n /**\n * Creates an instance of MultichainAssetsRatesController.\n *\n * @param options - Constructor options.\n * @param options.interval - The polling interval in milliseconds.\n * @param options.state - The initial state.\n * @param options.messenger - A reference to the messenger.\n */\n constructor({\n interval = 18000,\n state = {},\n messenger,\n }: {\n interval?: number;\n state?: Partial<MultichainAssetsRatesControllerState>;\n messenger: MultichainAssetsRatesControllerMessenger;\n }) {\n super({\n name: controllerName,\n messenger,\n state: {\n ...getDefaultMultichainAssetsRatesControllerState(),\n ...state,\n },\n metadata,\n });\n\n this.setIntervalLength(interval);\n\n messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);\n\n // Subscribe to keyring lock/unlock events.\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n });\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#isUnlocked = true;\n });\n\n ({ currentCurrency: this.#currentCurrency } = this.messenger.call(\n 'CurrencyRateController:getState',\n ));\n\n this.messenger.subscribe(\n 'CurrencyRateController:stateChange',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (currentCurrency: string) => {\n this.#currentCurrency = currentCurrency;\n await this.updateAssetsRates();\n },\n (currencyRateControllerState) =>\n currencyRateControllerState.currentCurrency,\n );\n\n this.messenger.subscribe(\n 'MultichainAssetsController:accountAssetListUpdated',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async ({ assets }) => {\n const newAccountAssets = Object.entries(assets).map(\n ([accountId, { added }]) => ({\n accountId,\n assets: [...added],\n }),\n );\n // TODO; removed can be used in future for further cleanup\n await this.#updateAssetsRatesForNewAssets(newAccountAssets);\n },\n );\n }\n\n /**\n * Executes a poll by updating token conversion rates for the current account.\n *\n * @returns A promise that resolves when the polling completes.\n */\n async _executePoll(): Promise<void> {\n await this.updateAssetsRates();\n }\n\n /**\n * Determines whether the controller is active.\n *\n * @returns True if the keyring is unlocked; otherwise, false.\n */\n get isActive(): boolean {\n return this.#isUnlocked;\n }\n\n /**\n * Checks if an account is a non-EVM account with a Snap.\n *\n * @param account - The account to check.\n * @returns True if the account is non-EVM and has Snap metadata; otherwise, false.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) && account.metadata.snap !== undefined\n );\n }\n\n /**\n * Retrieves all multichain accounts from the AccountsController.\n *\n * @returns An array of internal accounts.\n */\n #listMultichainAccounts(): InternalAccount[] {\n return this.messenger.call('AccountsController:listMultichainAccounts');\n }\n\n /**\n * Filters and returns non-EVM accounts that should have balances.\n *\n * @returns An array of non-EVM internal accounts.\n */\n #listAccounts(): InternalAccount[] {\n const accounts = this.#listMultichainAccounts();\n return accounts.filter((account) => this.#isNonEvmAccount(account));\n }\n\n /**\n * Adds the assets to a map of Snap ID to assets.\n *\n * @param snapIdToAssets - The map of Snap ID to assets.\n * @param account - The account to add the assets for.\n * @param assets - The assets to add.\n */\n #addAssetsToSnapIdMap(\n snapIdToAssets: Map<SnapId, Set<CaipAssetType>>,\n account: InternalAccount,\n assets: CaipAssetType[],\n ): void {\n // Prevent creating a new set if there are no assets to add.\n if (assets.length === 0) {\n return;\n }\n\n // FIXME: Instead of using the Snap ID from the account, we should\n // select the Snap based on the supported scopes defined in the Snaps'\n // manifest.\n const snapId = account.metadata.snap?.id as SnapId | undefined;\n if (!snapId) {\n return;\n }\n\n let snapAssets = snapIdToAssets.get(snapId);\n if (!snapAssets) {\n snapAssets = new Set();\n snapIdToAssets.set(snapId, snapAssets);\n }\n\n for (const asset of assets) {\n snapAssets.add(asset);\n }\n }\n\n /**\n * Updates token conversion rates for each non-EVM account.\n *\n * @returns A promise that resolves when the rates are updated.\n */\n async updateAssetsRates(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n\n return (async (): Promise<void> => {\n if (!this.isActive) {\n return;\n }\n\n // Compute the set of unique assets from all accounts. It's important to\n // deduplicate assets here to avoid duplicate requests to the Snap.\n const accounts = this.#listAccounts();\n const snapIdToAssets = new Map<SnapId, Set<CaipAssetType>>();\n for (const account of accounts) {\n this.#addAssetsToSnapIdMap(\n snapIdToAssets,\n account,\n this.#getAssetsForAccount(account.id),\n );\n }\n\n this.#applyUpdatedRates(await this.#getUpdatedRatesFor(snapIdToAssets));\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Returns the CAIP-19 asset type for the current selected currency. Defaults\n * to USD if the current selected currency is not supported.\n *\n * @returns The CAIP-19 asset type for the current selected currency.\n */\n #getCaipCurrentCurrency(): CaipAssetType {\n return (\n MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? MAP_CAIP_CURRENCIES.usd\n );\n }\n\n /**\n * Fetches the conversion rates for the given assets from the given Snap.\n *\n * @param snapId - The ID of the Snap.\n * @param assets - The assets to fetch the conversion rates for.\n * @param currency - The currency to fetch the conversion rates for.\n * @returns A record of CAIP-19 asset types to conversion rates.\n */\n async #getConversionRates(\n snapId: SnapId,\n assets: Set<CaipAssetType>,\n currency: CaipAssetType,\n ): Promise<Record<CaipAssetType, AssetConversion | undefined>> {\n // Prevent making a Snap call if there are no assets to fetch.\n if (assets.size === 0) {\n return {};\n }\n\n const response = await this.#handleSnapRequest({\n snapId,\n handler: HandlerType.OnAssetsConversion,\n params: {\n conversions: Array.from(assets).map((asset) => ({\n from: asset,\n to: currency,\n })),\n },\n });\n\n if (!response) {\n return {};\n }\n\n const assetToConversionRate: Record<\n CaipAssetType,\n AssetConversion | undefined\n > = {};\n\n for (const asset of assets) {\n assetToConversionRate[asset] =\n response.conversionRates?.[asset]?.[currency] ?? undefined;\n }\n\n return assetToConversionRate;\n }\n\n /**\n * Fetches the market data for the given assets from the given Snap.\n *\n * @param snapId - The ID of the Snap.\n * @param assets - The assets to fetch the market data for.\n * @param currency - The currency to fetch the market data for.\n * @returns A record of CAIP-19 asset types to market data.\n */\n async #getMarketData(\n snapId: SnapId,\n assets: Set<CaipAssetType>,\n currency: CaipAssetType,\n ): Promise<Record<CaipAssetType, FungibleAssetMarketData | undefined>> {\n // Prevent making a Snap call if there are no assets to fetch.\n if (assets.size === 0) {\n return {};\n }\n\n const response = await this.#handleSnapRequest({\n snapId,\n handler: HandlerType.OnAssetsMarketData,\n params: {\n assets: Array.from(assets).map((asset) => ({\n asset,\n unit: currency,\n })),\n },\n });\n\n if (!response) {\n return {};\n }\n\n const assetToMarketData: Record<\n CaipAssetType,\n FungibleAssetMarketData | undefined\n > = {};\n\n for (const asset of assets) {\n const assetMarketData = response.marketData?.[asset]?.[currency];\n\n // We do not consider NFTs here, so `fungible` must be `true`.\n if (assetMarketData?.fungible) {\n assetToMarketData[asset] = assetMarketData;\n } else {\n assetToMarketData[asset] = undefined;\n }\n }\n\n return assetToMarketData;\n }\n\n /**\n * Fetches the updated rates for the given assets from the given Snaps.\n *\n * @param snapIdToAssets - A map of Snap ID to CAIP-19 asset types.\n * @returns A record of CAIP-19 asset types to unified asset conversions.\n */\n async #getUpdatedRatesFor(\n snapIdToAssets: Map<SnapId, Set<CaipAssetType>>,\n ): Promise<\n Record<CaipAssetType, UnifiedAssetConversion & { currency: CaipAssetType }>\n > {\n const updatedRates: Record<\n CaipAssetType,\n UnifiedAssetConversion & { currency: CaipAssetType }\n > = {};\n\n // Keep a local copy to ensure that the currency is always the same for the\n // entire loop.\n const currency = this.#getCaipCurrentCurrency();\n\n // Note: Since the assets come from a 1-to-1 mapping with Snap IDs, we know\n // that a given asset will not appear under multiple Snap IDs.\n for (const [snapId, assets] of snapIdToAssets.entries()) {\n const [rates, marketData] = await Promise.all([\n this.#getConversionRates(snapId, assets, currency),\n this.#getMarketData(snapId, assets, currency),\n ]);\n\n for (const asset of assets) {\n const assetRate = rates[asset];\n const assetMarketData = marketData[asset];\n\n // Rates are mandatory, so skip the asset if not available.\n if (!assetRate) {\n continue;\n }\n\n updatedRates[asset] = {\n currency,\n ...assetRate,\n ...(assetMarketData && { marketData: assetMarketData }),\n };\n }\n }\n\n return updatedRates;\n }\n\n /**\n * Fetches historical prices for the current account\n *\n * @param asset - The asset to fetch historical prices for.\n * @param account - optional account to fetch historical prices for\n * @returns The historical prices.\n */\n async fetchHistoricalPricesForAsset(\n asset: CaipAssetType,\n account?: InternalAccount,\n ): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n return (async () => {\n const currentCaipCurrency =\n MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? MAP_CAIP_CURRENCIES.usd;\n // Check if we already have historical prices for this asset and currency\n const historicalPriceExpirationTime =\n this.state.historicalPrices[asset]?.[this.#currentCurrency]\n ?.expirationTime;\n\n const historicalPriceHasExpired =\n historicalPriceExpirationTime &&\n historicalPriceExpirationTime < Date.now();\n\n if (historicalPriceHasExpired === false) {\n return;\n }\n\n const selectedAccount =\n account ??\n this.messenger.call('AccountsController:getSelectedMultichainAccount');\n try {\n const historicalPricesResponse = await this.messenger.call(\n 'SnapController:handleRequest',\n {\n snapId: selectedAccount?.metadata.snap?.id as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetHistoricalPrice,\n request: {\n jsonrpc: '2.0',\n method: HandlerType.OnAssetHistoricalPrice,\n params: {\n from: asset,\n to: currentCaipCurrency,\n },\n },\n },\n );\n\n // skip state update if no historical prices are returned\n if (!historicalPricesResponse) {\n return;\n }\n\n this.update((state) => {\n state.historicalPrices = {\n ...state.historicalPrices,\n [asset]: {\n ...state.historicalPrices[asset],\n [this.#currentCurrency]: (\n historicalPricesResponse as OnAssetHistoricalPriceResponse\n )?.historicalPrice,\n },\n };\n });\n } catch {\n throw new Error(\n `Failed to fetch historical prices for asset: ${asset}`,\n );\n }\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Updates the conversion rates for new assets.\n *\n * @param accounts - The accounts to update the conversion rates for.\n * @returns A promise that resolves when the rates are updated.\n */\n async #updateAssetsRatesForNewAssets(\n accounts: {\n accountId: string;\n assets: CaipAssetType[];\n }[],\n ): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n\n return (async () => {\n if (!this.isActive) {\n return;\n }\n\n // First build a map containing all assets that need to be updated per\n // Snap ID, this will be used to batch the requests.\n const snapIdToAssets = new Map<SnapId, Set<CaipAssetType>>();\n\n for (const { accountId, assets } of accounts) {\n this.#addAssetsToSnapIdMap(\n snapIdToAssets,\n this.#getAccount(accountId),\n assets,\n );\n }\n\n this.#applyUpdatedRates(await this.#getUpdatedRatesFor(snapIdToAssets));\n })().finally(() => {\n releaseLock();\n });\n }\n\n /**\n * Get a non-EVM account from its ID.\n *\n * @param accountId - The account ID.\n * @returns The non-EVM account.\n */\n #getAccount(accountId: string): InternalAccount {\n const account: InternalAccount | undefined = this.#listAccounts().find(\n (multichainAccount) => multichainAccount.id === accountId,\n );\n\n if (!account) {\n throw new Error(`Unknown account: ${accountId}`);\n }\n\n return account;\n }\n\n /**\n * Returns the array of CAIP-19 assets for the given account ID.\n * If none are found, returns an empty array.\n *\n * @param accountId - The account ID to get the assets for.\n * @returns An array of CAIP-19 assets.\n */\n #getAssetsForAccount(accountId: string): CaipAssetType[] {\n // Always fetch fresh state - MultichainAssetsController uses Immer which creates\n // new object references on every update, so caching would become stale.\n const { accountsAssets } = this.messenger.call(\n 'MultichainAssetsController:getState',\n );\n return accountsAssets?.[accountId] ?? [];\n }\n\n /**\n * Merges the new rates into the controller's state.\n *\n * @param updatedRates - The new rates to merge.\n */\n #applyUpdatedRates(\n updatedRates: Record<\n CaipAssetType,\n UnifiedAssetConversion & { currency: CaipAssetType }\n >,\n ): void {\n if (Object.keys(updatedRates).length === 0) {\n return;\n }\n this.update((state: Draft<MultichainAssetsRatesControllerState>) => {\n state.conversionRates = {\n ...state.conversionRates,\n ...updatedRates,\n };\n });\n }\n\n /**\n * Forwards a Snap request to the SnapController.\n *\n * @param args - The request parameters.\n * @param args.snapId - The ID of the Snap.\n * @param args.handler - The handler type.\n * @param args.params - The asset conversions.\n * @returns A promise that resolves with the account rates.\n */\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetsConversionArguments>,\n ): Promise<OnAssetsConversionResponse | undefined>;\n\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetHistoricalPriceArguments>,\n ): Promise<OnAssetHistoricalPriceResponse | undefined>;\n\n async #handleSnapRequest(\n args: SnapRequestArgs<OnAssetsMarketDataArguments>,\n ): Promise<OnAssetsMarketDataResponse | undefined>;\n\n async #handleSnapRequest(args: SnapRequestArgs<unknown>): Promise<unknown> {\n const { snapId, handler, params } = args;\n try {\n return await this.messenger.call('SnapController:handleRequest', {\n snapId,\n origin: 'metamask',\n handler,\n request: {\n jsonrpc: '2.0',\n method: handler,\n params,\n },\n });\n } catch (error) {\n console.error(`Snap request failed for ${handler}:`, {\n snapId,\n handler,\n message: (error as Error).message,\n params,\n });\n return undefined;\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"MultichainAssetsRatesController.d.cts","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MultichainAssetsRatesController.d.cts","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MultichainAssetsRatesController.d.mts","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MultichainAssetsRatesController.d.mts","sourceRoot":"","sources":["../../src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _MultichainAssetsRatesController_instances, _MultichainAssetsRatesController_mutex, _MultichainAssetsRatesController_currentCurrency, _MultichainAssetsRatesController_isUnlocked, _MultichainAssetsRatesController_isNonEvmAccount, _MultichainAssetsRatesController_listMultichainAccounts, _MultichainAssetsRatesController_listAccounts, _MultichainAssetsRatesController_addAssetsToSnapIdMap, _MultichainAssetsRatesController_getCaipCurrentCurrency, _MultichainAssetsRatesController_getConversionRates, _MultichainAssetsRatesController_getMarketData, _MultichainAssetsRatesController_getUpdatedRatesFor, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets, _MultichainAssetsRatesController_getAccount, _MultichainAssetsRatesController_getAssetsForAccount, _MultichainAssetsRatesController_applyUpdatedRates, _MultichainAssetsRatesController_isAssetTracked, _MultichainAssetsRatesController_handleSnapRequest;
12
+ var _MultichainAssetsRatesController_instances, _MultichainAssetsRatesController_mutex, _MultichainAssetsRatesController_currentCurrency, _MultichainAssetsRatesController_isUnlocked, _MultichainAssetsRatesController_isNonEvmAccount, _MultichainAssetsRatesController_listMultichainAccounts, _MultichainAssetsRatesController_listAccounts, _MultichainAssetsRatesController_addAssetsToSnapIdMap, _MultichainAssetsRatesController_getCaipCurrentCurrency, _MultichainAssetsRatesController_getConversionRates, _MultichainAssetsRatesController_getMarketData, _MultichainAssetsRatesController_getUpdatedRatesFor, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets, _MultichainAssetsRatesController_getAccount, _MultichainAssetsRatesController_getAssetsForAccount, _MultichainAssetsRatesController_applyUpdatedRates, _MultichainAssetsRatesController_handleSnapRequest;
13
13
  import { isEvmAccountType } from "@metamask/keyring-api";
14
14
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
15
15
  import { HandlerType } from "@metamask/snaps-utils";
@@ -96,14 +96,12 @@ export class MultichainAssetsRatesController extends StaticIntervalPollingContro
96
96
  this.messenger.subscribe('MultichainAssetsController:accountAssetListUpdated',
97
97
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
98
98
  async ({ assets }) => {
99
- // Treat the event payload as a per-account delta so we can fetch only
100
- // newly added assets and independently clean up removed ones.
101
- const updatedAccountAssets = Object.entries(assets).map(([accountId, { added, removed }]) => ({
99
+ const newAccountAssets = Object.entries(assets).map(([accountId, { added }]) => ({
102
100
  accountId,
103
- added: [...added],
104
- removed: [...removed],
101
+ assets: [...added],
105
102
  }));
106
- await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_updateAssetsRatesForNewAssets).call(this, updatedAccountAssets);
103
+ // TODO; removed can be used in future for further cleanup
104
+ await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_updateAssetsRatesForNewAssets).call(this, newAccountAssets);
107
105
  });
108
106
  }
109
107
  /**
@@ -339,16 +337,9 @@ async function _MultichainAssetsRatesController_getUpdatedRatesFor(snapIdToAsset
339
337
  return updatedRates;
340
338
  }, _MultichainAssetsRatesController_updateAssetsRatesForNewAssets =
341
339
  /**
342
- * Reconciles cached rates after an account asset-list update event.
340
+ * Updates the conversion rates for new assets.
343
341
  *
344
- * The event payload is treated as a delta:
345
- * - `added` assets are batched by Snap and fetched for fresh rates
346
- * - `removed` assets are deleted from cached state only if they are no longer tracked by any account
347
- *
348
- * This global check is required because rate state is keyed by asset rather
349
- * than by account, so the same asset may still be shared by another account.
350
- *
351
- * @param accounts - The per-account asset deltas from the asset-list update event.
342
+ * @param accounts - The accounts to update the conversion rates for.
352
343
  * @returns A promise that resolves when the rates are updated.
353
344
  */
354
345
  async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(accounts) {
@@ -360,23 +351,10 @@ async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(ac
360
351
  // First build a map containing all assets that need to be updated per
361
352
  // Snap ID, this will be used to batch the requests.
362
353
  const snapIdToAssets = new Map();
363
- const removedAssets = new Set();
364
- for (const { accountId, added, removed } of accounts) {
365
- if (added.length !== 0) {
366
- // Only newly added assets need fresh rate requests.
367
- __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_addAssetsToSnapIdMap).call(this, snapIdToAssets, __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getAccount).call(this, accountId), added);
368
- }
369
- // Collect removed assets separately so we can decide later whether they
370
- // are truly stale or still referenced by another account.
371
- for (const asset of removed) {
372
- removedAssets.add(asset);
373
- }
354
+ for (const { accountId, assets } of accounts) {
355
+ __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_addAssetsToSnapIdMap).call(this, snapIdToAssets, __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getAccount).call(this, accountId), assets);
374
356
  }
375
- const updatedRates = await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getUpdatedRatesFor).call(this, snapIdToAssets);
376
- // Rates are stored globally by asset, so delete only assets that no
377
- // longer exist in any account asset list.
378
- const assetsToDelete = Array.from(removedAssets).filter((asset) => !__classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_isAssetTracked).call(this, asset));
379
- __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_applyUpdatedRates).call(this, updatedRates, assetsToDelete);
357
+ __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_applyUpdatedRates).call(this, await __classPrivateFieldGet(this, _MultichainAssetsRatesController_instances, "m", _MultichainAssetsRatesController_getUpdatedRatesFor).call(this, snapIdToAssets));
380
358
  })().finally(() => {
381
359
  releaseLock();
382
360
  });
@@ -391,28 +369,16 @@ async function _MultichainAssetsRatesController_updateAssetsRatesForNewAssets(ac
391
369
  // new object references on every update, so caching would become stale.
392
370
  const { accountsAssets } = this.messenger.call('MultichainAssetsController:getState');
393
371
  return accountsAssets?.[accountId] ?? [];
394
- }, _MultichainAssetsRatesController_applyUpdatedRates = function _MultichainAssetsRatesController_applyUpdatedRates(updatedRates, removedAssets = []) {
395
- if (Object.keys(updatedRates).length === 0 && removedAssets.length === 0) {
372
+ }, _MultichainAssetsRatesController_applyUpdatedRates = function _MultichainAssetsRatesController_applyUpdatedRates(updatedRates) {
373
+ if (Object.keys(updatedRates).length === 0) {
396
374
  return;
397
375
  }
398
376
  this.update((state) => {
399
- // Drop both current rates and historical prices for assets that are no
400
- // longer referenced anywhere in MultichainAssetsController state.
401
- for (const asset of removedAssets) {
402
- delete state.conversionRates[asset];
403
- delete state.historicalPrices[asset];
404
- }
405
- // Merge the freshly fetched rates after cleanup.
406
377
  state.conversionRates = {
407
378
  ...state.conversionRates,
408
379
  ...updatedRates,
409
380
  };
410
381
  });
411
- }, _MultichainAssetsRatesController_isAssetTracked = function _MultichainAssetsRatesController_isAssetTracked(asset) {
412
- const { accountsAssets } = this.messenger.call('MultichainAssetsController:getState');
413
- // Rate state is global per asset, so inspect all account asset lists before
414
- // deciding whether a removed asset should be purged from cache.
415
- return Object.values(accountsAssets ?? {}).some((accountAssets) => accountAssets.includes(asset));
416
382
  }, _MultichainAssetsRatesController_handleSnapRequest = async function _MultichainAssetsRatesController_handleSnapRequest(args) {
417
383
  const { snapId, handler, params } = args;
418
384
  try {