@metamask-previews/assets-controllers 95.1.0-preview-3e207569 → 95.1.0-preview-47cfb4e
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 +3 -0
- package/dist/CurrencyRateController.cjs +113 -74
- package/dist/CurrencyRateController.cjs.map +1 -1
- package/dist/CurrencyRateController.d.cts +0 -3
- package/dist/CurrencyRateController.d.cts.map +1 -1
- package/dist/CurrencyRateController.d.mts +0 -3
- package/dist/CurrencyRateController.d.mts.map +1 -1
- package/dist/CurrencyRateController.mjs +113 -74
- package/dist/CurrencyRateController.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
20
|
- Bump `@metamask/polling-controller` from `^16.0.0` to `^16.0.1` ([#7604](https://github.com/MetaMask/core/pull/7604))
|
|
21
21
|
- Update Plasma (0x2611) mapping to eip155:9745/erc20:0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for XPL ([#7601](https://github.com/MetaMask/core/pull/7601))
|
|
22
22
|
- `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))
|
|
23
|
+
- Refactor `CurrencyRateController` exchange rate fetching with improved fallback logic ([#7606](https://github.com/MetaMask/core/pull/7606))
|
|
24
|
+
- Improved partial success handling: when some currencies succeed via the Price API and others fail, only failed currencies trigger the fallback mechanism
|
|
25
|
+
- Extracted helper methods for better code organization and testability
|
|
23
26
|
|
|
24
27
|
## [95.1.0]
|
|
25
28
|
|
|
@@ -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
|
|
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
|
|
73
|
-
this
|
|
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.
|
|
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.
|
|
108
|
+
if (!__classPrivateFieldGet(this, _CurrencyRateController_useExternalServices, "f").call(this)) {
|
|
107
109
|
return;
|
|
108
110
|
}
|
|
109
|
-
const releaseLock = await this.
|
|
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
|
-
|
|
160
|
-
|
|
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
|
|
173
|
+
const response = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
|
|
163
174
|
baseCurrency: currentCurrency,
|
|
164
|
-
includeUsdRate: this
|
|
165
|
-
cryptocurrencies: [
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
const rates = {};
|
|
212
|
+
const failedCurrencies = {};
|
|
190
213
|
const networkControllerState = this.messenger.call('NetworkController:getState');
|
|
191
214
|
const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
|
|
192
|
-
//
|
|
193
|
-
const currencyToChainIds = Object.entries(
|
|
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
|
-
|
|
200
|
-
|
|
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,
|
|
243
|
+
usdConversionRate: null,
|
|
223
244
|
};
|
|
224
245
|
}));
|
|
225
|
-
|
|
226
|
-
const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[index];
|
|
227
|
-
if (result.status === 'fulfilled') {
|
|
228
|
-
|
|
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
|
-
|
|
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
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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;;;;;;;;;;;;;;;;
|
|
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;;;;;;;;;;;;;;;;
|
|
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
|
|
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
|
|
70
|
-
this
|
|
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.
|
|
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.
|
|
105
|
+
if (!__classPrivateFieldGet(this, _CurrencyRateController_useExternalServices, "f").call(this)) {
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
|
-
const releaseLock = await this.
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
169
|
+
const response = await __classPrivateFieldGet(this, _CurrencyRateController_tokenPricesService, "f").fetchExchangeRates({
|
|
159
170
|
baseCurrency: currentCurrency,
|
|
160
|
-
includeUsdRate: this
|
|
161
|
-
cryptocurrencies: [
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
+
const rates = {};
|
|
208
|
+
const failedCurrencies = {};
|
|
186
209
|
const networkControllerState = this.messenger.call('NetworkController:getState');
|
|
187
210
|
const networkConfigurations = networkControllerState.networkConfigurationsByChainId;
|
|
188
|
-
//
|
|
189
|
-
const currencyToChainIds = Object.entries(
|
|
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
|
-
|
|
196
|
-
|
|
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,
|
|
239
|
+
usdConversionRate: null,
|
|
219
240
|
};
|
|
220
241
|
}));
|
|
221
|
-
|
|
222
|
-
const [nativeCurrency, { chainId }] = currencyToChainIdsEntries[index];
|
|
223
|
-
if (result.status === 'fulfilled') {
|
|
224
|
-
|
|
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
|
-
|
|
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
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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":";;;;;;;;;;;;AAKA,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACtB,mCAAmC;AAOpC,OAAO,EAAE,+BAA+B,EAAE,qCAAqC;AAE/E,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAGpC,OAAO,EAAE,qBAAqB,EAAE,6CAAyC;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;AAcnC;;;GAGG;AACH,MAAM,OAAO,sBAAuB,SAAQ,+BAA+B,EAI1E;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;;QAvCI,wCAAS,IAAI,KAAK,EAAE,EAAC;QAErB,yDAAyB;QAEzB,8DAAoC;QAEpC,6DAAgD;QAkCvD,uBAAA,IAAI,0CAAmB,cAAc,MAAA,CAAC;QACtC,uBAAA,IAAI,+CAAwB,mBAAmB,MAAA,CAAC;QAChD,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,uBAAA,IAAI,qCAAO,CAAC,OAAO,EAAE,CAAC;QAChD,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;IA4ND;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CACtB,gBAAwC;QAExC,IAAI,CAAC,uBAAA,IAAI,mDAAqB,MAAzB,IAAI,CAAuB,EAAE,CAAC;YACjC,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,uBAAA,IAAI,qCAAO,CAAC,OAAO,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,wFAAwF;YACxF,kEAAkE;YAClE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,sBAAsB,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,qBAAqB;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;;AA/RC;;;;;;GAMG;AACH,KAAK,yDACH,uBAA+C,EAC/C,eAAuB;IAEvB,MAAM,KAAK,GAAuC,EAAE,CAAC;IACrD,IAAI,gBAAgB,GAA2B,EAAE,CAAC;IAElD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,kDAAoB,CAAC,kBAAkB,CAAC;YACjE,YAAY,EAAE,eAAe;YAC7B,cAAc,EAAE,uBAAA,IAAI,8CAAgB;YACpC,gBAAgB,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC;SACvE,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,OAAO,CAC7C,CAAC,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,EAAE;YACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC;YAErD,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;gBAChB,KAAK,CAAC,cAAc,CAAC,GAAG;oBACtB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI;oBACjC,cAAc,EAAE,sBAAsB,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;oBACtD,iBAAiB,EAAE,IAAI,EAAE,GAAG;wBAC1B,CAAC,CAAC,sBAAsB,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;wBACtC,CAAC,CAAC,IAAI;iBACT,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,gBAAgB,CAAC,cAAc,CAAC,GAAG,eAAe,CAAC;YACrD,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;QACxD,gBAAgB,GAAG,EAAE,GAAG,uBAAuB,EAAE,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,mEACH,iBAAyC,EACzC,eAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,KAAK,GAAuC,EAAE,CAAC;QACrD,MAAM,gBAAgB,GAA2B,EAAE,CAAC;QAEpD,MAAM,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAChD,4BAA4B,CAC7B,CAAC;QACF,MAAM,qBAAqB,GACzB,sBAAsB,CAAC,8BAA8B,CAAC;QAExD,mEAAmE;QACnE,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAEjE,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,EAAE;YAC3C,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,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,CAAC;iBAAM,CAAC;gBACN,qDAAqD;gBACrD,gBAAgB,CAAC,cAAc,CAAC,GAAG,eAAe,CAAC;YACrD,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,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,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAC1D,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;aACxB,CAAC;QACJ,CAAC,CAAC,CACH,CAAC;QAEF,YAAY,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YACrC,MAAM,CAAC,cAAc,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,GAClD,yBAAyB,CAAC,KAAK,CAAC,CAAC;YAEnC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACjE,KAAK,CAAC,cAAc,CAAC,GAAG;oBACtB,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc;oBAC3C,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc;oBAC3C,iBAAiB,EAAE,MAAM,CAAC,KAAK,CAAC,iBAAiB;iBAClD,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;oBACjC,OAAO,CAAC,KAAK,CACX,mCAAmC,cAAc,aAAa,OAAO,EAAE,EACvE,MAAM,CAAC,MAAM,CACd,CAAC;gBACJ,CAAC;gBACD,gBAAgB,CAAC,cAAc,CAAC,GAAG,eAAe,CAAC;YACrD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,2DAA2D,EAC3D,KAAK,CACN,CAAC;QACF,kCAAkC;QAClC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,GAAG,iBAAiB,EAAE,EAAE,CAAC;IACnE,CAAC;AACH,CAAC,uHASC,UAAoB;IAEpB,OAAO,UAAU,CAAC,MAAM,CACtB,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE;QACtB,GAAG,CAAC,cAAc,CAAC,GAAG;YACpB,cAAc,EAAE,IAAI;YACpB,cAAc,EAAE,IAAI;YACpB,iBAAiB,EAAE,IAAI;SACxB,CAAC;QACF,OAAO,GAAG,CAAC;IACb,CAAC,EACD,EAAE,CACH,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,KAAK,iEACH,uBAA+C;IAE/C,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IAEvC,iDAAiD;IACjD,MAAM,EACJ,KAAK,EAAE,aAAa,EACpB,gBAAgB,EAAE,4BAA4B,GAC/C,GAAG,MAAM,uBAAA,IAAI,yFAAwB,MAA5B,IAAI,EACZ,uBAAuB,EACvB,eAAe,CAChB,CAAC;IAEF,oDAAoD;IACpD,IAAI,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,oEAAoE;IACpE,MAAM,EACJ,KAAK,EAAE,iBAAiB,EACxB,gBAAgB,EAAE,4BAA4B,GAC/C,GAAG,MAAM,uBAAA,IAAI,mGAAkC,MAAtC,IAAI,EACZ,4BAA4B,EAC5B,eAAe,CAChB,CAAC;IAEF,uEAAuE;IACvE,MAAM,SAAS,GAAG,uBAAA,IAAI,+FAA8B,MAAlC,IAAI,EACpB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAC1C,CAAC;IAEF,4FAA4F;IAC5F,OAAO;QACL,GAAG,SAAS;QACZ,GAAG,iBAAiB;QACpB,GAAG,aAAa;KACjB,CAAC;AACJ,CAAC;AAyEH,eAAe,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 */\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-
|
|
3
|
+
"version": "95.1.0-preview-47cfb4e",
|
|
4
4
|
"description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MetaMask",
|