@metamask-previews/assets-controllers 29.0.0-preview-4e7f493 → 29.0.0-preview-4f42b747

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 (75) hide show
  1. package/CHANGELOG.md +6 -2
  2. package/dist/AccountTrackerController.js +2 -2
  3. package/dist/AccountTrackerController.mjs +1 -1
  4. package/dist/NftController.js +2 -2
  5. package/dist/NftController.mjs +1 -1
  6. package/dist/NftDetectionController.js +2 -2
  7. package/dist/NftDetectionController.mjs +1 -1
  8. package/dist/TokenBalancesController.js +2 -2
  9. package/dist/TokenBalancesController.mjs +1 -1
  10. package/dist/TokenDetectionController.js +2 -2
  11. package/dist/TokenDetectionController.mjs +1 -1
  12. package/dist/TokenRatesController.js +2 -2
  13. package/dist/TokenRatesController.mjs +1 -1
  14. package/dist/TokensController.js +2 -2
  15. package/dist/TokensController.mjs +1 -1
  16. package/dist/{chunk-X2MMIBYW.js → chunk-5I3VJR3W.js} +39 -13
  17. package/dist/chunk-5I3VJR3W.js.map +1 -0
  18. package/dist/{chunk-MBCN3MNX.js → chunk-6N3O4INT.js} +44 -17
  19. package/dist/chunk-6N3O4INT.js.map +1 -0
  20. package/dist/{chunk-HDI4L2DD.js → chunk-ABKOMHQ3.js} +40 -21
  21. package/dist/chunk-ABKOMHQ3.js.map +1 -0
  22. package/dist/{chunk-LAU6ZDZR.js → chunk-BJPNGBHL.js} +110 -58
  23. package/dist/chunk-BJPNGBHL.js.map +1 -0
  24. package/dist/{chunk-ASFD56OL.mjs → chunk-GI5HUKV3.mjs} +39 -13
  25. package/dist/chunk-GI5HUKV3.mjs.map +1 -0
  26. package/dist/{chunk-V4ZO3F2S.js → chunk-IBVBMCBC.js} +17 -12
  27. package/dist/chunk-IBVBMCBC.js.map +1 -0
  28. package/dist/{chunk-4LY47RPI.mjs → chunk-K2GBXL67.mjs} +6 -5
  29. package/dist/chunk-K2GBXL67.mjs.map +1 -0
  30. package/dist/{chunk-BYVFZD3C.js → chunk-LNARUJIF.js} +21 -14
  31. package/dist/chunk-LNARUJIF.js.map +1 -0
  32. package/dist/{chunk-MR6EF4B7.mjs → chunk-MSG22JIL.mjs} +40 -21
  33. package/dist/chunk-MSG22JIL.mjs.map +1 -0
  34. package/dist/{chunk-7MMEHAKG.mjs → chunk-QM4XFEAL.mjs} +44 -17
  35. package/dist/chunk-QM4XFEAL.mjs.map +1 -0
  36. package/dist/{chunk-46UZDIXW.mjs → chunk-RACML7LZ.mjs} +110 -58
  37. package/dist/chunk-RACML7LZ.mjs.map +1 -0
  38. package/dist/{chunk-TRKQFR4W.mjs → chunk-WBB76JFP.mjs} +21 -14
  39. package/dist/chunk-WBB76JFP.mjs.map +1 -0
  40. package/dist/{chunk-LD4GC7OR.js → chunk-YCDWCM4P.js} +6 -5
  41. package/dist/chunk-YCDWCM4P.js.map +1 -0
  42. package/dist/{chunk-PAJTKWEC.mjs → chunk-Z6ZA6Y2D.mjs} +17 -12
  43. package/dist/chunk-Z6ZA6Y2D.mjs.map +1 -0
  44. package/dist/index.js +8 -8
  45. package/dist/index.mjs +7 -7
  46. package/dist/tsconfig.build.tsbuildinfo +1 -1
  47. package/dist/types/AccountTrackerController.d.ts +11 -9
  48. package/dist/types/AccountTrackerController.d.ts.map +1 -1
  49. package/dist/types/NftController.d.ts +6 -4
  50. package/dist/types/NftController.d.ts.map +1 -1
  51. package/dist/types/NftDetectionController.d.ts +15 -6
  52. package/dist/types/NftDetectionController.d.ts.map +1 -1
  53. package/dist/types/TokenBalancesController.d.ts +2 -2
  54. package/dist/types/TokenBalancesController.d.ts.map +1 -1
  55. package/dist/types/TokenDetectionController.d.ts +6 -6
  56. package/dist/types/TokenDetectionController.d.ts.map +1 -1
  57. package/dist/types/TokenRatesController.d.ts +10 -7
  58. package/dist/types/TokenRatesController.d.ts.map +1 -1
  59. package/dist/types/TokensController.d.ts +5 -5
  60. package/dist/types/TokensController.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/chunk-46UZDIXW.mjs.map +0 -1
  63. package/dist/chunk-4LY47RPI.mjs.map +0 -1
  64. package/dist/chunk-7MMEHAKG.mjs.map +0 -1
  65. package/dist/chunk-ASFD56OL.mjs.map +0 -1
  66. package/dist/chunk-BYVFZD3C.js.map +0 -1
  67. package/dist/chunk-HDI4L2DD.js.map +0 -1
  68. package/dist/chunk-LAU6ZDZR.js.map +0 -1
  69. package/dist/chunk-LD4GC7OR.js.map +0 -1
  70. package/dist/chunk-MBCN3MNX.js.map +0 -1
  71. package/dist/chunk-MR6EF4B7.mjs.map +0 -1
  72. package/dist/chunk-PAJTKWEC.mjs.map +0 -1
  73. package/dist/chunk-TRKQFR4W.mjs.map +0 -1
  74. package/dist/chunk-V4ZO3F2S.js.map +0 -1
  75. package/dist/chunk-X2MMIBYW.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -9,8 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Uncategorized
11
11
 
12
- - **BREAKING:** The NftController messenger must now allow the `NetworkController:getNetworkClientById` action ([#XXXX](https://github.com/MetaMask/core/pull/XXXX))
13
- - NftControllerMessenger now makes use of `selectedNetworkClientId` when responding to changes in NetworkController state to capture the currently selected chain rather than `providerConfig` ([#XXXX](https://github.com/MetaMask/core/pull/XXXX))
12
+ - **BREAKING:** The NftController messenger must now allow the `NetworkController:getNetworkClientById` action ([#4305](https://github.com/MetaMask/core/pull/4305))
13
+ - NftControllerMessenger now makes use of `selectedNetworkClientId` when responding to changes in NetworkController state to capture the currently selected chain rather than `providerConfig` ([#4305](https://github.com/MetaMask/core/pull/4305))
14
+ - This should be functionally equivalent, but is being noted anyway.
15
+ - NftDetectionController now makes use of `selectedNetworkClientId` when responding to changes in NetworkController state to capture the currently selected chain rather than `providerConfig` ([#4307](https://github.com/MetaMask/core/pull/4307))
16
+ - This should be functionally equivalent, but is being noted anyway.
17
+ - TokenRatesController now makes use of `selectedNetworkClientId` when responding to changes in NetworkController state to capture the currently selected chain rather than `providerConfig` ([#4317](https://github.com/MetaMask/core/pull/4317))
14
18
  - This should be functionally equivalent, but is being noted anyway.
15
19
 
16
20
  ## [29.0.0]
@@ -1,10 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkV4ZO3F2Sjs = require('./chunk-V4ZO3F2S.js');
4
+ var _chunkIBVBMCBCjs = require('./chunk-IBVBMCBC.js');
5
5
  require('./chunk-Z4BLTVTB.js');
6
6
 
7
7
 
8
8
 
9
- exports.AccountTrackerController = _chunkV4ZO3F2Sjs.AccountTrackerController; exports.default = _chunkV4ZO3F2Sjs.AccountTrackerController_default;
9
+ exports.AccountTrackerController = _chunkIBVBMCBCjs.AccountTrackerController; exports.default = _chunkIBVBMCBCjs.AccountTrackerController_default;
10
10
  //# sourceMappingURL=AccountTrackerController.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AccountTrackerController,
3
3
  AccountTrackerController_default
4
- } from "./chunk-PAJTKWEC.mjs";
4
+ } from "./chunk-Z6ZA6Y2D.mjs";
5
5
  import "./chunk-XUI43LEZ.mjs";
6
6
  export {
7
7
  AccountTrackerController,
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkLAU6ZDZRjs = require('./chunk-LAU6ZDZR.js');
5
+ var _chunkBJPNGBHLjs = require('./chunk-BJPNGBHL.js');
6
6
  require('./chunk-RGHTNZQ6.js');
7
7
  require('./chunk-NEXY7SE2.js');
8
8
  require('./chunk-Z4BLTVTB.js');
@@ -10,5 +10,5 @@ require('./chunk-Z4BLTVTB.js');
10
10
 
11
11
 
12
12
 
13
- exports.NftController = _chunkLAU6ZDZRjs.NftController; exports.default = _chunkLAU6ZDZRjs.NftController_default; exports.getDefaultNftState = _chunkLAU6ZDZRjs.getDefaultNftState;
13
+ exports.NftController = _chunkBJPNGBHLjs.NftController; exports.default = _chunkBJPNGBHLjs.NftController_default; exports.getDefaultNftState = _chunkBJPNGBHLjs.getDefaultNftState;
14
14
  //# sourceMappingURL=NftController.js.map
@@ -2,7 +2,7 @@ import {
2
2
  NftController,
3
3
  NftController_default,
4
4
  getDefaultNftState
5
- } from "./chunk-46UZDIXW.mjs";
5
+ } from "./chunk-RACML7LZ.mjs";
6
6
  import "./chunk-CP3HC7AQ.mjs";
7
7
  import "./chunk-Q5JRBGWO.mjs";
8
8
  import "./chunk-XUI43LEZ.mjs";
@@ -2,12 +2,12 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkX2MMIBYWjs = require('./chunk-X2MMIBYW.js');
5
+ var _chunk5I3VJR3Wjs = require('./chunk-5I3VJR3W.js');
6
6
  require('./chunk-RGHTNZQ6.js');
7
7
  require('./chunk-Z4BLTVTB.js');
8
8
 
9
9
 
10
10
 
11
11
 
12
- exports.BlockaidResultType = _chunkX2MMIBYWjs.BlockaidResultType; exports.NftDetectionController = _chunkX2MMIBYWjs.NftDetectionController; exports.default = _chunkX2MMIBYWjs.NftDetectionController_default;
12
+ exports.BlockaidResultType = _chunk5I3VJR3Wjs.BlockaidResultType; exports.NftDetectionController = _chunk5I3VJR3Wjs.NftDetectionController; exports.default = _chunk5I3VJR3Wjs.NftDetectionController_default;
13
13
  //# sourceMappingURL=NftDetectionController.js.map
@@ -2,7 +2,7 @@ import {
2
2
  BlockaidResultType,
3
3
  NftDetectionController,
4
4
  NftDetectionController_default
5
- } from "./chunk-ASFD56OL.mjs";
5
+ } from "./chunk-GI5HUKV3.mjs";
6
6
  import "./chunk-CP3HC7AQ.mjs";
7
7
  import "./chunk-XUI43LEZ.mjs";
8
8
  export {
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkLD4GC7ORjs = require('./chunk-LD4GC7OR.js');
5
+ var _chunkYCDWCM4Pjs = require('./chunk-YCDWCM4P.js');
6
6
  require('./chunk-Z4BLTVTB.js');
7
7
 
8
8
 
9
9
 
10
10
 
11
- exports.TokenBalancesController = _chunkLD4GC7ORjs.TokenBalancesController; exports.default = _chunkLD4GC7ORjs.TokenBalancesController_default; exports.getDefaultTokenBalancesState = _chunkLD4GC7ORjs.getDefaultTokenBalancesState;
11
+ exports.TokenBalancesController = _chunkYCDWCM4Pjs.TokenBalancesController; exports.default = _chunkYCDWCM4Pjs.TokenBalancesController_default; exports.getDefaultTokenBalancesState = _chunkYCDWCM4Pjs.getDefaultTokenBalancesState;
12
12
  //# sourceMappingURL=TokenBalancesController.js.map
@@ -2,7 +2,7 @@ import {
2
2
  TokenBalancesController,
3
3
  TokenBalancesController_default,
4
4
  getDefaultTokenBalancesState
5
- } from "./chunk-4LY47RPI.mjs";
5
+ } from "./chunk-K2GBXL67.mjs";
6
6
  import "./chunk-XUI43LEZ.mjs";
7
7
  export {
8
8
  TokenBalancesController,
@@ -4,7 +4,7 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkHDI4L2DDjs = require('./chunk-HDI4L2DD.js');
7
+ var _chunkABKOMHQ3js = require('./chunk-ABKOMHQ3.js');
8
8
  require('./chunk-NEXY7SE2.js');
9
9
  require('./chunk-Z4BLTVTB.js');
10
10
 
@@ -13,5 +13,5 @@ require('./chunk-Z4BLTVTB.js');
13
13
 
14
14
 
15
15
 
16
- exports.STATIC_MAINNET_TOKEN_LIST = _chunkHDI4L2DDjs.STATIC_MAINNET_TOKEN_LIST; exports.TokenDetectionController = _chunkHDI4L2DDjs.TokenDetectionController; exports.controllerName = _chunkHDI4L2DDjs.controllerName; exports.default = _chunkHDI4L2DDjs.TokenDetectionController_default; exports.isEqualCaseInsensitive = _chunkHDI4L2DDjs.isEqualCaseInsensitive;
16
+ exports.STATIC_MAINNET_TOKEN_LIST = _chunkABKOMHQ3js.STATIC_MAINNET_TOKEN_LIST; exports.TokenDetectionController = _chunkABKOMHQ3js.TokenDetectionController; exports.controllerName = _chunkABKOMHQ3js.controllerName; exports.default = _chunkABKOMHQ3js.TokenDetectionController_default; exports.isEqualCaseInsensitive = _chunkABKOMHQ3js.isEqualCaseInsensitive;
17
17
  //# sourceMappingURL=TokenDetectionController.js.map
@@ -4,7 +4,7 @@ import {
4
4
  TokenDetectionController_default,
5
5
  controllerName,
6
6
  isEqualCaseInsensitive
7
- } from "./chunk-MR6EF4B7.mjs";
7
+ } from "./chunk-MSG22JIL.mjs";
8
8
  import "./chunk-Q5JRBGWO.mjs";
9
9
  import "./chunk-XUI43LEZ.mjs";
10
10
  export {
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkBYVFZD3Cjs = require('./chunk-BYVFZD3C.js');
4
+ var _chunkLNARUJIFjs = require('./chunk-LNARUJIF.js');
5
5
  require('./chunk-7K6PSEAA.js');
6
6
  require('./chunk-WB6KJX4N.js');
7
7
  require('./chunk-NEXY7SE2.js');
@@ -9,5 +9,5 @@ require('./chunk-Z4BLTVTB.js');
9
9
 
10
10
 
11
11
 
12
- exports.TokenRatesController = _chunkBYVFZD3Cjs.TokenRatesController; exports.default = _chunkBYVFZD3Cjs.TokenRatesController_default;
12
+ exports.TokenRatesController = _chunkLNARUJIFjs.TokenRatesController; exports.default = _chunkLNARUJIFjs.TokenRatesController_default;
13
13
  //# sourceMappingURL=TokenRatesController.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TokenRatesController,
3
3
  TokenRatesController_default
4
- } from "./chunk-TRKQFR4W.mjs";
4
+ } from "./chunk-WBB76JFP.mjs";
5
5
  import "./chunk-TTH3ES66.mjs";
6
6
  import "./chunk-CNKVITJO.mjs";
7
7
  import "./chunk-Q5JRBGWO.mjs";
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkMBCN3MNXjs = require('./chunk-MBCN3MNX.js');
5
+ var _chunk6N3O4INTjs = require('./chunk-6N3O4INT.js');
6
6
  require('./chunk-B5YY22QQ.js');
7
7
  require('./chunk-BOTVAG4A.js');
8
8
  require('./chunk-3R43XIIX.js');
@@ -12,5 +12,5 @@ require('./chunk-Z4BLTVTB.js');
12
12
 
13
13
 
14
14
 
15
- exports.TokensController = _chunkMBCN3MNXjs.TokensController; exports.default = _chunkMBCN3MNXjs.TokensController_default; exports.getDefaultTokensState = _chunkMBCN3MNXjs.getDefaultTokensState;
15
+ exports.TokensController = _chunk6N3O4INTjs.TokensController; exports.default = _chunk6N3O4INTjs.TokensController_default; exports.getDefaultTokensState = _chunk6N3O4INTjs.getDefaultTokensState;
16
16
  //# sourceMappingURL=TokensController.js.map
@@ -2,7 +2,7 @@ import {
2
2
  TokensController,
3
3
  TokensController_default,
4
4
  getDefaultTokensState
5
- } from "./chunk-7MMEHAKG.mjs";
5
+ } from "./chunk-QM4XFEAL.mjs";
6
6
  import "./chunk-D3K5MPMW.mjs";
7
7
  import "./chunk-LZ5ZGQEX.mjs";
8
8
  import "./chunk-Z3OQU4XW.mjs";
@@ -23,13 +23,16 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll
23
23
  * @param options.onNftsStateChange - Allows subscribing to assets controller state changes.
24
24
  * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.
25
25
  * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
26
+ * @param options.onSelectedAccountChange - Allows subscribing to the change of account from the accoutns controller.
26
27
  * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.
27
28
  * @param options.addNft - Add an NFT.
28
29
  * @param options.getNftApi - Gets the URL to fetch an NFT from OpenSea.
29
30
  * @param options.getNftState - Gets the current state of the Assets controller.
30
31
  * @param options.disabled - Represents previous value of useNftDetection. Used to detect changes of useNftDetection. Default value is true.
31
- * @param options.selectedAddress - Represents current selected address.
32
+ * @param options.selectedAccountId - Represents current selected account id.
32
33
  * @param options.getNetworkClientById - Gets the network client by ID, from the NetworkController.
34
+ * @param options.getInternalAccount - Gets the internal account from the AccountsController.
35
+ * @param options.getSelectedAccount - Gets the selected account from the AccountsController.
33
36
  * @param config - Initial options used to configure this controller.
34
37
  * @param state - Initial state to set on this controller.
35
38
  */
@@ -38,12 +41,15 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll
38
41
  getNetworkClientById,
39
42
  onPreferencesStateChange,
40
43
  onNetworkStateChange,
44
+ onSelectedAccountChange,
41
45
  getOpenSeaApiKey,
42
46
  addNft,
43
47
  getNftApi,
44
48
  getNftState,
49
+ getInternalAccount,
50
+ getSelectedAccount,
45
51
  disabled: initialDisabled,
46
- selectedAddress: initialSelectedAddress
52
+ selectedAccountId: initialSelectedAccountId
47
53
  }, config, state) {
48
54
  super(config, state);
49
55
  /**
@@ -62,16 +68,31 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll
62
68
  this.defaultConfig = {
63
69
  interval: DEFAULT_INTERVAL,
64
70
  chainId: initialChainId,
65
- selectedAddress: initialSelectedAddress,
71
+ selectedAccountId: initialSelectedAccountId,
66
72
  disabled: initialDisabled
67
73
  };
68
74
  this.initialize();
69
75
  this.getNftState = getNftState;
70
76
  this.getNetworkClientById = getNetworkClientById;
71
- onPreferencesStateChange(({ selectedAddress, useNftDetection }) => {
72
- const { selectedAddress: previouslySelectedAddress, disabled } = this.config;
73
- if (selectedAddress !== previouslySelectedAddress || !useNftDetection !== disabled) {
74
- this.configure({ selectedAddress, disabled: !useNftDetection });
77
+ this.getInternalAccount = getInternalAccount;
78
+ this.getSelectedAccount = getSelectedAccount;
79
+ onSelectedAccountChange((internalAccount) => {
80
+ this.configure({ selectedAccountId: internalAccount.id });
81
+ const { selectedAccountId, disabled } = this.config;
82
+ if (!disabled || selectedAccountId !== internalAccount.id) {
83
+ this.start();
84
+ } else {
85
+ this.stop();
86
+ }
87
+ });
88
+ onPreferencesStateChange(({ useNftDetection }) => {
89
+ const { selectedAccountId: previousSelectedAccountId, disabled } = this.config;
90
+ const newSelectedAccount = this.getSelectedAccount("eip155:*");
91
+ if (newSelectedAccount.id !== previousSelectedAccountId || !useNftDetection !== disabled) {
92
+ this.configure({
93
+ disabled: !useNftDetection,
94
+ selectedAccountId: newSelectedAccount.id
95
+ });
75
96
  if (useNftDetection) {
76
97
  this.start();
77
98
  } else {
@@ -79,10 +100,12 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll
79
100
  }
80
101
  }
81
102
  });
82
- onNetworkStateChange(({ providerConfig }) => {
83
- this.configure({
84
- chainId: providerConfig.chainId
85
- });
103
+ onNetworkStateChange(({ selectedNetworkClientId }) => {
104
+ const selectedNetworkClient = getNetworkClientById(
105
+ selectedNetworkClientId
106
+ );
107
+ const { chainId } = selectedNetworkClient.configuration;
108
+ this.configure({ chainId });
86
109
  });
87
110
  this.getOpenSeaApiKey = getOpenSeaApiKey;
88
111
  this.addNft = addNft;
@@ -166,7 +189,10 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll
166
189
  async detectNfts({
167
190
  networkClientId,
168
191
  userAddress
169
- } = { userAddress: this.config.selectedAddress }) {
192
+ } = {}) {
193
+ const { selectedAccountId } = this.config;
194
+ const internalAccount = this.getInternalAccount(selectedAccountId);
195
+ userAddress = userAddress || internalAccount?.address;
170
196
  if (!this.isMainnet() || this.disabled) {
171
197
  return;
172
198
  }
@@ -232,4 +258,4 @@ var NftDetectionController_default = NftDetectionController;
232
258
 
233
259
 
234
260
  exports.BlockaidResultType = BlockaidResultType; exports.NftDetectionController = NftDetectionController; exports.NftDetectionController_default = NftDetectionController_default;
235
- //# sourceMappingURL=chunk-X2MMIBYW.js.map
261
+ //# sourceMappingURL=chunk-5I3VJR3W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NftDetectionController.ts"],"names":["BlockaidResultType"],"mappings":"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","sourcesContent":["import type { AccountsController } from '@metamask/accounts-controller';\nimport type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport {\n fetchWithErrorHandling,\n toChecksumHexAddress,\n ChainId,\n NFT_API_BASE_URL,\n} from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-api';\nimport type {\n NetworkClientId,\n NetworkController,\n NetworkState,\n NetworkClient,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { Source } from './constants';\nimport {\n type NftController,\n type NftState,\n type NftMetadata,\n} from './NftController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type ApiNft\n *\n * NFT object coming from OpenSea api\n * @property token_id - The NFT identifier\n * @property num_sales - Number of sales\n * @property background_color - The background color to be displayed with the item\n * @property image_url - URI of an image associated with this NFT\n * @property image_preview_url - URI of a smaller image associated with this NFT\n * @property image_thumbnail_url - URI of a thumbnail image associated with this NFT\n * @property image_original_url - URI of the original image associated with this NFT\n * @property animation_url - URI of a animation associated with this NFT\n * @property animation_original_url - URI of the original animation associated with this NFT\n * @property name - The NFT name\n * @property description - The NFT description\n * @property external_link - External link containing additional information\n * @property assetContract - The NFT contract information object\n * @property creator - The NFT owner information object\n * @property lastSale - When this item was last sold\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 ApiNft {\n token_id: string;\n num_sales: number | null;\n background_color: string | null;\n image_url: string | null;\n image_preview_url: string | null;\n image_thumbnail_url: string | null;\n image_original_url: string | null;\n animation_url: string | null;\n animation_original_url: string | null;\n name: string | null;\n description: string | null;\n external_link: string | null;\n asset_contract: ApiNftContract;\n creator: ApiNftCreator;\n last_sale: ApiNftLastSale | null;\n}\n\n/**\n * @type ApiNftContract\n *\n * NFT contract object coming from OpenSea api\n * @property address - Address of the NFT contract\n * @property asset_contract_type - The NFT type, it could be `semi-fungible` or `non-fungible`\n * @property created_date - Creation date\n * @property collection - Object containing the contract name and URI of an image associated\n * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property symbol - The NFT contract symbol\n * @property total_supply - Total supply of NFTs\n * @property description - The NFT contract description\n * @property external_link - External link containing additional information\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 ApiNftContract {\n address: string;\n asset_contract_type: string | null;\n created_date: string | null;\n schema_name: string | null;\n symbol: string | null;\n total_supply: string | null;\n description: string | null;\n external_link: string | null;\n collection: {\n name: string | null;\n image_url?: string | null;\n tokenCount?: string | null;\n };\n}\n\n/**\n * @type ApiNftLastSale\n *\n * NFT sale object coming from OpenSea api\n * @property event_timestamp - Object containing a `username`\n * @property total_price - URI of NFT image associated with this owner\n * @property transaction - Object containing transaction_hash and block_hash\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 ApiNftLastSale {\n event_timestamp: string;\n total_price: string;\n transaction: { transaction_hash: string; block_hash: string };\n}\n\n/**\n * @type ApiNftCreator\n *\n * NFT creator object coming from OpenSea api\n * @property user - Object containing a `username`\n * @property profile_img_url - URI of NFT image associated with this owner\n * @property address - The owner address\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 ApiNftCreator {\n user: { username: string };\n profile_img_url: string;\n address: string;\n}\n\n/**\n * @type NftDetectionConfig\n *\n * NftDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property chainId - Current chain ID\n * @property selectedAccountId - Vault selected account id\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 NftDetectionConfig extends BaseConfig {\n interval: number;\n chainId: Hex;\n selectedAccountId: string;\n}\n\nexport type ReservoirResponse = {\n tokens: TokensResponse[];\n continuation?: string;\n};\n\nexport type TokensResponse = {\n token: TokenResponse;\n ownership: Ownership;\n market?: Market;\n blockaidResult?: Blockaid;\n};\n\nexport enum BlockaidResultType {\n Benign = 'Benign',\n Spam = 'Spam',\n Warning = 'Warning',\n Malicious = 'Malicious',\n}\n\nexport type Blockaid = {\n contract: string;\n chainId: number;\n result_type: BlockaidResultType;\n malicious_score: string;\n attack_types: object;\n};\n\nexport type Market = {\n floorAsk?: FloorAsk;\n topBid?: TopBid;\n};\n\nexport type TokenResponse = {\n chainId: number;\n contract: string;\n tokenId: string;\n kind?: string;\n name?: string;\n image?: string;\n imageSmall?: string;\n imageLarge?: string;\n metadata?: Metadata;\n description?: string;\n supply?: number;\n remainingSupply?: number;\n rarityScore?: number;\n rarity?: number;\n rarityRank?: number;\n media?: string;\n isFlagged?: boolean;\n isSpam?: boolean;\n isNsfw?: boolean;\n metadataDisabled?: boolean;\n lastFlagUpdate?: string;\n lastFlagChange?: string;\n collection?: Collection;\n lastSale?: LastSale;\n topBid?: TopBid;\n lastAppraisalValue?: number;\n attributes?: Attributes[];\n};\n\nexport type TopBid = {\n id?: string;\n price?: Price;\n source?: {\n id?: string;\n domain?: string;\n name?: string;\n icon?: string;\n url?: string;\n };\n};\n\nexport type LastSale = {\n saleId?: string;\n token?: {\n contract?: string;\n tokenId?: string;\n name?: string;\n image?: string;\n collection?: {\n id?: string;\n name?: string;\n };\n };\n orderSource?: string;\n orderSide?: 'ask' | 'bid';\n orderKind?: string;\n orderId?: string;\n from?: string;\n to?: string;\n amount?: string;\n fillSource?: string;\n block?: number;\n txHash?: string;\n logIndex?: number;\n batchIndex?: number;\n timestamp?: number;\n price?: Price;\n washTradingScore?: number;\n royaltyFeeBps?: number;\n marketplaceFeeBps?: number;\n paidFullRoyalty?: boolean;\n feeBreakdown?: FeeBreakdown[];\n isDeleted?: boolean;\n createdAt?: string;\n updatedAt?: string;\n};\n\nexport type FeeBreakdown = {\n kind?: string;\n bps?: number;\n recipient?: string;\n source?: string;\n rawAmount?: string;\n};\n\nexport type Attributes = {\n key?: string;\n kind?: string;\n value: string;\n tokenCount?: number;\n onSaleCount?: number;\n floorAskPrice?: Price | null;\n topBidValue?: number | null;\n createdAt?: string;\n};\n\nexport type Collection = {\n id?: string;\n name?: string;\n slug?: string;\n symbol?: string;\n imageUrl?: string;\n image?: string;\n isSpam?: boolean;\n isNsfw?: boolean;\n creator?: string;\n tokenCount?: string;\n metadataDisabled?: boolean;\n openseaVerificationStatus?: string;\n floorAskPrice?: Price;\n royaltiesBps?: number;\n royalties?: Royalties[];\n};\n\nexport type Royalties = {\n bps?: number;\n recipient?: string;\n};\n\nexport type Ownership = {\n tokenCount?: string;\n onSaleCount?: string;\n floorAsk?: FloorAsk;\n acquiredAt?: string;\n};\n\nexport type FloorAsk = {\n id?: string;\n price?: Price;\n maker?: string;\n kind?: string;\n validFrom?: number;\n validUntil?: number;\n source?: Source;\n rawData?: Metadata;\n isNativeOffChainCancellable?: boolean;\n};\n\nexport type Price = {\n currency?: {\n contract?: string;\n name?: string;\n symbol?: string;\n decimals?: number;\n chainId?: number;\n };\n amount?: {\n raw?: string;\n decimal?: number;\n usd?: number;\n native?: number;\n };\n netAmount?: {\n raw?: string;\n decimal?: number;\n usd?: number;\n native?: number;\n };\n};\n\nexport type Metadata = {\n imageOriginal?: string;\n tokenURI?: string;\n};\n\n/**\n * Controller that passively polls on a set interval for NFT auto detection\n */\nexport class NftDetectionController extends StaticIntervalPollingControllerV1<\n NftDetectionConfig,\n BaseState\n> {\n private intervalId?: ReturnType<typeof setTimeout>;\n\n private getOwnerNftApi({\n address,\n next,\n }: {\n address: string;\n next?: string;\n }) {\n return `${NFT_API_BASE_URL}/users/${address}/tokens?chainIds=1&limit=50&includeTopBid=true&continuation=${\n next ?? ''\n }`;\n }\n\n private async getOwnerNfts(address: string) {\n let nftApiResponse: ReservoirResponse;\n let nfts: TokensResponse[] = [];\n let next;\n\n do {\n nftApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerNftApi({ address, next }),\n options: {\n headers: {\n Version: '1',\n },\n },\n timeout: 15000,\n });\n\n if (!nftApiResponse) {\n return nfts;\n }\n\n const newNfts = nftApiResponse.tokens.filter(\n (elm) =>\n elm.token.isSpam === false &&\n (elm.blockaidResult?.result_type\n ? elm.blockaidResult?.result_type === BlockaidResultType.Benign\n : true),\n );\n\n nfts = [...nfts, ...newNfts];\n } while ((next = nftApiResponse.continuation));\n\n return nfts;\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'NftDetectionController';\n\n private readonly getOpenSeaApiKey: () => string | undefined;\n\n private readonly addNft: NftController['addNft'];\n\n private readonly getNftApi: NftController['getNftApi'];\n\n private readonly getNftState: () => NftState;\n\n private readonly getNetworkClientById: NetworkController['getNetworkClientById'];\n\n private readonly getInternalAccount: AccountsController['getAccount'];\n\n private readonly getSelectedAccount: AccountsController['getSelectedAccount'];\n\n /**\n * Creates an NftDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNftsStateChange - Allows subscribing to assets controller state changes.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.onSelectedAccountChange - Allows subscribing to the change of account from the accoutns controller.\n * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.\n * @param options.addNft - Add an NFT.\n * @param options.getNftApi - Gets the URL to fetch an NFT from OpenSea.\n * @param options.getNftState - Gets the current state of the Assets controller.\n * @param options.disabled - Represents previous value of useNftDetection. Used to detect changes of useNftDetection. Default value is true.\n * @param options.selectedAccountId - Represents current selected account id.\n * @param options.getNetworkClientById - Gets the network client by ID, from the NetworkController.\n * @param options.getInternalAccount - Gets the internal account from the AccountsController.\n * @param options.getSelectedAccount - Gets the selected account from the AccountsController.\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 getNetworkClientById,\n onPreferencesStateChange,\n onNetworkStateChange,\n onSelectedAccountChange,\n getOpenSeaApiKey,\n addNft,\n getNftApi,\n getNftState,\n getInternalAccount,\n getSelectedAccount,\n disabled: initialDisabled,\n selectedAccountId: initialSelectedAccountId,\n }: {\n chainId: Hex;\n getNetworkClientById: NetworkController['getNetworkClientById'];\n onNftsStateChange: (listener: (nftsState: NftState) => void) => void;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n onSelectedAccountChange: (\n listener: (internalAccount: InternalAccount) => void,\n ) => void;\n getOpenSeaApiKey: () => string | undefined;\n addNft: NftController['addNft'];\n getNftApi: NftController['getNftApi'];\n getNftState: () => NftState;\n getInternalAccount: (accountId: string) => InternalAccount;\n getSelectedAccount: () => InternalAccount;\n disabled: boolean;\n selectedAccountId: string;\n },\n config?: Partial<NftDetectionConfig>,\n state?: Partial<BaseState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n chainId: initialChainId,\n selectedAccountId: initialSelectedAccountId,\n disabled: initialDisabled,\n };\n this.initialize();\n this.getNftState = getNftState;\n this.getNetworkClientById = getNetworkClientById;\n this.getInternalAccount = getInternalAccount;\n this.getSelectedAccount = getSelectedAccount;\n\n onSelectedAccountChange((internalAccount) => {\n this.configure({ selectedAccountId: internalAccount.id });\n\n const { selectedAccountId, disabled } = this.config;\n if (!disabled || selectedAccountId !== internalAccount.id) {\n this.start();\n } else {\n this.stop();\n }\n });\n\n onPreferencesStateChange(({ useNftDetection }) => {\n const { selectedAccountId: previousSelectedAccountId, disabled } =\n this.config;\n const newSelectedAccount = this.getSelectedAccount('eip155:*');\n\n if (\n newSelectedAccount.id !== previousSelectedAccountId ||\n !useNftDetection !== disabled\n ) {\n this.configure({\n disabled: !useNftDetection,\n selectedAccountId: newSelectedAccount.id,\n });\n if (useNftDetection) {\n this.start();\n } else {\n this.stop();\n }\n }\n });\n\n onNetworkStateChange(({ selectedNetworkClientId }) => {\n const selectedNetworkClient = getNetworkClientById(\n selectedNetworkClientId,\n );\n const { chainId } = selectedNetworkClient.configuration;\n\n this.configure({ chainId });\n });\n this.getOpenSeaApiKey = getOpenSeaApiKey;\n this.addNft = addNft;\n this.getNftApi = getNftApi;\n this.setIntervalLength(this.config.interval);\n }\n\n async _executePoll(\n networkClientId: string,\n options: { address: string },\n ): Promise<void> {\n await this.detectNfts({ networkClientId, userAddress: options.address });\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise<void> {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectNfts();\n this.intervalId = setInterval(async () => {\n await this.detectNfts();\n }, this.config.interval);\n }\n\n /**\n * Checks whether network is mainnet or not.\n *\n * @returns Whether current network is mainnet.\n */\n isMainnet = (): boolean => this.config.chainId === ChainId.mainnet;\n\n isMainnetByNetworkClientId = (networkClient: NetworkClient): boolean => {\n return networkClient.configuration.chainId === ChainId.mainnet;\n };\n\n /**\n * Triggers asset ERC721 token auto detection on mainnet. Any newly detected NFTs are\n * added.\n *\n * @param options - Options bag.\n * @param options.networkClientId - The network client ID to detect NFTs on.\n * @param options.userAddress - The address to detect NFTs for.\n */\n async detectNfts({\n networkClientId,\n userAddress,\n }: {\n networkClientId?: NetworkClientId;\n userAddress?: string;\n } = {}) {\n const { selectedAccountId } = this.config;\n const internalAccount = this.getInternalAccount(selectedAccountId);\n\n userAddress = userAddress || internalAccount?.address;\n\n /* istanbul ignore if */\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n /* istanbul ignore else */\n if (!userAddress) {\n return;\n }\n\n const apiNfts = await this.getOwnerNfts(userAddress);\n const addNftPromises = apiNfts.map(async (nft) => {\n const {\n tokenId: token_id,\n contract,\n kind,\n image: image_url,\n imageSmall: image_thumbnail_url,\n metadata: { imageOriginal: image_original_url } = {},\n name,\n description,\n attributes,\n topBid,\n lastSale,\n rarityRank,\n rarityScore,\n collection,\n } = nft.token;\n\n let ignored;\n /* istanbul ignore else */\n const { ignoredNfts } = this.getNftState();\n if (ignoredNfts.length) {\n ignored = ignoredNfts.find((c) => {\n /* istanbul ignore next */\n return (\n c.address === toChecksumHexAddress(contract) &&\n c.tokenId === token_id\n );\n });\n }\n\n /* istanbul ignore else */\n if (!ignored) {\n /* istanbul ignore next */\n const nftMetadata: NftMetadata = Object.assign(\n {},\n { name },\n description && { description },\n image_url && { image: image_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n kind && { standard: kind.toUpperCase() },\n lastSale && { lastSale },\n attributes && { attributes },\n topBid && { topBid },\n rarityRank && { rarityRank },\n rarityScore && { rarityScore },\n collection && { collection },\n );\n\n await this.addNft(contract, token_id, {\n nftMetadata,\n userAddress,\n source: Source.Detected,\n networkClientId,\n });\n }\n });\n await Promise.all(addNftPromises);\n }\n}\n\nexport default NftDetectionController;\n"]}
@@ -72,7 +72,7 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
72
72
  */
73
73
  this.name = "TokensController";
74
74
  this.defaultConfig = {
75
- selectedAddress: "",
75
+ selectedAccountId: "",
76
76
  chainId: initialChainId,
77
77
  provider: void 0,
78
78
  ...config
@@ -89,15 +89,15 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
89
89
  this.addDetectedTokens.bind(this)
90
90
  );
91
91
  this.messagingSystem.subscribe(
92
- "PreferencesController:stateChange",
93
- ({ selectedAddress }) => {
92
+ "AccountsController:selectedEvmAccountChange",
93
+ (internalAccount) => {
94
+ this.configure({ selectedAccountId: internalAccount.id });
94
95
  const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
95
96
  const { chainId } = this.config;
96
- this.configure({ selectedAddress });
97
97
  this.update({
98
- tokens: allTokens[chainId]?.[selectedAddress] ?? [],
99
- ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] ?? [],
100
- detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] ?? []
98
+ tokens: allTokens[chainId]?.[internalAccount.address] ?? [],
99
+ ignoredTokens: allIgnoredTokens[chainId]?.[internalAccount.address] ?? [],
100
+ detectedTokens: allDetectedTokens[chainId]?.[internalAccount.address] ?? []
101
101
  });
102
102
  }
103
103
  );
@@ -105,7 +105,12 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
105
105
  "NetworkController:networkDidChange",
106
106
  ({ providerConfig }) => {
107
107
  const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
108
- const { selectedAddress } = this.config;
108
+ const { selectedAccountId } = this.config;
109
+ const selectedInternalAccount = this.messagingSystem.call(
110
+ "AccountsController:getAccount",
111
+ selectedAccountId
112
+ );
113
+ const selectedAddress = selectedInternalAccount?.address || "";
109
114
  const { chainId } = providerConfig;
110
115
  this.abortController.abort();
111
116
  this.abortController = new AbortController();
@@ -170,7 +175,7 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
170
175
  interactingAddress,
171
176
  networkClientId
172
177
  }) {
173
- const { chainId, selectedAddress } = this.config;
178
+ const { chainId, selectedAccountId } = this.config;
174
179
  const releaseLock = await this.mutex.acquire();
175
180
  const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
176
181
  let currentChainId = chainId;
@@ -180,8 +185,12 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
180
185
  networkClientId
181
186
  ).configuration.chainId;
182
187
  }
183
- const accountAddress = interactingAddress || selectedAddress;
184
- const isInteractingWithWalletAccount = accountAddress === selectedAddress;
188
+ const internalAccount = this.messagingSystem.call(
189
+ "AccountsController:getAccount",
190
+ selectedAccountId
191
+ );
192
+ const accountAddress = interactingAddress || internalAccount?.address || "";
193
+ const isInteractingWithWalletAccount = accountAddress === internalAccount?.address;
185
194
  try {
186
195
  address = _controllerutils.toChecksumHexAddress.call(void 0, address);
187
196
  const tokens = allTokens[currentChainId]?.[accountAddress] || [];
@@ -357,8 +366,12 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
357
366
  */
358
367
  async addDetectedTokens(incomingDetectedTokens, detectionDetails) {
359
368
  const releaseLock = await this.mutex.acquire();
369
+ const internalAccount = this.messagingSystem.call(
370
+ "AccountsController:getAccount",
371
+ this.config.selectedAccountId
372
+ );
360
373
  const chainId = detectionDetails?.chainId ?? this.config.chainId;
361
- const accountAddress = detectionDetails?.selectedAddress ?? this.config.selectedAddress;
374
+ const accountAddress = detectionDetails?.selectedAddress ?? internalAccount?.address ?? "";
362
375
  const { allTokens, allDetectedTokens, allIgnoredTokens } = this.state;
363
376
  let newTokens = [...allTokens?.[chainId]?.[accountAddress] ?? []];
364
377
  let newDetectedTokens = [
@@ -412,7 +425,12 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
412
425
  interactingChainId: chainId
413
426
  }
414
427
  );
415
- const { chainId: currentChain, selectedAddress: currentAddress } = this.config;
428
+ const { chainId: currentChain, selectedAccountId: currentAccountId } = this.config;
429
+ const currentInternalAccount = this.messagingSystem.call(
430
+ "AccountsController:getAccount",
431
+ currentAccountId
432
+ );
433
+ const currentAddress = currentInternalAccount?.address || "";
416
434
  newTokens = newAllTokens?.[currentChain]?.[currentAddress] || [];
417
435
  newDetectedTokens = newAllDetectedTokens?.[currentChain]?.[currentAddress] || [];
418
436
  this.update({
@@ -525,6 +543,10 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
525
543
  if (!_controllerutils.isValidHexAddress.call(void 0, asset.address)) {
526
544
  throw _rpcerrors.rpcErrors.invalidParams(`Invalid address "${asset.address}"`);
527
545
  }
546
+ const selectedAccount = this.messagingSystem.call(
547
+ "AccountsController:getAccount",
548
+ this.config.selectedAccountId
549
+ );
528
550
  if (await this._detectIsERC721(asset.address, networkClientId)) {
529
551
  throw _rpcerrors.rpcErrors.invalidParams(
530
552
  `Contract ${asset.address} must match type ${type}, but was detected as ${_controllerutils.ERC721}`
@@ -590,7 +612,8 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
590
612
  id: this._generateRandomId(),
591
613
  time: Date.now(),
592
614
  type,
593
- interactingAddress: interactingAddress || this.config.selectedAddress
615
+ // Previously selectedAddress could be an empty string. This is to preserve the behaviour
616
+ interactingAddress: interactingAddress || selectedAccount?.address || ""
594
617
  };
595
618
  await this._requestApproval(suggestedAssetMeta);
596
619
  const { address, symbol, decimals, name, image } = asset;
@@ -625,8 +648,12 @@ var TokensController = class extends _basecontroller.BaseControllerV1 {
625
648
  interactingChainId
626
649
  } = params;
627
650
  const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;
628
- const { chainId, selectedAddress } = this.config;
629
- const userAddressToAddTokens = interactingAddress ?? selectedAddress;
651
+ const { chainId, selectedAccountId } = this.config;
652
+ const selectedInternalAccount = this.messagingSystem.call(
653
+ "AccountsController:getAccount",
654
+ selectedAccountId
655
+ );
656
+ const userAddressToAddTokens = interactingAddress ?? selectedInternalAccount?.address ?? "";
630
657
  const chainIdToAddTokens = interactingChainId ?? chainId;
631
658
  let newAllTokens = allTokens;
632
659
  if (newTokens?.length || newTokens && allTokens && allTokens[chainIdToAddTokens] && allTokens[chainIdToAddTokens][userAddressToAddTokens]) {
@@ -701,4 +728,4 @@ var TokensController_default = TokensController;
701
728
 
702
729
 
703
730
  exports.getDefaultTokensState = getDefaultTokensState; exports.TokensController = TokensController; exports.TokensController_default = TokensController_default;
704
- //# sourceMappingURL=chunk-MBCN3MNX.js.map
731
+ //# sourceMappingURL=chunk-6N3O4INT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/TokensController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerGetAccountAction,\n AccountsControllerSelectedEvmAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { AddApprovalRequest } from '@metamask/approval-controller';\nimport type {\n BaseConfig,\n BaseState,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { BaseControllerV1 } from '@metamask/base-controller';\nimport contractsMap from '@metamask/contract-metadata';\nimport {\n toChecksumHexAddress,\n ERC721_INTERFACE_ID,\n ORIGIN_METAMASK,\n ApprovalType,\n ERC20,\n ERC721,\n ERC1155,\n isValidHexAddress,\n safelyExecute,\n} from '@metamask/controller-utils';\nimport { abiERC721 } from '@metamask/metamask-eth-abis';\nimport type {\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerNetworkDidChangeEvent,\n Provider,\n} from '@metamask/network-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport { EventEmitter } from 'events';\nimport type { Patch } from 'immer/dist/immer';\nimport { v1 as random } from 'uuid';\n\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\nimport { ERC20Standard } from './Standards/ERC20Standard';\nimport { ERC1155Standard } from './Standards/NftStandards/ERC1155/ERC1155Standard';\nimport {\n fetchTokenMetadata,\n TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from './token-service';\nimport type {\n TokenListMap,\n TokenListStateChange,\n TokenListToken,\n} from './TokenListController';\nimport type { Token } from './TokenRatesController';\n\n/**\n * @type TokensConfig\n *\n * Tokens controller configuration\n * @property selectedAccountId - Vault selected address\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 TokensConfig extends BaseConfig {\n selectedAccountId: string;\n chainId: Hex;\n provider: Provider | undefined;\n}\n\n/**\n * @type SuggestedAssetMeta\n *\n * Suggested asset by EIP747 meta data\n * @property id - Generated UUID associated with 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 * @property interactingAddress - Account address that requested watch asset\n */\ntype SuggestedAssetMeta = {\n id: string;\n time: number;\n type: string;\n asset: Token;\n interactingAddress: string;\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 */\nexport type TokensState = {\n tokens: Token[];\n ignoredTokens: string[];\n detectedTokens: Token[];\n allTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n allIgnoredTokens: { [chainId: Hex]: { [key: string]: string[] } };\n allDetectedTokens: { [chainId: Hex]: { [key: string]: Token[] } };\n};\n\n/**\n * The name of the {@link TokensController}.\n */\nconst controllerName = 'TokensController';\n\nexport type TokensControllerActions =\n | TokensControllerGetStateAction\n | TokensControllerAddDetectedTokensAction;\n\n// TODO: Once `TokensController` is upgraded to V2, rewrite this type using the `ControllerGetStateAction` type, which constrains `TokensState` as `Record<string, Json>`.\nexport type TokensControllerGetStateAction = {\n type: `${typeof controllerName}:getState`;\n handler: () => TokensState;\n};\n\nexport type TokensControllerAddDetectedTokensAction = {\n type: `${typeof controllerName}:addDetectedTokens`;\n handler: TokensController['addDetectedTokens'];\n};\n\n/**\n * The external actions available to the {@link TokensController}.\n */\nexport type AllowedActions =\n | AddApprovalRequest\n | NetworkControllerGetNetworkClientByIdAction\n | AccountsControllerGetAccountAction;\n\n// TODO: Once `TokensController` is upgraded to V2, rewrite this type using the `ControllerStateChangeEvent` type, which constrains `TokensState` as `Record<string, Json>`.\nexport type TokensControllerStateChangeEvent = {\n type: `${typeof controllerName}:stateChange`;\n payload: [TokensState, Patch[]];\n};\n\nexport type TokensControllerEvents = TokensControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | NetworkControllerNetworkDidChangeEvent\n | TokenListStateChange\n | AccountsControllerSelectedEvmAccountChangeEvent;\n\n/**\n * The messenger of the {@link TokensController}.\n */\nexport type TokensControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokensControllerActions | AllowedActions,\n TokensControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\nexport const getDefaultTokensState = (): TokensState => {\n return {\n tokens: [],\n ignoredTokens: [],\n detectedTokens: [],\n allTokens: {},\n allIgnoredTokens: {},\n allDetectedTokens: {},\n };\n};\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class TokensController extends BaseControllerV1<\n TokensConfig,\n TokensState & BaseState\n> {\n private readonly mutex = new Mutex();\n\n private abortController: AbortController;\n\n private readonly messagingSystem: TokensControllerMessenger;\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.chainId - The chain ID of the current network.\n * @param options.config - Initial options used to configure this controller.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller messenger.\n */\n constructor({\n chainId: initialChainId,\n config,\n state,\n messenger,\n }: {\n chainId: Hex;\n config?: Partial<TokensConfig>;\n state?: Partial<TokensState>;\n messenger: TokensControllerMessenger;\n }) {\n super(config, state);\n\n this.defaultConfig = {\n selectedAccountId: '',\n chainId: initialChainId,\n provider: undefined,\n ...config,\n };\n\n this.defaultState = {\n ...getDefaultTokensState(),\n ...state,\n };\n\n this.initialize();\n this.abortController = new AbortController();\n\n this.messagingSystem = messenger;\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addDetectedTokens` as const,\n this.addDetectedTokens.bind(this),\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:selectedEvmAccountChange',\n (internalAccount) => {\n this.configure({ selectedAccountId: internalAccount.id });\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId } = this.config;\n this.update({\n tokens: allTokens[chainId]?.[internalAccount.address] ?? [],\n ignoredTokens:\n allIgnoredTokens[chainId]?.[internalAccount.address] ?? [],\n detectedTokens:\n allDetectedTokens[chainId]?.[internalAccount.address] ?? [],\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'NetworkController:networkDidChange',\n ({ providerConfig }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { selectedAccountId } = this.config;\n const selectedInternalAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n selectedAccountId,\n );\n\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n const selectedAddress = selectedInternalAccount?.address || '';\n\n const { chainId } = providerConfig;\n this.abortController.abort();\n this.abortController = new AbortController();\n this.configure({ chainId });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n },\n );\n\n this.messagingSystem.subscribe(\n 'TokenListController:stateChange',\n ({ tokenList }) => {\n const { tokens } = this.state;\n if (tokens.length && !tokens[0].name) {\n this.updateTokensAttribute(tokenList, 'name');\n }\n },\n );\n }\n\n /**\n * Adds a token to the stored token list.\n *\n * @param options - The method argument object.\n * @param options.address - Hex address of the token contract.\n * @param options.symbol - Symbol of the token.\n * @param options.decimals - Number of decimals the token uses.\n * @param options.name - Name of the token.\n * @param options.image - Image of the token.\n * @param options.interactingAddress - The address of the account to add a token to.\n * @param options.networkClientId - Network Client ID.\n * @returns Current token list.\n */\n async addToken({\n address,\n symbol,\n decimals,\n name,\n image,\n interactingAddress,\n networkClientId,\n }: {\n address: string;\n symbol: string;\n decimals: number;\n name?: string;\n image?: string;\n interactingAddress?: string;\n networkClientId?: NetworkClientId;\n }): Promise<Token[]> {\n const { chainId, selectedAccountId } = this.config;\n const releaseLock = await this.mutex.acquire();\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n let currentChainId = chainId;\n if (networkClientId) {\n currentChainId = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).configuration.chainId;\n }\n\n const internalAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n selectedAccountId,\n );\n\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n const accountAddress = interactingAddress || internalAccount?.address || '';\n const isInteractingWithWalletAccount =\n accountAddress === internalAccount?.address;\n\n try {\n address = toChecksumHexAddress(address);\n const tokens = allTokens[currentChainId]?.[accountAddress] || [];\n const ignoredTokens =\n allIgnoredTokens[currentChainId]?.[accountAddress] || [];\n const detectedTokens =\n allDetectedTokens[currentChainId]?.[accountAddress] || [];\n const newTokens: Token[] = [...tokens];\n const [isERC721, tokenMetadata] = await Promise.all([\n this._detectIsERC721(address, networkClientId),\n // TODO parameterize the token metadata fetch by networkClientId\n this.fetchTokenMetadata(address),\n ]);\n // TODO remove this once this method is fully parameterized by networkClientId\n if (!networkClientId && 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: currentChainId,\n tokenAddress: address,\n }),\n isERC721,\n aggregators: formatAggregatorNames(tokenMetadata?.aggregators || []),\n name,\n };\n const previousIndex = newTokens.findIndex(\n (token) => token.address.toLowerCase() === address.toLowerCase(),\n );\n if (previousIndex !== -1) {\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\n const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } =\n this._getNewAllTokensState({\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n interactingAddress: accountAddress,\n interactingChainId: currentChainId,\n });\n\n let newState: Partial<TokensState> = {\n allTokens: newAllTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n };\n\n // Only update active tokens if user is interacting with their active wallet account.\n if (isInteractingWithWalletAccount) {\n newState = {\n ...newState,\n tokens: newTokens,\n ignoredTokens: newIgnoredTokens,\n detectedTokens: newDetectedTokens,\n };\n }\n\n this.update(newState);\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 * @param networkClientId - Optional network client ID used to determine interacting chain ID.\n */\n async addTokens(tokensToImport: Token[], networkClientId?: NetworkClientId) {\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 try {\n tokensToImport.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators, name } =\n tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const formattedToken: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n aggregators,\n name,\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 let interactingChainId;\n if (networkClientId) {\n interactingChainId = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).configuration.chainId;\n }\n\n const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } =\n this._getNewAllTokensState({\n newTokens,\n newDetectedTokens,\n newIgnoredTokens,\n interactingChainId,\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 * @param detectionDetails - An object containing the chain ID and address of the currently selected network on which the incomingDetectedTokens were detected.\n * @param detectionDetails.selectedAddress - the account address on which the incomingDetectedTokens were detected.\n * @param detectionDetails.chainId - the chainId on which the incomingDetectedTokens were detected.\n */\n async addDetectedTokens(\n incomingDetectedTokens: Token[],\n detectionDetails?: { selectedAddress: string; chainId: Hex },\n ) {\n const releaseLock = await this.mutex.acquire();\n\n const internalAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n this.config.selectedAccountId,\n );\n\n // Get existing tokens for the chain + account\n const chainId = detectionDetails?.chainId ?? this.config.chainId;\n\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n const accountAddress =\n detectionDetails?.selectedAddress ?? internalAccount?.address ?? '';\n\n const { allTokens, allDetectedTokens, allIgnoredTokens } = this.state;\n let newTokens = [...(allTokens?.[chainId]?.[accountAddress] ?? [])];\n let newDetectedTokens = [\n ...(allDetectedTokens?.[chainId]?.[accountAddress] ?? []),\n ];\n\n try {\n incomingDetectedTokens.forEach((tokenToAdd) => {\n const {\n address,\n symbol,\n decimals,\n image,\n aggregators,\n isERC721,\n name,\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 name,\n };\n const previousImportedIndex = newTokens.findIndex(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousImportedIndex !== -1) {\n // Update existing data of imported token\n newTokens[previousImportedIndex] = newEntry;\n } else {\n const ignoredTokenIndex =\n allIgnoredTokens?.[chainId]?.[accountAddress]?.indexOf(address) ??\n -1;\n\n if (ignoredTokenIndex === -1) {\n // Add detected token\n const previousDetectedIndex = newDetectedTokens.findIndex(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousDetectedIndex !== -1) {\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 interactingAddress: accountAddress,\n interactingChainId: chainId,\n },\n );\n\n // We may be detecting tokens on a different chain/account pair than are currently configured.\n // Re-point `tokens` and `detectedTokens` to keep them referencing the current chain/account.\n const { chainId: currentChain, selectedAccountId: currentAccountId } =\n this.config;\n\n const currentInternalAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n currentAccountId,\n );\n\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n const currentAddress = currentInternalAccount?.address || '';\n\n newTokens = newAllTokens?.[currentChain]?.[currentAddress] || [];\n newDetectedTokens =\n newAllDetectedTokens?.[currentChain]?.[currentAddress] || [];\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 * This is a function that updates the tokens name for the tokens name if it is not defined.\n *\n * @param tokenList - Represents the fetched token list from service API\n * @param tokenAttribute - Represents the token attribute that we want to update on the token list\n */\n private updateTokensAttribute(\n tokenList: TokenListMap,\n tokenAttribute: keyof Token & keyof TokenListToken,\n ) {\n const { tokens } = this.state;\n\n const newTokens = tokens.map((token) => {\n const newToken = tokenList[token.address.toLowerCase()];\n\n return !token[tokenAttribute] && newToken?.[tokenAttribute]\n ? { ...token, [tokenAttribute]: newToken[tokenAttribute] }\n : { ...token };\n });\n\n this.update({ tokens: newTokens });\n }\n\n /**\n * Detects whether or not a token is ERC-721 compatible.\n *\n * @param tokenAddress - The token contract address.\n * @param networkClientId - Optional network client ID to fetch contract info with.\n * @returns A boolean indicating whether the token address passed in supports the EIP-721\n * interface.\n */\n async _detectIsERC721(\n tokenAddress: string,\n networkClientId?: NetworkClientId,\n ) {\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 networkClientId,\n );\n try {\n return await tokenContract.supportsInterface(ERC721_INTERFACE_ID);\n } catch (error) {\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 _getProvider(networkClientId?: NetworkClientId): Web3Provider {\n return new Web3Provider(\n // @ts-expect-error TODO: remove this annotation once the `Eip1193Provider` class is released\n networkClientId\n ? this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n ).provider\n : this.config.provider,\n );\n }\n\n _createEthersContract(\n tokenAddress: string,\n abi: string,\n networkClientId?: NetworkClientId,\n ): Contract {\n const web3provider = this._getProvider(networkClientId);\n const tokenContract = new Contract(tokenAddress, abi, web3provider);\n return tokenContract;\n }\n\n _generateRandomId(): string {\n return random();\n }\n\n /**\n * Adds a new suggestedAsset to the list of watched assets.\n * Parameters will be validated according to the asset type being watched.\n *\n * @param options - The method options.\n * @param options.asset - The asset to be watched. For now only ERC20 tokens are accepted.\n * @param options.type - The asset type.\n * @param options.interactingAddress - The address of the account that is requesting to watch the asset.\n * @param options.networkClientId - Network Client ID.\n * @returns A promise that resolves if the asset was watched successfully, and rejects otherwise.\n */\n async watchAsset({\n asset,\n type,\n interactingAddress,\n networkClientId,\n }: {\n asset: Token;\n type: string;\n interactingAddress?: string;\n networkClientId?: NetworkClientId;\n }): Promise<void> {\n if (type !== ERC20) {\n throw new Error(`Asset of type ${type} not supported`);\n }\n\n if (!asset.address) {\n throw rpcErrors.invalidParams('Address must be specified');\n }\n\n if (!isValidHexAddress(asset.address)) {\n throw rpcErrors.invalidParams(`Invalid address \"${asset.address}\"`);\n }\n\n // Validate if account is an evm account\n const selectedAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n this.config.selectedAccountId,\n );\n\n // Validate contract\n\n if (await this._detectIsERC721(asset.address, networkClientId)) {\n throw rpcErrors.invalidParams(\n `Contract ${asset.address} must match type ${type}, but was detected as ${ERC721}`,\n );\n }\n\n const provider = this._getProvider(networkClientId);\n const isErc1155 = await safelyExecute(() =>\n new ERC1155Standard(provider).contractSupportsBase1155Interface(\n asset.address,\n ),\n );\n if (isErc1155) {\n throw rpcErrors.invalidParams(\n `Contract ${asset.address} must match type ${type}, but was detected as ${ERC1155}`,\n );\n }\n\n const erc20 = new ERC20Standard(provider);\n const [contractName, contractSymbol, contractDecimals] = await Promise.all([\n safelyExecute(() => erc20.getTokenName(asset.address)),\n safelyExecute(() => erc20.getTokenSymbol(asset.address)),\n safelyExecute(async () => erc20.getTokenDecimals(asset.address)),\n ]);\n\n asset.name = contractName;\n\n // Validate symbol\n\n if (!asset.symbol && !contractSymbol) {\n throw rpcErrors.invalidParams(\n 'A symbol is required, but was not found in either the request or contract',\n );\n }\n\n if (\n contractSymbol !== undefined &&\n asset.symbol !== undefined &&\n asset.symbol.toUpperCase() !== contractSymbol.toUpperCase()\n ) {\n throw rpcErrors.invalidParams(\n `The symbol in the request (${asset.symbol}) does not match the symbol in the contract (${contractSymbol})`,\n );\n }\n\n asset.symbol = contractSymbol ?? asset.symbol;\n if (typeof asset.symbol !== 'string') {\n throw rpcErrors.invalidParams(`Invalid symbol: not a string`);\n }\n\n if (asset.symbol.length > 11) {\n throw rpcErrors.invalidParams(\n `Invalid symbol \"${asset.symbol}\": longer than 11 characters`,\n );\n }\n\n // Validate decimals\n\n if (asset.decimals === undefined && contractDecimals === undefined) {\n throw rpcErrors.invalidParams(\n 'Decimals are required, but were not found in either the request or contract',\n );\n }\n\n if (\n contractDecimals !== undefined &&\n asset.decimals !== undefined &&\n String(asset.decimals) !== contractDecimals\n ) {\n throw rpcErrors.invalidParams(\n `The decimals in the request (${asset.decimals}) do not match the decimals in the contract (${contractDecimals})`,\n );\n }\n\n const decimalsStr = contractDecimals ?? asset.decimals;\n const decimalsNum = parseInt(decimalsStr as unknown as string, 10);\n if (!Number.isInteger(decimalsNum) || decimalsNum > 36 || decimalsNum < 0) {\n throw rpcErrors.invalidParams(\n `Invalid decimals \"${decimalsStr}\": must be an integer 0 <= 36`,\n );\n }\n asset.decimals = decimalsNum;\n\n const suggestedAssetMeta: SuggestedAssetMeta = {\n asset,\n id: this._generateRandomId(),\n time: Date.now(),\n type,\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n interactingAddress: interactingAddress || selectedAccount?.address || '',\n };\n\n await this._requestApproval(suggestedAssetMeta);\n\n const { address, symbol, decimals, name, image } = asset;\n await this.addToken({\n address,\n symbol,\n decimals,\n name,\n image,\n interactingAddress: suggestedAssetMeta.interactingAddress,\n networkClientId,\n });\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 * @param params.interactingAddress - The account address to use to store the tokens.\n * @param params.interactingChainId - The chainId to use to store the tokens.\n * @returns The updated `allTokens` and `allIgnoredTokens` state.\n */\n _getNewAllTokensState(params: {\n newTokens?: Token[];\n newIgnoredTokens?: string[];\n newDetectedTokens?: Token[];\n interactingAddress?: string;\n interactingChainId?: Hex;\n }) {\n const {\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n interactingAddress,\n interactingChainId,\n } = params;\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId, selectedAccountId } = this.config;\n const selectedInternalAccount = this.messagingSystem.call(\n 'AccountsController:getAccount',\n selectedAccountId,\n );\n // Previously selectedAddress could be an empty string. This is to preserve the behaviour\n const userAddressToAddTokens =\n interactingAddress ?? selectedInternalAccount?.address ?? '';\n const chainIdToAddTokens = interactingChainId ?? chainId;\n\n let newAllTokens = allTokens;\n if (\n newTokens?.length ||\n (newTokens &&\n allTokens &&\n allTokens[chainIdToAddTokens] &&\n allTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkTokens = allTokens[chainIdToAddTokens];\n const newNetworkTokens = {\n ...networkTokens,\n ...{ [userAddressToAddTokens]: newTokens },\n };\n newAllTokens = {\n ...allTokens,\n ...{ [chainIdToAddTokens]: newNetworkTokens },\n };\n }\n\n let newAllIgnoredTokens = allIgnoredTokens;\n if (\n newIgnoredTokens?.length ||\n (newIgnoredTokens &&\n allIgnoredTokens &&\n allIgnoredTokens[chainIdToAddTokens] &&\n allIgnoredTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkIgnoredTokens = allIgnoredTokens[chainIdToAddTokens];\n const newIgnoredNetworkTokens = {\n ...networkIgnoredTokens,\n ...{ [userAddressToAddTokens]: newIgnoredTokens },\n };\n newAllIgnoredTokens = {\n ...allIgnoredTokens,\n ...{ [chainIdToAddTokens]: newIgnoredNetworkTokens },\n };\n }\n\n let newAllDetectedTokens = allDetectedTokens;\n if (\n newDetectedTokens?.length ||\n (newDetectedTokens &&\n allDetectedTokens &&\n allDetectedTokens[chainIdToAddTokens] &&\n allDetectedTokens[chainIdToAddTokens][userAddressToAddTokens])\n ) {\n const networkDetectedTokens = allDetectedTokens[chainIdToAddTokens];\n const newDetectedNetworkTokens = {\n ...networkDetectedTokens,\n ...{ [userAddressToAddTokens]: newDetectedTokens },\n };\n newAllDetectedTokens = {\n ...allDetectedTokens,\n ...{ [chainIdToAddTokens]: 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 async _requestApproval(suggestedAssetMeta: SuggestedAssetMeta) {\n return this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id: suggestedAssetMeta.id,\n origin: ORIGIN_METAMASK,\n type: ApprovalType.WatchAsset,\n requestData: {\n id: suggestedAssetMeta.id,\n interactingAddress: suggestedAssetMeta.interactingAddress,\n asset: {\n address: suggestedAssetMeta.asset.address,\n decimals: suggestedAssetMeta.asset.decimals,\n symbol: suggestedAssetMeta.asset.symbol,\n image: suggestedAssetMeta.asset.image || null,\n },\n },\n },\n true,\n );\n }\n}\n\nexport default TokensController;\n"]}