@metamask-previews/assets-controllers 88.0.0-preview-8d5916c4 → 88.0.0-preview-8647bc1c

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
@@ -7,8 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - Remove fallback to CryptoCompare on `CurrencyRatesController` and `TokenRatesController` ([#7167](https://github.com/MetaMask/core/pull/7167))
13
+ - Bump `@metamask/core-backend` from `^4.0.0` to `^4.1.0`
14
+
10
15
  ### Fixed
11
16
 
17
+ - Enable RPC fallback when Accounts API fails or times out in `TokenBalancesController` ([#7155](https://github.com/MetaMask/core/pull/7155))
18
+ - Add 30-second timeout protection for Accounts API balance fetching requests
19
+ - Propagate API errors to `TokenBalancesController` to trigger automatic RPC fallback
20
+ - Remove error catching that prevented RPC fetcher from processing failed chains
21
+ - Ensures native and staked balances are always fetched via RPC when API is unavailable
12
22
  - Add 30-second timeout protection for Accounts API calls in `TokenDetectionController` to prevent hanging requests ([#7106](https://github.com/MetaMask/core/pull/7106))
13
23
  - Prevents token detection from hanging indefinitely on slow or unresponsive API requests
14
24
  - Automatically falls back to RPC-based token detection when API call times out or fails
@@ -17,6 +17,7 @@ const controller_utils_1 = require("@metamask/controller-utils");
17
17
  const polling_controller_1 = require("@metamask/polling-controller");
18
18
  const async_mutex_1 = require("async-mutex");
19
19
  const crypto_compare_service_1 = require("./crypto-compare-service/index.cjs");
20
+ const codefi_v2_1 = require("./token-prices-service/codefi-v2.cjs");
20
21
  const name = 'CurrencyRateController';
21
22
  const metadata = {
22
23
  currentCurrency: {
@@ -185,7 +186,72 @@ _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateControl
185
186
  catch (error) {
186
187
  console.error('Failed to fetch exchange rates.', error);
187
188
  }
188
- // fallback to crypto compare
189
+ // fallback using spot price from token prices service
190
+ try {
191
+ // Step 1: Get all network configurations to find matching chainIds for native currencies
192
+ const networkControllerState = this.messenger.call('NetworkController:getState');
193
+ const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
194
+ // Step 2: Build a map of nativeCurrency -> chainId(s)
195
+ const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
196
+ // Find chainIds that have this native currency
197
+ const chainIds = Object.entries(networkConfigurations)
198
+ .filter(([, config]) => config.nativeCurrency.toUpperCase() ===
199
+ fetchedCurrency.toUpperCase())
200
+ .map(([chainId]) => chainId);
201
+ if (chainIds.length > 0) {
202
+ acc[nativeCurrency] = {
203
+ fetchedCurrency,
204
+ chainId: chainIds[0], // Use the first matching chainId
205
+ };
206
+ }
207
+ return acc;
208
+ }, {});
209
+ // Step 3: Fetch token prices for each chainId
210
+ const ratesResults = await Promise.allSettled(Object.entries(currencyToChainIds).map(async ([nativeCurrency, { chainId }]) => {
211
+ const nativeTokenAddress = (0, codefi_v2_1.getNativeTokenAddress)(chainId);
212
+ const tokenPrices = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchTokenPrices({
213
+ chainId,
214
+ tokenAddresses: [nativeTokenAddress],
215
+ currency: currentCurrency,
216
+ });
217
+ const tokenPrice = tokenPrices[nativeTokenAddress];
218
+ return {
219
+ nativeCurrency,
220
+ conversionDate: tokenPrice ? Date.now() / 1000 : null,
221
+ conversionRate: tokenPrice?.price ?? null,
222
+ usdConversionRate: null, // Token prices service doesn't provide USD rate in this context
223
+ };
224
+ }));
225
+ const ratesFromTokenPrices = ratesResults.map((result, index) => {
226
+ const [nativeCurrency, { chainId }] = Object.entries(currencyToChainIds)[index];
227
+ if (result.status === 'fulfilled') {
228
+ return result.value;
229
+ }
230
+ console.error(`Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`, result.reason);
231
+ return {
232
+ nativeCurrency,
233
+ conversionDate: null,
234
+ conversionRate: null,
235
+ usdConversionRate: null,
236
+ };
237
+ });
238
+ // Step 4: Convert to the expected format
239
+ const ratesFromTokenPricesService = ratesFromTokenPrices.reduce((acc, rate) => {
240
+ acc[rate.nativeCurrency] = {
241
+ conversionDate: rate.conversionDate,
242
+ conversionRate: rate.conversionRate,
243
+ usdConversionRate: rate.usdConversionRate,
244
+ };
245
+ return acc;
246
+ }, {});
247
+ // If we got any rates, return them
248
+ if (Object.keys(ratesFromTokenPricesService).length > 0) {
249
+ return ratesFromTokenPricesService;
250
+ }
251
+ }
252
+ catch (error) {
253
+ console.error('Failed to fetch exchange rates from token prices service.', error);
254
+ }
189
255
  try {
190
256
  const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(currentCurrency, [...new Set(Object.values(nativeCurrenciesToFetch))], this.includeUsdRate);
191
257
  const rates = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyRateController.cjs","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport {\n TESTNET_TICKER_SYMBOLS,\n FALL_BACK_VS_CURRENCY,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { NetworkControllerGetNetworkClientByIdAction } from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { Mutex } from 'async-mutex';\n\nimport { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from './crypto-compare-service';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\n\n/**\n * currencyRates - Object keyed by native currency\n *\n * currencyRates.conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n *\n * currencyRates.conversionRate - Conversion rate from current base asset to the current currency\n *\n * currentCurrency - Currently-active ISO 4217 currency code\n *\n * usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n currentCurrency: string;\n currencyRates: Record<\n string,\n {\n conversionDate: number | null;\n conversionRate: number | null;\n usdConversionRate: number | null;\n }\n >;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = ControllerStateChangeEvent<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerEvents = CurrencyRateStateChange;\n\nexport type GetCurrencyRateState = ControllerGetStateAction<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerActions = GetCurrencyRateState;\n\ntype AllowedActions = NetworkControllerGetNetworkClientByIdAction;\n\nexport type CurrencyRateMessenger = Messenger<\n typeof name,\n CurrencyRateControllerActions | AllowedActions,\n CurrencyRateControllerEvents\n>;\n\nconst metadata: StateMetadata<CurrencyRateState> = {\n currentCurrency: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n currencyRates: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nconst defaultState = {\n currentCurrency: 'usd',\n currencyRates: {\n ETH: {\n conversionDate: 0,\n conversionRate: 0,\n usdConversionRate: null,\n },\n },\n};\n\n/** The input to start polling for the {@link CurrencyRateController} */\ntype CurrencyRatePollingInput = {\n nativeCurrencies: string[];\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current network\n * asset to the user's preferred currency.\n */\nexport class CurrencyRateController extends StaticIntervalPollingController<CurrencyRatePollingInput>()<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private readonly mutex = new Mutex();\n\n private readonly fetchMultiExchangeRate;\n\n private readonly includeUsdRate;\n\n private readonly useExternalServices: () => boolean;\n\n readonly #tokenPricesService: AbstractTokenPricesService;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messenger.\n * @param options.state - Initial state to set on this controller.\n * @param options.useExternalServices - Feature Switch for using external services (default: true)\n * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n * @param options.tokenPricesService - An object in charge of retrieving token prices\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n useExternalServices = () => true,\n messenger,\n state,\n fetchMultiExchangeRate = defaultFetchMultiExchangeRate,\n tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\n fetchMultiExchangeRate?: typeof defaultFetchMultiExchangeRate;\n tokenPricesService: AbstractTokenPricesService;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.useExternalServices = useExternalServices;\n this.setIntervalLength(interval);\n this.fetchMultiExchangeRate = fetchMultiExchangeRate;\n this.#tokenPricesService = tokenPricesService;\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n const releaseLock = await this.mutex.acquire();\n const nativeCurrencies = Object.keys(this.state.currencyRates);\n try {\n this.update(() => {\n return {\n ...defaultState,\n currentCurrency,\n };\n });\n } finally {\n releaseLock();\n }\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateExchangeRate(nativeCurrencies);\n }\n\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n try {\n const priceApiExchangeRatesResponse =\n await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.includeUsdRate,\n cryptocurrencies: [\n ...new Set(Object.values(nativeCurrenciesToFetch)),\n ],\n });\n\n const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate =\n priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];\n\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.value\n ? Number((1 / rate?.value).toFixed(2))\n : null,\n usdConversionRate: rate?.usd\n ? Number((1 / rate?.usd).toFixed(2))\n : null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n return ratesPriceApi;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n }\n\n // fallback to crypto compare\n\n try {\n const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(\n currentCurrency,\n [...new Set(Object.values(nativeCurrenciesToFetch))],\n this.includeUsdRate,\n );\n\n const rates = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate = fetchExchangeRateResponse[fetchedCurrency.toLowerCase()];\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.[currentCurrency.toLowerCase()] ?? null,\n usdConversionRate: rate?.usd ?? null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n return rates;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n }\n }\n\n /**\n * Updates the exchange rate for the current currency and native currency pairs.\n *\n * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.\n */\n async updateExchangeRate(\n nativeCurrencies: (string | undefined)[],\n ): Promise<void> {\n if (!this.useExternalServices()) {\n return;\n }\n\n const releaseLock = await this.mutex.acquire();\n try {\n // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.\n // Map each native currency to the symbol we want to fetch for it.\n const testnetSymbols = Object.values(TESTNET_TICKER_SYMBOLS);\n const nativeCurrenciesToFetch = nativeCurrencies.reduce(\n (acc, nativeCurrency) => {\n if (!nativeCurrency) {\n return acc;\n }\n\n acc[nativeCurrency] = testnetSymbols.includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY\n : nativeCurrency;\n return acc;\n },\n {} as Record<string, string>,\n );\n\n const rates = await this.#fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch,\n );\n\n this.update((state) => {\n state.currencyRates = {\n ...state.currencyRates,\n ...rates,\n };\n });\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopAllPolling();\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @param input - The input for the poll.\n * @param input.nativeCurrencies - The native currency symbols to poll prices for.\n */\n async _executePoll({\n nativeCurrencies,\n }: CurrencyRatePollingInput): Promise<void> {\n await this.updateExchangeRate(nativeCurrencies);\n }\n}\n\nexport default CurrencyRateController;\n"]}
1
+ {"version":3,"file":"CurrencyRateController.cjs","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport {\n TESTNET_TICKER_SYMBOLS,\n FALL_BACK_VS_CURRENCY,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkConfiguration,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from './crypto-compare-service';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\nimport { getNativeTokenAddress } from './token-prices-service/codefi-v2';\n\n/**\n * currencyRates - Object keyed by native currency\n *\n * currencyRates.conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n *\n * currencyRates.conversionRate - Conversion rate from current base asset to the current currency\n *\n * currentCurrency - Currently-active ISO 4217 currency code\n *\n * usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n currentCurrency: string;\n currencyRates: Record<\n string,\n {\n conversionDate: number | null;\n conversionRate: number | null;\n usdConversionRate: number | null;\n }\n >;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = ControllerStateChangeEvent<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerEvents = CurrencyRateStateChange;\n\nexport type GetCurrencyRateState = ControllerGetStateAction<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerActions = GetCurrencyRateState;\n\ntype AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction;\n\nexport type CurrencyRateMessenger = Messenger<\n typeof name,\n CurrencyRateControllerActions | AllowedActions,\n CurrencyRateControllerEvents\n>;\n\nconst metadata: StateMetadata<CurrencyRateState> = {\n currentCurrency: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n currencyRates: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nconst defaultState = {\n currentCurrency: 'usd',\n currencyRates: {\n ETH: {\n conversionDate: 0,\n conversionRate: 0,\n usdConversionRate: null,\n },\n },\n};\n\n/** The input to start polling for the {@link CurrencyRateController} */\ntype CurrencyRatePollingInput = {\n nativeCurrencies: string[];\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current network\n * asset to the user's preferred currency.\n */\nexport class CurrencyRateController extends StaticIntervalPollingController<CurrencyRatePollingInput>()<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private readonly mutex = new Mutex();\n\n private readonly fetchMultiExchangeRate;\n\n private readonly includeUsdRate;\n\n private readonly useExternalServices: () => boolean;\n\n readonly #tokenPricesService: AbstractTokenPricesService;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messenger.\n * @param options.state - Initial state to set on this controller.\n * @param options.useExternalServices - Feature Switch for using external services (default: true)\n * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n * @param options.tokenPricesService - An object in charge of retrieving token prices\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n useExternalServices = () => true,\n messenger,\n state,\n fetchMultiExchangeRate = defaultFetchMultiExchangeRate,\n tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\n fetchMultiExchangeRate?: typeof defaultFetchMultiExchangeRate;\n tokenPricesService: AbstractTokenPricesService;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.useExternalServices = useExternalServices;\n this.setIntervalLength(interval);\n this.fetchMultiExchangeRate = fetchMultiExchangeRate;\n this.#tokenPricesService = tokenPricesService;\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n const releaseLock = await this.mutex.acquire();\n const nativeCurrencies = Object.keys(this.state.currencyRates);\n try {\n this.update(() => {\n return {\n ...defaultState,\n currentCurrency,\n };\n });\n } finally {\n releaseLock();\n }\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateExchangeRate(nativeCurrencies);\n }\n\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n try {\n const priceApiExchangeRatesResponse =\n await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.includeUsdRate,\n cryptocurrencies: [\n ...new Set(Object.values(nativeCurrenciesToFetch)),\n ],\n });\n\n const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate =\n priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];\n\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.value\n ? Number((1 / rate?.value).toFixed(2))\n : null,\n usdConversionRate: rate?.usd\n ? Number((1 / rate?.usd).toFixed(2))\n : null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n return ratesPriceApi;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n }\n\n // fallback using spot price from token prices service\n try {\n // Step 1: Get all network configurations to find matching chainIds for native currencies\n const networkControllerState = this.messenger.call(\n 'NetworkController:getState',\n );\n const networkConfigurations =\n networkControllerState.networkConfigurationsByChainId;\n\n // Step 2: Build a map of nativeCurrency -> chainId(s)\n const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n // Find chainIds that have this native currency\n const chainIds = (\n Object.entries(networkConfigurations) as [\n Hex,\n NetworkConfiguration,\n ][]\n )\n .filter(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n )\n .map(([chainId]) => chainId);\n\n if (chainIds.length > 0) {\n acc[nativeCurrency] = {\n fetchedCurrency,\n chainId: chainIds[0], // Use the first matching chainId\n };\n }\n\n return acc;\n },\n {} as Record<string, { fetchedCurrency: string; chainId: Hex }>,\n );\n\n // Step 3: Fetch token prices for each chainId\n const ratesResults = await Promise.allSettled(\n Object.entries(currencyToChainIds).map(\n async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices(\n {\n chainId,\n tokenAddresses: [nativeTokenAddress],\n currency: currentCurrency,\n },\n );\n\n const tokenPrice = tokenPrices[nativeTokenAddress];\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price ?? null,\n usdConversionRate: null, // Token prices service doesn't provide USD rate in this context\n };\n },\n ),\n );\n\n const ratesFromTokenPrices = ratesResults.map((result, index) => {\n const [nativeCurrency, { chainId }] =\n Object.entries(currencyToChainIds)[index];\n if (result.status === 'fulfilled') {\n return result.value;\n }\n console.error(\n `Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`,\n result.reason,\n );\n return {\n nativeCurrency,\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n });\n\n // Step 4: Convert to the expected format\n const ratesFromTokenPricesService = ratesFromTokenPrices.reduce(\n (acc, rate) => {\n acc[rate.nativeCurrency] = {\n conversionDate: rate.conversionDate,\n conversionRate: rate.conversionRate,\n usdConversionRate: rate.usdConversionRate,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n // If we got any rates, return them\n if (Object.keys(ratesFromTokenPricesService).length > 0) {\n return ratesFromTokenPricesService;\n }\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n }\n\n try {\n const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(\n currentCurrency,\n [...new Set(Object.values(nativeCurrenciesToFetch))],\n this.includeUsdRate,\n );\n\n const rates = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate = fetchExchangeRateResponse[fetchedCurrency.toLowerCase()];\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.[currentCurrency.toLowerCase()] ?? null,\n usdConversionRate: rate?.usd ?? null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n return rates;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n }\n }\n\n /**\n * Updates the exchange rate for the current currency and native currency pairs.\n *\n * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.\n */\n async updateExchangeRate(\n nativeCurrencies: (string | undefined)[],\n ): Promise<void> {\n if (!this.useExternalServices()) {\n return;\n }\n\n const releaseLock = await this.mutex.acquire();\n try {\n // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.\n // Map each native currency to the symbol we want to fetch for it.\n const testnetSymbols = Object.values(TESTNET_TICKER_SYMBOLS);\n const nativeCurrenciesToFetch = nativeCurrencies.reduce(\n (acc, nativeCurrency) => {\n if (!nativeCurrency) {\n return acc;\n }\n\n acc[nativeCurrency] = testnetSymbols.includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY\n : nativeCurrency;\n return acc;\n },\n {} as Record<string, string>,\n );\n\n const rates = await this.#fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch,\n );\n\n this.update((state) => {\n state.currencyRates = {\n ...state.currencyRates,\n ...rates,\n };\n });\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopAllPolling();\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @param input - The input for the poll.\n * @param input.nativeCurrencies - The native currency symbols to poll prices for.\n */\n async _executePoll({\n nativeCurrencies,\n }: CurrencyRatePollingInput): Promise<void> {\n await this.updateExchangeRate(nativeCurrencies);\n }\n}\n\nexport default CurrencyRateController;\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
2
2
  import type { Messenger } from "@metamask/messenger";
3
- import type { NetworkControllerGetNetworkClientByIdAction } from "@metamask/network-controller";
3
+ import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller";
4
4
  import { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from "./crypto-compare-service/index.cjs";
5
5
  import type { AbstractTokenPricesService } from "./token-prices-service/abstract-token-prices-service.cjs";
6
6
  /**
@@ -27,7 +27,7 @@ export type CurrencyRateStateChange = ControllerStateChangeEvent<typeof name, Cu
27
27
  export type CurrencyRateControllerEvents = CurrencyRateStateChange;
28
28
  export type GetCurrencyRateState = ControllerGetStateAction<typeof name, CurrencyRateState>;
29
29
  export type CurrencyRateControllerActions = GetCurrencyRateState;
30
- type AllowedActions = NetworkControllerGetNetworkClientByIdAction;
30
+ type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction;
31
31
  export type CurrencyRateMessenger = Messenger<typeof name, CurrencyRateControllerActions | AllowedActions, CurrencyRateControllerEvents>;
32
32
  /** The input to start polling for the {@link CurrencyRateController} */
33
33
  type CurrencyRatePollingInput = {
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyRateController.d.cts","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,2CAA2C,EAAE,qCAAqC;AAIhG,OAAO,EAAE,sBAAsB,IAAI,6BAA6B,EAAE,2CAAiC;AACnG,OAAO,KAAK,EAAE,0BAA0B,EAAE,iEAA6D;AAEvG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CACnB,MAAM,EACN;QACE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,CACF,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,IAAI,2BAA2B,CAAC;AAEtC,MAAM,MAAM,uBAAuB,GAAG,0BAA0B,CAC9D,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CACzD,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,oBAAoB,CAAC;AAEjE,KAAK,cAAc,GAAG,2CAA2C,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,IAAI,EACX,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,CAC7B,CAAC;AA4BF,wEAAwE;AACxE,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IAExC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;;OAWG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,sBAAsD,EACtD,kBAAkB,GACnB,EAAE;QACD,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,qBAAqB,CAAC;QACjC,KAAK,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnC,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;QACpC,sBAAsB,CAAC,EAAE,OAAO,6BAA6B,CAAC;QAC9D,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAcD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IAqFhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;OAIG;IACM,OAAO;IAKhB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"CurrencyRateController.d.cts","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAEhC,qCAAqC;AAKtC,OAAO,EAAE,sBAAsB,IAAI,6BAA6B,EAAE,2CAAiC;AACnG,OAAO,KAAK,EAAE,0BAA0B,EAAE,iEAA6D;AAGvG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CACnB,MAAM,EACN;QACE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,CACF,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,IAAI,2BAA2B,CAAC;AAEtC,MAAM,MAAM,uBAAuB,GAAG,0BAA0B,CAC9D,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CACzD,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,oBAAoB,CAAC;AAEjE,KAAK,cAAc,GACf,2CAA2C,GAC3C,+BAA+B,CAAC;AAEpC,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,IAAI,EACX,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,CAC7B,CAAC;AA4BF,wEAAwE;AACxE,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IAExC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;;OAWG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,sBAAsD,EACtD,kBAAkB,GACnB,EAAE;QACD,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,qBAAqB,CAAC;QACjC,KAAK,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnC,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;QACpC,sBAAsB,CAAC,EAAE,OAAO,6BAA6B,CAAC;QAC9D,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAcD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IA4LhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;OAIG;IACM,OAAO;IAKhB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
2
2
  import type { Messenger } from "@metamask/messenger";
3
- import type { NetworkControllerGetNetworkClientByIdAction } from "@metamask/network-controller";
3
+ import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller";
4
4
  import { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from "./crypto-compare-service/index.mjs";
5
5
  import type { AbstractTokenPricesService } from "./token-prices-service/abstract-token-prices-service.mjs";
6
6
  /**
@@ -27,7 +27,7 @@ export type CurrencyRateStateChange = ControllerStateChangeEvent<typeof name, Cu
27
27
  export type CurrencyRateControllerEvents = CurrencyRateStateChange;
28
28
  export type GetCurrencyRateState = ControllerGetStateAction<typeof name, CurrencyRateState>;
29
29
  export type CurrencyRateControllerActions = GetCurrencyRateState;
30
- type AllowedActions = NetworkControllerGetNetworkClientByIdAction;
30
+ type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction;
31
31
  export type CurrencyRateMessenger = Messenger<typeof name, CurrencyRateControllerActions | AllowedActions, CurrencyRateControllerEvents>;
32
32
  /** The input to start polling for the {@link CurrencyRateController} */
33
33
  type CurrencyRatePollingInput = {
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyRateController.d.mts","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,2CAA2C,EAAE,qCAAqC;AAIhG,OAAO,EAAE,sBAAsB,IAAI,6BAA6B,EAAE,2CAAiC;AACnG,OAAO,KAAK,EAAE,0BAA0B,EAAE,iEAA6D;AAEvG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CACnB,MAAM,EACN;QACE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,CACF,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,IAAI,2BAA2B,CAAC;AAEtC,MAAM,MAAM,uBAAuB,GAAG,0BAA0B,CAC9D,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CACzD,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,oBAAoB,CAAC;AAEjE,KAAK,cAAc,GAAG,2CAA2C,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,IAAI,EACX,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,CAC7B,CAAC;AA4BF,wEAAwE;AACxE,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IAExC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;;OAWG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,sBAAsD,EACtD,kBAAkB,GACnB,EAAE;QACD,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,qBAAqB,CAAC;QACjC,KAAK,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnC,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;QACpC,sBAAsB,CAAC,EAAE,OAAO,6BAA6B,CAAC;QAC9D,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAcD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IAqFhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;OAIG;IACM,OAAO;IAKhB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"CurrencyRateController.d.mts","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAEhC,qCAAqC;AAKtC,OAAO,EAAE,sBAAsB,IAAI,6BAA6B,EAAE,2CAAiC;AACnG,OAAO,KAAK,EAAE,0BAA0B,EAAE,iEAA6D;AAGvG;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CACnB,MAAM,EACN;QACE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,CACF,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,IAAI,2BAA2B,CAAC;AAEtC,MAAM,MAAM,uBAAuB,GAAG,0BAA0B,CAC9D,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CACzD,OAAO,IAAI,EACX,iBAAiB,CAClB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,oBAAoB,CAAC;AAEjE,KAAK,cAAc,GACf,2CAA2C,GAC3C,+BAA+B,CAAC;AAEpC,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,IAAI,EACX,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,CAC7B,CAAC;AA4BF,wEAAwE;AACxE,KAAK,wBAAwB,GAAG;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IACC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IAExC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;;OAWG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,sBAAsD,EACtD,kBAAkB,GACnB,EAAE;QACD,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,qBAAqB,CAAC;QACjC,KAAK,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnC,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;QACpC,sBAAsB,CAAC,EAAE,OAAO,6BAA6B,CAAC;QAC9D,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAcD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IA4LhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IA0ChB;;;;OAIG;IACM,OAAO;IAKhB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
@@ -14,6 +14,7 @@ import { TESTNET_TICKER_SYMBOLS, FALL_BACK_VS_CURRENCY } from "@metamask/control
14
14
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
15
15
  import { Mutex } from "async-mutex";
16
16
  import { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from "./crypto-compare-service/index.mjs";
17
+ import { getNativeTokenAddress } from "./token-prices-service/codefi-v2.mjs";
17
18
  const name = 'CurrencyRateController';
18
19
  const metadata = {
19
20
  currentCurrency: {
@@ -181,7 +182,72 @@ _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateControl
181
182
  catch (error) {
182
183
  console.error('Failed to fetch exchange rates.', error);
183
184
  }
184
- // fallback to crypto compare
185
+ // fallback using spot price from token prices service
186
+ try {
187
+ // Step 1: Get all network configurations to find matching chainIds for native currencies
188
+ const networkControllerState = this.messenger.call('NetworkController:getState');
189
+ const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
190
+ // Step 2: Build a map of nativeCurrency -> chainId(s)
191
+ const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
192
+ // Find chainIds that have this native currency
193
+ const chainIds = Object.entries(networkConfigurations)
194
+ .filter(([, config]) => config.nativeCurrency.toUpperCase() ===
195
+ fetchedCurrency.toUpperCase())
196
+ .map(([chainId]) => chainId);
197
+ if (chainIds.length > 0) {
198
+ acc[nativeCurrency] = {
199
+ fetchedCurrency,
200
+ chainId: chainIds[0], // Use the first matching chainId
201
+ };
202
+ }
203
+ return acc;
204
+ }, {});
205
+ // Step 3: Fetch token prices for each chainId
206
+ const ratesResults = await Promise.allSettled(Object.entries(currencyToChainIds).map(async ([nativeCurrency, { chainId }]) => {
207
+ const nativeTokenAddress = getNativeTokenAddress(chainId);
208
+ const tokenPrices = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchTokenPrices({
209
+ chainId,
210
+ tokenAddresses: [nativeTokenAddress],
211
+ currency: currentCurrency,
212
+ });
213
+ const tokenPrice = tokenPrices[nativeTokenAddress];
214
+ return {
215
+ nativeCurrency,
216
+ conversionDate: tokenPrice ? Date.now() / 1000 : null,
217
+ conversionRate: tokenPrice?.price ?? null,
218
+ usdConversionRate: null, // Token prices service doesn't provide USD rate in this context
219
+ };
220
+ }));
221
+ const ratesFromTokenPrices = ratesResults.map((result, index) => {
222
+ const [nativeCurrency, { chainId }] = Object.entries(currencyToChainIds)[index];
223
+ if (result.status === 'fulfilled') {
224
+ return result.value;
225
+ }
226
+ console.error(`Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`, result.reason);
227
+ return {
228
+ nativeCurrency,
229
+ conversionDate: null,
230
+ conversionRate: null,
231
+ usdConversionRate: null,
232
+ };
233
+ });
234
+ // Step 4: Convert to the expected format
235
+ const ratesFromTokenPricesService = ratesFromTokenPrices.reduce((acc, rate) => {
236
+ acc[rate.nativeCurrency] = {
237
+ conversionDate: rate.conversionDate,
238
+ conversionRate: rate.conversionRate,
239
+ usdConversionRate: rate.usdConversionRate,
240
+ };
241
+ return acc;
242
+ }, {});
243
+ // If we got any rates, return them
244
+ if (Object.keys(ratesFromTokenPricesService).length > 0) {
245
+ return ratesFromTokenPricesService;
246
+ }
247
+ }
248
+ catch (error) {
249
+ console.error('Failed to fetch exchange rates from token prices service.', error);
250
+ }
185
251
  try {
186
252
  const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(currentCurrency, [...new Set(Object.values(nativeCurrenciesToFetch))], this.includeUsdRate);
187
253
  const rates = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyRateController.mjs","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport {\n TESTNET_TICKER_SYMBOLS,\n FALL_BACK_VS_CURRENCY,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { NetworkControllerGetNetworkClientByIdAction } from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { Mutex } from 'async-mutex';\n\nimport { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from './crypto-compare-service';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\n\n/**\n * currencyRates - Object keyed by native currency\n *\n * currencyRates.conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n *\n * currencyRates.conversionRate - Conversion rate from current base asset to the current currency\n *\n * currentCurrency - Currently-active ISO 4217 currency code\n *\n * usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n currentCurrency: string;\n currencyRates: Record<\n string,\n {\n conversionDate: number | null;\n conversionRate: number | null;\n usdConversionRate: number | null;\n }\n >;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = ControllerStateChangeEvent<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerEvents = CurrencyRateStateChange;\n\nexport type GetCurrencyRateState = ControllerGetStateAction<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerActions = GetCurrencyRateState;\n\ntype AllowedActions = NetworkControllerGetNetworkClientByIdAction;\n\nexport type CurrencyRateMessenger = Messenger<\n typeof name,\n CurrencyRateControllerActions | AllowedActions,\n CurrencyRateControllerEvents\n>;\n\nconst metadata: StateMetadata<CurrencyRateState> = {\n currentCurrency: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n currencyRates: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nconst defaultState = {\n currentCurrency: 'usd',\n currencyRates: {\n ETH: {\n conversionDate: 0,\n conversionRate: 0,\n usdConversionRate: null,\n },\n },\n};\n\n/** The input to start polling for the {@link CurrencyRateController} */\ntype CurrencyRatePollingInput = {\n nativeCurrencies: string[];\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current network\n * asset to the user's preferred currency.\n */\nexport class CurrencyRateController extends StaticIntervalPollingController<CurrencyRatePollingInput>()<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private readonly mutex = new Mutex();\n\n private readonly fetchMultiExchangeRate;\n\n private readonly includeUsdRate;\n\n private readonly useExternalServices: () => boolean;\n\n readonly #tokenPricesService: AbstractTokenPricesService;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messenger.\n * @param options.state - Initial state to set on this controller.\n * @param options.useExternalServices - Feature Switch for using external services (default: true)\n * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n * @param options.tokenPricesService - An object in charge of retrieving token prices\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n useExternalServices = () => true,\n messenger,\n state,\n fetchMultiExchangeRate = defaultFetchMultiExchangeRate,\n tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\n fetchMultiExchangeRate?: typeof defaultFetchMultiExchangeRate;\n tokenPricesService: AbstractTokenPricesService;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.useExternalServices = useExternalServices;\n this.setIntervalLength(interval);\n this.fetchMultiExchangeRate = fetchMultiExchangeRate;\n this.#tokenPricesService = tokenPricesService;\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n const releaseLock = await this.mutex.acquire();\n const nativeCurrencies = Object.keys(this.state.currencyRates);\n try {\n this.update(() => {\n return {\n ...defaultState,\n currentCurrency,\n };\n });\n } finally {\n releaseLock();\n }\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateExchangeRate(nativeCurrencies);\n }\n\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n try {\n const priceApiExchangeRatesResponse =\n await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.includeUsdRate,\n cryptocurrencies: [\n ...new Set(Object.values(nativeCurrenciesToFetch)),\n ],\n });\n\n const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate =\n priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];\n\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.value\n ? Number((1 / rate?.value).toFixed(2))\n : null,\n usdConversionRate: rate?.usd\n ? Number((1 / rate?.usd).toFixed(2))\n : null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n return ratesPriceApi;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n }\n\n // fallback to crypto compare\n\n try {\n const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(\n currentCurrency,\n [...new Set(Object.values(nativeCurrenciesToFetch))],\n this.includeUsdRate,\n );\n\n const rates = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate = fetchExchangeRateResponse[fetchedCurrency.toLowerCase()];\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.[currentCurrency.toLowerCase()] ?? null,\n usdConversionRate: rate?.usd ?? null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n return rates;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n }\n }\n\n /**\n * Updates the exchange rate for the current currency and native currency pairs.\n *\n * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.\n */\n async updateExchangeRate(\n nativeCurrencies: (string | undefined)[],\n ): Promise<void> {\n if (!this.useExternalServices()) {\n return;\n }\n\n const releaseLock = await this.mutex.acquire();\n try {\n // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.\n // Map each native currency to the symbol we want to fetch for it.\n const testnetSymbols = Object.values(TESTNET_TICKER_SYMBOLS);\n const nativeCurrenciesToFetch = nativeCurrencies.reduce(\n (acc, nativeCurrency) => {\n if (!nativeCurrency) {\n return acc;\n }\n\n acc[nativeCurrency] = testnetSymbols.includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY\n : nativeCurrency;\n return acc;\n },\n {} as Record<string, string>,\n );\n\n const rates = await this.#fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch,\n );\n\n this.update((state) => {\n state.currencyRates = {\n ...state.currencyRates,\n ...rates,\n };\n });\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopAllPolling();\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @param input - The input for the poll.\n * @param input.nativeCurrencies - The native currency symbols to poll prices for.\n */\n async _executePoll({\n nativeCurrencies,\n }: CurrencyRatePollingInput): Promise<void> {\n await this.updateExchangeRate(nativeCurrencies);\n }\n}\n\nexport default CurrencyRateController;\n"]}
1
+ {"version":3,"file":"CurrencyRateController.mjs","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport {\n TESTNET_TICKER_SYMBOLS,\n FALL_BACK_VS_CURRENCY,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkConfiguration,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport { fetchMultiExchangeRate as defaultFetchMultiExchangeRate } from './crypto-compare-service';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\nimport { getNativeTokenAddress } from './token-prices-service/codefi-v2';\n\n/**\n * currencyRates - Object keyed by native currency\n *\n * currencyRates.conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n *\n * currencyRates.conversionRate - Conversion rate from current base asset to the current currency\n *\n * currentCurrency - Currently-active ISO 4217 currency code\n *\n * usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n currentCurrency: string;\n currencyRates: Record<\n string,\n {\n conversionDate: number | null;\n conversionRate: number | null;\n usdConversionRate: number | null;\n }\n >;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = ControllerStateChangeEvent<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerEvents = CurrencyRateStateChange;\n\nexport type GetCurrencyRateState = ControllerGetStateAction<\n typeof name,\n CurrencyRateState\n>;\n\nexport type CurrencyRateControllerActions = GetCurrencyRateState;\n\ntype AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction;\n\nexport type CurrencyRateMessenger = Messenger<\n typeof name,\n CurrencyRateControllerActions | AllowedActions,\n CurrencyRateControllerEvents\n>;\n\nconst metadata: StateMetadata<CurrencyRateState> = {\n currentCurrency: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n currencyRates: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nconst defaultState = {\n currentCurrency: 'usd',\n currencyRates: {\n ETH: {\n conversionDate: 0,\n conversionRate: 0,\n usdConversionRate: null,\n },\n },\n};\n\n/** The input to start polling for the {@link CurrencyRateController} */\ntype CurrencyRatePollingInput = {\n nativeCurrencies: string[];\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current network\n * asset to the user's preferred currency.\n */\nexport class CurrencyRateController extends StaticIntervalPollingController<CurrencyRatePollingInput>()<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private readonly mutex = new Mutex();\n\n private readonly fetchMultiExchangeRate;\n\n private readonly includeUsdRate;\n\n private readonly useExternalServices: () => boolean;\n\n readonly #tokenPricesService: AbstractTokenPricesService;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messenger.\n * @param options.state - Initial state to set on this controller.\n * @param options.useExternalServices - Feature Switch for using external services (default: true)\n * @param options.fetchMultiExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n * @param options.tokenPricesService - An object in charge of retrieving token prices\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n useExternalServices = () => true,\n messenger,\n state,\n fetchMultiExchangeRate = defaultFetchMultiExchangeRate,\n tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\n fetchMultiExchangeRate?: typeof defaultFetchMultiExchangeRate;\n tokenPricesService: AbstractTokenPricesService;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.useExternalServices = useExternalServices;\n this.setIntervalLength(interval);\n this.fetchMultiExchangeRate = fetchMultiExchangeRate;\n this.#tokenPricesService = tokenPricesService;\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n const releaseLock = await this.mutex.acquire();\n const nativeCurrencies = Object.keys(this.state.currencyRates);\n try {\n this.update(() => {\n return {\n ...defaultState,\n currentCurrency,\n };\n });\n } finally {\n releaseLock();\n }\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateExchangeRate(nativeCurrencies);\n }\n\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n try {\n const priceApiExchangeRatesResponse =\n await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.includeUsdRate,\n cryptocurrencies: [\n ...new Set(Object.values(nativeCurrenciesToFetch)),\n ],\n });\n\n const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate =\n priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];\n\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.value\n ? Number((1 / rate?.value).toFixed(2))\n : null,\n usdConversionRate: rate?.usd\n ? Number((1 / rate?.usd).toFixed(2))\n : null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n return ratesPriceApi;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n }\n\n // fallback using spot price from token prices service\n try {\n // Step 1: Get all network configurations to find matching chainIds for native currencies\n const networkControllerState = this.messenger.call(\n 'NetworkController:getState',\n );\n const networkConfigurations =\n networkControllerState.networkConfigurationsByChainId;\n\n // Step 2: Build a map of nativeCurrency -> chainId(s)\n const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n // Find chainIds that have this native currency\n const chainIds = (\n Object.entries(networkConfigurations) as [\n Hex,\n NetworkConfiguration,\n ][]\n )\n .filter(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n )\n .map(([chainId]) => chainId);\n\n if (chainIds.length > 0) {\n acc[nativeCurrency] = {\n fetchedCurrency,\n chainId: chainIds[0], // Use the first matching chainId\n };\n }\n\n return acc;\n },\n {} as Record<string, { fetchedCurrency: string; chainId: Hex }>,\n );\n\n // Step 3: Fetch token prices for each chainId\n const ratesResults = await Promise.allSettled(\n Object.entries(currencyToChainIds).map(\n async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices(\n {\n chainId,\n tokenAddresses: [nativeTokenAddress],\n currency: currentCurrency,\n },\n );\n\n const tokenPrice = tokenPrices[nativeTokenAddress];\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price ?? null,\n usdConversionRate: null, // Token prices service doesn't provide USD rate in this context\n };\n },\n ),\n );\n\n const ratesFromTokenPrices = ratesResults.map((result, index) => {\n const [nativeCurrency, { chainId }] =\n Object.entries(currencyToChainIds)[index];\n if (result.status === 'fulfilled') {\n return result.value;\n }\n console.error(\n `Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`,\n result.reason,\n );\n return {\n nativeCurrency,\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n });\n\n // Step 4: Convert to the expected format\n const ratesFromTokenPricesService = ratesFromTokenPrices.reduce(\n (acc, rate) => {\n acc[rate.nativeCurrency] = {\n conversionDate: rate.conversionDate,\n conversionRate: rate.conversionRate,\n usdConversionRate: rate.usdConversionRate,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n // If we got any rates, return them\n if (Object.keys(ratesFromTokenPricesService).length > 0) {\n return ratesFromTokenPricesService;\n }\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n }\n\n try {\n const fetchExchangeRateResponse = await this.fetchMultiExchangeRate(\n currentCurrency,\n [...new Set(Object.values(nativeCurrenciesToFetch))],\n this.includeUsdRate,\n );\n\n const rates = Object.entries(nativeCurrenciesToFetch).reduce(\n (acc, [nativeCurrency, fetchedCurrency]) => {\n const rate = fetchExchangeRateResponse[fetchedCurrency.toLowerCase()];\n acc[nativeCurrency] = {\n conversionDate: rate !== undefined ? Date.now() / 1000 : null,\n conversionRate: rate?.[currentCurrency.toLowerCase()] ?? null,\n usdConversionRate: rate?.usd ?? null,\n };\n return acc;\n },\n {} as CurrencyRateState['currencyRates'],\n );\n\n return rates;\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n }\n }\n\n /**\n * Updates the exchange rate for the current currency and native currency pairs.\n *\n * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.\n */\n async updateExchangeRate(\n nativeCurrencies: (string | undefined)[],\n ): Promise<void> {\n if (!this.useExternalServices()) {\n return;\n }\n\n const releaseLock = await this.mutex.acquire();\n try {\n // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.\n // Map each native currency to the symbol we want to fetch for it.\n const testnetSymbols = Object.values(TESTNET_TICKER_SYMBOLS);\n const nativeCurrenciesToFetch = nativeCurrencies.reduce(\n (acc, nativeCurrency) => {\n if (!nativeCurrency) {\n return acc;\n }\n\n acc[nativeCurrency] = testnetSymbols.includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY\n : nativeCurrency;\n return acc;\n },\n {} as Record<string, string>,\n );\n\n const rates = await this.#fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch,\n );\n\n this.update((state) => {\n state.currencyRates = {\n ...state.currencyRates,\n ...rates,\n };\n });\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n throw error;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopAllPolling();\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @param input - The input for the poll.\n * @param input.nativeCurrencies - The native currency symbols to poll prices for.\n */\n async _executePoll({\n nativeCurrencies,\n }: CurrencyRatePollingInput): Promise<void> {\n await this.updateExchangeRate(nativeCurrencies);\n }\n}\n\nexport default CurrencyRateController;\n"]}
@@ -18,7 +18,6 @@ const polling_controller_1 = require("@metamask/polling-controller");
18
18
  const utils_1 = require("@metamask/utils");
19
19
  const lodash_1 = require("lodash");
20
20
  const assetsUtil_1 = require("./assetsUtil.cjs");
21
- const crypto_compare_service_1 = require("./crypto-compare-service/index.cjs");
22
21
  const codefi_v2_1 = require("./token-prices-service/codefi-v2.cjs");
23
22
  const DEFAULT_INTERVAL = 180000;
24
23
  var PollState;
@@ -30,31 +29,6 @@ var PollState;
30
29
  * The name of the {@link TokenRatesController}.
31
30
  */
32
31
  exports.controllerName = 'TokenRatesController';
33
- /**
34
- * Uses the CryptoCompare API to fetch the exchange rate between one currency
35
- * and another, i.e., the multiplier to apply the amount of one currency in
36
- * order to convert it to another.
37
- *
38
- * @param args - The arguments to this function.
39
- * @param args.from - The currency to convert from.
40
- * @param args.to - The currency to convert to.
41
- * @returns The exchange rate between `fromCurrency` to `toCurrency` if one
42
- * exists, or null if one does not.
43
- */
44
- async function getCurrencyConversionRate({ from, to, }) {
45
- const includeUSDRate = false;
46
- try {
47
- const result = await (0, crypto_compare_service_1.fetchExchangeRate)(to, from, includeUSDRate);
48
- return result.conversionRate;
49
- }
50
- catch (error) {
51
- if (error instanceof Error &&
52
- error.message.includes('market does not exist for this coin pair')) {
53
- return null;
54
- }
55
- throw error;
56
- }
57
- }
58
32
  const tokenRatesControllerMetadata = {
59
33
  marketData: {
60
34
  includeInStateLogs: false,
@@ -458,43 +432,51 @@ async function _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeC
458
432
  * native currency.
459
433
  */
460
434
  async function _TokenRatesController_fetchAndMapExchangeRatesForUnsupportedNativeCurrency({ chainId, tokenAddresses, nativeCurrency, }) {
461
- const [contractExchangeInformations, fallbackCurrencyToNativeCurrencyConversionRate,] = await Promise.all([
462
- __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency).call(this, {
463
- tokenAddresses,
464
- chainId,
465
- nativeCurrency: controller_utils_1.FALL_BACK_VS_CURRENCY,
466
- }),
467
- getCurrencyConversionRate({
468
- from: controller_utils_1.FALL_BACK_VS_CURRENCY,
469
- to: nativeCurrency,
470
- }),
471
- ]);
472
- if (fallbackCurrencyToNativeCurrencyConversionRate === null) {
435
+ const nativeTokenAddress = (0, codefi_v2_1.getNativeTokenAddress)(chainId);
436
+ // Step -1: First fetch native token priced in USD
437
+ const nativeTokenPriceMap = await __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency).call(this, {
438
+ tokenAddresses: [], // special-case: returns only native token
439
+ chainId,
440
+ nativeCurrency: 'usd',
441
+ });
442
+ // Step -2: Then fetch all tracked tokens priced in USD
443
+ const tokenPricesInUSD = await __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency).call(this, {
444
+ tokenAddresses,
445
+ chainId,
446
+ nativeCurrency: 'usd',
447
+ });
448
+ const nativeTokenInfo = nativeTokenPriceMap[nativeTokenAddress];
449
+ const nativeTokenPriceInUSD = nativeTokenInfo?.price;
450
+ if (!nativeTokenPriceInUSD || nativeTokenPriceInUSD === 0) {
451
+ // If we can't price the native token in the fallback currency,
452
+ // we can't safely convert; return empty so callers know there is no data.
473
453
  return {};
474
454
  }
475
- // Converts the price in the fallback currency to the native currency
476
- const convertFallbackToNative = (value) => value !== undefined && value !== null
477
- ? value * fallbackCurrencyToNativeCurrencyConversionRate
455
+ // Step -3: Convert USD prices to native currency
456
+ // Formula: price_in_native = token_usd / native_usd
457
+ const convertUSDToNative = (valueInUSD) => valueInUSD !== undefined && valueInUSD !== null
458
+ ? valueInUSD / nativeTokenPriceInUSD
478
459
  : undefined;
479
- const updatedContractExchangeRates = Object.entries(contractExchangeInformations).reduce((acc, [tokenAddress, token]) => {
460
+ // Step -4 & -5: Apply conversion to all token fields and return
461
+ const tokenPricesInNative = Object.entries(tokenPricesInUSD).reduce((acc, [tokenAddress, tokenData]) => {
480
462
  acc = {
481
463
  ...acc,
482
464
  [tokenAddress]: {
483
- ...token,
465
+ ...tokenData,
484
466
  currency: nativeCurrency,
485
- price: convertFallbackToNative(token.price),
486
- marketCap: convertFallbackToNative(token.marketCap),
487
- allTimeHigh: convertFallbackToNative(token.allTimeHigh),
488
- allTimeLow: convertFallbackToNative(token.allTimeLow),
489
- totalVolume: convertFallbackToNative(token.totalVolume),
490
- high1d: convertFallbackToNative(token.high1d),
491
- low1d: convertFallbackToNative(token.low1d),
492
- dilutedMarketCap: convertFallbackToNative(token.dilutedMarketCap),
467
+ price: convertUSDToNative(tokenData.price),
468
+ marketCap: convertUSDToNative(tokenData.marketCap),
469
+ allTimeHigh: convertUSDToNative(tokenData.allTimeHigh),
470
+ allTimeLow: convertUSDToNative(tokenData.allTimeLow),
471
+ totalVolume: convertUSDToNative(tokenData.totalVolume),
472
+ high1d: convertUSDToNative(tokenData.high1d),
473
+ low1d: convertUSDToNative(tokenData.low1d),
474
+ dilutedMarketCap: convertUSDToNative(tokenData.dilutedMarketCap),
493
475
  },
494
476
  };
495
477
  return acc;
496
478
  }, {});
497
- return updatedContractExchangeRates;
479
+ return tokenPricesInNative;
498
480
  };
499
481
  exports.default = TokenRatesController;
500
482
  //# sourceMappingURL=TokenRatesController.cjs.map