@metamask/assets-controllers 19.0.0 → 21.0.0
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 +100 -1
- package/dist/AccountTrackerController.d.ts +28 -7
- package/dist/AccountTrackerController.d.ts.map +1 -1
- package/dist/AccountTrackerController.js +104 -30
- package/dist/AccountTrackerController.js.map +1 -1
- package/dist/AssetsContractController.d.ts +5 -5
- package/dist/AssetsContractController.d.ts.map +1 -1
- package/dist/AssetsContractController.js +10 -4
- package/dist/AssetsContractController.js.map +1 -1
- package/dist/CurrencyRateController.d.ts +9 -14
- package/dist/CurrencyRateController.d.ts.map +1 -1
- package/dist/CurrencyRateController.js +1 -1
- package/dist/CurrencyRateController.js.map +1 -1
- package/dist/NftController.d.ts +2 -2
- package/dist/NftController.d.ts.map +1 -1
- package/dist/NftController.js +7 -3
- package/dist/NftController.js.map +1 -1
- package/dist/NftDetectionController.d.ts +2 -2
- package/dist/NftDetectionController.d.ts.map +1 -1
- package/dist/NftDetectionController.js +1 -1
- package/dist/NftDetectionController.js.map +1 -1
- package/dist/Standards/ERC20Standard.d.ts.map +1 -1
- package/dist/Standards/ERC20Standard.js +4 -0
- package/dist/Standards/ERC20Standard.js.map +1 -1
- package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.d.ts +11 -10
- package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.d.ts.map +1 -1
- package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js +106 -85
- package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js.map +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.d.ts.map +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.js +2 -0
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.js.map +1 -1
- package/dist/Standards/standards-types.d.ts.map +1 -1
- package/dist/Standards/standards-types.js.map +1 -1
- package/dist/TokenBalancesController.d.ts +2 -2
- package/dist/TokenBalancesController.d.ts.map +1 -1
- package/dist/TokenBalancesController.js +1 -1
- package/dist/TokenBalancesController.js.map +1 -1
- package/dist/TokenDetectionController.d.ts +2 -2
- package/dist/TokenDetectionController.d.ts.map +1 -1
- package/dist/TokenDetectionController.js +1 -1
- package/dist/TokenDetectionController.js.map +1 -1
- package/dist/TokenListController.d.ts +9 -13
- package/dist/TokenListController.d.ts.map +1 -1
- package/dist/TokenListController.js +3 -15
- package/dist/TokenListController.js.map +1 -1
- package/dist/TokenRatesController.d.ts +31 -62
- package/dist/TokenRatesController.d.ts.map +1 -1
- package/dist/TokenRatesController.js +207 -175
- package/dist/TokenRatesController.js.map +1 -1
- package/dist/TokensController.d.ts +8 -10
- package/dist/TokensController.d.ts.map +1 -1
- package/dist/TokensController.js +71 -22
- package/dist/TokensController.js.map +1 -1
- package/dist/assetsUtil.d.ts +38 -8
- package/dist/assetsUtil.d.ts.map +1 -1
- package/dist/assetsUtil.js +60 -27
- package/dist/assetsUtil.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/token-prices-service/abstract-token-prices-service.d.ts +62 -0
- package/dist/token-prices-service/abstract-token-prices-service.d.ts.map +1 -0
- package/dist/token-prices-service/abstract-token-prices-service.js +3 -0
- package/dist/token-prices-service/abstract-token-prices-service.js.map +1 -0
- package/dist/token-prices-service/codefi-v2.d.ts +80 -0
- package/dist/token-prices-service/codefi-v2.d.ts.map +1 -0
- package/dist/token-prices-service/codefi-v2.js +327 -0
- package/dist/token-prices-service/codefi-v2.js.map +1 -0
- package/dist/token-prices-service/index.d.ts +3 -0
- package/dist/token-prices-service/index.d.ts.map +1 -0
- package/dist/token-prices-service/index.js +6 -0
- package/dist/token-prices-service/index.js.map +1 -0
- package/dist/token-service.d.ts.map +1 -1
- package/dist/token-service.js +1 -1
- package/dist/token-service.js.map +1 -1
- package/package.json +14 -12
|
@@ -8,126 +8,130 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
12
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
13
|
-
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");
|
|
14
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
|
-
};
|
|
16
11
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
17
12
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
18
13
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
19
14
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
20
15
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
21
16
|
};
|
|
22
|
-
var
|
|
17
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
18
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
19
|
+
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");
|
|
20
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
21
|
+
};
|
|
22
|
+
var _TokenRatesController_instances, _TokenRatesController_pollState, _TokenRatesController_tokenPricesService, _TokenRatesController_inProcessExchangeRateUpdates, _TokenRatesController_getTokenAddresses, _TokenRatesController_stopPoll, _TokenRatesController_poll, _TokenRatesController_fetchAndMapExchangeRates, _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency, _TokenRatesController_fetchAndMapExchangeRatesForUnsupportedNativeCurrency;
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.TokenRatesController = void 0;
|
|
25
|
-
const base_controller_1 = require("@metamask/base-controller");
|
|
26
25
|
const controller_utils_1 = require("@metamask/controller-utils");
|
|
26
|
+
const polling_controller_1 = require("@metamask/polling-controller");
|
|
27
|
+
const lodash_1 = require("lodash");
|
|
28
|
+
const assetsUtil_1 = require("./assetsUtil");
|
|
27
29
|
const crypto_compare_1 = require("./crypto-compare");
|
|
28
30
|
var PollState;
|
|
29
31
|
(function (PollState) {
|
|
30
32
|
PollState["Active"] = "Active";
|
|
31
33
|
PollState["Inactive"] = "Inactive";
|
|
32
34
|
})(PollState || (PollState = {}));
|
|
33
|
-
const CoinGeckoApi = {
|
|
34
|
-
BASE_URL: 'https://api.coingecko.com/api/v3',
|
|
35
|
-
getTokenPriceURL(chainSlug, query) {
|
|
36
|
-
return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`;
|
|
37
|
-
},
|
|
38
|
-
getPlatformsURL() {
|
|
39
|
-
return `${this.BASE_URL}/asset_platforms`;
|
|
40
|
-
},
|
|
41
|
-
getSupportedVsCurrencies() {
|
|
42
|
-
return `${this.BASE_URL}/simple/supported_vs_currencies`;
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
35
|
/**
|
|
46
|
-
*
|
|
36
|
+
* The maximum number of token addresses that should be sent to the Price API in
|
|
37
|
+
* a single request.
|
|
38
|
+
*/
|
|
39
|
+
const TOKEN_PRICES_BATCH_SIZE = 100;
|
|
40
|
+
/**
|
|
41
|
+
* Uses the CryptoCompare API to fetch the exchange rate between one currency
|
|
42
|
+
* and another, i.e., the multiplier to apply the amount of one currency in
|
|
43
|
+
* order to convert it to another.
|
|
47
44
|
*
|
|
48
|
-
* @param
|
|
49
|
-
* @param
|
|
50
|
-
* @
|
|
45
|
+
* @param args - The arguments to this function.
|
|
46
|
+
* @param args.from - The currency to convert from.
|
|
47
|
+
* @param args.to - The currency to convert to.
|
|
48
|
+
* @returns The exchange rate between `fromCurrency` to `toCurrency` if one
|
|
49
|
+
* exists, or null if one does not.
|
|
51
50
|
*/
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
function getCurrencyConversionRate({ from, to, }) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const includeUSDRate = false;
|
|
54
|
+
try {
|
|
55
|
+
const result = yield (0, crypto_compare_1.fetchExchangeRate)(to, from, includeUSDRate);
|
|
56
|
+
return result.conversionRate;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error instanceof Error &&
|
|
60
|
+
error.message.includes('market does not exist for this coin pair')) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
68
|
* Controller that passively polls on a set interval for token-to-fiat exchange rates
|
|
62
69
|
* for tokens stored in the TokensController
|
|
63
70
|
*/
|
|
64
|
-
class TokenRatesController extends
|
|
71
|
+
class TokenRatesController extends polling_controller_1.StaticIntervalPollingControllerV1 {
|
|
65
72
|
/**
|
|
66
73
|
* Creates a TokenRatesController instance.
|
|
67
74
|
*
|
|
68
75
|
* @param options - The controller options.
|
|
76
|
+
* @param options.interval - The polling interval in ms
|
|
77
|
+
* @param options.threshold - The duration in ms before metadata fetched from CoinGecko is considered stale
|
|
78
|
+
* @param options.getNetworkClientById - Gets the network client with the given id from the NetworkController.
|
|
69
79
|
* @param options.chainId - The chain ID of the current network.
|
|
70
80
|
* @param options.ticker - The ticker for the current network.
|
|
71
81
|
* @param options.selectedAddress - The current selected address.
|
|
72
82
|
* @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.
|
|
73
83
|
* @param options.onTokensStateChange - Allows subscribing to token controller state changes.
|
|
74
84
|
* @param options.onNetworkStateChange - Allows subscribing to network state changes.
|
|
85
|
+
* @param options.tokenPricesService - An object in charge of retrieving token prices.
|
|
75
86
|
* @param config - Initial options used to configure this controller.
|
|
76
87
|
* @param state - Initial state to set on this controller.
|
|
77
88
|
*/
|
|
78
|
-
constructor({ chainId: initialChainId, ticker: initialTicker, selectedAddress: initialSelectedAddress, onPreferencesStateChange, onTokensStateChange, onNetworkStateChange, }, config, state) {
|
|
89
|
+
constructor({ interval = 3 * 60 * 1000, threshold = 6 * 60 * 60 * 1000, getNetworkClientById, chainId: initialChainId, ticker: initialTicker, selectedAddress: initialSelectedAddress, onPreferencesStateChange, onTokensStateChange, onNetworkStateChange, tokenPricesService, }, config, state) {
|
|
79
90
|
super(config, state);
|
|
80
91
|
_TokenRatesController_instances.add(this);
|
|
81
|
-
this.tokenList = [];
|
|
82
|
-
this.supportedChains = {
|
|
83
|
-
timestamp: 0,
|
|
84
|
-
data: null,
|
|
85
|
-
};
|
|
86
|
-
this.supportedVsCurrencies = {
|
|
87
|
-
timestamp: 0,
|
|
88
|
-
data: [],
|
|
89
|
-
};
|
|
90
92
|
_TokenRatesController_pollState.set(this, PollState.Inactive);
|
|
93
|
+
_TokenRatesController_tokenPricesService.set(this, void 0);
|
|
94
|
+
_TokenRatesController_inProcessExchangeRateUpdates.set(this, {});
|
|
91
95
|
/**
|
|
92
96
|
* Name of this controller used during composition
|
|
93
97
|
*/
|
|
94
98
|
this.name = 'TokenRatesController';
|
|
95
99
|
this.defaultConfig = {
|
|
100
|
+
interval,
|
|
101
|
+
threshold,
|
|
96
102
|
disabled: false,
|
|
97
|
-
interval: 3 * 60 * 1000,
|
|
98
103
|
nativeCurrency: initialTicker,
|
|
99
104
|
chainId: initialChainId,
|
|
100
105
|
selectedAddress: initialSelectedAddress,
|
|
101
106
|
allTokens: {},
|
|
102
107
|
allDetectedTokens: {},
|
|
103
|
-
threshold: 6 * 60 * 60 * 1000,
|
|
104
108
|
};
|
|
105
109
|
this.defaultState = {
|
|
106
110
|
contractExchangeRates: {},
|
|
111
|
+
contractExchangeRatesByChainId: {},
|
|
107
112
|
};
|
|
108
113
|
this.initialize();
|
|
114
|
+
this.setIntervalLength(interval);
|
|
115
|
+
this.getNetworkClientById = getNetworkClientById;
|
|
116
|
+
__classPrivateFieldSet(this, _TokenRatesController_tokenPricesService, tokenPricesService, "f");
|
|
109
117
|
if (config === null || config === void 0 ? void 0 : config.disabled) {
|
|
110
118
|
this.configure({ disabled: true }, false, false);
|
|
111
119
|
}
|
|
112
|
-
__classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this);
|
|
113
120
|
onPreferencesStateChange(({ selectedAddress }) => __awaiter(this, void 0, void 0, function* () {
|
|
114
121
|
if (this.config.selectedAddress !== selectedAddress) {
|
|
115
122
|
this.configure({ selectedAddress });
|
|
116
|
-
__classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this);
|
|
117
123
|
if (__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active) {
|
|
118
124
|
yield this.updateExchangeRates();
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
}));
|
|
122
128
|
onTokensStateChange(({ allTokens, allDetectedTokens }) => __awaiter(this, void 0, void 0, function* () {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
__classPrivateFieldGet(this,
|
|
128
|
-
|
|
129
|
-
yield this.updateExchangeRates();
|
|
130
|
-
}
|
|
129
|
+
const previousTokenAddresses = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_getTokenAddresses).call(this, this.config.chainId);
|
|
130
|
+
this.configure({ allTokens, allDetectedTokens });
|
|
131
|
+
const newTokenAddresses = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_getTokenAddresses).call(this, this.config.chainId);
|
|
132
|
+
if (!(0, lodash_1.isEqual)(previousTokenAddresses, newTokenAddresses) &&
|
|
133
|
+
__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active) {
|
|
134
|
+
yield this.updateExchangeRates();
|
|
131
135
|
}
|
|
132
136
|
}));
|
|
133
137
|
onNetworkStateChange(({ providerConfig }) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -136,7 +140,6 @@ class TokenRatesController extends base_controller_1.BaseController {
|
|
|
136
140
|
this.config.nativeCurrency !== ticker) {
|
|
137
141
|
this.update({ contractExchangeRates: {} });
|
|
138
142
|
this.configure({ chainId, nativeCurrency: ticker });
|
|
139
|
-
__classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_updateTokenList).call(this);
|
|
140
143
|
if (__classPrivateFieldGet(this, _TokenRatesController_pollState, "f") === PollState.Active) {
|
|
141
144
|
yield this.updateExchangeRates();
|
|
142
145
|
}
|
|
@@ -161,154 +164,97 @@ class TokenRatesController extends base_controller_1.BaseController {
|
|
|
161
164
|
__classPrivateFieldSet(this, _TokenRatesController_pollState, PollState.Inactive, "f");
|
|
162
165
|
}
|
|
163
166
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* @param chainSlug - Chain string identifier.
|
|
167
|
-
* @param vsCurrency - Query according to tokens in tokenList and native currency.
|
|
168
|
-
* @returns The exchange rates for the given pairs.
|
|
167
|
+
* Updates exchange rates for all tokens.
|
|
169
168
|
*/
|
|
170
|
-
|
|
169
|
+
updateExchangeRates() {
|
|
171
170
|
return __awaiter(this, void 0, void 0, function* () {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const { chainId, nativeCurrency } = this.config;
|
|
172
|
+
yield this.updateExchangeRatesByChainId({
|
|
173
|
+
chainId,
|
|
174
|
+
nativeCurrency,
|
|
175
|
+
});
|
|
175
176
|
});
|
|
176
177
|
}
|
|
177
178
|
/**
|
|
178
|
-
*
|
|
179
|
-
* to query for token exchange rates.
|
|
179
|
+
* Updates exchange rates for all tokens.
|
|
180
180
|
*
|
|
181
|
-
* @param
|
|
182
|
-
* @
|
|
181
|
+
* @param options - The options to fetch exchange rates.
|
|
182
|
+
* @param options.chainId - The chain ID.
|
|
183
|
+
* @param options.nativeCurrency - The ticker for the chain.
|
|
183
184
|
*/
|
|
184
|
-
|
|
185
|
+
updateExchangeRatesByChainId({ chainId, nativeCurrency, }) {
|
|
186
|
+
var _a;
|
|
185
187
|
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const now = Date.now();
|
|
189
|
-
if (now - timestamp > threshold) {
|
|
190
|
-
const currencies = yield (0, controller_utils_1.handleFetch)(CoinGeckoApi.getSupportedVsCurrencies());
|
|
191
|
-
this.supportedVsCurrencies = {
|
|
192
|
-
data: currencies,
|
|
193
|
-
timestamp: Date.now(),
|
|
194
|
-
};
|
|
195
|
-
return currencies.includes(nativeCurrency.toLowerCase());
|
|
188
|
+
if (this.disabled) {
|
|
189
|
+
return;
|
|
196
190
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Gets current chain ID slug from cached supported platforms CoinGecko API response.
|
|
202
|
-
* If cached supported platforms response is stale, fetches and updates it.
|
|
203
|
-
*
|
|
204
|
-
* @returns The CoinGecko slug for the current chain ID.
|
|
205
|
-
*/
|
|
206
|
-
getChainSlug() {
|
|
207
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
-
const { threshold, chainId } = this.config;
|
|
209
|
-
const { data, timestamp } = this.supportedChains;
|
|
210
|
-
const now = Date.now();
|
|
211
|
-
if (now - timestamp > threshold) {
|
|
212
|
-
const platforms = yield (0, controller_utils_1.handleFetch)(CoinGeckoApi.getPlatformsURL());
|
|
213
|
-
this.supportedChains = {
|
|
214
|
-
data: platforms,
|
|
215
|
-
timestamp: Date.now(),
|
|
216
|
-
};
|
|
217
|
-
return findChainSlug(chainId, platforms);
|
|
191
|
+
const tokenAddresses = __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_getTokenAddresses).call(this, chainId);
|
|
192
|
+
if (tokenAddresses.length === 0) {
|
|
193
|
+
return;
|
|
218
194
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
updateExchangeRates() {
|
|
226
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
-
if (this.tokenList.length === 0 || this.disabled) {
|
|
195
|
+
const updateKey = `${chainId}:${nativeCurrency}`;
|
|
196
|
+
if (updateKey in __classPrivateFieldGet(this, _TokenRatesController_inProcessExchangeRateUpdates, "f")) {
|
|
197
|
+
// This prevents redundant updates
|
|
198
|
+
// This promise is resolved after the in-progress update has finished,
|
|
199
|
+
// and state has been updated.
|
|
200
|
+
yield __classPrivateFieldGet(this, _TokenRatesController_inProcessExchangeRateUpdates, "f")[updateKey];
|
|
228
201
|
return;
|
|
229
202
|
}
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
203
|
+
const { promise: inProgressUpdate, resolve: updateSucceeded, reject: updateFailed, } = createDeferredPromise({ suppressUnhandledRejection: true });
|
|
204
|
+
__classPrivateFieldGet(this, _TokenRatesController_inProcessExchangeRateUpdates, "f")[updateKey] = inProgressUpdate;
|
|
205
|
+
try {
|
|
206
|
+
const newContractExchangeRates = yield __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRates).call(this, {
|
|
207
|
+
tokenAddresses,
|
|
208
|
+
chainId,
|
|
209
|
+
nativeCurrency,
|
|
236
210
|
});
|
|
211
|
+
const existingContractExchangeRates = this.state.contractExchangeRates;
|
|
212
|
+
const updatedContractExchangeRates = chainId === this.config.chainId &&
|
|
213
|
+
nativeCurrency === this.config.nativeCurrency
|
|
214
|
+
? newContractExchangeRates
|
|
215
|
+
: existingContractExchangeRates;
|
|
216
|
+
const existingContractExchangeRatesForChainId = (_a = this.state.contractExchangeRatesByChainId[chainId]) !== null && _a !== void 0 ? _a : {};
|
|
217
|
+
const updatedContractExchangeRatesForChainId = Object.assign(Object.assign({}, this.state.contractExchangeRatesByChainId), { [chainId]: Object.assign(Object.assign({}, existingContractExchangeRatesForChainId), { [nativeCurrency]: Object.assign(Object.assign({}, existingContractExchangeRatesForChainId[nativeCurrency]), newContractExchangeRates) }) });
|
|
218
|
+
this.update({
|
|
219
|
+
contractExchangeRates: updatedContractExchangeRates,
|
|
220
|
+
contractExchangeRatesByChainId: updatedContractExchangeRatesForChainId,
|
|
221
|
+
});
|
|
222
|
+
updateSucceeded();
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
updateFailed(error);
|
|
226
|
+
throw error;
|
|
237
227
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug);
|
|
228
|
+
finally {
|
|
229
|
+
delete __classPrivateFieldGet(this, _TokenRatesController_inProcessExchangeRateUpdates, "f")[updateKey];
|
|
241
230
|
}
|
|
242
|
-
this.update({ contractExchangeRates: newContractExchangeRates });
|
|
243
231
|
});
|
|
244
232
|
}
|
|
245
233
|
/**
|
|
246
|
-
*
|
|
247
|
-
* If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI.
|
|
248
|
-
* If not supported, it fetches contractExchange rates and maps them from token/fallback-currency
|
|
249
|
-
* to token/nativeCurrency.
|
|
234
|
+
* Updates token rates for the given networkClientId
|
|
250
235
|
*
|
|
251
|
-
* @param
|
|
252
|
-
* @
|
|
253
|
-
* should be used to query token exchange rates.
|
|
254
|
-
* @returns An object with conversion rates for each token
|
|
255
|
-
* related to the network's native currency.
|
|
236
|
+
* @param networkClientId - The network client ID used to get a ticker value.
|
|
237
|
+
* @returns The controller state.
|
|
256
238
|
*/
|
|
257
|
-
|
|
239
|
+
_executePoll(networkClientId) {
|
|
258
240
|
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const prices = yield this.fetchExchangeRate(slug, nativeCurrency);
|
|
265
|
-
this.tokenList.forEach((token) => {
|
|
266
|
-
const price = prices[token.address.toLowerCase()];
|
|
267
|
-
contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(token.address)] = price
|
|
268
|
-
? price[nativeCurrency.toLowerCase()]
|
|
269
|
-
: 0;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
// if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates
|
|
274
|
-
// in token/fallback-currency format and convert them to expected token/nativeCurrency format.
|
|
275
|
-
let tokenExchangeRates;
|
|
276
|
-
let vsCurrencyToNativeCurrencyConversionRate = 0;
|
|
277
|
-
try {
|
|
278
|
-
[
|
|
279
|
-
tokenExchangeRates,
|
|
280
|
-
{ conversionRate: vsCurrencyToNativeCurrencyConversionRate },
|
|
281
|
-
] = yield Promise.all([
|
|
282
|
-
this.fetchExchangeRate(slug, controller_utils_1.FALL_BACK_VS_CURRENCY),
|
|
283
|
-
(0, crypto_compare_1.fetchExchangeRate)(nativeCurrency, controller_utils_1.FALL_BACK_VS_CURRENCY, false),
|
|
284
|
-
]);
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
if (error instanceof Error &&
|
|
288
|
-
error.message.includes('market does not exist for this coin pair')) {
|
|
289
|
-
return {};
|
|
290
|
-
}
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) {
|
|
294
|
-
const tokenToVsCurrencyConversionRate = conversion[controller_utils_1.FALL_BACK_VS_CURRENCY.toLowerCase()];
|
|
295
|
-
contractExchangeRates[(0, controller_utils_1.toChecksumHexAddress)(tokenAddress)] =
|
|
296
|
-
tokenToVsCurrencyConversionRate *
|
|
297
|
-
vsCurrencyToNativeCurrencyConversionRate;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return contractExchangeRates;
|
|
241
|
+
const networkClient = this.getNetworkClientById(networkClientId);
|
|
242
|
+
yield this.updateExchangeRatesByChainId({
|
|
243
|
+
chainId: networkClient.configuration.chainId,
|
|
244
|
+
nativeCurrency: networkClient.configuration.ticker,
|
|
245
|
+
});
|
|
301
246
|
});
|
|
302
247
|
}
|
|
303
248
|
}
|
|
304
249
|
exports.TokenRatesController = TokenRatesController;
|
|
305
|
-
_TokenRatesController_pollState = new WeakMap(), _TokenRatesController_instances = new WeakSet(),
|
|
250
|
+
_TokenRatesController_pollState = new WeakMap(), _TokenRatesController_tokenPricesService = new WeakMap(), _TokenRatesController_inProcessExchangeRateUpdates = new WeakMap(), _TokenRatesController_instances = new WeakSet(), _TokenRatesController_getTokenAddresses = function _TokenRatesController_getTokenAddresses(chainId) {
|
|
306
251
|
var _a, _b;
|
|
307
252
|
const { allTokens, allDetectedTokens } = this.config;
|
|
308
|
-
const tokens = ((_a = allTokens[
|
|
309
|
-
const detectedTokens = ((_b = allDetectedTokens[
|
|
310
|
-
|
|
311
|
-
|
|
253
|
+
const tokens = ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[this.config.selectedAddress]) || [];
|
|
254
|
+
const detectedTokens = ((_b = allDetectedTokens[chainId]) === null || _b === void 0 ? void 0 : _b[this.config.selectedAddress]) || [];
|
|
255
|
+
return [
|
|
256
|
+
...new Set([...tokens, ...detectedTokens].map((token) => (0, controller_utils_1.toHex)((0, controller_utils_1.toChecksumHexAddress)(token.address)))),
|
|
257
|
+
].sort();
|
|
312
258
|
}, _TokenRatesController_stopPoll = function _TokenRatesController_stopPoll() {
|
|
313
259
|
if (this.handle) {
|
|
314
260
|
clearTimeout(this.handle);
|
|
@@ -322,6 +268,92 @@ _TokenRatesController_pollState = new WeakMap(), _TokenRatesController_instances
|
|
|
322
268
|
__classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_poll).call(this);
|
|
323
269
|
}, this.config.interval);
|
|
324
270
|
});
|
|
271
|
+
}, _TokenRatesController_fetchAndMapExchangeRates = function _TokenRatesController_fetchAndMapExchangeRates({ tokenAddresses, chainId, nativeCurrency, }) {
|
|
272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
if (!__classPrivateFieldGet(this, _TokenRatesController_tokenPricesService, "f").validateChainIdSupported(chainId)) {
|
|
274
|
+
return tokenAddresses.reduce((obj, tokenAddress) => {
|
|
275
|
+
return Object.assign(Object.assign({}, obj), { [tokenAddress]: undefined });
|
|
276
|
+
}, {});
|
|
277
|
+
}
|
|
278
|
+
if (__classPrivateFieldGet(this, _TokenRatesController_tokenPricesService, "f").validateCurrencySupported(nativeCurrency)) {
|
|
279
|
+
return yield __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency).call(this, {
|
|
280
|
+
tokenAddresses,
|
|
281
|
+
chainId,
|
|
282
|
+
nativeCurrency,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return yield __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForUnsupportedNativeCurrency).call(this, {
|
|
286
|
+
tokenAddresses,
|
|
287
|
+
nativeCurrency,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}, _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency = function _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency({ tokenAddresses, chainId, nativeCurrency, }) {
|
|
291
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
+
const tokenPricesByTokenAddress = yield (0, assetsUtil_1.reduceInBatchesSerially)({
|
|
293
|
+
values: tokenAddresses,
|
|
294
|
+
batchSize: TOKEN_PRICES_BATCH_SIZE,
|
|
295
|
+
eachBatch: (allTokenPricesByTokenAddress, batch) => __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const tokenPricesByTokenAddressForBatch = yield __classPrivateFieldGet(this, _TokenRatesController_tokenPricesService, "f").fetchTokenPrices({
|
|
297
|
+
tokenAddresses: batch,
|
|
298
|
+
chainId,
|
|
299
|
+
currency: nativeCurrency,
|
|
300
|
+
});
|
|
301
|
+
return Object.assign(Object.assign({}, allTokenPricesByTokenAddress), tokenPricesByTokenAddressForBatch);
|
|
302
|
+
}),
|
|
303
|
+
initialResult: {},
|
|
304
|
+
});
|
|
305
|
+
return Object.entries(tokenPricesByTokenAddress).reduce((obj, [tokenAddress, tokenPrice]) => {
|
|
306
|
+
return Object.assign(Object.assign({}, obj), { [tokenAddress]: tokenPrice.value });
|
|
307
|
+
}, {});
|
|
308
|
+
});
|
|
309
|
+
}, _TokenRatesController_fetchAndMapExchangeRatesForUnsupportedNativeCurrency = function _TokenRatesController_fetchAndMapExchangeRatesForUnsupportedNativeCurrency({ tokenAddresses, nativeCurrency, }) {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
const [contractExchangeRates, fallbackCurrencyToNativeCurrencyConversionRate,] = yield Promise.all([
|
|
312
|
+
__classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_fetchAndMapExchangeRatesForSupportedNativeCurrency).call(this, {
|
|
313
|
+
tokenAddresses,
|
|
314
|
+
chainId: this.config.chainId,
|
|
315
|
+
nativeCurrency: controller_utils_1.FALL_BACK_VS_CURRENCY,
|
|
316
|
+
}),
|
|
317
|
+
getCurrencyConversionRate({
|
|
318
|
+
from: controller_utils_1.FALL_BACK_VS_CURRENCY,
|
|
319
|
+
to: nativeCurrency,
|
|
320
|
+
}),
|
|
321
|
+
]);
|
|
322
|
+
if (fallbackCurrencyToNativeCurrencyConversionRate === null) {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
return Object.entries(contractExchangeRates).reduce((obj, [tokenAddress, tokenValue]) => {
|
|
326
|
+
return Object.assign(Object.assign({}, obj), { [tokenAddress]: tokenValue
|
|
327
|
+
? tokenValue * fallbackCurrencyToNativeCurrencyConversionRate
|
|
328
|
+
: undefined });
|
|
329
|
+
}, {});
|
|
330
|
+
});
|
|
325
331
|
};
|
|
332
|
+
/**
|
|
333
|
+
* Create a defered Promise.
|
|
334
|
+
*
|
|
335
|
+
* TODO: Migrate this to utils
|
|
336
|
+
*
|
|
337
|
+
* @param args - The arguments.
|
|
338
|
+
* @param args.suppressUnhandledRejection - This option adds an empty error handler
|
|
339
|
+
* to the Promise to suppress the UnhandledPromiseRejection error. This can be
|
|
340
|
+
* useful if the deferred Promise is sometimes intentionally not used.
|
|
341
|
+
* @returns A deferred Promise.
|
|
342
|
+
*/
|
|
343
|
+
function createDeferredPromise({ suppressUnhandledRejection = false, }) {
|
|
344
|
+
let resolve;
|
|
345
|
+
let reject;
|
|
346
|
+
const promise = new Promise((innerResolve, innerReject) => {
|
|
347
|
+
resolve = innerResolve;
|
|
348
|
+
reject = innerReject;
|
|
349
|
+
});
|
|
350
|
+
if (suppressUnhandledRejection) {
|
|
351
|
+
promise.catch((_error) => {
|
|
352
|
+
// This handler is used to suppress the UnhandledPromiseRejection error
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
// @ts-expect-error We know that these are assigned, but TypeScript doesn't
|
|
356
|
+
return { promise, resolve, reject };
|
|
357
|
+
}
|
|
326
358
|
exports.default = TokenRatesController;
|
|
327
359
|
//# sourceMappingURL=TokenRatesController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenRatesController.js","sourceRoot":"","sources":["../src/TokenRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport {\n safelyExecute,\n handleFetch,\n toChecksumHexAddress,\n FALL_BACK_VS_CURRENCY,\n toHex,\n} from '@metamask/controller-utils';\nimport type { NetworkState } from '@metamask/network-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { fetchExchangeRate as fetchNativeExchangeRate } from './crypto-compare';\nimport type { TokensState } from './TokensController';\n\n/**\n * @type CoinGeckoResponse\n *\n * CoinGecko API response representation\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface CoinGeckoResponse {\n [address: string]: {\n [currency: string]: number;\n };\n}\n/**\n * @type CoinGeckoPlatform\n *\n * CoinGecko supported platform API representation\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface CoinGeckoPlatform {\n id: string;\n chain_identifier: null | number;\n name: string;\n shortname: string;\n}\n\n/**\n * @type Token\n *\n * Token representation\n * @property address - Hex address of the token contract\n * @property decimals - Number of decimals the token uses\n * @property symbol - Symbol of the token\n * @property image - Image of the token, url or bit32 image\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface Token {\n address: string;\n decimals: number;\n symbol: string;\n aggregators?: string[];\n image?: string;\n balanceError?: unknown;\n isERC721?: boolean;\n name?: string;\n}\n\n/**\n * @type TokenRatesConfig\n *\n * Token rates controller configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property nativeCurrency - Current native currency selected to use base of rates\n * @property chainId - Current network chainId\n * @property tokens - List of tokens to track exchange rates for\n * @property threshold - Threshold to invalidate the supportedChains\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesConfig extends BaseConfig {\n interval: number;\n nativeCurrency: string;\n chainId: Hex;\n selectedAddress: string;\n allTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n allDetectedTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n threshold: number;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface ContractExchangeRates {\n [address: string]: number | undefined;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface SupportedChainsCache {\n timestamp: number;\n data: CoinGeckoPlatform[] | null;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface SupportedVsCurrenciesCache {\n timestamp: number;\n data: string[];\n}\n\nenum PollState {\n Active = 'Active',\n Inactive = 'Inactive',\n}\n\n/**\n * @type TokenRatesState\n *\n * Token rates controller state\n * @property contractExchangeRates - Hash of token contract addresses to exchange rates\n * @property supportedChains - Cached chain data\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesState extends BaseState {\n contractExchangeRates: ContractExchangeRates;\n}\n\nconst CoinGeckoApi = {\n BASE_URL: 'https://api.coingecko.com/api/v3',\n getTokenPriceURL(chainSlug: string, query: string) {\n return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`;\n },\n getPlatformsURL() {\n return `${this.BASE_URL}/asset_platforms`;\n },\n getSupportedVsCurrencies() {\n return `${this.BASE_URL}/simple/supported_vs_currencies`;\n },\n};\n\n/**\n * Finds the chain slug in the data array given a chainId.\n *\n * @param chainId - The current chain ID.\n * @param data - A list platforms supported by the CoinGecko API.\n * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found.\n */\nfunction findChainSlug(\n chainId: Hex,\n data: CoinGeckoPlatform[] | null,\n): string | null {\n if (!data) {\n return null;\n }\n const chain =\n data.find(\n ({ chain_identifier }) =>\n chain_identifier !== null && toHex(chain_identifier) === chainId,\n ) ?? null;\n return chain?.id || null;\n}\n\n/**\n * Controller that passively polls on a set interval for token-to-fiat exchange rates\n * for tokens stored in the TokensController\n */\nexport class TokenRatesController extends BaseController<\n TokenRatesConfig,\n TokenRatesState\n> {\n private handle?: ReturnType<typeof setTimeout>;\n\n private tokenList: Token[] = [];\n\n private supportedChains: SupportedChainsCache = {\n timestamp: 0,\n data: null,\n };\n\n private supportedVsCurrencies: SupportedVsCurrenciesCache = {\n timestamp: 0,\n data: [],\n };\n\n #pollState = PollState.Inactive;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenRatesController';\n\n /**\n * Creates a TokenRatesController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.ticker - The ticker for the current network.\n * @param options.selectedAddress - The current selected address.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onTokensStateChange - Allows subscribing to token controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network state changes.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n chainId: initialChainId,\n ticker: initialTicker,\n selectedAddress: initialSelectedAddress,\n onPreferencesStateChange,\n onTokensStateChange,\n onNetworkStateChange,\n }: {\n chainId: Hex;\n ticker: string;\n selectedAddress: string;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onTokensStateChange: (\n listener: (tokensState: TokensState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n },\n config?: Partial<TokenRatesConfig>,\n state?: Partial<TokenRatesState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n disabled: false,\n interval: 3 * 60 * 1000,\n nativeCurrency: initialTicker,\n chainId: initialChainId,\n selectedAddress: initialSelectedAddress,\n allTokens: {}, // TODO: initialize these correctly, maybe as part of BaseControllerV2 migration\n allDetectedTokens: {},\n threshold: 6 * 60 * 60 * 1000,\n };\n\n this.defaultState = {\n contractExchangeRates: {},\n };\n this.initialize();\n if (config?.disabled) {\n this.configure({ disabled: true }, false, false);\n }\n this.#updateTokenList();\n\n onPreferencesStateChange(async ({ selectedAddress }) => {\n if (this.config.selectedAddress !== selectedAddress) {\n this.configure({ selectedAddress });\n this.#updateTokenList();\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n\n onTokensStateChange(async ({ allTokens, allDetectedTokens }) => {\n // These two state properties are assumed to be immutable\n if (\n this.config.allTokens !== allTokens ||\n this.config.allDetectedTokens !== allDetectedTokens\n ) {\n this.configure({ allTokens, allDetectedTokens });\n this.#updateTokenList();\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n\n onNetworkStateChange(async ({ providerConfig }) => {\n const { chainId, ticker } = providerConfig;\n if (\n this.config.chainId !== chainId ||\n this.config.nativeCurrency !== ticker\n ) {\n this.update({ contractExchangeRates: {} });\n this.configure({ chainId, nativeCurrency: ticker });\n this.#updateTokenList();\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n }\n\n #updateTokenList() {\n const { allTokens, allDetectedTokens } = this.config;\n const tokens =\n allTokens[this.config.chainId]?.[this.config.selectedAddress] || [];\n const detectedTokens =\n allDetectedTokens[this.config.chainId]?.[this.config.selectedAddress] ||\n [];\n this.tokenList = [...tokens, ...detectedTokens];\n }\n\n /**\n * Start (or restart) polling.\n */\n async start() {\n this.#stopPoll();\n this.#pollState = PollState.Active;\n await this.#poll();\n }\n\n /**\n * Stop polling.\n */\n stop() {\n this.#stopPoll();\n this.#pollState = PollState.Inactive;\n }\n\n /**\n * Clear the active polling timer, if present.\n */\n #stopPoll() {\n if (this.handle) {\n clearTimeout(this.handle);\n }\n }\n\n /**\n * Poll for exchange rate updates.\n */\n async #poll() {\n await safelyExecute(() => this.updateExchangeRates());\n\n // Poll using recursive `setTimeout` instead of `setInterval` so that\n // requests don't stack if they take longer than the polling interval\n this.handle = setTimeout(() => {\n this.#poll();\n }, this.config.interval);\n }\n\n /**\n * Fetches a pairs of token address and native currency.\n *\n * @param chainSlug - Chain string identifier.\n * @param vsCurrency - Query according to tokens in tokenList and native currency.\n * @returns The exchange rates for the given pairs.\n */\n async fetchExchangeRate(\n chainSlug: string,\n vsCurrency: string,\n ): Promise<CoinGeckoResponse> {\n const tokenPairs = this.tokenList.map((token) => token.address).join(',');\n const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`;\n return handleFetch(CoinGeckoApi.getTokenPriceURL(chainSlug, query));\n }\n\n /**\n * Checks if the current native currency is a supported vs currency to use\n * to query for token exchange rates.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @returns A boolean indicating whether it's a supported vsCurrency.\n */\n private async checkIsSupportedVsCurrency(nativeCurrency: string) {\n const { threshold } = this.config;\n const { timestamp, data } = this.supportedVsCurrencies;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const currencies = await handleFetch(\n CoinGeckoApi.getSupportedVsCurrencies(),\n );\n this.supportedVsCurrencies = {\n data: currencies,\n timestamp: Date.now(),\n };\n return currencies.includes(nativeCurrency.toLowerCase());\n }\n\n return data.includes(nativeCurrency.toLowerCase());\n }\n\n /**\n * Gets current chain ID slug from cached supported platforms CoinGecko API response.\n * If cached supported platforms response is stale, fetches and updates it.\n *\n * @returns The CoinGecko slug for the current chain ID.\n */\n async getChainSlug(): Promise<string | null> {\n const { threshold, chainId } = this.config;\n const { data, timestamp } = this.supportedChains;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const platforms = await handleFetch(CoinGeckoApi.getPlatformsURL());\n this.supportedChains = {\n data: platforms,\n timestamp: Date.now(),\n };\n return findChainSlug(chainId, platforms);\n }\n\n return findChainSlug(chainId, data);\n }\n\n /**\n * Updates exchange rates for all tokens.\n */\n async updateExchangeRates() {\n if (this.tokenList.length === 0 || this.disabled) {\n return;\n }\n const slug = await this.getChainSlug();\n\n let newContractExchangeRates: ContractExchangeRates = {};\n if (!slug) {\n this.tokenList.forEach((token) => {\n const address = toChecksumHexAddress(token.address);\n newContractExchangeRates[address] = undefined;\n });\n } else {\n const { nativeCurrency } = this.config;\n newContractExchangeRates = await this.fetchAndMapExchangeRates(\n nativeCurrency,\n slug,\n );\n }\n this.update({ contractExchangeRates: newContractExchangeRates });\n }\n\n /**\n * Checks if the active network's native currency is supported by the coingecko API.\n * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI.\n * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency\n * to token/nativeCurrency.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @param slug - The unique slug used to id the chain by the coingecko api\n * should be used to query token exchange rates.\n * @returns An object with conversion rates for each token\n * related to the network's native currency.\n */\n async fetchAndMapExchangeRates(\n nativeCurrency: string,\n slug: string,\n ): Promise<ContractExchangeRates> {\n const contractExchangeRates: ContractExchangeRates = {};\n\n // check if native currency is supported as a vs_currency by the API\n const nativeCurrencySupported = await this.checkIsSupportedVsCurrency(\n nativeCurrency,\n );\n\n if (nativeCurrencySupported) {\n // If it is we can do a simple fetch against the CoinGecko API\n const prices = await this.fetchExchangeRate(slug, nativeCurrency);\n this.tokenList.forEach((token) => {\n const price = prices[token.address.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(token.address)] = price\n ? price[nativeCurrency.toLowerCase()]\n : 0;\n });\n } else {\n // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates\n // in token/fallback-currency format and convert them to expected token/nativeCurrency format.\n let tokenExchangeRates;\n let vsCurrencyToNativeCurrencyConversionRate = 0;\n try {\n [\n tokenExchangeRates,\n { conversionRate: vsCurrencyToNativeCurrencyConversionRate },\n ] = await Promise.all([\n this.fetchExchangeRate(slug, FALL_BACK_VS_CURRENCY),\n fetchNativeExchangeRate(nativeCurrency, FALL_BACK_VS_CURRENCY, false),\n ]);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n ) {\n return {};\n }\n throw error;\n }\n\n for (const [tokenAddress, conversion] of Object.entries(\n tokenExchangeRates,\n )) {\n const tokenToVsCurrencyConversionRate =\n conversion[FALL_BACK_VS_CURRENCY.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(tokenAddress)] =\n tokenToVsCurrencyConversionRate *\n vsCurrencyToNativeCurrencyConversionRate;\n }\n }\n\n return contractExchangeRates;\n }\n}\n\nexport default TokenRatesController;\n"]}
|
|
1
|
+
{"version":3,"file":"TokenRatesController.js","sourceRoot":"","sources":["../src/TokenRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport {\n safelyExecute,\n toChecksumHexAddress,\n FALL_BACK_VS_CURRENCY,\n toHex,\n} from '@metamask/controller-utils';\nimport type {\n NetworkClientId,\n NetworkController,\n NetworkState,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\nimport { isEqual } from 'lodash';\n\nimport { reduceInBatchesSerially } from './assetsUtil';\nimport { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare';\nimport type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service';\nimport type { TokensState } from './TokensController';\n\n/**\n * @type Token\n *\n * Token representation\n * @property address - Hex address of the token contract\n * @property decimals - Number of decimals the token uses\n * @property symbol - Symbol of the token\n * @property image - Image of the token, url or bit32 image\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface Token {\n address: string;\n decimals: number;\n symbol: string;\n aggregators?: string[];\n image?: string;\n balanceError?: unknown;\n isERC721?: boolean;\n name?: string;\n}\n\n/**\n * @type TokenRatesConfig\n *\n * Token rates controller configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property nativeCurrency - Current native currency selected to use base of rates\n * @property chainId - Current network chainId\n * @property tokens - List of tokens to track exchange rates for\n * @property threshold - Threshold to invalidate the supportedChains\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesConfig extends BaseConfig {\n interval: number;\n nativeCurrency: string;\n chainId: Hex;\n selectedAddress: string;\n allTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n allDetectedTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n threshold: number;\n}\n\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface ContractExchangeRates {\n [address: string]: number | undefined;\n}\n\nenum PollState {\n Active = 'Active',\n Inactive = 'Inactive',\n}\n\n/**\n * @type TokenRatesState\n *\n * Token rates controller state\n * @property contractExchangeRates - Hash of token contract addresses to exchange rates (single globally selected chain, will be deprecated soon)\n * @property contractExchangeRatesByChainId - Hash of token contract addresses to exchange rates keyed by chain ID and native currency (ticker)\n */\n// This interface was created before this ESLint rule was added.\n// Convert to a `type` in a future major version.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface TokenRatesState extends BaseState {\n contractExchangeRates: ContractExchangeRates;\n contractExchangeRatesByChainId: Record<\n Hex,\n Record<string, ContractExchangeRates>\n >;\n}\n\n/**\n * The maximum number of token addresses that should be sent to the Price API in\n * a single request.\n */\nconst TOKEN_PRICES_BATCH_SIZE = 100;\n\n/**\n * Uses the CryptoCompare API to fetch the exchange rate between one currency\n * and another, i.e., the multiplier to apply the amount of one currency in\n * order to convert it to another.\n *\n * @param args - The arguments to this function.\n * @param args.from - The currency to convert from.\n * @param args.to - The currency to convert to.\n * @returns The exchange rate between `fromCurrency` to `toCurrency` if one\n * exists, or null if one does not.\n */\nasync function getCurrencyConversionRate({\n from,\n to,\n}: {\n from: string;\n to: string;\n}) {\n const includeUSDRate = false;\n try {\n const result = await fetchNativeCurrencyExchangeRate(\n to,\n from,\n includeUSDRate,\n );\n return result.conversionRate;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n ) {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Controller that passively polls on a set interval for token-to-fiat exchange rates\n * for tokens stored in the TokensController\n */\nexport class TokenRatesController extends StaticIntervalPollingControllerV1<\n TokenRatesConfig,\n TokenRatesState\n> {\n private handle?: ReturnType<typeof setTimeout>;\n\n #pollState = PollState.Inactive;\n\n #tokenPricesService: AbstractTokenPricesService;\n\n #inProcessExchangeRateUpdates: Record<`${Hex}:${string}`, Promise<void>> = {};\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenRatesController';\n\n private readonly getNetworkClientById: NetworkController['getNetworkClientById'];\n\n /**\n * Creates a TokenRatesController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The polling interval in ms\n * @param options.threshold - The duration in ms before metadata fetched from CoinGecko is considered stale\n * @param options.getNetworkClientById - Gets the network client with the given id from the NetworkController.\n * @param options.chainId - The chain ID of the current network.\n * @param options.ticker - The ticker for the current network.\n * @param options.selectedAddress - The current selected address.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onTokensStateChange - Allows subscribing to token controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network state changes.\n * @param options.tokenPricesService - An object in charge of retrieving token prices.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n interval = 3 * 60 * 1000,\n threshold = 6 * 60 * 60 * 1000,\n getNetworkClientById,\n chainId: initialChainId,\n ticker: initialTicker,\n selectedAddress: initialSelectedAddress,\n onPreferencesStateChange,\n onTokensStateChange,\n onNetworkStateChange,\n tokenPricesService,\n }: {\n interval?: number;\n threshold?: number;\n getNetworkClientById: NetworkController['getNetworkClientById'];\n chainId: Hex;\n ticker: string;\n selectedAddress: string;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onTokensStateChange: (\n listener: (tokensState: TokensState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n tokenPricesService: AbstractTokenPricesService;\n },\n config?: Partial<TokenRatesConfig>,\n state?: Partial<TokenRatesState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval,\n threshold,\n disabled: false,\n nativeCurrency: initialTicker,\n chainId: initialChainId,\n selectedAddress: initialSelectedAddress,\n allTokens: {}, // TODO: initialize these correctly, maybe as part of BaseControllerV2 migration\n allDetectedTokens: {},\n };\n\n this.defaultState = {\n contractExchangeRates: {},\n contractExchangeRatesByChainId: {},\n };\n this.initialize();\n this.setIntervalLength(interval);\n this.getNetworkClientById = getNetworkClientById;\n this.#tokenPricesService = tokenPricesService;\n\n if (config?.disabled) {\n this.configure({ disabled: true }, false, false);\n }\n\n onPreferencesStateChange(async ({ selectedAddress }) => {\n if (this.config.selectedAddress !== selectedAddress) {\n this.configure({ selectedAddress });\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n\n onTokensStateChange(async ({ allTokens, allDetectedTokens }) => {\n const previousTokenAddresses = this.#getTokenAddresses(\n this.config.chainId,\n );\n this.configure({ allTokens, allDetectedTokens });\n const newTokenAddresses = this.#getTokenAddresses(this.config.chainId);\n if (\n !isEqual(previousTokenAddresses, newTokenAddresses) &&\n this.#pollState === PollState.Active\n ) {\n await this.updateExchangeRates();\n }\n });\n\n onNetworkStateChange(async ({ providerConfig }) => {\n const { chainId, ticker } = providerConfig;\n if (\n this.config.chainId !== chainId ||\n this.config.nativeCurrency !== ticker\n ) {\n this.update({ contractExchangeRates: {} });\n this.configure({ chainId, nativeCurrency: ticker });\n if (this.#pollState === PollState.Active) {\n await this.updateExchangeRates();\n }\n }\n });\n }\n\n /**\n * Get the user's tokens for the given chain.\n *\n * @param chainId - The chain ID.\n * @returns The list of tokens addresses for the current chain\n */\n #getTokenAddresses(chainId: Hex): Hex[] {\n const { allTokens, allDetectedTokens } = this.config;\n const tokens = allTokens[chainId]?.[this.config.selectedAddress] || [];\n const detectedTokens =\n allDetectedTokens[chainId]?.[this.config.selectedAddress] || [];\n\n return [\n ...new Set(\n [...tokens, ...detectedTokens].map((token) =>\n toHex(toChecksumHexAddress(token.address)),\n ),\n ),\n ].sort();\n }\n\n /**\n * Start (or restart) polling.\n */\n async start() {\n this.#stopPoll();\n this.#pollState = PollState.Active;\n await this.#poll();\n }\n\n /**\n * Stop polling.\n */\n stop() {\n this.#stopPoll();\n this.#pollState = PollState.Inactive;\n }\n\n /**\n * Clear the active polling timer, if present.\n */\n #stopPoll() {\n if (this.handle) {\n clearTimeout(this.handle);\n }\n }\n\n /**\n * Poll for exchange rate updates.\n */\n async #poll() {\n await safelyExecute(() => this.updateExchangeRates());\n\n // Poll using recursive `setTimeout` instead of `setInterval` so that\n // requests don't stack if they take longer than the polling interval\n this.handle = setTimeout(() => {\n this.#poll();\n }, this.config.interval);\n }\n\n /**\n * Updates exchange rates for all tokens.\n */\n async updateExchangeRates() {\n const { chainId, nativeCurrency } = this.config;\n await this.updateExchangeRatesByChainId({\n chainId,\n nativeCurrency,\n });\n }\n\n /**\n * Updates exchange rates for all tokens.\n *\n * @param options - The options to fetch exchange rates.\n * @param options.chainId - The chain ID.\n * @param options.nativeCurrency - The ticker for the chain.\n */\n async updateExchangeRatesByChainId({\n chainId,\n nativeCurrency,\n }: {\n chainId: Hex;\n nativeCurrency: string;\n }) {\n if (this.disabled) {\n return;\n }\n\n const tokenAddresses = this.#getTokenAddresses(chainId);\n if (tokenAddresses.length === 0) {\n return;\n }\n\n const updateKey: `${Hex}:${string}` = `${chainId}:${nativeCurrency}`;\n if (updateKey in this.#inProcessExchangeRateUpdates) {\n // This prevents redundant updates\n // This promise is resolved after the in-progress update has finished,\n // and state has been updated.\n await this.#inProcessExchangeRateUpdates[updateKey];\n return;\n }\n\n const {\n promise: inProgressUpdate,\n resolve: updateSucceeded,\n reject: updateFailed,\n } = createDeferredPromise({ suppressUnhandledRejection: true });\n this.#inProcessExchangeRateUpdates[updateKey] = inProgressUpdate;\n\n try {\n const newContractExchangeRates = await this.#fetchAndMapExchangeRates({\n tokenAddresses,\n chainId,\n nativeCurrency,\n });\n\n const existingContractExchangeRates = this.state.contractExchangeRates;\n const updatedContractExchangeRates =\n chainId === this.config.chainId &&\n nativeCurrency === this.config.nativeCurrency\n ? newContractExchangeRates\n : existingContractExchangeRates;\n\n const existingContractExchangeRatesForChainId =\n this.state.contractExchangeRatesByChainId[chainId] ?? {};\n const updatedContractExchangeRatesForChainId = {\n ...this.state.contractExchangeRatesByChainId,\n [chainId]: {\n ...existingContractExchangeRatesForChainId,\n [nativeCurrency]: {\n ...existingContractExchangeRatesForChainId[nativeCurrency],\n ...newContractExchangeRates,\n },\n },\n };\n\n this.update({\n contractExchangeRates: updatedContractExchangeRates,\n contractExchangeRatesByChainId: updatedContractExchangeRatesForChainId,\n });\n updateSucceeded();\n } catch (error: unknown) {\n updateFailed(error);\n throw error;\n } finally {\n delete this.#inProcessExchangeRateUpdates[updateKey];\n }\n }\n\n /**\n * Uses the token prices service to retrieve exchange rates for tokens in a\n * particular currency.\n *\n * If the price API does not support the given chain ID, returns an empty\n * object.\n *\n * If the price API does not support the given currency, retrieves exchange\n * rates in a known currency instead, then converts those rates using the\n * exchange rate between the known currency and desired currency.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.chainId - The EIP-155 ID of the chain where the tokens live.\n * @param args.nativeCurrency - The native currency in which to request\n * exchange rates.\n * @returns A map from token address to its exchange rate in the native\n * currency, or an empty map if no exchange rates can be obtained for the\n * chain ID.\n */\n async #fetchAndMapExchangeRates({\n tokenAddresses,\n chainId,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n chainId: Hex;\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n if (!this.#tokenPricesService.validateChainIdSupported(chainId)) {\n return tokenAddresses.reduce((obj, tokenAddress) => {\n return {\n ...obj,\n [tokenAddress]: undefined,\n };\n }, {});\n }\n\n if (this.#tokenPricesService.validateCurrencySupported(nativeCurrency)) {\n return await this.#fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId,\n nativeCurrency,\n });\n }\n\n return await this.#fetchAndMapExchangeRatesForUnsupportedNativeCurrency({\n tokenAddresses,\n nativeCurrency,\n });\n }\n\n /**\n * Updates token rates for the given networkClientId\n *\n * @param networkClientId - The network client ID used to get a ticker value.\n * @returns The controller state.\n */\n async _executePoll(networkClientId: NetworkClientId): Promise<void> {\n const networkClient = this.getNetworkClientById(networkClientId);\n await this.updateExchangeRatesByChainId({\n chainId: networkClient.configuration.chainId,\n nativeCurrency: networkClient.configuration.ticker,\n });\n }\n\n /**\n * Retrieves prices in the given currency for the given tokens on the given\n * chain. Ensures that token addresses are checksum addresses.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.chainId - The EIP-155 ID of the chain where the tokens live.\n * @param args.nativeCurrency - The native currency in which to request\n * prices.\n * @returns A map of the token addresses (as checksums) to their prices in the\n * native currency.\n */\n async #fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n chainId: Hex;\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n const tokenPricesByTokenAddress = await reduceInBatchesSerially<\n Hex,\n Awaited<ReturnType<AbstractTokenPricesService['fetchTokenPrices']>>\n >({\n values: tokenAddresses,\n batchSize: TOKEN_PRICES_BATCH_SIZE,\n eachBatch: async (allTokenPricesByTokenAddress, batch) => {\n const tokenPricesByTokenAddressForBatch =\n await this.#tokenPricesService.fetchTokenPrices({\n tokenAddresses: batch,\n chainId,\n currency: nativeCurrency,\n });\n\n return {\n ...allTokenPricesByTokenAddress,\n ...tokenPricesByTokenAddressForBatch,\n };\n },\n initialResult: {},\n });\n\n return Object.entries(tokenPricesByTokenAddress).reduce(\n (obj, [tokenAddress, tokenPrice]) => {\n return {\n ...obj,\n [tokenAddress]: tokenPrice.value,\n };\n },\n {},\n );\n }\n\n /**\n * If the price API does not support a given native currency, then we need to\n * convert it to a fallback currency and feed that currency into the price\n * API, then convert the prices to our desired native currency.\n *\n * @param args - The arguments to this function.\n * @param args.tokenAddresses - Addresses for tokens.\n * @param args.nativeCurrency - The native currency in which to request\n * prices.\n * @returns A map of the token addresses (as checksums) to their prices in the\n * native currency.\n */\n async #fetchAndMapExchangeRatesForUnsupportedNativeCurrency({\n tokenAddresses,\n nativeCurrency,\n }: {\n tokenAddresses: Hex[];\n nativeCurrency: string;\n }): Promise<ContractExchangeRates> {\n const [\n contractExchangeRates,\n fallbackCurrencyToNativeCurrencyConversionRate,\n ] = await Promise.all([\n this.#fetchAndMapExchangeRatesForSupportedNativeCurrency({\n tokenAddresses,\n chainId: this.config.chainId,\n nativeCurrency: FALL_BACK_VS_CURRENCY,\n }),\n getCurrencyConversionRate({\n from: FALL_BACK_VS_CURRENCY,\n to: nativeCurrency,\n }),\n ]);\n\n if (fallbackCurrencyToNativeCurrencyConversionRate === null) {\n return {};\n }\n\n return Object.entries(contractExchangeRates).reduce(\n (obj, [tokenAddress, tokenValue]) => {\n return {\n ...obj,\n [tokenAddress]: tokenValue\n ? tokenValue * fallbackCurrencyToNativeCurrencyConversionRate\n : undefined,\n };\n },\n {},\n );\n }\n}\n\n/**\n * A deferred Promise.\n *\n * A deferred Promise is one that can be resolved or rejected independently of\n * the Promise construction.\n */\ntype DeferredPromise = {\n /**\n * The Promise that has been deferred.\n */\n promise: Promise<void>;\n /**\n * A function that resolves the Promise.\n */\n resolve: () => void;\n /**\n * A function that rejects the Promise.\n */\n reject: (error: unknown) => void;\n};\n\n/**\n * Create a defered Promise.\n *\n * TODO: Migrate this to utils\n *\n * @param args - The arguments.\n * @param args.suppressUnhandledRejection - This option adds an empty error handler\n * to the Promise to suppress the UnhandledPromiseRejection error. This can be\n * useful if the deferred Promise is sometimes intentionally not used.\n * @returns A deferred Promise.\n */\nfunction createDeferredPromise({\n suppressUnhandledRejection = false,\n}: {\n suppressUnhandledRejection: boolean;\n}): DeferredPromise {\n let resolve: DeferredPromise['resolve'];\n let reject: DeferredPromise['reject'];\n const promise = new Promise<void>(\n (innerResolve: () => void, innerReject: () => void) => {\n resolve = innerResolve;\n reject = innerReject;\n },\n );\n\n if (suppressUnhandledRejection) {\n promise.catch((_error) => {\n // This handler is used to suppress the UnhandledPromiseRejection error\n });\n }\n\n // @ts-expect-error We know that these are assigned, but TypeScript doesn't\n return { promise, resolve, reject };\n}\n\nexport default TokenRatesController;\n"]}
|