@metamask/assets-controllers 1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +20 -0
  3. package/README.md +30 -0
  4. package/dist/AccountTrackerController.d.ts +88 -0
  5. package/dist/AccountTrackerController.js +138 -0
  6. package/dist/AccountTrackerController.js.map +1 -0
  7. package/dist/AssetsContractController.d.ts +176 -0
  8. package/dist/AssetsContractController.js +314 -0
  9. package/dist/AssetsContractController.js.map +1 -0
  10. package/dist/CurrencyRateController.d.ts +98 -0
  11. package/dist/CurrencyRateController.js +193 -0
  12. package/dist/CurrencyRateController.js.map +1 -0
  13. package/dist/NftController.d.ts +409 -0
  14. package/dist/NftController.js +835 -0
  15. package/dist/NftController.js.map +1 -0
  16. package/dist/NftDetectionController.d.ts +179 -0
  17. package/dist/NftDetectionController.js +204 -0
  18. package/dist/NftDetectionController.js.map +1 -0
  19. package/dist/Standards/ERC20Standard.d.ts +42 -0
  20. package/dist/Standards/ERC20Standard.js +121 -0
  21. package/dist/Standards/ERC20Standard.js.map +1 -0
  22. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.d.ts +78 -0
  23. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js +148 -0
  24. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js.map +1 -0
  25. package/dist/Standards/NftStandards/ERC721/ERC721Standard.d.ts +88 -0
  26. package/dist/Standards/NftStandards/ERC721/ERC721Standard.js +182 -0
  27. package/dist/Standards/NftStandards/ERC721/ERC721Standard.js.map +1 -0
  28. package/dist/Standards/standards-types.d.ts +14 -0
  29. package/dist/Standards/standards-types.js +3 -0
  30. package/dist/Standards/standards-types.js.map +1 -0
  31. package/dist/TokenBalancesController.d.ts +69 -0
  32. package/dist/TokenBalancesController.js +94 -0
  33. package/dist/TokenBalancesController.js.map +1 -0
  34. package/dist/TokenDetectionController.d.ts +84 -0
  35. package/dist/TokenDetectionController.js +185 -0
  36. package/dist/TokenDetectionController.js.map +1 -0
  37. package/dist/TokenListController.d.ts +114 -0
  38. package/dist/TokenListController.js +256 -0
  39. package/dist/TokenListController.js.map +1 -0
  40. package/dist/TokenRatesController.d.ts +167 -0
  41. package/dist/TokenRatesController.js +284 -0
  42. package/dist/TokenRatesController.js.map +1 -0
  43. package/dist/TokensController.d.ts +238 -0
  44. package/dist/TokensController.js +530 -0
  45. package/dist/TokensController.js.map +1 -0
  46. package/dist/assetsUtil.d.ts +106 -0
  47. package/dist/assetsUtil.js +228 -0
  48. package/dist/assetsUtil.js.map +1 -0
  49. package/dist/crypto-compare.d.ts +12 -0
  50. package/dist/crypto-compare.js +67 -0
  51. package/dist/crypto-compare.js.map +1 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +31 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/token-service.d.ts +29 -0
  56. package/dist/token-service.js +134 -0
  57. package/dist/token-service.js.map +1 -0
  58. package/package.json +75 -0
@@ -0,0 +1,530 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TokensController = void 0;
16
+ const events_1 = require("events");
17
+ const contract_metadata_1 = __importDefault(require("@metamask/contract-metadata"));
18
+ const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis");
19
+ const uuid_1 = require("uuid");
20
+ const async_mutex_1 = require("async-mutex");
21
+ const contracts_1 = require("@ethersproject/contracts");
22
+ const providers_1 = require("@ethersproject/providers");
23
+ const abort_controller_1 = require("abort-controller");
24
+ const base_controller_1 = require("@metamask/base-controller");
25
+ const controller_utils_1 = require("@metamask/controller-utils");
26
+ const assetsUtil_1 = require("./assetsUtil");
27
+ const token_service_1 = require("./token-service");
28
+ var SuggestedAssetStatus;
29
+ (function (SuggestedAssetStatus) {
30
+ SuggestedAssetStatus["accepted"] = "accepted";
31
+ SuggestedAssetStatus["failed"] = "failed";
32
+ SuggestedAssetStatus["pending"] = "pending";
33
+ SuggestedAssetStatus["rejected"] = "rejected";
34
+ })(SuggestedAssetStatus || (SuggestedAssetStatus = {}));
35
+ /**
36
+ * Controller that stores assets and exposes convenience methods
37
+ */
38
+ class TokensController extends base_controller_1.BaseController {
39
+ /**
40
+ * Creates a TokensController instance.
41
+ *
42
+ * @param options - The controller options.
43
+ * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.
44
+ * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
45
+ * @param options.config - Initial options used to configure this controller.
46
+ * @param options.state - Initial state to set on this controller.
47
+ */
48
+ constructor({ onPreferencesStateChange, onNetworkStateChange, config, state, }) {
49
+ super(config, state);
50
+ this.mutex = new async_mutex_1.Mutex();
51
+ /**
52
+ * EventEmitter instance used to listen to specific EIP747 events
53
+ */
54
+ this.hub = new events_1.EventEmitter();
55
+ /**
56
+ * Name of this controller used during composition
57
+ */
58
+ this.name = 'TokensController';
59
+ this.defaultConfig = Object.assign({ networkType: controller_utils_1.MAINNET, selectedAddress: '', chainId: '', provider: undefined }, config);
60
+ this.defaultState = Object.assign({ tokens: [], ignoredTokens: [], detectedTokens: [], allTokens: {}, allIgnoredTokens: {}, allDetectedTokens: {}, suggestedAssets: [] }, state);
61
+ this.initialize();
62
+ this.abortController = new abort_controller_1.AbortController();
63
+ onPreferencesStateChange(({ selectedAddress }) => {
64
+ var _a, _b, _c;
65
+ const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
66
+ const { chainId } = this.config;
67
+ this.configure({ selectedAddress });
68
+ this.update({
69
+ tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [],
70
+ ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [],
71
+ detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [],
72
+ });
73
+ });
74
+ onNetworkStateChange(({ provider }) => {
75
+ var _a, _b, _c;
76
+ const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
77
+ const { selectedAddress } = this.config;
78
+ const { chainId } = provider;
79
+ this.abortController.abort();
80
+ this.abortController = new abort_controller_1.AbortController();
81
+ this.configure({ chainId });
82
+ this.ethersProvider = this._instantiateNewEthersProvider();
83
+ this.update({
84
+ tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [],
85
+ ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [],
86
+ detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [],
87
+ });
88
+ });
89
+ }
90
+ failSuggestedAsset(suggestedAssetMeta, error) {
91
+ const failedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.failed, error });
92
+ this.hub.emit(`${suggestedAssetMeta.id}:finished`, failedSuggestedAssetMeta);
93
+ }
94
+ /**
95
+ * Fetch metadata for a token.
96
+ *
97
+ * @param tokenAddress - The address of the token.
98
+ * @returns The token metadata.
99
+ */
100
+ fetchTokenMetadata(tokenAddress) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ try {
103
+ const token = yield (0, token_service_1.fetchTokenMetadata)(this.config.chainId, tokenAddress, this.abortController.signal);
104
+ return token;
105
+ }
106
+ catch (error) {
107
+ if (error instanceof Error &&
108
+ error.message.includes(token_service_1.TOKEN_METADATA_NO_SUPPORT_ERROR)) {
109
+ return undefined;
110
+ }
111
+ throw error;
112
+ }
113
+ });
114
+ }
115
+ _instantiateNewEthersProvider() {
116
+ var _a;
117
+ return new providers_1.Web3Provider((_a = this.config) === null || _a === void 0 ? void 0 : _a.provider);
118
+ }
119
+ /**
120
+ * Adds a token to the stored token list.
121
+ *
122
+ * @param address - Hex address of the token contract.
123
+ * @param symbol - Symbol of the token.
124
+ * @param decimals - Number of decimals the token uses.
125
+ * @param image - Image of the token.
126
+ * @returns Current token list.
127
+ */
128
+ addToken(address, symbol, decimals, image) {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const currentChainId = this.config.chainId;
131
+ const releaseLock = yield this.mutex.acquire();
132
+ try {
133
+ address = (0, controller_utils_1.toChecksumHexAddress)(address);
134
+ const { tokens, ignoredTokens, detectedTokens } = this.state;
135
+ const newTokens = [...tokens];
136
+ const [isERC721, tokenMetadata] = yield Promise.all([
137
+ this._detectIsERC721(address),
138
+ this.fetchTokenMetadata(address),
139
+ ]);
140
+ if (currentChainId !== this.config.chainId) {
141
+ throw new Error('TokensController Error: Switched networks while adding token');
142
+ }
143
+ const newEntry = {
144
+ address,
145
+ symbol,
146
+ decimals,
147
+ image: image ||
148
+ (0, assetsUtil_1.formatIconUrlWithProxy)({
149
+ chainId: this.config.chainId,
150
+ tokenAddress: address,
151
+ }),
152
+ isERC721,
153
+ aggregators: (0, assetsUtil_1.formatAggregatorNames)((tokenMetadata === null || tokenMetadata === void 0 ? void 0 : tokenMetadata.aggregators) || []),
154
+ };
155
+ const previousEntry = newTokens.find((token) => token.address.toLowerCase() === address.toLowerCase());
156
+ if (previousEntry) {
157
+ const previousIndex = newTokens.indexOf(previousEntry);
158
+ newTokens[previousIndex] = newEntry;
159
+ }
160
+ else {
161
+ newTokens.push(newEntry);
162
+ }
163
+ const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase());
164
+ const newDetectedTokens = detectedTokens.filter((token) => token.address.toLowerCase() !== address.toLowerCase());
165
+ const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } = this._getNewAllTokensState({
166
+ newTokens,
167
+ newIgnoredTokens,
168
+ newDetectedTokens,
169
+ });
170
+ this.update({
171
+ tokens: newTokens,
172
+ ignoredTokens: newIgnoredTokens,
173
+ detectedTokens: newDetectedTokens,
174
+ allTokens: newAllTokens,
175
+ allIgnoredTokens: newAllIgnoredTokens,
176
+ allDetectedTokens: newAllDetectedTokens,
177
+ });
178
+ return newTokens;
179
+ }
180
+ finally {
181
+ releaseLock();
182
+ }
183
+ });
184
+ }
185
+ /**
186
+ * Add a batch of tokens.
187
+ *
188
+ * @param tokensToImport - Array of tokens to import.
189
+ */
190
+ addTokens(tokensToImport) {
191
+ return __awaiter(this, void 0, void 0, function* () {
192
+ const releaseLock = yield this.mutex.acquire();
193
+ const { tokens, detectedTokens, ignoredTokens } = this.state;
194
+ const importedTokensMap = {};
195
+ // Used later to dedupe imported tokens
196
+ const newTokensMap = tokens.reduce((output, current) => {
197
+ output[current.address] = current;
198
+ return output;
199
+ }, {});
200
+ try {
201
+ tokensToImport.forEach((tokenToAdd) => {
202
+ const { address, symbol, decimals, image, aggregators } = tokenToAdd;
203
+ const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
204
+ const formattedToken = {
205
+ address: checksumAddress,
206
+ symbol,
207
+ decimals,
208
+ image,
209
+ aggregators,
210
+ };
211
+ newTokensMap[address] = formattedToken;
212
+ importedTokensMap[address.toLowerCase()] = true;
213
+ return formattedToken;
214
+ });
215
+ const newTokens = Object.values(newTokensMap);
216
+ const newDetectedTokens = detectedTokens.filter((token) => !importedTokensMap[token.address.toLowerCase()]);
217
+ const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()]);
218
+ const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } = this._getNewAllTokensState({
219
+ newTokens,
220
+ newDetectedTokens,
221
+ newIgnoredTokens,
222
+ });
223
+ this.update({
224
+ tokens: newTokens,
225
+ allTokens: newAllTokens,
226
+ detectedTokens: newDetectedTokens,
227
+ allDetectedTokens: newAllDetectedTokens,
228
+ ignoredTokens: newIgnoredTokens,
229
+ allIgnoredTokens: newAllIgnoredTokens,
230
+ });
231
+ }
232
+ finally {
233
+ releaseLock();
234
+ }
235
+ });
236
+ }
237
+ /**
238
+ * Ignore a batch of tokens.
239
+ *
240
+ * @param tokenAddressesToIgnore - Array of token addresses to ignore.
241
+ */
242
+ ignoreTokens(tokenAddressesToIgnore) {
243
+ const { ignoredTokens, detectedTokens, tokens } = this.state;
244
+ const ignoredTokensMap = {};
245
+ let newIgnoredTokens = [...ignoredTokens];
246
+ const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {
247
+ const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
248
+ ignoredTokensMap[address.toLowerCase()] = true;
249
+ return checksumAddress;
250
+ });
251
+ newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses];
252
+ const newDetectedTokens = detectedTokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]);
253
+ const newTokens = tokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]);
254
+ const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } = this._getNewAllTokensState({
255
+ newIgnoredTokens,
256
+ newDetectedTokens,
257
+ newTokens,
258
+ });
259
+ this.update({
260
+ ignoredTokens: newIgnoredTokens,
261
+ tokens: newTokens,
262
+ detectedTokens: newDetectedTokens,
263
+ allIgnoredTokens: newAllIgnoredTokens,
264
+ allDetectedTokens: newAllDetectedTokens,
265
+ allTokens: newAllTokens,
266
+ });
267
+ }
268
+ /**
269
+ * Adds a batch of detected tokens to the stored token list.
270
+ *
271
+ * @param incomingDetectedTokens - Array of detected tokens to be added or updated.
272
+ */
273
+ addDetectedTokens(incomingDetectedTokens) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ const releaseLock = yield this.mutex.acquire();
276
+ const { tokens, detectedTokens, ignoredTokens } = this.state;
277
+ const newTokens = [...tokens];
278
+ const newDetectedTokens = [...detectedTokens];
279
+ try {
280
+ incomingDetectedTokens.forEach((tokenToAdd) => {
281
+ const { address, symbol, decimals, image, aggregators, isERC721 } = tokenToAdd;
282
+ const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
283
+ const newEntry = {
284
+ address: checksumAddress,
285
+ symbol,
286
+ decimals,
287
+ image,
288
+ isERC721,
289
+ aggregators,
290
+ };
291
+ const previousImportedEntry = newTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase());
292
+ if (previousImportedEntry) {
293
+ // Update existing data of imported token
294
+ const previousImportedIndex = newTokens.indexOf(previousImportedEntry);
295
+ newTokens[previousImportedIndex] = newEntry;
296
+ }
297
+ else {
298
+ const ignoredTokenIndex = ignoredTokens.indexOf(address);
299
+ if (ignoredTokenIndex === -1) {
300
+ // Add detected token
301
+ const previousDetectedEntry = newDetectedTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase());
302
+ if (previousDetectedEntry) {
303
+ const previousDetectedIndex = newDetectedTokens.indexOf(previousDetectedEntry);
304
+ newDetectedTokens[previousDetectedIndex] = newEntry;
305
+ }
306
+ else {
307
+ newDetectedTokens.push(newEntry);
308
+ }
309
+ }
310
+ }
311
+ });
312
+ const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState({
313
+ newTokens,
314
+ newDetectedTokens,
315
+ });
316
+ this.update({
317
+ tokens: newTokens,
318
+ allTokens: newAllTokens,
319
+ detectedTokens: newDetectedTokens,
320
+ allDetectedTokens: newAllDetectedTokens,
321
+ });
322
+ }
323
+ finally {
324
+ releaseLock();
325
+ }
326
+ });
327
+ }
328
+ /**
329
+ * Adds isERC721 field to token object. This is called when a user attempts to add tokens that
330
+ * were previously added which do not yet had isERC721 field.
331
+ *
332
+ * @param tokenAddress - The contract address of the token requiring the isERC721 field added.
333
+ * @returns The new token object with the added isERC721 field.
334
+ */
335
+ updateTokenType(tokenAddress) {
336
+ return __awaiter(this, void 0, void 0, function* () {
337
+ const isERC721 = yield this._detectIsERC721(tokenAddress);
338
+ const { tokens } = this.state;
339
+ const tokenIndex = tokens.findIndex((token) => {
340
+ return token.address.toLowerCase() === tokenAddress.toLowerCase();
341
+ });
342
+ tokens[tokenIndex].isERC721 = isERC721;
343
+ this.update({ tokens });
344
+ return tokens[tokenIndex];
345
+ });
346
+ }
347
+ /**
348
+ * Detects whether or not a token is ERC-721 compatible.
349
+ *
350
+ * @param tokenAddress - The token contract address.
351
+ * @returns A boolean indicating whether the token address passed in supports the EIP-721
352
+ * interface.
353
+ */
354
+ _detectIsERC721(tokenAddress) {
355
+ var _a, _b;
356
+ return __awaiter(this, void 0, void 0, function* () {
357
+ const checksumAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
358
+ // if this token is already in our contract metadata map we don't need
359
+ // to check against the contract
360
+ if (((_a = contract_metadata_1.default[checksumAddress]) === null || _a === void 0 ? void 0 : _a.erc721) === true) {
361
+ return Promise.resolve(true);
362
+ }
363
+ else if (((_b = contract_metadata_1.default[checksumAddress]) === null || _b === void 0 ? void 0 : _b.erc20) === true) {
364
+ return Promise.resolve(false);
365
+ }
366
+ const tokenContract = this._createEthersContract(tokenAddress, metamask_eth_abis_1.abiERC721, this.ethersProvider);
367
+ try {
368
+ return yield tokenContract.supportsInterface(controller_utils_1.ERC721_INTERFACE_ID);
369
+ }
370
+ catch (error) {
371
+ // currently we see a variety of errors across different networks when
372
+ // token contracts are not ERC721 compatible. We need to figure out a better
373
+ // way of differentiating token interface types but for now if we get an error
374
+ // we have to assume the token is not ERC721 compatible.
375
+ return false;
376
+ }
377
+ });
378
+ }
379
+ _createEthersContract(tokenAddress, abi, ethersProvider) {
380
+ const tokenContract = new contracts_1.Contract(tokenAddress, abi, ethersProvider);
381
+ return tokenContract;
382
+ }
383
+ _generateRandomId() {
384
+ return (0, uuid_1.v1)();
385
+ }
386
+ /**
387
+ * Adds a new suggestedAsset to state. Parameters will be validated according to
388
+ * asset type being watched. A `<suggestedAssetMeta.id>:pending` hub event will be emitted once added.
389
+ *
390
+ * @param asset - The asset to be watched. For now only ERC20 tokens are accepted.
391
+ * @param type - The asset type.
392
+ * @returns Object containing a Promise resolving to the suggestedAsset address if accepted.
393
+ */
394
+ watchAsset(asset, type) {
395
+ return __awaiter(this, void 0, void 0, function* () {
396
+ const suggestedAssetMeta = {
397
+ asset,
398
+ id: this._generateRandomId(),
399
+ status: SuggestedAssetStatus.pending,
400
+ time: Date.now(),
401
+ type,
402
+ };
403
+ try {
404
+ switch (type) {
405
+ case 'ERC20':
406
+ (0, assetsUtil_1.validateTokenToWatch)(asset);
407
+ break;
408
+ default:
409
+ throw new Error(`Asset of type ${type} not supported`);
410
+ }
411
+ }
412
+ catch (error) {
413
+ this.failSuggestedAsset(suggestedAssetMeta, error);
414
+ return Promise.reject(error);
415
+ }
416
+ const result = new Promise((resolve, reject) => {
417
+ this.hub.once(`${suggestedAssetMeta.id}:finished`, (meta) => {
418
+ switch (meta.status) {
419
+ case SuggestedAssetStatus.accepted:
420
+ return resolve(meta.asset.address);
421
+ case SuggestedAssetStatus.rejected:
422
+ return reject(new Error('User rejected to watch the asset.'));
423
+ case SuggestedAssetStatus.failed:
424
+ return reject(new Error(meta.error.message));
425
+ /* istanbul ignore next */
426
+ default:
427
+ return reject(new Error(`Unknown status: ${meta.status}`));
428
+ }
429
+ });
430
+ });
431
+ const { suggestedAssets } = this.state;
432
+ suggestedAssets.push(suggestedAssetMeta);
433
+ this.update({ suggestedAssets: [...suggestedAssets] });
434
+ this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta);
435
+ return { result, suggestedAssetMeta };
436
+ });
437
+ }
438
+ /**
439
+ * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state,
440
+ * adding the asset to corresponding asset state. In this case ERC20 tokens.
441
+ * A `<suggestedAssetMeta.id>:finished` hub event is fired after accepted or failure.
442
+ *
443
+ * @param suggestedAssetID - The ID of the suggestedAsset to accept.
444
+ */
445
+ acceptWatchAsset(suggestedAssetID) {
446
+ return __awaiter(this, void 0, void 0, function* () {
447
+ const { suggestedAssets } = this.state;
448
+ const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id);
449
+ const suggestedAssetMeta = suggestedAssets[index];
450
+ try {
451
+ switch (suggestedAssetMeta.type) {
452
+ case 'ERC20':
453
+ const { address, symbol, decimals, image } = suggestedAssetMeta.asset;
454
+ yield this.addToken(address, symbol, decimals, image);
455
+ suggestedAssetMeta.status = SuggestedAssetStatus.accepted;
456
+ this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta);
457
+ break;
458
+ default:
459
+ throw new Error(`Asset of type ${suggestedAssetMeta.type} not supported`);
460
+ }
461
+ }
462
+ catch (error) {
463
+ this.failSuggestedAsset(suggestedAssetMeta, error);
464
+ }
465
+ const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID);
466
+ this.update({ suggestedAssets: [...newSuggestedAssets] });
467
+ });
468
+ }
469
+ /**
470
+ * Rejects a watchAsset request based on its ID by setting its status to "rejected"
471
+ * and emitting a `<suggestedAssetMeta.id>:finished` hub event.
472
+ *
473
+ * @param suggestedAssetID - The ID of the suggestedAsset to accept.
474
+ */
475
+ rejectWatchAsset(suggestedAssetID) {
476
+ const { suggestedAssets } = this.state;
477
+ const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id);
478
+ const suggestedAssetMeta = suggestedAssets[index];
479
+ if (!suggestedAssetMeta) {
480
+ return;
481
+ }
482
+ suggestedAssetMeta.status = SuggestedAssetStatus.rejected;
483
+ this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta);
484
+ const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID);
485
+ this.update({ suggestedAssets: [...newSuggestedAssets] });
486
+ }
487
+ /**
488
+ * Takes a new tokens and ignoredTokens array for the current network/account combination
489
+ * and returns new allTokens and allIgnoredTokens state to update to.
490
+ *
491
+ * @param params - Object that holds token params.
492
+ * @param params.newTokens - The new tokens to set for the current network and selected account.
493
+ * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account.
494
+ * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account.
495
+ * @returns The updated `allTokens` and `allIgnoredTokens` state.
496
+ */
497
+ _getNewAllTokensState(params) {
498
+ const { newTokens, newIgnoredTokens, newDetectedTokens } = params;
499
+ const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
500
+ const { chainId, selectedAddress } = this.config;
501
+ let newAllTokens = allTokens;
502
+ if (newTokens) {
503
+ const networkTokens = allTokens[chainId];
504
+ const newNetworkTokens = Object.assign(Object.assign({}, networkTokens), { [selectedAddress]: newTokens });
505
+ newAllTokens = Object.assign(Object.assign({}, allTokens), { [chainId]: newNetworkTokens });
506
+ }
507
+ let newAllIgnoredTokens = allIgnoredTokens;
508
+ if (newIgnoredTokens) {
509
+ const networkIgnoredTokens = allIgnoredTokens[chainId];
510
+ const newIgnoredNetworkTokens = Object.assign(Object.assign({}, networkIgnoredTokens), { [selectedAddress]: newIgnoredTokens });
511
+ newAllIgnoredTokens = Object.assign(Object.assign({}, allIgnoredTokens), { [chainId]: newIgnoredNetworkTokens });
512
+ }
513
+ let newAllDetectedTokens = allDetectedTokens;
514
+ if (newDetectedTokens) {
515
+ const networkDetectedTokens = allDetectedTokens[chainId];
516
+ const newDetectedNetworkTokens = Object.assign(Object.assign({}, networkDetectedTokens), { [selectedAddress]: newDetectedTokens });
517
+ newAllDetectedTokens = Object.assign(Object.assign({}, allDetectedTokens), { [chainId]: newDetectedNetworkTokens });
518
+ }
519
+ return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens };
520
+ }
521
+ /**
522
+ * Removes all tokens from the ignored list.
523
+ */
524
+ clearIgnoredTokens() {
525
+ this.update({ ignoredTokens: [], allIgnoredTokens: {} });
526
+ }
527
+ }
528
+ exports.TokensController = TokensController;
529
+ exports.default = TokensController;
530
+ //# sourceMappingURL=TokensController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokensController.js","sourceRoot":"","sources":["../src/TokensController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport contractsMap from '@metamask/contract-metadata';\nimport { abiERC721 } from '@metamask/metamask-eth-abis';\nimport { v1 as random } from 'uuid';\nimport { Mutex } from 'async-mutex';\nimport { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport { AbortController as WhatwgAbortController } from 'abort-controller';\nimport {\n BaseController,\n BaseConfig,\n BaseState,\n} from '@metamask/base-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { NetworkState } from '@metamask/network-controller';\nimport {\n NetworkType,\n toChecksumHexAddress,\n MAINNET,\n ERC721_INTERFACE_ID,\n} from '@metamask/controller-utils';\nimport type { Token } from './TokenRatesController';\nimport { TokenListToken } from './TokenListController';\nimport {\n formatAggregatorNames,\n formatIconUrlWithProxy,\n validateTokenToWatch,\n} from './assetsUtil';\nimport {\n fetchTokenMetadata,\n TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from './token-service';\n\n/**\n * @type TokensConfig\n *\n * Tokens controller configuration\n * @property networkType - Network ID as per net_version\n * @property selectedAddress - Vault selected address\n */\nexport interface TokensConfig extends BaseConfig {\n networkType: NetworkType;\n selectedAddress: string;\n chainId: string;\n provider: any;\n}\n\n/**\n * @type AssetSuggestionResult\n * @property result - Promise resolving to a new suggested asset address\n * @property suggestedAssetMeta - Meta information about this new suggested asset\n */\ninterface AssetSuggestionResult {\n result: Promise<string>;\n suggestedAssetMeta: SuggestedAssetMeta;\n}\n\nenum SuggestedAssetStatus {\n accepted = 'accepted',\n failed = 'failed',\n pending = 'pending',\n rejected = 'rejected',\n}\n\nexport type SuggestedAssetMetaBase = {\n id: string;\n time: number;\n type: string;\n asset: Token;\n};\n\n/**\n * @type SuggestedAssetMeta\n *\n * Suggested asset by EIP747 meta data\n * @property error - Synthesized error information for failed asset suggestions\n * @property id - Generated UUID associated with this suggested asset\n * @property status - String status of this this suggested asset\n * @property time - Timestamp associated with this this suggested asset\n * @property type - Type type this suggested asset\n * @property asset - Asset suggested object\n */\nexport type SuggestedAssetMeta =\n | (SuggestedAssetMetaBase & {\n status: SuggestedAssetStatus.failed;\n error: Error;\n })\n | (SuggestedAssetMetaBase & {\n status:\n | SuggestedAssetStatus.accepted\n | SuggestedAssetStatus.rejected\n | SuggestedAssetStatus.pending;\n });\n\n/**\n * @type TokensState\n *\n * Assets controller state\n * @property tokens - List of tokens associated with the active network and address pair\n * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair\n * @property detectedTokens - List of detected tokens associated with the active network and address pair\n * @property allTokens - Object containing tokens by network and account\n * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account\n * @property allDetectedTokens - Object containing tokens detected with non-zero balances\n * @property suggestedAssets - List of pending suggested assets to be added or canceled\n */\nexport interface TokensState extends BaseState {\n tokens: Token[];\n ignoredTokens: string[];\n detectedTokens: Token[];\n allTokens: { [key: string]: { [key: string]: Token[] } };\n allIgnoredTokens: { [key: string]: { [key: string]: string[] } };\n allDetectedTokens: { [key: string]: { [key: string]: Token[] } };\n suggestedAssets: SuggestedAssetMeta[];\n}\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class TokensController extends BaseController<\n TokensConfig,\n TokensState\n> {\n private mutex = new Mutex();\n\n private ethersProvider: any;\n\n private abortController: WhatwgAbortController;\n\n private failSuggestedAsset(\n suggestedAssetMeta: SuggestedAssetMeta,\n error: unknown,\n ) {\n const failedSuggestedAssetMeta = {\n ...suggestedAssetMeta,\n status: SuggestedAssetStatus.failed,\n error,\n };\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n failedSuggestedAssetMeta,\n );\n }\n\n /**\n * Fetch metadata for a token.\n *\n * @param tokenAddress - The address of the token.\n * @returns The token metadata.\n */\n private async fetchTokenMetadata(\n tokenAddress: string,\n ): Promise<TokenListToken | undefined> {\n try {\n const token = await fetchTokenMetadata<TokenListToken>(\n this.config.chainId,\n tokenAddress,\n this.abortController.signal,\n );\n return token;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(TOKEN_METADATA_NO_SUPPORT_ERROR)\n ) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokensController';\n\n /**\n * Creates a TokensController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.config - Initial options used to configure this controller.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n onPreferencesStateChange,\n onNetworkStateChange,\n config,\n state,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n config?: Partial<TokensConfig>;\n state?: Partial<TokensState>;\n }) {\n super(config, state);\n\n this.defaultConfig = {\n networkType: MAINNET,\n selectedAddress: '',\n chainId: '',\n provider: undefined,\n ...config,\n };\n\n this.defaultState = {\n tokens: [],\n ignoredTokens: [],\n detectedTokens: [],\n allTokens: {},\n allIgnoredTokens: {},\n allDetectedTokens: {},\n suggestedAssets: [],\n ...state,\n };\n\n this.initialize();\n this.abortController = new WhatwgAbortController();\n\n onPreferencesStateChange(({ selectedAddress }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId } = this.config;\n this.configure({ selectedAddress });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n\n onNetworkStateChange(({ provider }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { selectedAddress } = this.config;\n const { chainId } = provider;\n this.abortController.abort();\n this.abortController = new WhatwgAbortController();\n this.configure({ chainId });\n this.ethersProvider = this._instantiateNewEthersProvider();\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n }\n\n _instantiateNewEthersProvider(): any {\n return new Web3Provider(this.config?.provider);\n }\n\n /**\n * Adds a token to the stored token list.\n *\n * @param address - Hex address of the token contract.\n * @param symbol - Symbol of the token.\n * @param decimals - Number of decimals the token uses.\n * @param image - Image of the token.\n * @returns Current token list.\n */\n async addToken(\n address: string,\n symbol: string,\n decimals: number,\n image?: string,\n ): Promise<Token[]> {\n const currentChainId = this.config.chainId;\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { tokens, ignoredTokens, detectedTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const [isERC721, tokenMetadata] = await Promise.all([\n this._detectIsERC721(address),\n this.fetchTokenMetadata(address),\n ]);\n if (currentChainId !== this.config.chainId) {\n throw new Error(\n 'TokensController Error: Switched networks while adding token',\n );\n }\n const newEntry: Token = {\n address,\n symbol,\n decimals,\n image:\n image ||\n formatIconUrlWithProxy({\n chainId: this.config.chainId,\n tokenAddress: address,\n }),\n isERC721,\n aggregators: formatAggregatorNames(tokenMetadata?.aggregators || []),\n };\n const previousEntry = newTokens.find(\n (token) => token.address.toLowerCase() === address.toLowerCase(),\n );\n if (previousEntry) {\n const previousIndex = newTokens.indexOf(previousEntry);\n newTokens[previousIndex] = newEntry;\n } else {\n newTokens.push(newEntry);\n }\n\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase(),\n );\n const newDetectedTokens = detectedTokens.filter(\n (token) => token.address.toLowerCase() !== address.toLowerCase(),\n );\n const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } =\n this._getNewAllTokensState({\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n });\n\n this.update({\n tokens: newTokens,\n ignoredTokens: newIgnoredTokens,\n detectedTokens: newDetectedTokens,\n allTokens: newAllTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n return newTokens;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a batch of tokens.\n *\n * @param tokensToImport - Array of tokens to import.\n */\n async addTokens(tokensToImport: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const importedTokensMap: { [key: string]: true } = {};\n // Used later to dedupe imported tokens\n const newTokensMap = tokens.reduce((output, current) => {\n output[current.address] = current;\n return output;\n }, {} as { [address: string]: Token });\n\n try {\n tokensToImport.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators } = tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const formattedToken: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n aggregators,\n };\n newTokensMap[address] = formattedToken;\n importedTokensMap[address.toLowerCase()] = true;\n return formattedToken;\n });\n const newTokens = Object.values(newTokensMap);\n\n const newDetectedTokens = detectedTokens.filter(\n (token) => !importedTokensMap[token.address.toLowerCase()],\n );\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()],\n );\n\n const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } =\n this._getNewAllTokensState({\n newTokens,\n newDetectedTokens,\n newIgnoredTokens,\n });\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n ignoredTokens: newIgnoredTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Ignore a batch of tokens.\n *\n * @param tokenAddressesToIgnore - Array of token addresses to ignore.\n */\n ignoreTokens(tokenAddressesToIgnore: string[]) {\n const { ignoredTokens, detectedTokens, tokens } = this.state;\n const ignoredTokensMap: { [key: string]: true } = {};\n let newIgnoredTokens: string[] = [...ignoredTokens];\n\n const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {\n const checksumAddress = toChecksumHexAddress(address);\n ignoredTokensMap[address.toLowerCase()] = true;\n return checksumAddress;\n });\n newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses];\n const newDetectedTokens = detectedTokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n const newTokens = tokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n\n const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } =\n this._getNewAllTokensState({\n newIgnoredTokens,\n newDetectedTokens,\n newTokens,\n });\n\n this.update({\n ignoredTokens: newIgnoredTokens,\n tokens: newTokens,\n detectedTokens: newDetectedTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n allTokens: newAllTokens,\n });\n }\n\n /**\n * Adds a batch of detected tokens to the stored token list.\n *\n * @param incomingDetectedTokens - Array of detected tokens to be added or updated.\n */\n async addDetectedTokens(incomingDetectedTokens: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const newDetectedTokens: Token[] = [...detectedTokens];\n\n try {\n incomingDetectedTokens.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators, isERC721 } =\n tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const newEntry: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n isERC721,\n aggregators,\n };\n const previousImportedEntry = newTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousImportedEntry) {\n // Update existing data of imported token\n const previousImportedIndex = newTokens.indexOf(\n previousImportedEntry,\n );\n newTokens[previousImportedIndex] = newEntry;\n } else {\n const ignoredTokenIndex = ignoredTokens.indexOf(address);\n if (ignoredTokenIndex === -1) {\n // Add detected token\n const previousDetectedEntry = newDetectedTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousDetectedEntry) {\n const previousDetectedIndex = newDetectedTokens.indexOf(\n previousDetectedEntry,\n );\n newDetectedTokens[previousDetectedIndex] = newEntry;\n } else {\n newDetectedTokens.push(newEntry);\n }\n }\n }\n });\n\n const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState(\n {\n newTokens,\n newDetectedTokens,\n },\n );\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds isERC721 field to token object. This is called when a user attempts to add tokens that\n * were previously added which do not yet had isERC721 field.\n *\n * @param tokenAddress - The contract address of the token requiring the isERC721 field added.\n * @returns The new token object with the added isERC721 field.\n */\n async updateTokenType(tokenAddress: string) {\n const isERC721 = await this._detectIsERC721(tokenAddress);\n const { tokens } = this.state;\n const tokenIndex = tokens.findIndex((token) => {\n return token.address.toLowerCase() === tokenAddress.toLowerCase();\n });\n tokens[tokenIndex].isERC721 = isERC721;\n this.update({ tokens });\n return tokens[tokenIndex];\n }\n\n /**\n * Detects whether or not a token is ERC-721 compatible.\n *\n * @param tokenAddress - The token contract address.\n * @returns A boolean indicating whether the token address passed in supports the EIP-721\n * interface.\n */\n async _detectIsERC721(tokenAddress: string) {\n const checksumAddress = toChecksumHexAddress(tokenAddress);\n // if this token is already in our contract metadata map we don't need\n // to check against the contract\n if (contractsMap[checksumAddress]?.erc721 === true) {\n return Promise.resolve(true);\n } else if (contractsMap[checksumAddress]?.erc20 === true) {\n return Promise.resolve(false);\n }\n\n const tokenContract = this._createEthersContract(\n tokenAddress,\n abiERC721,\n this.ethersProvider,\n );\n try {\n return await tokenContract.supportsInterface(ERC721_INTERFACE_ID);\n } catch (error: any) {\n // currently we see a variety of errors across different networks when\n // token contracts are not ERC721 compatible. We need to figure out a better\n // way of differentiating token interface types but for now if we get an error\n // we have to assume the token is not ERC721 compatible.\n return false;\n }\n }\n\n _createEthersContract(\n tokenAddress: string,\n abi: string,\n ethersProvider: any,\n ): Contract {\n const tokenContract = new Contract(tokenAddress, abi, ethersProvider);\n return tokenContract;\n }\n\n _generateRandomId(): string {\n return random();\n }\n\n /**\n * Adds a new suggestedAsset to state. Parameters will be validated according to\n * asset type being watched. A `<suggestedAssetMeta.id>:pending` hub event will be emitted once added.\n *\n * @param asset - The asset to be watched. For now only ERC20 tokens are accepted.\n * @param type - The asset type.\n * @returns Object containing a Promise resolving to the suggestedAsset address if accepted.\n */\n async watchAsset(asset: Token, type: string): Promise<AssetSuggestionResult> {\n const suggestedAssetMeta = {\n asset,\n id: this._generateRandomId(),\n status: SuggestedAssetStatus.pending as SuggestedAssetStatus.pending,\n time: Date.now(),\n type,\n };\n try {\n switch (type) {\n case 'ERC20':\n validateTokenToWatch(asset);\n break;\n default:\n throw new Error(`Asset of type ${type} not supported`);\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n return Promise.reject(error);\n }\n\n const result: Promise<string> = new Promise((resolve, reject) => {\n this.hub.once(\n `${suggestedAssetMeta.id}:finished`,\n (meta: SuggestedAssetMeta) => {\n switch (meta.status) {\n case SuggestedAssetStatus.accepted:\n return resolve(meta.asset.address);\n case SuggestedAssetStatus.rejected:\n return reject(new Error('User rejected to watch the asset.'));\n case SuggestedAssetStatus.failed:\n return reject(new Error(meta.error.message));\n /* istanbul ignore next */\n default:\n return reject(new Error(`Unknown status: ${meta.status}`));\n }\n },\n );\n });\n\n const { suggestedAssets } = this.state;\n suggestedAssets.push(suggestedAssetMeta);\n this.update({ suggestedAssets: [...suggestedAssets] });\n this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta);\n return { result, suggestedAssetMeta };\n }\n\n /**\n * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state,\n * adding the asset to corresponding asset state. In this case ERC20 tokens.\n * A `<suggestedAssetMeta.id>:finished` hub event is fired after accepted or failure.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n async acceptWatchAsset(suggestedAssetID: string): Promise<void> {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n try {\n switch (suggestedAssetMeta.type) {\n case 'ERC20':\n const { address, symbol, decimals, image } = suggestedAssetMeta.asset;\n await this.addToken(address, symbol, decimals, image);\n suggestedAssetMeta.status = SuggestedAssetStatus.accepted;\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n suggestedAssetMeta,\n );\n break;\n default:\n throw new Error(\n `Asset of type ${suggestedAssetMeta.type} not supported`,\n );\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n }\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Rejects a watchAsset request based on its ID by setting its status to \"rejected\"\n * and emitting a `<suggestedAssetMeta.id>:finished` hub event.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n rejectWatchAsset(suggestedAssetID: string) {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n if (!suggestedAssetMeta) {\n return;\n }\n suggestedAssetMeta.status = SuggestedAssetStatus.rejected;\n this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta);\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Takes a new tokens and ignoredTokens array for the current network/account combination\n * and returns new allTokens and allIgnoredTokens state to update to.\n *\n * @param params - Object that holds token params.\n * @param params.newTokens - The new tokens to set for the current network and selected account.\n * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account.\n * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account.\n * @returns The updated `allTokens` and `allIgnoredTokens` state.\n */\n _getNewAllTokensState(params: {\n newTokens?: Token[];\n newIgnoredTokens?: string[];\n newDetectedTokens?: Token[];\n }) {\n const { newTokens, newIgnoredTokens, newDetectedTokens } = params;\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId, selectedAddress } = this.config;\n\n let newAllTokens = allTokens;\n if (newTokens) {\n const networkTokens = allTokens[chainId];\n const newNetworkTokens = {\n ...networkTokens,\n ...{ [selectedAddress]: newTokens },\n };\n newAllTokens = {\n ...allTokens,\n ...{ [chainId]: newNetworkTokens },\n };\n }\n\n let newAllIgnoredTokens = allIgnoredTokens;\n if (newIgnoredTokens) {\n const networkIgnoredTokens = allIgnoredTokens[chainId];\n const newIgnoredNetworkTokens = {\n ...networkIgnoredTokens,\n ...{ [selectedAddress]: newIgnoredTokens },\n };\n newAllIgnoredTokens = {\n ...allIgnoredTokens,\n ...{ [chainId]: newIgnoredNetworkTokens },\n };\n }\n\n let newAllDetectedTokens = allDetectedTokens;\n if (newDetectedTokens) {\n const networkDetectedTokens = allDetectedTokens[chainId];\n const newDetectedNetworkTokens = {\n ...networkDetectedTokens,\n ...{ [selectedAddress]: newDetectedTokens },\n };\n newAllDetectedTokens = {\n ...allDetectedTokens,\n ...{ [chainId]: newDetectedNetworkTokens },\n };\n }\n return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens };\n }\n\n /**\n * Removes all tokens from the ignored list.\n */\n clearIgnoredTokens() {\n this.update({ ignoredTokens: [], allIgnoredTokens: {} });\n }\n}\n\nexport default TokensController;\n"]}