@metamask-previews/assets-controllers 95.1.0-preview-3e207569 → 95.2.0-preview-85ecacba

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,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [95.2.0]
11
+
10
12
  ### Added
11
13
 
12
14
  - Add RWA data to asset fetching endpoints ([#7548](https://github.com/MetaMask/core/pull/7548))
@@ -20,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
22
  - Bump `@metamask/polling-controller` from `^16.0.0` to `^16.0.1` ([#7604](https://github.com/MetaMask/core/pull/7604))
21
23
  - Update Plasma (0x2611) mapping to eip155:9745/erc20:0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for XPL ([#7601](https://github.com/MetaMask/core/pull/7601))
22
24
  - `TokensController.watchAsset` now supports optional origin/page metadata and safely falls back for empty origins to avoid rejected approvals ([#7612](https://github.com/MetaMask/core/pull/7612))
25
+ - Refactor `CurrencyRateController` exchange rate fetching with improved fallback logic ([#7606](https://github.com/MetaMask/core/pull/7606))
26
+ - Improved partial success handling: when some currencies succeed via the Price API and others fail, only failed currencies trigger the fallback mechanism
27
+ - Extracted helper methods for better code organization and testability
23
28
 
24
29
  ## [95.1.0]
25
30
 
@@ -2506,7 +2511,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2506
2511
 
2507
2512
  - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845))
2508
2513
 
2509
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@95.1.0...HEAD
2514
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@95.2.0...HEAD
2515
+ [95.2.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@95.1.0...@metamask/assets-controllers@95.2.0
2510
2516
  [95.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@95.0.0...@metamask/assets-controllers@95.1.0
2511
2517
  [95.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@94.1.0...@metamask/assets-controllers@95.0.0
2512
2518
  [94.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@94.0.0...@metamask/assets-controllers@94.1.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 _CurrencyRateController_instances, _CurrencyRateController_tokenPricesService, _CurrencyRateController_fetchExchangeRatesWithFallback;
13
+ var _CurrencyRateController_instances, _CurrencyRateController_mutex, _CurrencyRateController_includeUsdRate, _CurrencyRateController_useExternalServices, _CurrencyRateController_tokenPricesService, _CurrencyRateController_fetchRatesFromPriceApi, _CurrencyRateController_fetchRatesFromTokenPricesService, _CurrencyRateController_createNullRatesForCurrencies, _CurrencyRateController_fetchExchangeRatesWithFallback;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.CurrencyRateController = void 0;
16
16
  const controller_utils_1 = require("@metamask/controller-utils");
@@ -67,10 +67,12 @@ class CurrencyRateController extends (0, polling_controller_1.StaticIntervalPoll
67
67
  state: { ...defaultState, ...state },
68
68
  });
69
69
  _CurrencyRateController_instances.add(this);
70
- this.mutex = new async_mutex_1.Mutex();
70
+ _CurrencyRateController_mutex.set(this, new async_mutex_1.Mutex());
71
+ _CurrencyRateController_includeUsdRate.set(this, void 0);
72
+ _CurrencyRateController_useExternalServices.set(this, void 0);
71
73
  _CurrencyRateController_tokenPricesService.set(this, void 0);
72
- this.includeUsdRate = includeUsdRate;
73
- this.useExternalServices = useExternalServices;
74
+ __classPrivateFieldSet(this, _CurrencyRateController_includeUsdRate, includeUsdRate, "f");
75
+ __classPrivateFieldSet(this, _CurrencyRateController_useExternalServices, useExternalServices, "f");
74
76
  this.setIntervalLength(interval);
75
77
  __classPrivateFieldSet(this, _CurrencyRateController_tokenPricesService, tokenPricesService, "f");
76
78
  }
@@ -80,7 +82,7 @@ class CurrencyRateController extends (0, polling_controller_1.StaticIntervalPoll
80
82
  * @param currentCurrency - ISO 4217 currency code.
81
83
  */
82
84
  async setCurrentCurrency(currentCurrency) {
83
- const releaseLock = await this.mutex.acquire();
85
+ const releaseLock = await __classPrivateFieldGet(this, _CurrencyRateController_mutex, "f").acquire();
84
86
  const nativeCurrencies = Object.keys(this.state.currencyRates);
85
87
  try {
86
88
  this.update(() => {
@@ -103,10 +105,10 @@ class CurrencyRateController extends (0, polling_controller_1.StaticIntervalPoll
103
105
  * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.
104
106
  */
105
107
  async updateExchangeRate(nativeCurrencies) {
106
- if (!this.useExternalServices()) {
108
+ if (!__classPrivateFieldGet(this, _CurrencyRateController_useExternalServices, "f").call(this)) {
107
109
  return;
108
110
  }
109
- const releaseLock = await this.mutex.acquire();
111
+ const releaseLock = await __classPrivateFieldGet(this, _CurrencyRateController_mutex, "f").acquire();
110
112
  try {
111
113
  // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.
112
114
  // Map each native currency to the symbol we want to fetch for it.
@@ -156,57 +158,76 @@ class CurrencyRateController extends (0, polling_controller_1.StaticIntervalPoll
156
158
  }
157
159
  }
158
160
  exports.CurrencyRateController = CurrencyRateController;
159
- _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateController_instances = new WeakSet(), _CurrencyRateController_fetchExchangeRatesWithFallback = async function _CurrencyRateController_fetchExchangeRatesWithFallback(nativeCurrenciesToFetch) {
160
- const { currentCurrency } = this.state;
161
+ _CurrencyRateController_mutex = new WeakMap(), _CurrencyRateController_includeUsdRate = new WeakMap(), _CurrencyRateController_useExternalServices = new WeakMap(), _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateController_instances = new WeakSet(), _CurrencyRateController_fetchRatesFromPriceApi =
162
+ /**
163
+ * Attempts to fetch exchange rates from the primary Price API.
164
+ *
165
+ * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.
166
+ * @param currentCurrency - The current fiat currency to get rates for.
167
+ * @returns Object containing successful rates and currencies that failed.
168
+ */
169
+ async function _CurrencyRateController_fetchRatesFromPriceApi(nativeCurrenciesToFetch, currentCurrency) {
170
+ const rates = {};
171
+ let failedCurrencies = {};
161
172
  try {
162
- const priceApiExchangeRatesResponse = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
173
+ const response = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
163
174
  baseCurrency: currentCurrency,
164
- includeUsdRate: this.includeUsdRate,
165
- cryptocurrencies: [
166
- ...new Set(Object.values(nativeCurrenciesToFetch)),
167
- ],
175
+ includeUsdRate: __classPrivateFieldGet(this, _CurrencyRateController_includeUsdRate, "f"),
176
+ cryptocurrencies: [...new Set(Object.values(nativeCurrenciesToFetch))],
177
+ });
178
+ Object.entries(nativeCurrenciesToFetch).forEach(([nativeCurrency, fetchedCurrency]) => {
179
+ const rate = response[fetchedCurrency.toLowerCase()];
180
+ if (rate?.value) {
181
+ rates[nativeCurrency] = {
182
+ conversionDate: Date.now() / 1000,
183
+ conversionRate: boundedPrecisionNumber(1 / rate.value),
184
+ usdConversionRate: rate?.usd
185
+ ? boundedPrecisionNumber(1 / rate.usd)
186
+ : null,
187
+ };
188
+ }
189
+ else {
190
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
191
+ }
168
192
  });
169
- const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
170
- const rate = priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];
171
- acc[nativeCurrency] = {
172
- conversionDate: rate !== undefined ? Date.now() / 1000 : null,
173
- conversionRate: rate?.value
174
- ? boundedPrecisionNumber(1 / rate.value)
175
- : null,
176
- usdConversionRate: rate?.usd
177
- ? boundedPrecisionNumber(1 / rate.usd)
178
- : null,
179
- };
180
- return acc;
181
- }, {});
182
- return ratesPriceApi;
183
193
  }
184
194
  catch (error) {
185
195
  console.error('Failed to fetch exchange rates.', error);
196
+ failedCurrencies = { ...nativeCurrenciesToFetch };
186
197
  }
187
- // fallback using spot price from token prices service
198
+ return { rates, failedCurrencies };
199
+ }, _CurrencyRateController_fetchRatesFromTokenPricesService =
200
+ /**
201
+ * Fetches exchange rates from the token prices service as a fallback.
202
+ * This method is designed to never throw - all errors are handled internally
203
+ * and result in currencies being marked as failed.
204
+ *
205
+ * @param currenciesToFetch - Map of native currencies that need fallback fetching.
206
+ * @param currentCurrency - The current fiat currency to get rates for.
207
+ * @returns Object containing successful rates and currencies that failed.
208
+ */
209
+ async function _CurrencyRateController_fetchRatesFromTokenPricesService(currenciesToFetch, currentCurrency) {
188
210
  try {
189
- // Step 1: Get all network configurations to find matching chainIds for native currencies
211
+ const rates = {};
212
+ const failedCurrencies = {};
190
213
  const networkControllerState = this.messenger.call('NetworkController:getState');
191
214
  const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
192
- // Step 2: Build a map of nativeCurrency -> chainId(s)
193
- const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
194
- // Find the first chainId that has this native currency
215
+ // Build a map of nativeCurrency -> chainId for currencies to fetch
216
+ const currencyToChainIds = Object.entries(currenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
195
217
  const matchingEntry = Object.entries(networkConfigurations).find(([, config]) => config.nativeCurrency.toUpperCase() ===
196
218
  fetchedCurrency.toUpperCase());
197
219
  if (matchingEntry) {
198
- acc[nativeCurrency] = {
199
- fetchedCurrency,
200
- chainId: matchingEntry[0],
201
- };
220
+ acc[nativeCurrency] = { fetchedCurrency, chainId: matchingEntry[0] };
221
+ }
222
+ else {
223
+ // No matching network configuration - mark as failed
224
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
202
225
  }
203
226
  return acc;
204
227
  }, {});
205
- // Step 3: Fetch token prices for each chainId
206
228
  const currencyToChainIdsEntries = Object.entries(currencyToChainIds);
207
229
  const ratesResults = await Promise.allSettled(currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {
208
230
  const nativeTokenAddress = (0, codefi_v2_1.getNativeTokenAddress)(chainId);
209
- // Pass empty array as fetchTokenPrices automatically includes the native token address
210
231
  const tokenPrices = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchTokenPrices({
211
232
  assets: [{ chainId, tokenAddress: nativeTokenAddress }],
212
233
  currency: currentCurrency,
@@ -219,49 +240,67 @@ _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateControl
219
240
  conversionRate: tokenPrice?.price
220
241
  ? boundedPrecisionNumber(tokenPrice.price)
221
242
  : null,
222
- usdConversionRate: null, // Token prices service doesn't provide USD rate in this context
243
+ usdConversionRate: null,
223
244
  };
224
245
  }));
225
- const ratesFromTokenPrices = ratesResults.map((result, index) => {
226
- const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[index];
227
- if (result.status === 'fulfilled') {
228
- return result.value;
246
+ ratesResults.forEach((result, index) => {
247
+ const [nativeCurrency, { fetchedCurrency, chainId }] = currencyToChainIdsEntries[index];
248
+ if (result.status === 'fulfilled' && result.value.conversionRate) {
249
+ rates[nativeCurrency] = {
250
+ conversionDate: result.value.conversionDate,
251
+ conversionRate: result.value.conversionRate,
252
+ usdConversionRate: result.value.usdConversionRate,
253
+ };
254
+ }
255
+ else {
256
+ if (result.status === 'rejected') {
257
+ console.error(`Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`, result.reason);
258
+ }
259
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
229
260
  }
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
261
  });
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
- ? boundedPrecisionNumber(rate.conversionRate)
244
- : null,
245
- usdConversionRate: rate.usdConversionRate
246
- ? boundedPrecisionNumber(rate.usdConversionRate)
247
- : null,
248
- };
249
- return acc;
250
- }, {});
251
- return ratesFromTokenPricesService;
262
+ return { rates, failedCurrencies };
252
263
  }
253
264
  catch (error) {
254
265
  console.error('Failed to fetch exchange rates from token prices service.', error);
255
- // Return null state for all requested currencies
256
- return Object.keys(nativeCurrenciesToFetch).reduce((acc, nativeCurrency) => {
257
- acc[nativeCurrency] = {
258
- conversionDate: null,
259
- conversionRate: null,
260
- usdConversionRate: null,
261
- };
262
- return acc;
263
- }, {});
266
+ // Return all currencies as failed
267
+ return { rates: {}, failedCurrencies: { ...currenciesToFetch } };
268
+ }
269
+ }, _CurrencyRateController_createNullRatesForCurrencies = function _CurrencyRateController_createNullRatesForCurrencies(currencies) {
270
+ return currencies.reduce((acc, nativeCurrency) => {
271
+ acc[nativeCurrency] = {
272
+ conversionDate: null,
273
+ conversionRate: null,
274
+ usdConversionRate: null,
275
+ };
276
+ return acc;
277
+ }, {});
278
+ }, _CurrencyRateController_fetchExchangeRatesWithFallback =
279
+ /**
280
+ * Fetches exchange rates with fallback logic.
281
+ * First tries the Price API, then falls back to token prices service for any failed currencies.
282
+ *
283
+ * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.
284
+ * @returns Exchange rates for all requested currencies.
285
+ */
286
+ async function _CurrencyRateController_fetchExchangeRatesWithFallback(nativeCurrenciesToFetch) {
287
+ const { currentCurrency } = this.state;
288
+ // Step 1: Try the Price API exchange rates first
289
+ const { rates: ratesPriceApi, failedCurrencies: failedCurrenciesFromPriceApi, } = await __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_fetchRatesFromPriceApi).call(this, nativeCurrenciesToFetch, currentCurrency);
290
+ // Step 2: If all currencies succeeded, return early
291
+ if (Object.keys(failedCurrenciesFromPriceApi).length === 0) {
292
+ return ratesPriceApi;
264
293
  }
294
+ // Step 3: Fallback using token prices service for failed currencies
295
+ const { rates: ratesFromFallback, failedCurrencies: failedCurrenciesFromFallback, } = await __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_fetchRatesFromTokenPricesService).call(this, failedCurrenciesFromPriceApi, currentCurrency);
296
+ // Step 4: Create null rates for currencies that failed both approaches
297
+ const nullRates = __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_createNullRatesForCurrencies).call(this, Object.keys(failedCurrenciesFromFallback));
298
+ // Step 5: Merge all results - Price API rates take priority, then fallback, then null rates
299
+ return {
300
+ ...nullRates,
301
+ ...ratesFromFallback,
302
+ ...ratesPriceApi,
303
+ };
265
304
  };
266
305
  exports.default = CurrencyRateController;
267
306
  //# sourceMappingURL=CurrencyRateController.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CurrencyRateController.cjs","sourceRoot":"","sources":["../src/CurrencyRateController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAKA,iEAGoC;AAOpC,qEAA+E;AAE/E,6CAAoC;AAGpC,oEAAyE;AAyBzE,MAAM,IAAI,GAAG,wBAAwB,CAAC;AA0BtC,MAAM,QAAQ,GAAqC;IACjD,eAAe,EAAE;QACf,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;IACD,aAAa,EAAE;QACb,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;CACF,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,eAAe,EAAE,KAAK;IACtB,aAAa,EAAE;QACb,GAAG,EAAE;YACH,cAAc,EAAE,CAAC;YACjB,cAAc,EAAE,CAAC;YACjB,iBAAiB,EAAE,IAAI;SACxB;KACF;CACF,CAAC;AAOF,MAAM,sBAAsB,GAAG,CAAC,KAAa,EAAE,SAAS,GAAG,CAAC,EAAU,EAAE,CACtE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;AAEnC;;;GAGG;AACH,MAAa,sBAAuB,SAAQ,IAAA,oDAA+B,GAI1E;IASC;;;;;;;;;;OAUG;IACH,YAAY,EACV,cAAc,GAAG,KAAK,EACtB,QAAQ,GAAG,MAAM,EACjB,mBAAmB,GAAG,GAAG,EAAE,CAAC,IAAI,EAChC,SAAS,EACT,KAAK,EACL,kBAAkB,GAQnB;QACC,KAAK,CAAC;YACJ,IAAI;YACJ,QAAQ;YACR,SAAS;YACT,KAAK,EAAE,EAAE,GAAG,YAAY,EAAE,GAAG,KAAK,EAAE;SACrC,CAAC,CAAC;;QAvCY,UAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;QAM5B,6DAAgD;QAkCvD,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACjC,uBAAA,IAAI,8CAAuB,kBAAkB,MAAA,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,eAAuB;QAC9C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/C,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC/D,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;gBACf,OAAO;oBACL,GAAG,YAAY;oBACf,eAAe;iBAChB,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,WAAW,EAAE,CAAC;QAChB,CAAC;QACD,gFAAgF;QAChF,mEAAmE;QACnE,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IAC5C,CAAC;IAuJD;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CACtB,gBAAwC;QAExC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,wFAAwF;YACxF,kEAAkE;YAClE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,yCAAsB,CAAC,CAAC;YAC7D,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,MAAM,CAErD,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE;gBACxB,IAAI,CAAC,cAAc,EAAE,CAAC;oBACpB,OAAO,GAAG,CAAC;gBACb,CAAC;gBAED,GAAG,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC,QAAQ,CAAC,cAAc,CAAC;oBAC3D,CAAC,CAAC,wCAAqB;oBACvB,CAAC,CAAC,cAAc,CAAC;gBACnB,OAAO,GAAG,CAAC;YACb,CAAC,EAAE,EAAE,CAAC,CAAC;YAEP,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,iGAAgC,MAApC,IAAI,EACtB,uBAAuB,CACxB,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpB,KAAK,CAAC,aAAa,GAAG;oBACpB,GAAG,KAAK,CAAC,aAAa;oBACtB,GAAG,KAAK;iBACT,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACxD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACM,OAAO;QACd,KAAK,CAAC,OAAO,EAAE,CAAC;QAChB,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,YAAY,CAAC,EACjB,gBAAgB,GACS;QACzB,MAAM,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IAClD,CAAC;CACF;AApSD,wDAoSC;wKA1NC,KAAK,iEACH,uBAA+C;IAE/C,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,6BAA6B,GACjC,MAAM,uBAAA,IAAI,kDAAoB,CAAC,kBAAkB,CAAC;YAChD,YAAY,EAAE,eAAe;YAC7B,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,gBAAgB,EAAE;gBAChB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC;aACnD;SACF,CAAC,CAAC;QAEL,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAElE,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,EAAE;YAC3C,MAAM,IAAI,GACR,6BAA6B,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC;YAE/D,GAAG,CAAC,cAAc,CAAC,GAAG;gBACpB,cAAc,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI;gBAC7D,cAAc,EAAE,IAAI,EAAE,KAAK;oBACzB,CAAC,CAAC,sBAAsB,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;oBACxC,CAAC,CAAC,IAAI;gBACR,iBAAiB,EAAE,IAAI,EAAE,GAAG;oBAC1B,CAAC,CAAC,sBAAsB,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;oBACtC,CAAC,CAAC,IAAI;aACT,CAAC;YACF,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;IAC1D,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC;QACH,yFAAyF;QACzF,MAAM,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAChD,4BAA4B,CAC7B,CAAC;QACF,MAAM,qBAAqB,GACzB,sBAAsB,CAAC,8BAA8B,CAAC;QAExD,sDAAsD;QACtD,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAEvE,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,EAAE;YAC3C,uDAAuD;YACvD,MAAM,aAAa,GACjB,MAAM,CAAC,OAAO,CAAC,qBAAqB,CACrC,CAAC,IAAI,CACJ,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CACb,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE;gBACnC,eAAe,CAAC,WAAW,EAAE,CAChC,CAAC;YAEF,IAAI,aAAa,EAAE,CAAC;gBAClB,GAAG,CAAC,cAAc,CAAC,GAAG;oBACpB,eAAe;oBACf,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;iBAC1B,CAAC;YACJ,CAAC;YAED,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,8CAA8C;QAC9C,MAAM,yBAAyB,GAAG,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACrE,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,UAAU,CAC3C,yBAAyB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;YACpE,MAAM,kBAAkB,GAAG,IAAA,iCAAqB,EAAC,OAAO,CAAC,CAAC;YAC1D,uFAAuF;YACvF,MAAM,WAAW,GAAG,MAAM,uBAAA,IAAI,kDAAoB,CAAC,gBAAgB,CAAC;gBAClE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;gBACvD,QAAQ,EAAE,eAAe;aAC1B,CAAC,CAAC;YAEH,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CACjC,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;gBAC/B,kBAAkB,CAAC,WAAW,EAAE,CACnC,CAAC;YAEF,OAAO;gBACL,cAAc;gBACd,cAAc,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI;gBACrD,cAAc,EAAE,UAAU,EAAE,KAAK;oBAC/B,CAAC,CAAC,sBAAsB,CAAC,UAAU,CAAC,KAAK,CAAC;oBAC1C,CAAC,CAAC,IAAI;gBACR,iBAAiB,EAAE,IAAI,EAAE,gEAAgE;aAC1F,CAAC;QACJ,CAAC,CAAC,CACH,CAAC;QACF,MAAM,oBAAoB,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAC9D,MAAM,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACvE,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,OAAO,MAAM,CAAC,KAAK,CAAC;YACtB,CAAC;YACD,OAAO,CAAC,KAAK,CACX,mCAAmC,cAAc,aAAa,OAAO,EAAE,EACvE,MAAM,CAAC,MAAM,CACd,CAAC;YACF,OAAO;gBACL,cAAc;gBACd,cAAc,EAAE,IAAI;gBACpB,cAAc,EAAE,IAAI;gBACpB,iBAAiB,EAAE,IAAI;aACxB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,yCAAyC;QACzC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC,MAAM,CAE7D,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACd,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG;gBACzB,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,IAAI,CAAC,cAAc;oBACjC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,cAAc,CAAC;oBAC7C,CAAC,CAAC,IAAI;gBACR,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;oBACvC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,iBAAiB,CAAC;oBAChD,CAAC,CAAC,IAAI;aACT,CAAC;YACF,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,OAAO,2BAA2B,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,2DAA2D,EAC3D,KAAK,CACN,CAAC;QACF,iDAAiD;QACjD,OAAO,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAEhD,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE;YACxB,GAAG,CAAC,cAAc,CAAC,GAAG;gBACpB,cAAc,EAAE,IAAI;gBACpB,cAAc,EAAE,IAAI;gBACpB,iBAAiB,EAAE,IAAI;aACxB,CAAC;YACF,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;AACH,CAAC;AAyEH,kBAAe,sBAAsB,CAAC","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 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\nconst boundedPrecisionNumber = (value: number, precision = 9): number =>\n Number(value.toFixed(precision));\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 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.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 tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\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.#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 CurrencyRateState['currencyRates']\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 ? boundedPrecisionNumber(1 / rate.value)\n : null,\n usdConversionRate: rate?.usd\n ? boundedPrecisionNumber(1 / rate.usd)\n : null,\n };\n return acc;\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 Record<string, { fetchedCurrency: string; chainId: Hex }>\n >((acc, [nativeCurrency, fetchedCurrency]) => {\n // Find the first chainId that has this native currency\n const matchingEntry = (\n Object.entries(networkConfigurations) as [Hex, NetworkConfiguration][]\n ).find(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n );\n\n if (matchingEntry) {\n acc[nativeCurrency] = {\n fetchedCurrency,\n chainId: matchingEntry[0],\n };\n }\n\n return acc;\n }, {});\n\n // Step 3: Fetch token prices for each chainId\n const currencyToChainIdsEntries = Object.entries(currencyToChainIds);\n const ratesResults = await Promise.allSettled(\n currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n // Pass empty array as fetchTokenPrices automatically includes the native token address\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices({\n assets: [{ chainId, tokenAddress: nativeTokenAddress }],\n currency: currentCurrency,\n });\n\n const tokenPrice = tokenPrices.find(\n (item) =>\n item.tokenAddress.toLowerCase() ===\n nativeTokenAddress.toLowerCase(),\n );\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price\n ? boundedPrecisionNumber(tokenPrice.price)\n : null,\n usdConversionRate: null, // Token prices service doesn't provide USD rate in this context\n };\n }),\n );\n const ratesFromTokenPrices = ratesResults.map((result, index) => {\n const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[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 CurrencyRateState['currencyRates']\n >((acc, rate) => {\n acc[rate.nativeCurrency] = {\n conversionDate: rate.conversionDate,\n conversionRate: rate.conversionRate\n ? boundedPrecisionNumber(rate.conversionRate)\n : null,\n usdConversionRate: rate.usdConversionRate\n ? boundedPrecisionNumber(rate.usdConversionRate)\n : null,\n };\n return acc;\n }, {});\n\n return ratesFromTokenPricesService;\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n // Return null state for all requested currencies\n return Object.keys(nativeCurrenciesToFetch).reduce<\n CurrencyRateState['currencyRates']\n >((acc, nativeCurrency) => {\n acc[nativeCurrency] = {\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n return acc;\n }, {});\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 Record<string, string>\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\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 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\nconst boundedPrecisionNumber = (value: number, precision = 9): number =>\n Number(value.toFixed(precision));\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 */\n/** Result from attempting to fetch rates from an API */\ntype FetchRatesResult = {\n /** Successfully fetched rates */\n rates: CurrencyRateState['currencyRates'];\n /** Currencies that failed and need fallback or null state */\n failedCurrencies: Record<string, 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 readonly #mutex = new Mutex();\n\n readonly #includeUsdRate: boolean;\n\n 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.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 tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\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.#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): Promise<void> {\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 /**\n * Attempts to fetch exchange rates from the primary Price API.\n *\n * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.\n * @param currentCurrency - The current fiat currency to get rates for.\n * @returns Object containing successful rates and currencies that failed.\n */\n async #fetchRatesFromPriceApi(\n nativeCurrenciesToFetch: Record<string, string>,\n currentCurrency: string,\n ): Promise<FetchRatesResult> {\n const rates: CurrencyRateState['currencyRates'] = {};\n let failedCurrencies: Record<string, string> = {};\n\n try {\n const response = await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.#includeUsdRate,\n cryptocurrencies: [...new Set(Object.values(nativeCurrenciesToFetch))],\n });\n\n Object.entries(nativeCurrenciesToFetch).forEach(\n ([nativeCurrency, fetchedCurrency]) => {\n const rate = response[fetchedCurrency.toLowerCase()];\n\n if (rate?.value) {\n rates[nativeCurrency] = {\n conversionDate: Date.now() / 1000,\n conversionRate: boundedPrecisionNumber(1 / rate.value),\n usdConversionRate: rate?.usd\n ? boundedPrecisionNumber(1 / rate.usd)\n : null,\n };\n } else {\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n },\n );\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n failedCurrencies = { ...nativeCurrenciesToFetch };\n }\n\n return { rates, failedCurrencies };\n }\n\n /**\n * Fetches exchange rates from the token prices service as a fallback.\n * This method is designed to never throw - all errors are handled internally\n * and result in currencies being marked as failed.\n *\n * @param currenciesToFetch - Map of native currencies that need fallback fetching.\n * @param currentCurrency - The current fiat currency to get rates for.\n * @returns Object containing successful rates and currencies that failed.\n */\n async #fetchRatesFromTokenPricesService(\n currenciesToFetch: Record<string, string>,\n currentCurrency: string,\n ): Promise<FetchRatesResult> {\n try {\n const rates: CurrencyRateState['currencyRates'] = {};\n const failedCurrencies: Record<string, string> = {};\n\n const networkControllerState = this.messenger.call(\n 'NetworkController:getState',\n );\n const networkConfigurations =\n networkControllerState.networkConfigurationsByChainId;\n\n // Build a map of nativeCurrency -> chainId for currencies to fetch\n const currencyToChainIds = Object.entries(currenciesToFetch).reduce<\n Record<string, { fetchedCurrency: string; chainId: Hex }>\n >((acc, [nativeCurrency, fetchedCurrency]) => {\n const matchingEntry = (\n Object.entries(networkConfigurations) as [Hex, NetworkConfiguration][]\n ).find(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n );\n\n if (matchingEntry) {\n acc[nativeCurrency] = { fetchedCurrency, chainId: matchingEntry[0] };\n } else {\n // No matching network configuration - mark as failed\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n return acc;\n }, {});\n\n const currencyToChainIdsEntries = Object.entries(currencyToChainIds);\n const ratesResults = await Promise.allSettled(\n currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices({\n assets: [{ chainId, tokenAddress: nativeTokenAddress }],\n currency: currentCurrency,\n });\n\n const tokenPrice = tokenPrices.find(\n (item) =>\n item.tokenAddress.toLowerCase() ===\n nativeTokenAddress.toLowerCase(),\n );\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price\n ? boundedPrecisionNumber(tokenPrice.price)\n : null,\n usdConversionRate: null,\n };\n }),\n );\n\n ratesResults.forEach((result, index) => {\n const [nativeCurrency, { fetchedCurrency, chainId }] =\n currencyToChainIdsEntries[index];\n\n if (result.status === 'fulfilled' && result.value.conversionRate) {\n rates[nativeCurrency] = {\n conversionDate: result.value.conversionDate,\n conversionRate: result.value.conversionRate,\n usdConversionRate: result.value.usdConversionRate,\n };\n } else {\n if (result.status === 'rejected') {\n console.error(\n `Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`,\n result.reason,\n );\n }\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n });\n\n return { rates, failedCurrencies };\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n // Return all currencies as failed\n return { rates: {}, failedCurrencies: { ...currenciesToFetch } };\n }\n }\n\n /**\n * Creates null rate entries for currencies that couldn't be fetched.\n *\n * @param currencies - Array of currency symbols to create null entries for.\n * @returns Null rate entries for all provided currencies.\n */\n #createNullRatesForCurrencies(\n currencies: string[],\n ): CurrencyRateState['currencyRates'] {\n return currencies.reduce<CurrencyRateState['currencyRates']>(\n (acc, nativeCurrency) => {\n acc[nativeCurrency] = {\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n return acc;\n },\n {},\n );\n }\n\n /**\n * Fetches exchange rates with fallback logic.\n * First tries the Price API, then falls back to token prices service for any failed currencies.\n *\n * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.\n * @returns Exchange rates for all requested currencies.\n */\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n // Step 1: Try the Price API exchange rates first\n const {\n rates: ratesPriceApi,\n failedCurrencies: failedCurrenciesFromPriceApi,\n } = await this.#fetchRatesFromPriceApi(\n nativeCurrenciesToFetch,\n currentCurrency,\n );\n\n // Step 2: If all currencies succeeded, return early\n if (Object.keys(failedCurrenciesFromPriceApi).length === 0) {\n return ratesPriceApi;\n }\n\n // Step 3: Fallback using token prices service for failed currencies\n const {\n rates: ratesFromFallback,\n failedCurrencies: failedCurrenciesFromFallback,\n } = await this.#fetchRatesFromTokenPricesService(\n failedCurrenciesFromPriceApi,\n currentCurrency,\n );\n\n // Step 4: Create null rates for currencies that failed both approaches\n const nullRates = this.#createNullRatesForCurrencies(\n Object.keys(failedCurrenciesFromFallback),\n );\n\n // Step 5: Merge all results - Price API rates take priority, then fallback, then null rates\n return {\n ...nullRates,\n ...ratesFromFallback,\n ...ratesPriceApi,\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 Record<string, string>\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\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(): void {\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"]}
@@ -53,9 +53,6 @@ declare const CurrencyRateController_base: (abstract new (...args: any[]) => {
53
53
  */
54
54
  export declare class CurrencyRateController extends CurrencyRateController_base<typeof name, CurrencyRateState, CurrencyRateMessenger> {
55
55
  #private;
56
- private readonly mutex;
57
- private readonly includeUsdRate;
58
- private readonly useExternalServices;
59
56
  /**
60
57
  * Creates a CurrencyRateController instance.
61
58
  *
@@ -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,EACV,2CAA2C,EAC3C,+BAA+B,EAEhC,qCAAqC;AAKtC,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;;;;;;;;;;;;;;;;AAKF;;;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,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;OAUG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,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,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAaD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IAuKhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IAyChB;;;;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,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;;;;;;;;;;;;;;;;AAiBF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IASC;;;;;;;;;;OAUG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,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,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAaD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4OhE;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IAyChB;;;;OAIG;IACM,OAAO,IAAI,IAAI;IAKxB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
@@ -53,9 +53,6 @@ declare const CurrencyRateController_base: (abstract new (...args: any[]) => {
53
53
  */
54
54
  export declare class CurrencyRateController extends CurrencyRateController_base<typeof name, CurrencyRateState, CurrencyRateMessenger> {
55
55
  #private;
56
- private readonly mutex;
57
- private readonly includeUsdRate;
58
- private readonly useExternalServices;
59
56
  /**
60
57
  * Creates a CurrencyRateController instance.
61
58
  *
@@ -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,EACV,2CAA2C,EAC3C,+BAA+B,EAEhC,qCAAqC;AAKtC,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;;;;;;;;;;;;;;;;AAKF;;;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,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAIpD;;;;;;;;;;OAUG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,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,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAaD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM;IAuKhD;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IAyChB;;;;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,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;;;;;;;;;;;;;;;;AAiBF;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,4BAC1C,OAAO,IAAI,EACX,iBAAiB,EACjB,qBAAqB,CACtB;;IASC;;;;;;;;;;OAUG;gBACS,EACV,cAAsB,EACtB,QAAiB,EACjB,mBAAgC,EAChC,SAAS,EACT,KAAK,EACL,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,kBAAkB,EAAE,0BAA0B,CAAC;KAChD;IAaD;;;;OAIG;IACG,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4OhE;;;;OAIG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACvC,OAAO,CAAC,IAAI,CAAC;IAyChB;;;;OAIG;IACM,OAAO,IAAI,IAAI;IAKxB;;;;;OAKG;IACG,YAAY,CAAC,EACjB,gBAAgB,GACjB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C;AAED,eAAe,sBAAsB,CAAC"}
@@ -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 _CurrencyRateController_instances, _CurrencyRateController_tokenPricesService, _CurrencyRateController_fetchExchangeRatesWithFallback;
12
+ var _CurrencyRateController_instances, _CurrencyRateController_mutex, _CurrencyRateController_includeUsdRate, _CurrencyRateController_useExternalServices, _CurrencyRateController_tokenPricesService, _CurrencyRateController_fetchRatesFromPriceApi, _CurrencyRateController_fetchRatesFromTokenPricesService, _CurrencyRateController_createNullRatesForCurrencies, _CurrencyRateController_fetchExchangeRatesWithFallback;
13
13
  import { TESTNET_TICKER_SYMBOLS, FALL_BACK_VS_CURRENCY } from "@metamask/controller-utils";
14
14
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
15
15
  import { Mutex } from "async-mutex";
@@ -64,10 +64,12 @@ export class CurrencyRateController extends StaticIntervalPollingController() {
64
64
  state: { ...defaultState, ...state },
65
65
  });
66
66
  _CurrencyRateController_instances.add(this);
67
- this.mutex = new Mutex();
67
+ _CurrencyRateController_mutex.set(this, new Mutex());
68
+ _CurrencyRateController_includeUsdRate.set(this, void 0);
69
+ _CurrencyRateController_useExternalServices.set(this, void 0);
68
70
  _CurrencyRateController_tokenPricesService.set(this, void 0);
69
- this.includeUsdRate = includeUsdRate;
70
- this.useExternalServices = useExternalServices;
71
+ __classPrivateFieldSet(this, _CurrencyRateController_includeUsdRate, includeUsdRate, "f");
72
+ __classPrivateFieldSet(this, _CurrencyRateController_useExternalServices, useExternalServices, "f");
71
73
  this.setIntervalLength(interval);
72
74
  __classPrivateFieldSet(this, _CurrencyRateController_tokenPricesService, tokenPricesService, "f");
73
75
  }
@@ -77,7 +79,7 @@ export class CurrencyRateController extends StaticIntervalPollingController() {
77
79
  * @param currentCurrency - ISO 4217 currency code.
78
80
  */
79
81
  async setCurrentCurrency(currentCurrency) {
80
- const releaseLock = await this.mutex.acquire();
82
+ const releaseLock = await __classPrivateFieldGet(this, _CurrencyRateController_mutex, "f").acquire();
81
83
  const nativeCurrencies = Object.keys(this.state.currencyRates);
82
84
  try {
83
85
  this.update(() => {
@@ -100,10 +102,10 @@ export class CurrencyRateController extends StaticIntervalPollingController() {
100
102
  * @param nativeCurrencies - The native currency symbols to fetch exchange rates for.
101
103
  */
102
104
  async updateExchangeRate(nativeCurrencies) {
103
- if (!this.useExternalServices()) {
105
+ if (!__classPrivateFieldGet(this, _CurrencyRateController_useExternalServices, "f").call(this)) {
104
106
  return;
105
107
  }
106
- const releaseLock = await this.mutex.acquire();
108
+ const releaseLock = await __classPrivateFieldGet(this, _CurrencyRateController_mutex, "f").acquire();
107
109
  try {
108
110
  // For preloaded testnets (Goerli, Sepolia) we want to fetch exchange rate for real ETH.
109
111
  // Map each native currency to the symbol we want to fetch for it.
@@ -152,57 +154,76 @@ export class CurrencyRateController extends StaticIntervalPollingController() {
152
154
  await this.updateExchangeRate(nativeCurrencies);
153
155
  }
154
156
  }
155
- _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateController_instances = new WeakSet(), _CurrencyRateController_fetchExchangeRatesWithFallback = async function _CurrencyRateController_fetchExchangeRatesWithFallback(nativeCurrenciesToFetch) {
156
- const { currentCurrency } = this.state;
157
+ _CurrencyRateController_mutex = new WeakMap(), _CurrencyRateController_includeUsdRate = new WeakMap(), _CurrencyRateController_useExternalServices = new WeakMap(), _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateController_instances = new WeakSet(), _CurrencyRateController_fetchRatesFromPriceApi =
158
+ /**
159
+ * Attempts to fetch exchange rates from the primary Price API.
160
+ *
161
+ * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.
162
+ * @param currentCurrency - The current fiat currency to get rates for.
163
+ * @returns Object containing successful rates and currencies that failed.
164
+ */
165
+ async function _CurrencyRateController_fetchRatesFromPriceApi(nativeCurrenciesToFetch, currentCurrency) {
166
+ const rates = {};
167
+ let failedCurrencies = {};
157
168
  try {
158
- const priceApiExchangeRatesResponse = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
169
+ const response = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
159
170
  baseCurrency: currentCurrency,
160
- includeUsdRate: this.includeUsdRate,
161
- cryptocurrencies: [
162
- ...new Set(Object.values(nativeCurrenciesToFetch)),
163
- ],
171
+ includeUsdRate: __classPrivateFieldGet(this, _CurrencyRateController_includeUsdRate, "f"),
172
+ cryptocurrencies: [...new Set(Object.values(nativeCurrenciesToFetch))],
173
+ });
174
+ Object.entries(nativeCurrenciesToFetch).forEach(([nativeCurrency, fetchedCurrency]) => {
175
+ const rate = response[fetchedCurrency.toLowerCase()];
176
+ if (rate?.value) {
177
+ rates[nativeCurrency] = {
178
+ conversionDate: Date.now() / 1000,
179
+ conversionRate: boundedPrecisionNumber(1 / rate.value),
180
+ usdConversionRate: rate?.usd
181
+ ? boundedPrecisionNumber(1 / rate.usd)
182
+ : null,
183
+ };
184
+ }
185
+ else {
186
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
187
+ }
164
188
  });
165
- const ratesPriceApi = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
166
- const rate = priceApiExchangeRatesResponse[fetchedCurrency.toLowerCase()];
167
- acc[nativeCurrency] = {
168
- conversionDate: rate !== undefined ? Date.now() / 1000 : null,
169
- conversionRate: rate?.value
170
- ? boundedPrecisionNumber(1 / rate.value)
171
- : null,
172
- usdConversionRate: rate?.usd
173
- ? boundedPrecisionNumber(1 / rate.usd)
174
- : null,
175
- };
176
- return acc;
177
- }, {});
178
- return ratesPriceApi;
179
189
  }
180
190
  catch (error) {
181
191
  console.error('Failed to fetch exchange rates.', error);
192
+ failedCurrencies = { ...nativeCurrenciesToFetch };
182
193
  }
183
- // fallback using spot price from token prices service
194
+ return { rates, failedCurrencies };
195
+ }, _CurrencyRateController_fetchRatesFromTokenPricesService =
196
+ /**
197
+ * Fetches exchange rates from the token prices service as a fallback.
198
+ * This method is designed to never throw - all errors are handled internally
199
+ * and result in currencies being marked as failed.
200
+ *
201
+ * @param currenciesToFetch - Map of native currencies that need fallback fetching.
202
+ * @param currentCurrency - The current fiat currency to get rates for.
203
+ * @returns Object containing successful rates and currencies that failed.
204
+ */
205
+ async function _CurrencyRateController_fetchRatesFromTokenPricesService(currenciesToFetch, currentCurrency) {
184
206
  try {
185
- // Step 1: Get all network configurations to find matching chainIds for native currencies
207
+ const rates = {};
208
+ const failedCurrencies = {};
186
209
  const networkControllerState = this.messenger.call('NetworkController:getState');
187
210
  const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
188
- // Step 2: Build a map of nativeCurrency -> chainId(s)
189
- const currencyToChainIds = Object.entries(nativeCurrenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
190
- // Find the first chainId that has this native currency
211
+ // Build a map of nativeCurrency -> chainId for currencies to fetch
212
+ const currencyToChainIds = Object.entries(currenciesToFetch).reduce((acc, [nativeCurrency, fetchedCurrency]) => {
191
213
  const matchingEntry = Object.entries(networkConfigurations).find(([, config]) => config.nativeCurrency.toUpperCase() ===
192
214
  fetchedCurrency.toUpperCase());
193
215
  if (matchingEntry) {
194
- acc[nativeCurrency] = {
195
- fetchedCurrency,
196
- chainId: matchingEntry[0],
197
- };
216
+ acc[nativeCurrency] = { fetchedCurrency, chainId: matchingEntry[0] };
217
+ }
218
+ else {
219
+ // No matching network configuration - mark as failed
220
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
198
221
  }
199
222
  return acc;
200
223
  }, {});
201
- // Step 3: Fetch token prices for each chainId
202
224
  const currencyToChainIdsEntries = Object.entries(currencyToChainIds);
203
225
  const ratesResults = await Promise.allSettled(currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {
204
226
  const nativeTokenAddress = getNativeTokenAddress(chainId);
205
- // Pass empty array as fetchTokenPrices automatically includes the native token address
206
227
  const tokenPrices = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchTokenPrices({
207
228
  assets: [{ chainId, tokenAddress: nativeTokenAddress }],
208
229
  currency: currentCurrency,
@@ -215,49 +236,67 @@ _CurrencyRateController_tokenPricesService = new WeakMap(), _CurrencyRateControl
215
236
  conversionRate: tokenPrice?.price
216
237
  ? boundedPrecisionNumber(tokenPrice.price)
217
238
  : null,
218
- usdConversionRate: null, // Token prices service doesn't provide USD rate in this context
239
+ usdConversionRate: null,
219
240
  };
220
241
  }));
221
- const ratesFromTokenPrices = ratesResults.map((result, index) => {
222
- const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[index];
223
- if (result.status === 'fulfilled') {
224
- return result.value;
242
+ ratesResults.forEach((result, index) => {
243
+ const [nativeCurrency, { fetchedCurrency, chainId }] = currencyToChainIdsEntries[index];
244
+ if (result.status === 'fulfilled' && result.value.conversionRate) {
245
+ rates[nativeCurrency] = {
246
+ conversionDate: result.value.conversionDate,
247
+ conversionRate: result.value.conversionRate,
248
+ usdConversionRate: result.value.usdConversionRate,
249
+ };
250
+ }
251
+ else {
252
+ if (result.status === 'rejected') {
253
+ console.error(`Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`, result.reason);
254
+ }
255
+ failedCurrencies[nativeCurrency] = fetchedCurrency;
225
256
  }
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
257
  });
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
- ? boundedPrecisionNumber(rate.conversionRate)
240
- : null,
241
- usdConversionRate: rate.usdConversionRate
242
- ? boundedPrecisionNumber(rate.usdConversionRate)
243
- : null,
244
- };
245
- return acc;
246
- }, {});
247
- return ratesFromTokenPricesService;
258
+ return { rates, failedCurrencies };
248
259
  }
249
260
  catch (error) {
250
261
  console.error('Failed to fetch exchange rates from token prices service.', error);
251
- // Return null state for all requested currencies
252
- return Object.keys(nativeCurrenciesToFetch).reduce((acc, nativeCurrency) => {
253
- acc[nativeCurrency] = {
254
- conversionDate: null,
255
- conversionRate: null,
256
- usdConversionRate: null,
257
- };
258
- return acc;
259
- }, {});
262
+ // Return all currencies as failed
263
+ return { rates: {}, failedCurrencies: { ...currenciesToFetch } };
264
+ }
265
+ }, _CurrencyRateController_createNullRatesForCurrencies = function _CurrencyRateController_createNullRatesForCurrencies(currencies) {
266
+ return currencies.reduce((acc, nativeCurrency) => {
267
+ acc[nativeCurrency] = {
268
+ conversionDate: null,
269
+ conversionRate: null,
270
+ usdConversionRate: null,
271
+ };
272
+ return acc;
273
+ }, {});
274
+ }, _CurrencyRateController_fetchExchangeRatesWithFallback =
275
+ /**
276
+ * Fetches exchange rates with fallback logic.
277
+ * First tries the Price API, then falls back to token prices service for any failed currencies.
278
+ *
279
+ * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.
280
+ * @returns Exchange rates for all requested currencies.
281
+ */
282
+ async function _CurrencyRateController_fetchExchangeRatesWithFallback(nativeCurrenciesToFetch) {
283
+ const { currentCurrency } = this.state;
284
+ // Step 1: Try the Price API exchange rates first
285
+ const { rates: ratesPriceApi, failedCurrencies: failedCurrenciesFromPriceApi, } = await __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_fetchRatesFromPriceApi).call(this, nativeCurrenciesToFetch, currentCurrency);
286
+ // Step 2: If all currencies succeeded, return early
287
+ if (Object.keys(failedCurrenciesFromPriceApi).length === 0) {
288
+ return ratesPriceApi;
260
289
  }
290
+ // Step 3: Fallback using token prices service for failed currencies
291
+ const { rates: ratesFromFallback, failedCurrencies: failedCurrenciesFromFallback, } = await __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_fetchRatesFromTokenPricesService).call(this, failedCurrenciesFromPriceApi, currentCurrency);
292
+ // Step 4: Create null rates for currencies that failed both approaches
293
+ const nullRates = __classPrivateFieldGet(this, _CurrencyRateController_instances, "m", _CurrencyRateController_createNullRatesForCurrencies).call(this, Object.keys(failedCurrenciesFromFallback));
294
+ // Step 5: Merge all results - Price API rates take priority, then fallback, then null rates
295
+ return {
296
+ ...nullRates,
297
+ ...ratesFromFallback,
298
+ ...ratesPriceApi,
299
+ };
261
300
  };
262
301
  export default CurrencyRateController;
263
302
  //# sourceMappingURL=CurrencyRateController.mjs.map
@@ -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 {\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 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\nconst boundedPrecisionNumber = (value: number, precision = 9): number =>\n Number(value.toFixed(precision));\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 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.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 tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\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.#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 CurrencyRateState['currencyRates']\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 ? boundedPrecisionNumber(1 / rate.value)\n : null,\n usdConversionRate: rate?.usd\n ? boundedPrecisionNumber(1 / rate.usd)\n : null,\n };\n return acc;\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 Record<string, { fetchedCurrency: string; chainId: Hex }>\n >((acc, [nativeCurrency, fetchedCurrency]) => {\n // Find the first chainId that has this native currency\n const matchingEntry = (\n Object.entries(networkConfigurations) as [Hex, NetworkConfiguration][]\n ).find(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n );\n\n if (matchingEntry) {\n acc[nativeCurrency] = {\n fetchedCurrency,\n chainId: matchingEntry[0],\n };\n }\n\n return acc;\n }, {});\n\n // Step 3: Fetch token prices for each chainId\n const currencyToChainIdsEntries = Object.entries(currencyToChainIds);\n const ratesResults = await Promise.allSettled(\n currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n // Pass empty array as fetchTokenPrices automatically includes the native token address\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices({\n assets: [{ chainId, tokenAddress: nativeTokenAddress }],\n currency: currentCurrency,\n });\n\n const tokenPrice = tokenPrices.find(\n (item) =>\n item.tokenAddress.toLowerCase() ===\n nativeTokenAddress.toLowerCase(),\n );\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price\n ? boundedPrecisionNumber(tokenPrice.price)\n : null,\n usdConversionRate: null, // Token prices service doesn't provide USD rate in this context\n };\n }),\n );\n const ratesFromTokenPrices = ratesResults.map((result, index) => {\n const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[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 CurrencyRateState['currencyRates']\n >((acc, rate) => {\n acc[rate.nativeCurrency] = {\n conversionDate: rate.conversionDate,\n conversionRate: rate.conversionRate\n ? boundedPrecisionNumber(rate.conversionRate)\n : null,\n usdConversionRate: rate.usdConversionRate\n ? boundedPrecisionNumber(rate.usdConversionRate)\n : null,\n };\n return acc;\n }, {});\n\n return ratesFromTokenPricesService;\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n // Return null state for all requested currencies\n return Object.keys(nativeCurrenciesToFetch).reduce<\n CurrencyRateState['currencyRates']\n >((acc, nativeCurrency) => {\n acc[nativeCurrency] = {\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n return acc;\n }, {});\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 Record<string, string>\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\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 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\nconst boundedPrecisionNumber = (value: number, precision = 9): number =>\n Number(value.toFixed(precision));\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 */\n/** Result from attempting to fetch rates from an API */\ntype FetchRatesResult = {\n /** Successfully fetched rates */\n rates: CurrencyRateState['currencyRates'];\n /** Currencies that failed and need fallback or null state */\n failedCurrencies: Record<string, 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 readonly #mutex = new Mutex();\n\n readonly #includeUsdRate: boolean;\n\n 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.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 tokenPricesService,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial<CurrencyRateState>;\n useExternalServices?: () => boolean;\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.#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): Promise<void> {\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 /**\n * Attempts to fetch exchange rates from the primary Price API.\n *\n * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.\n * @param currentCurrency - The current fiat currency to get rates for.\n * @returns Object containing successful rates and currencies that failed.\n */\n async #fetchRatesFromPriceApi(\n nativeCurrenciesToFetch: Record<string, string>,\n currentCurrency: string,\n ): Promise<FetchRatesResult> {\n const rates: CurrencyRateState['currencyRates'] = {};\n let failedCurrencies: Record<string, string> = {};\n\n try {\n const response = await this.#tokenPricesService.fetchExchangeRates({\n baseCurrency: currentCurrency,\n includeUsdRate: this.#includeUsdRate,\n cryptocurrencies: [...new Set(Object.values(nativeCurrenciesToFetch))],\n });\n\n Object.entries(nativeCurrenciesToFetch).forEach(\n ([nativeCurrency, fetchedCurrency]) => {\n const rate = response[fetchedCurrency.toLowerCase()];\n\n if (rate?.value) {\n rates[nativeCurrency] = {\n conversionDate: Date.now() / 1000,\n conversionRate: boundedPrecisionNumber(1 / rate.value),\n usdConversionRate: rate?.usd\n ? boundedPrecisionNumber(1 / rate.usd)\n : null,\n };\n } else {\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n },\n );\n } catch (error) {\n console.error('Failed to fetch exchange rates.', error);\n failedCurrencies = { ...nativeCurrenciesToFetch };\n }\n\n return { rates, failedCurrencies };\n }\n\n /**\n * Fetches exchange rates from the token prices service as a fallback.\n * This method is designed to never throw - all errors are handled internally\n * and result in currencies being marked as failed.\n *\n * @param currenciesToFetch - Map of native currencies that need fallback fetching.\n * @param currentCurrency - The current fiat currency to get rates for.\n * @returns Object containing successful rates and currencies that failed.\n */\n async #fetchRatesFromTokenPricesService(\n currenciesToFetch: Record<string, string>,\n currentCurrency: string,\n ): Promise<FetchRatesResult> {\n try {\n const rates: CurrencyRateState['currencyRates'] = {};\n const failedCurrencies: Record<string, string> = {};\n\n const networkControllerState = this.messenger.call(\n 'NetworkController:getState',\n );\n const networkConfigurations =\n networkControllerState.networkConfigurationsByChainId;\n\n // Build a map of nativeCurrency -> chainId for currencies to fetch\n const currencyToChainIds = Object.entries(currenciesToFetch).reduce<\n Record<string, { fetchedCurrency: string; chainId: Hex }>\n >((acc, [nativeCurrency, fetchedCurrency]) => {\n const matchingEntry = (\n Object.entries(networkConfigurations) as [Hex, NetworkConfiguration][]\n ).find(\n ([, config]) =>\n config.nativeCurrency.toUpperCase() ===\n fetchedCurrency.toUpperCase(),\n );\n\n if (matchingEntry) {\n acc[nativeCurrency] = { fetchedCurrency, chainId: matchingEntry[0] };\n } else {\n // No matching network configuration - mark as failed\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n return acc;\n }, {});\n\n const currencyToChainIdsEntries = Object.entries(currencyToChainIds);\n const ratesResults = await Promise.allSettled(\n currencyToChainIdsEntries.map(async ([nativeCurrency, { chainId }]) => {\n const nativeTokenAddress = getNativeTokenAddress(chainId);\n const tokenPrices = await this.#tokenPricesService.fetchTokenPrices({\n assets: [{ chainId, tokenAddress: nativeTokenAddress }],\n currency: currentCurrency,\n });\n\n const tokenPrice = tokenPrices.find(\n (item) =>\n item.tokenAddress.toLowerCase() ===\n nativeTokenAddress.toLowerCase(),\n );\n\n return {\n nativeCurrency,\n conversionDate: tokenPrice ? Date.now() / 1000 : null,\n conversionRate: tokenPrice?.price\n ? boundedPrecisionNumber(tokenPrice.price)\n : null,\n usdConversionRate: null,\n };\n }),\n );\n\n ratesResults.forEach((result, index) => {\n const [nativeCurrency, { fetchedCurrency, chainId }] =\n currencyToChainIdsEntries[index];\n\n if (result.status === 'fulfilled' && result.value.conversionRate) {\n rates[nativeCurrency] = {\n conversionDate: result.value.conversionDate,\n conversionRate: result.value.conversionRate,\n usdConversionRate: result.value.usdConversionRate,\n };\n } else {\n if (result.status === 'rejected') {\n console.error(\n `Failed to fetch token price for ${nativeCurrency} on chain ${chainId}`,\n result.reason,\n );\n }\n failedCurrencies[nativeCurrency] = fetchedCurrency;\n }\n });\n\n return { rates, failedCurrencies };\n } catch (error) {\n console.error(\n 'Failed to fetch exchange rates from token prices service.',\n error,\n );\n // Return all currencies as failed\n return { rates: {}, failedCurrencies: { ...currenciesToFetch } };\n }\n }\n\n /**\n * Creates null rate entries for currencies that couldn't be fetched.\n *\n * @param currencies - Array of currency symbols to create null entries for.\n * @returns Null rate entries for all provided currencies.\n */\n #createNullRatesForCurrencies(\n currencies: string[],\n ): CurrencyRateState['currencyRates'] {\n return currencies.reduce<CurrencyRateState['currencyRates']>(\n (acc, nativeCurrency) => {\n acc[nativeCurrency] = {\n conversionDate: null,\n conversionRate: null,\n usdConversionRate: null,\n };\n return acc;\n },\n {},\n );\n }\n\n /**\n * Fetches exchange rates with fallback logic.\n * First tries the Price API, then falls back to token prices service for any failed currencies.\n *\n * @param nativeCurrenciesToFetch - Map of native currency to the currency symbol to fetch.\n * @returns Exchange rates for all requested currencies.\n */\n async #fetchExchangeRatesWithFallback(\n nativeCurrenciesToFetch: Record<string, string>,\n ): Promise<CurrencyRateState['currencyRates']> {\n const { currentCurrency } = this.state;\n\n // Step 1: Try the Price API exchange rates first\n const {\n rates: ratesPriceApi,\n failedCurrencies: failedCurrenciesFromPriceApi,\n } = await this.#fetchRatesFromPriceApi(\n nativeCurrenciesToFetch,\n currentCurrency,\n );\n\n // Step 2: If all currencies succeeded, return early\n if (Object.keys(failedCurrenciesFromPriceApi).length === 0) {\n return ratesPriceApi;\n }\n\n // Step 3: Fallback using token prices service for failed currencies\n const {\n rates: ratesFromFallback,\n failedCurrencies: failedCurrenciesFromFallback,\n } = await this.#fetchRatesFromTokenPricesService(\n failedCurrenciesFromPriceApi,\n currentCurrency,\n );\n\n // Step 4: Create null rates for currencies that failed both approaches\n const nullRates = this.#createNullRatesForCurrencies(\n Object.keys(failedCurrenciesFromFallback),\n );\n\n // Step 5: Merge all results - Price API rates take priority, then fallback, then null rates\n return {\n ...nullRates,\n ...ratesFromFallback,\n ...ratesPriceApi,\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 Record<string, string>\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\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(): void {\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/assets-controllers",
3
- "version": "95.1.0-preview-3e207569",
3
+ "version": "95.2.0-preview-85ecacba",
4
4
  "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
5
5
  "keywords": [
6
6
  "MetaMask",