@metamask-previews/assets-controllers 65.0.0-preview-88a682b → 65.0.0-preview-e5f15167

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 (34) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/AccountTrackerController.d.cts +2 -2
  3. package/dist/AccountTrackerController.d.mts +2 -2
  4. package/dist/CurrencyRateController.d.cts +2 -2
  5. package/dist/CurrencyRateController.d.mts +2 -2
  6. package/dist/DeFiPositionsController/DeFiPositionsController.d.cts +2 -2
  7. package/dist/DeFiPositionsController/DeFiPositionsController.d.mts +2 -2
  8. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.cts +2 -2
  9. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.mts +2 -2
  10. package/dist/NftController.cjs +207 -93
  11. package/dist/NftController.cjs.map +1 -1
  12. package/dist/NftController.d.cts +34 -41
  13. package/dist/NftController.d.cts.map +1 -1
  14. package/dist/NftController.d.mts +34 -41
  15. package/dist/NftController.d.mts.map +1 -1
  16. package/dist/NftController.mjs +208 -94
  17. package/dist/NftController.mjs.map +1 -1
  18. package/dist/NftDetectionController.cjs +2 -2
  19. package/dist/NftDetectionController.cjs.map +1 -1
  20. package/dist/NftDetectionController.d.cts +2 -1
  21. package/dist/NftDetectionController.d.cts.map +1 -1
  22. package/dist/NftDetectionController.d.mts +2 -1
  23. package/dist/NftDetectionController.d.mts.map +1 -1
  24. package/dist/NftDetectionController.mjs +2 -2
  25. package/dist/NftDetectionController.mjs.map +1 -1
  26. package/dist/TokenBalancesController.d.cts +2 -2
  27. package/dist/TokenBalancesController.d.mts +2 -2
  28. package/dist/TokenDetectionController.d.cts +2 -2
  29. package/dist/TokenDetectionController.d.mts +2 -2
  30. package/dist/TokenListController.d.cts +2 -2
  31. package/dist/TokenListController.d.mts +2 -2
  32. package/dist/TokenRatesController.d.cts +2 -2
  33. package/dist/TokenRatesController.d.mts +2 -2
  34. package/package.json +3 -1
@@ -13,12 +13,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_openSeaEnabled, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount;
16
+ var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_ipfsGateway, _NftController_openSeaEnabled, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount, _NftController_bulkSanitizeNftMetadata, _NftController_sanitizeNftMetadata;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.NftController = exports.getDefaultNftControllerState = void 0;
19
19
  const address_1 = require("@ethersproject/address");
20
20
  const base_controller_1 = require("@metamask/base-controller");
21
21
  const controller_utils_1 = require("@metamask/controller-utils");
22
+ const phishing_controller_1 = require("@metamask/phishing-controller");
22
23
  const rpc_errors_1 = require("@metamask/rpc-errors");
23
24
  const utils_1 = require("@metamask/utils");
24
25
  const async_mutex_1 = require("async-mutex");
@@ -52,7 +53,6 @@ class NftController extends base_controller_1.BaseController {
52
53
  * Creates an NftController instance.
53
54
  *
54
55
  * @param options - The controller options.
55
- * @param options.chainId - The chain ID of the current network.
56
56
  * @param options.ipfsGateway - The configured IPFS gateway.
57
57
  * @param options.openSeaEnabled - Controls whether the OpenSea API is used.
58
58
  * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used.
@@ -62,7 +62,7 @@ class NftController extends base_controller_1.BaseController {
62
62
  * @param options.messenger - The messenger.
63
63
  * @param options.state - Initial state to set on this controller.
64
64
  */
65
- constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) {
65
+ constructor({ ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) {
66
66
  super({
67
67
  name: controllerName,
68
68
  metadata: nftControllerMetadata,
@@ -75,14 +75,12 @@ class NftController extends base_controller_1.BaseController {
75
75
  _NftController_instances.add(this);
76
76
  _NftController_mutex.set(this, new async_mutex_1.Mutex());
77
77
  _NftController_selectedAccountId.set(this, void 0);
78
- _NftController_chainId.set(this, void 0);
79
78
  _NftController_ipfsGateway.set(this, void 0);
80
79
  _NftController_openSeaEnabled.set(this, void 0);
81
80
  _NftController_useIpfsSubdomains.set(this, void 0);
82
81
  _NftController_isIpfsGatewayEnabled.set(this, void 0);
83
82
  _NftController_onNftAdded.set(this, void 0);
84
83
  __classPrivateFieldSet(this, _NftController_selectedAccountId, this.messagingSystem.call('AccountsController:getSelectedAccount').id, "f");
85
- __classPrivateFieldSet(this, _NftController_chainId, initialChainId, "f");
86
84
  __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f");
87
85
  __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f");
88
86
  __classPrivateFieldSet(this, _NftController_useIpfsSubdomains, useIpfsSubdomains, "f");
@@ -92,7 +90,6 @@ class NftController extends base_controller_1.BaseController {
92
90
  // TODO: Either fix this lint violation or explain why it's necessary to ignore.
93
91
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
94
92
  __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_onPreferencesControllerStateChange).bind(this));
95
- this.messagingSystem.subscribe('NetworkController:networkDidChange', __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_onNetworkControllerNetworkDidChange).bind(this));
96
93
  this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange',
97
94
  // TODO: Either fix this lint violation or explain why it's necessary to ignore.
98
95
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
@@ -112,23 +109,28 @@ class NftController extends base_controller_1.BaseController {
112
109
  * @param asset.tokenId - The ID of the asset.
113
110
  * @param type - The asset type.
114
111
  * @param origin - Domain origin to register the asset from.
112
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
115
113
  * @param options - Options bag.
116
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
117
114
  * @param options.userAddress - The address of the account where the NFT is being added.
118
115
  * @returns Object containing a Promise resolving to the suggestedAsset address if accepted.
119
116
  */
120
- async watchNft(asset, type, origin, { networkClientId, userAddress, } = {}) {
117
+ async watchNft(asset, type, origin, networkClientId, { userAddress, } = {}) {
121
118
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
122
119
  if (!addressToSearch) {
123
120
  return;
124
121
  }
125
- await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_validateWatchNft).call(this, asset, type, addressToSearch);
122
+ if (!networkClientId) {
123
+ throw rpc_errors_1.rpcErrors.invalidParams('Network client id is required');
124
+ }
125
+ await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_validateWatchNft).call(this, asset, type, addressToSearch, networkClientId);
126
126
  const nftMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformation).call(this, asset.address, asset.tokenId, networkClientId);
127
- if (nftMetadata.standard && nftMetadata.standard !== type) {
128
- throw rpc_errors_1.rpcErrors.invalidInput(`Suggested NFT of type ${nftMetadata.standard} does not match received type ${type}`);
127
+ // Sanitize metadata
128
+ const sanitizedMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, nftMetadata);
129
+ if (sanitizedMetadata.standard && sanitizedMetadata.standard !== type) {
130
+ throw rpc_errors_1.rpcErrors.invalidInput(`Suggested NFT of type ${sanitizedMetadata.standard} does not match received type ${type}`);
129
131
  }
130
132
  const suggestedNftMeta = {
131
- asset: { ...asset, ...nftMetadata },
133
+ asset: { ...asset, ...sanitizedMetadata },
132
134
  type,
133
135
  id: (0, uuid_1.v4)(),
134
136
  time: Date.now(),
@@ -137,8 +139,8 @@ class NftController extends base_controller_1.BaseController {
137
139
  };
138
140
  await this._requestApproval(suggestedNftMeta);
139
141
  const { address, tokenId } = asset;
140
- const { name, standard, description, image } = nftMetadata;
141
- await this.addNft(address, tokenId, {
142
+ const { name, standard, description, image } = sanitizedMetadata;
143
+ await this.addNft(address, tokenId, networkClientId, {
142
144
  nftMetadata: {
143
145
  name: name ?? null,
144
146
  description: description ?? null,
@@ -147,7 +149,6 @@ class NftController extends base_controller_1.BaseController {
147
149
  },
148
150
  userAddress,
149
151
  source: constants_1.Source.Dapp,
150
- networkClientId,
151
152
  });
152
153
  }
153
154
  /**
@@ -164,11 +165,10 @@ class NftController extends base_controller_1.BaseController {
164
165
  * @param ownerAddress - User public address.
165
166
  * @param nftAddress - NFT contract address.
166
167
  * @param tokenId - NFT token ID.
167
- * @param options - Options bag.
168
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
168
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
169
169
  * @returns Promise resolving the NFT ownership.
170
170
  */
171
- async isNftOwner(ownerAddress, nftAddress, tokenId, { networkClientId, } = {}) {
171
+ async isNftOwner(ownerAddress, nftAddress, tokenId, networkClientId) {
172
172
  // Checks the ownership for ERC-721.
173
173
  try {
174
174
  const owner = await this.messagingSystem.call('AssetsContractController:getERC721OwnerOf', nftAddress, tokenId, networkClientId);
@@ -195,20 +195,17 @@ class NftController extends base_controller_1.BaseController {
195
195
  *
196
196
  * @param address - Hex address of the NFT contract.
197
197
  * @param tokenId - The NFT identifier.
198
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
198
199
  * @param options - an object of arguments
199
200
  * @param options.userAddress - The address of the current user.
200
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
201
201
  * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp.
202
202
  */
203
- async addNftVerifyOwnership(address, tokenId, { userAddress, networkClientId, source, } = {}) {
203
+ async addNftVerifyOwnership(address, tokenId, networkClientId, { userAddress, source, } = {}) {
204
204
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
205
- if (!(await this.isNftOwner(addressToSearch, address, tokenId, {
206
- networkClientId,
207
- }))) {
205
+ if (!(await this.isNftOwner(addressToSearch, address, tokenId, networkClientId))) {
208
206
  throw new Error('This NFT is not owned by the user');
209
207
  }
210
- await this.addNft(address, tokenId, {
211
- networkClientId,
208
+ await this.addNft(address, tokenId, networkClientId, {
212
209
  userAddress: addressToSearch,
213
210
  source,
214
211
  });
@@ -218,42 +215,45 @@ class NftController extends base_controller_1.BaseController {
218
215
  *
219
216
  * @param tokenAddress - Hex address of the NFT contract.
220
217
  * @param tokenId - The NFT identifier.
218
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
221
219
  * @param options - an object of arguments
222
220
  * @param options.nftMetadata - NFT optional metadata.
223
221
  * @param options.userAddress - The address of the current user.
224
222
  * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp.
225
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
226
- * @param options.chainId - The chain ID to add the NFT to.
227
223
  * @returns Promise resolving to the current NFT list.
228
224
  */
229
- async addNft(tokenAddress, tokenId, { nftMetadata, userAddress, source = constants_1.Source.Custom, networkClientId, chainId, } = {}) {
225
+ async addNft(tokenAddress, tokenId, networkClientId, { nftMetadata, userAddress, source = constants_1.Source.Custom, } = {}) {
230
226
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
231
227
  if (!addressToSearch) {
232
228
  return;
233
229
  }
234
230
  const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
235
- // TODO: revisit this with Solana support and instead of passing chainId, make sure chainId is read from nftMetadata
236
- const chainIdToAddTo = chainId || __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
237
- nftMetadata =
238
- nftMetadata ||
239
- (await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformation).call(this, checksumHexAddress, tokenId, networkClientId));
240
- const newNftContracts = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContract).call(this, {
231
+ if (!nftMetadata) {
232
+ const fetchedMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformation).call(this, checksumHexAddress, tokenId, networkClientId);
233
+ // Sanitize metadata
234
+ nftMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, fetchedMetadata);
235
+ }
236
+ else {
237
+ // Sanitize provided metadata
238
+ nftMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, nftMetadata);
239
+ }
240
+ const newNftContracts = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContract).call(this, networkClientId, {
241
241
  tokenAddress: checksumHexAddress,
242
242
  userAddress: addressToSearch,
243
- networkClientId,
244
243
  source,
245
244
  nftMetadata,
246
- chainIdHex: source === constants_1.Source.Detected ? chainIdToAddTo : undefined,
247
245
  });
248
246
  // If NFT contract was not added, do not add individual NFT
249
247
  const nftContract = newNftContracts.find((contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase());
248
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
250
249
  // This is the case when the NFT is added manually and not detected automatically
250
+ // TODO: An improvement would be to make the chainId a required field and return it when getting the NFT information
251
251
  if (!nftMetadata.chainId) {
252
- nftMetadata.chainId = (0, controller_utils_1.convertHexToDecimal)(chainIdToAddTo);
252
+ nftMetadata.chainId = (0, controller_utils_1.convertHexToDecimal)(chainId);
253
253
  }
254
254
  // If NFT contract information, add individual NFT
255
255
  if (nftContract) {
256
- await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addIndividualNft).call(this, checksumHexAddress, tokenId, nftMetadata, nftContract, chainIdToAddTo, addressToSearch, source);
256
+ await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addIndividualNft).call(this, checksumHexAddress, tokenId, nftMetadata, nftContract, chainId, addressToSearch, source);
257
257
  }
258
258
  }
259
259
  /**
@@ -262,42 +262,62 @@ class NftController extends base_controller_1.BaseController {
262
262
  * @param options - Options for refetching NFT metadata
263
263
  * @param options.nfts - nfts to update metadata for.
264
264
  * @param options.userAddress - The current user address
265
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
266
265
  */
267
- async updateNftMetadata({ nfts, userAddress, networkClientId, }) {
266
+ async updateNftMetadata({ nfts, userAddress, }) {
268
267
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
269
268
  const releaseLock = await __classPrivateFieldGet(this, _NftController_mutex, "f").acquire();
270
269
  try {
271
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
272
270
  const nftsWithChecksumAdr = nfts.map((nft) => {
273
271
  return {
274
272
  ...nft,
275
273
  address: (0, controller_utils_1.toChecksumHexAddress)(nft.address),
276
274
  };
277
275
  });
278
- const nftMetadataResults = await Promise.all(nftsWithChecksumAdr.map(async (nft) => {
279
- const resMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformation).call(this, nft.address, nft.tokenId, networkClientId);
276
+ // Get all unsanitized nft metadata
277
+ const unsanitizedResults = await Promise.all(nftsWithChecksumAdr.map(async (nft) => {
278
+ // Each NFT should have a chainId; convert nft.chainId to networkClientId
279
+ const networkClientId = this.messagingSystem.call('NetworkController:findNetworkClientIdByChainId', (0, controller_utils_1.toHex)(nft.chainId));
280
+ const resMetadata = networkClientId
281
+ ? await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformation).call(this, nft.address, nft.tokenId, networkClientId)
282
+ : undefined;
280
283
  return {
281
284
  nft,
282
285
  newMetadata: resMetadata,
283
286
  };
284
287
  }));
288
+ // Extract metadata
289
+ const unsanitizedMetadata = unsanitizedResults.map((result) => result.newMetadata);
290
+ // Sanitize all metadata
291
+ const sanitizedMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_bulkSanitizeNftMetadata).call(this, unsanitizedMetadata);
292
+ // Reassemble the results with sanitized metadata
293
+ const nftMetadataResults = unsanitizedResults.map((result, index) => ({
294
+ nft: result.nft,
295
+ newMetadata: sanitizedMetadata[index],
296
+ }));
285
297
  // We want to avoid updating the state if the state and fetched nft info are the same
286
298
  const nftsWithDifferentMetadata = [];
287
299
  const { allNfts } = this.state;
288
- const stateNfts = allNfts[addressToSearch]?.[chainId] || [];
300
+ // get from state allNfts that match nftsWithChecksumAdr
301
+ const stateNfts = nftsWithChecksumAdr.map((nft) => {
302
+ return allNfts[addressToSearch]?.[(0, controller_utils_1.toHex)(nft.chainId)]?.find((nftElement) => nftElement.address.toLowerCase() === nft.address.toLowerCase() &&
303
+ nftElement.tokenId === nft.tokenId);
304
+ });
289
305
  nftMetadataResults.forEach((singleNft) => {
290
- const existingEntry = stateNfts.find((nft) => nft.address.toLowerCase() === singleNft.nft.address.toLowerCase() &&
291
- nft.tokenId === singleNft.nft.tokenId);
292
- if (existingEntry) {
306
+ const existingEntry = stateNfts.find((nft) => nft?.address.toLowerCase() ===
307
+ singleNft.nft.address.toLowerCase() &&
308
+ nft?.tokenId === singleNft.nft.tokenId);
309
+ if (existingEntry && singleNft.newMetadata) {
293
310
  const differentMetadata = (0, assetsUtil_1.compareNftMetadata)(singleNft.newMetadata, existingEntry);
294
311
  if (differentMetadata) {
295
- nftsWithDifferentMetadata.push(singleNft);
312
+ nftsWithDifferentMetadata.push({
313
+ nft: singleNft.nft,
314
+ newMetadata: singleNft.newMetadata,
315
+ });
296
316
  }
297
317
  }
298
318
  });
299
319
  if (nftsWithDifferentMetadata.length !== 0) {
300
- nftsWithDifferentMetadata.forEach((elm) => this.updateNft(elm.nft, elm.newMetadata, addressToSearch, chainId));
320
+ nftsWithDifferentMetadata.forEach((elm) => this.updateNft(elm.nft, elm.newMetadata, addressToSearch, (0, controller_utils_1.toHex)(elm.nft.chainId)));
301
321
  }
302
322
  }
303
323
  finally {
@@ -309,13 +329,13 @@ class NftController extends base_controller_1.BaseController {
309
329
  *
310
330
  * @param address - Hex address of the NFT contract.
311
331
  * @param tokenId - Token identifier of the NFT.
332
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
312
333
  * @param options - an object of arguments
313
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
314
334
  * @param options.userAddress - The address of the account where the NFT is being removed.
315
335
  */
316
- removeNft(address, tokenId, { networkClientId, userAddress, } = {}) {
336
+ removeNft(address, tokenId, networkClientId, { userAddress } = {}) {
317
337
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
318
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
338
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
319
339
  const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
320
340
  __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_removeIndividualNft).call(this, checksumHexAddress, tokenId, {
321
341
  chainId,
@@ -336,13 +356,13 @@ class NftController extends base_controller_1.BaseController {
336
356
  *
337
357
  * @param address - Hex address of the NFT contract.
338
358
  * @param tokenId - Token identifier of the NFT.
359
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
339
360
  * @param options - an object of arguments
340
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
341
361
  * @param options.userAddress - The address of the account where the NFT is being removed.
342
362
  */
343
- removeAndIgnoreNft(address, tokenId, { networkClientId, userAddress, } = {}) {
363
+ removeAndIgnoreNft(address, tokenId, networkClientId, { userAddress } = {}) {
344
364
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
345
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
365
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
346
366
  const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(address);
347
367
  __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_removeAndIgnoreIndividualNft).call(this, checksumHexAddress, tokenId, {
348
368
  chainId,
@@ -372,20 +392,18 @@ class NftController extends base_controller_1.BaseController {
372
392
  *
373
393
  * @param nft - The NFT object to check and update.
374
394
  * @param batch - A boolean indicating whether this method is being called as part of a batch or single update.
395
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
375
396
  * @param accountParams - The userAddress and chainId to check ownership against
376
397
  * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure assets are stored to the correct account
377
- * @param accountParams.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
378
398
  * @returns the NFT with the updated isCurrentlyOwned value
379
399
  */
380
- async checkAndUpdateSingleNftOwnershipStatus(nft, batch, { userAddress, networkClientId, } = {}) {
400
+ async checkAndUpdateSingleNftOwnershipStatus(nft, batch, networkClientId, { userAddress } = {}) {
381
401
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
382
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
402
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
383
403
  const { address, tokenId } = nft;
384
404
  let isOwned = nft.isCurrentlyOwned;
385
405
  try {
386
- isOwned = await this.isNftOwner(addressToSearch, address, tokenId, {
387
- networkClientId,
388
- });
406
+ isOwned = await this.isNftOwner(addressToSearch, address, tokenId, networkClientId);
389
407
  }
390
408
  catch {
391
409
  // ignore error
@@ -421,18 +439,18 @@ class NftController extends base_controller_1.BaseController {
421
439
  /**
422
440
  * Checks whether NFTs associated with current selectedAddress/chainId combination are still owned by the user
423
441
  * And updates the isCurrentlyOwned value on each accordingly.
442
+ *
443
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
424
444
  * @param options - an object of arguments
425
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
426
445
  * @param options.userAddress - The address of the account where the NFT ownership status is checked/updated.
427
446
  */
428
- async checkAndUpdateAllNftsOwnershipStatus({ networkClientId, userAddress, } = {}) {
447
+ async checkAndUpdateAllNftsOwnershipStatus(networkClientId, { userAddress, } = {}) {
429
448
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
430
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
449
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
431
450
  const { allNfts } = this.state;
432
451
  const nfts = allNfts[addressToSearch]?.[chainId] || [];
433
452
  const updatedNfts = await Promise.all(nfts.map(async (nft) => {
434
- return ((await this.checkAndUpdateSingleNftOwnershipStatus(nft, true, {
435
- networkClientId,
453
+ return ((await this.checkAndUpdateSingleNftOwnershipStatus(nft, true, networkClientId, {
436
454
  userAddress,
437
455
  })) ?? nft);
438
456
  }));
@@ -447,13 +465,13 @@ class NftController extends base_controller_1.BaseController {
447
465
  * @param address - Hex address of the NFT contract.
448
466
  * @param tokenId - Hex address of the NFT contract.
449
467
  * @param favorite - NFT new favorite status.
468
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
450
469
  * @param options - an object of arguments
451
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
452
470
  * @param options.userAddress - The address of the account where the NFT is being removed.
453
471
  */
454
- updateNftFavoriteStatus(address, tokenId, favorite, { networkClientId, userAddress, } = {}) {
472
+ updateNftFavoriteStatus(address, tokenId, favorite, networkClientId, { userAddress, } = {}) {
455
473
  const addressToSearch = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getAddressOrSelectedAddress).call(this, userAddress);
456
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
474
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
457
475
  const { allNfts } = this.state;
458
476
  const nfts = [...(allNfts[addressToSearch]?.[chainId] || [])];
459
477
  const index = nfts.findIndex((nft) => nft.address === address && nft.tokenId === tokenId);
@@ -597,12 +615,10 @@ class NftController extends base_controller_1.BaseController {
597
615
  }
598
616
  }
599
617
  exports.NftController = NftController;
600
- _NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_openSeaEnabled = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) {
601
- const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
602
- __classPrivateFieldSet(this, _NftController_chainId, chainId, "f");
603
- }, _NftController_onPreferencesControllerStateChange =
618
+ _NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_openSeaEnabled = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onPreferencesControllerStateChange =
604
619
  /**
605
620
  * Handles the state change of the preference controller.
621
+ *
606
622
  * @param preferencesState - The new state of the preference controller.
607
623
  * @param preferencesState.ipfsGateway - The configured IPFS gateway.
608
624
  * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used.
@@ -626,6 +642,7 @@ async function _NftController_onPreferencesControllerStateChange({ ipfsGateway,
626
642
  }, _NftController_onSelectedAccountChange =
627
643
  /**
628
644
  * Handles the selected account change on the accounts controller.
645
+ *
629
646
  * @param internalAccount - The new selected account.
630
647
  */
631
648
  async function _NftController_onSelectedAccountChange(internalAccount) {
@@ -848,16 +865,14 @@ async function _NftController_getNftURIAndStandard(contractAddress, tokenId, net
848
865
  * @returns Promise resolving to the current NFT name and image.
849
866
  */
850
867
  async function _NftController_getNftInformation(contractAddress, tokenId, networkClientId) {
851
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, {
852
- networkClientId,
853
- });
868
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
854
869
  const [blockchainMetadata, nftApiMetadata] = await Promise.all([
855
870
  (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromTokenURI).call(this, contractAddress, tokenId, networkClientId)),
856
871
  __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f") && chainId === '0x1'
857
872
  ? (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromApi).call(this, contractAddress, tokenId))
858
873
  : undefined,
859
874
  ]);
860
- return {
875
+ const metadata = {
861
876
  ...nftApiMetadata,
862
877
  name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null,
863
878
  description: blockchainMetadata?.description ?? nftApiMetadata?.description ?? null,
@@ -865,6 +880,8 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ
865
880
  standard: blockchainMetadata?.standard ?? nftApiMetadata?.standard ?? null,
866
881
  tokenURI: blockchainMetadata?.tokenURI ?? null,
867
882
  };
883
+ // Sanitize the metadata by checking external links against phishing protection
884
+ return await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, metadata);
868
885
  }, _NftController_getNftContractInformationFromContract =
869
886
  /**
870
887
  * Request NFT contract information from the contract itself.
@@ -873,7 +890,9 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ
873
890
  * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
874
891
  * @returns Promise resolving to the current NFT name and image.
875
892
  */
876
- async function _NftController_getNftContractInformationFromContract(contractAddress, networkClientId) {
893
+ async function _NftController_getNftContractInformationFromContract(
894
+ // TODO for calls to blockchain we need to explicitly pass the currentNetworkClientId since its relying on the provider
895
+ contractAddress, networkClientId) {
877
896
  const [name, symbol] = await Promise.all([
878
897
  this.messagingSystem.call('AssetsContractController:getERC721AssetName', contractAddress, networkClientId),
879
898
  this.messagingSystem.call('AssetsContractController:getERC721AssetSymbol', contractAddress, networkClientId),
@@ -1008,22 +1027,20 @@ async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadat
1008
1027
  /**
1009
1028
  * Adds an NFT contract to the stored NFT contracts list.
1010
1029
  *
1030
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
1011
1031
  * @param options - options.
1012
1032
  * @param options.tokenAddress - Hex address of the NFT contract.
1013
1033
  * @param options.userAddress - The address of the account where the NFT is being added.
1014
1034
  * @param options.nftMetadata - The retrieved NFTMetadata from API.
1015
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
1016
1035
  * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp.
1017
- * @param options.chainIdHex - The chainId to add the NFT contract to.
1018
1036
  * @returns Promise resolving to the current NFT contracts list.
1019
1037
  */
1020
- async function _NftController_addNftContract({ tokenAddress, userAddress, networkClientId, source, nftMetadata, chainIdHex, }) {
1038
+ async function _NftController_addNftContract(networkClientId, { tokenAddress, userAddress, source, nftMetadata, }) {
1021
1039
  const releaseLock = await __classPrivateFieldGet(this, _NftController_mutex, "f").acquire();
1022
1040
  try {
1023
1041
  const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
1024
1042
  const { allNftContracts } = this.state;
1025
- // TODO: revisit this with Solana support and instead of passing chainId, make sure chainId is read from nftMetadata when nftMetadata is available
1026
- const chainId = chainIdHex || __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
1043
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
1027
1044
  const nftContracts = allNftContracts[userAddress]?.[chainId] || [];
1028
1045
  const existingEntry = nftContracts.find((nftContract) => nftContract.address.toLowerCase() ===
1029
1046
  checksumHexAddress.toLowerCase());
@@ -1117,7 +1134,7 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1117
1134
  userAddress,
1118
1135
  });
1119
1136
  return newNftContracts;
1120
- }, _NftController_validateWatchNft = async function _NftController_validateWatchNft(asset, type, userAddress, { networkClientId } = {}) {
1137
+ }, _NftController_validateWatchNft = async function _NftController_validateWatchNft(asset, type, userAddress, networkClientId) {
1121
1138
  const { address: contractAddress, tokenId } = asset;
1122
1139
  // Validate parameters
1123
1140
  if (!type) {
@@ -1140,7 +1157,7 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1140
1157
  }
1141
1158
  // Check if the user owns the suggested NFT
1142
1159
  try {
1143
- const isOwner = await this.isNftOwner(userAddress, contractAddress, tokenId, { networkClientId });
1160
+ const isOwner = await this.isNftOwner(userAddress, contractAddress, tokenId, networkClientId);
1144
1161
  if (!isOwner) {
1145
1162
  throw rpc_errors_1.rpcErrors.invalidInput('Suggested NFT is not owned by the selected account');
1146
1163
  }
@@ -1152,12 +1169,6 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1152
1169
  }
1153
1170
  throw error;
1154
1171
  }
1155
- }, _NftController_getCorrectChainId = function _NftController_getCorrectChainId({ networkClientId, }) {
1156
- if (networkClientId) {
1157
- const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
1158
- return chainId;
1159
- }
1160
- return __classPrivateFieldGet(this, _NftController_chainId, "f");
1161
1172
  }, _NftController_getAddressOrSelectedAddress = function _NftController_getAddressOrSelectedAddress(address) {
1162
1173
  if (address) {
1163
1174
  return address;
@@ -1165,8 +1176,16 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1165
1176
  // If the address is not defined (or empty), we fallback to the currently selected account's address
1166
1177
  const selectedAccount = this.messagingSystem.call('AccountsController:getAccount', __classPrivateFieldGet(this, _NftController_selectedAccountId, "f"));
1167
1178
  return selectedAccount?.address || '';
1168
- }, _NftController_updateNftUpdateForAccount = async function _NftController_updateNftUpdateForAccount(account) {
1169
- const nfts = this.state.allNfts[account.address]?.[__classPrivateFieldGet(this, _NftController_chainId, "f")] ?? [];
1179
+ }, _NftController_updateNftUpdateForAccount =
1180
+ /**
1181
+ * Updates the all nfts in state for the account.
1182
+ * Nfts will be updated if they don't have a name, description or image.
1183
+ *
1184
+ * @param account - The account to update the NFT metadata for.
1185
+ */
1186
+ async function _NftController_updateNftUpdateForAccount(account) {
1187
+ // get all nfts for the account for all chains
1188
+ const nfts = Object.values(this.state.allNfts[account.address] || {}).flat();
1170
1189
  // Filter only nfts
1171
1190
  const nftsToUpdate = nfts.filter((singleNft) => !singleNft.name && !singleNft.description && !singleNft.image);
1172
1191
  if (nftsToUpdate.length !== 0 &&
@@ -1176,6 +1195,101 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1176
1195
  userAddress: account.address,
1177
1196
  });
1178
1197
  }
1198
+ }, _NftController_bulkSanitizeNftMetadata =
1199
+ /**
1200
+ * Sanitizes multiple NFT metadata objects by checking external links against PhishingController in a single bulk request
1201
+ *
1202
+ * @param metadataList - Array of NFT metadata objects to sanitize
1203
+ * @returns Array of sanitized NFT metadata objects
1204
+ */
1205
+ async function _NftController_bulkSanitizeNftMetadata(metadataList) {
1206
+ // Create a copy of the metadata list to avoid mutating the input
1207
+ const sanitizedMetadataList = metadataList.map((metadata) => ({
1208
+ ...metadata,
1209
+ }));
1210
+ // Maps URL to a list of {metadataIndex, fieldName} to track where each URL is used
1211
+ const urlMap = {};
1212
+ const fieldsToCheck = [
1213
+ 'externalLink',
1214
+ 'image',
1215
+ 'imagePreview',
1216
+ 'imageThumbnail',
1217
+ 'imageOriginal',
1218
+ 'animation',
1219
+ 'animationOriginal',
1220
+ ];
1221
+ // Collect all URLs from all metadata objects
1222
+ sanitizedMetadataList.forEach((metadata, metadataIndex) => {
1223
+ // Check regular fields
1224
+ for (const field of fieldsToCheck) {
1225
+ const url = metadata[field];
1226
+ if (typeof url === 'string' && url && url.startsWith('http')) {
1227
+ if (!urlMap[url]) {
1228
+ urlMap[url] = [];
1229
+ }
1230
+ urlMap[url].push({ metadataIndex, fieldName: field });
1231
+ }
1232
+ }
1233
+ // Check collection links if they exist
1234
+ if (metadata.collection) {
1235
+ const { collection } = metadata;
1236
+ if ('externalLink' in collection &&
1237
+ typeof collection.externalLink === 'string') {
1238
+ const url = collection.externalLink;
1239
+ if (!urlMap[url]) {
1240
+ urlMap[url] = [];
1241
+ }
1242
+ urlMap[url].push({
1243
+ metadataIndex,
1244
+ fieldName: 'collection.externalLink',
1245
+ });
1246
+ }
1247
+ }
1248
+ });
1249
+ const urlsToCheck = Object.keys(urlMap);
1250
+ if (urlsToCheck.length === 0) {
1251
+ return sanitizedMetadataList;
1252
+ }
1253
+ try {
1254
+ // Use bulkScanUrls to check all URLs at once
1255
+ const bulkScanResponse = await this.messagingSystem.call('PhishingController:bulkScanUrls', urlsToCheck);
1256
+ // Apply scan results to all metadata objects
1257
+ Object.entries(bulkScanResponse.results).forEach(([url, result]) => {
1258
+ if (result.recommendedAction === phishing_controller_1.RecommendedAction.Block) {
1259
+ // Remove this URL from all metadata objects where it appears
1260
+ urlMap[url].forEach(({ metadataIndex, fieldName }) => {
1261
+ if (fieldName === 'collection.externalLink' &&
1262
+ sanitizedMetadataList[metadataIndex].collection // Check if collection exists
1263
+ ) {
1264
+ const { collection } = sanitizedMetadataList[metadataIndex];
1265
+ // Ensure collection is not undefined again just to be safe before using 'in'
1266
+ if (collection && 'externalLink' in collection) {
1267
+ delete collection.externalLink;
1268
+ }
1269
+ }
1270
+ else {
1271
+ delete sanitizedMetadataList[metadataIndex][fieldName];
1272
+ }
1273
+ });
1274
+ }
1275
+ });
1276
+ }
1277
+ catch (error) {
1278
+ console.error('Error during bulk URL scanning:', error);
1279
+ // If bulk scan fails, we fall back to keeping all URLs
1280
+ }
1281
+ return sanitizedMetadataList;
1282
+ }, _NftController_sanitizeNftMetadata =
1283
+ /**
1284
+ * Sanitizes NFT metadata by checking external links against PhishingController
1285
+ *
1286
+ * @param metadata - The NFT metadata to sanitize
1287
+ * @returns Sanitized NFT metadata with potentially dangerous links removed
1288
+ */
1289
+ async function _NftController_sanitizeNftMetadata(metadata) {
1290
+ // Use the bulk sanitize function with just a single metadata object
1291
+ const sanitized = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_bulkSanitizeNftMetadata).call(this, [metadata]);
1292
+ return sanitized[0];
1179
1293
  };
1180
1294
  exports.default = NftController;
1181
1295
  //# sourceMappingURL=NftController.cjs.map