@metamask/assets-controllers 100.1.0 → 100.2.1

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 (64) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/dist/AccountTrackerController.cjs +25 -4
  3. package/dist/AccountTrackerController.cjs.map +1 -1
  4. package/dist/AccountTrackerController.d.cts +5 -2
  5. package/dist/AccountTrackerController.d.cts.map +1 -1
  6. package/dist/AccountTrackerController.d.mts +5 -2
  7. package/dist/AccountTrackerController.d.mts.map +1 -1
  8. package/dist/AccountTrackerController.mjs +26 -5
  9. package/dist/AccountTrackerController.mjs.map +1 -1
  10. package/dist/NftController.cjs +198 -150
  11. package/dist/NftController.cjs.map +1 -1
  12. package/dist/NftController.d.cts.map +1 -1
  13. package/dist/NftController.d.mts.map +1 -1
  14. package/dist/NftController.mjs +198 -150
  15. package/dist/NftController.mjs.map +1 -1
  16. package/dist/TokenBalancesController.cjs +51 -12
  17. package/dist/TokenBalancesController.cjs.map +1 -1
  18. package/dist/TokenBalancesController.d.cts.map +1 -1
  19. package/dist/TokenBalancesController.d.mts.map +1 -1
  20. package/dist/TokenBalancesController.mjs +51 -12
  21. package/dist/TokenBalancesController.mjs.map +1 -1
  22. package/dist/TokenDetectionController.cjs +0 -2
  23. package/dist/TokenDetectionController.cjs.map +1 -1
  24. package/dist/TokenDetectionController.d.cts.map +1 -1
  25. package/dist/TokenDetectionController.d.mts.map +1 -1
  26. package/dist/TokenDetectionController.mjs +0 -2
  27. package/dist/TokenDetectionController.mjs.map +1 -1
  28. package/dist/TokenListController.cjs +38 -5
  29. package/dist/TokenListController.cjs.map +1 -1
  30. package/dist/TokenListController.d.cts.map +1 -1
  31. package/dist/TokenListController.d.mts.map +1 -1
  32. package/dist/TokenListController.mjs +38 -5
  33. package/dist/TokenListController.mjs.map +1 -1
  34. package/dist/balances.cjs +46 -12
  35. package/dist/balances.cjs.map +1 -1
  36. package/dist/balances.d.cts +14 -3
  37. package/dist/balances.d.cts.map +1 -1
  38. package/dist/balances.d.mts +14 -3
  39. package/dist/balances.d.mts.map +1 -1
  40. package/dist/balances.mjs +46 -12
  41. package/dist/balances.mjs.map +1 -1
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +1 -1
  44. package/dist/index.d.cts.map +1 -1
  45. package/dist/index.d.mts +1 -1
  46. package/dist/index.d.mts.map +1 -1
  47. package/dist/index.mjs.map +1 -1
  48. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +18 -9
  49. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +1 -1
  50. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts +10 -0
  51. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +1 -1
  52. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts +10 -0
  53. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +1 -1
  54. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +18 -9
  55. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +1 -1
  56. package/dist/rpc-service/rpc-balance-fetcher.cjs +99 -58
  57. package/dist/rpc-service/rpc-balance-fetcher.cjs.map +1 -1
  58. package/dist/rpc-service/rpc-balance-fetcher.d.cts +4 -1
  59. package/dist/rpc-service/rpc-balance-fetcher.d.cts.map +1 -1
  60. package/dist/rpc-service/rpc-balance-fetcher.d.mts +4 -1
  61. package/dist/rpc-service/rpc-balance-fetcher.d.mts.map +1 -1
  62. package/dist/rpc-service/rpc-balance-fetcher.mjs +99 -58
  63. package/dist/rpc-service/rpc-balance-fetcher.mjs.map +1 -1
  64. package/package.json +8 -8
@@ -13,7 +13,7 @@ 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_ipfsGateway, _NftController_displayNftMedia, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _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;
16
+ var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_ipfsGateway, _NftController_displayNftMedia, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformation, _NftController_addMultipleNfts, _NftController_addNftContracts, _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");
@@ -234,23 +234,35 @@ class NftController extends base_controller_1.BaseController {
234
234
  // Sanitize provided metadata
235
235
  nftMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, nftMetadata);
236
236
  }
237
- const newNftContracts = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContract).call(this, networkClientId, {
238
- tokenAddress: checksumHexAddress,
239
- userAddress: addressToSearch,
240
- source,
241
- nftMetadata,
242
- });
237
+ const { contracts: newNftContracts } = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContracts).call(this, addressToSearch, [
238
+ {
239
+ tokenAddress: checksumHexAddress,
240
+ networkClientId,
241
+ source,
242
+ nftMetadata,
243
+ },
244
+ ]);
243
245
  // If NFT contract was not added, do not add individual NFT
244
- const nftContract = newNftContracts.find((contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase());
246
+ const nftContract = Object.values(newNftContracts)
247
+ .flat()
248
+ .find((contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase());
245
249
  const { configuration: { chainId }, } = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
246
250
  // This is the case when the NFT is added manually and not detected automatically
247
251
  // TODO: An improvement would be to make the chainId a required field and return it when getting the NFT information
248
252
  if (!nftMetadata.chainId) {
249
253
  nftMetadata.chainId = (0, controller_utils_1.convertHexToDecimal)(chainId);
250
254
  }
251
- // If NFT contract information, add individual NFT
252
255
  if (nftContract) {
253
- await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addIndividualNft).call(this, checksumHexAddress, tokenId, nftMetadata, nftContract, chainId, addressToSearch, source);
256
+ await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addMultipleNfts).call(this, addressToSearch, [
257
+ {
258
+ tokenAddress: checksumHexAddress,
259
+ tokenId,
260
+ nftMetadata,
261
+ nftContract,
262
+ chainId,
263
+ source,
264
+ },
265
+ ]);
254
266
  }
255
267
  }
256
268
  /**
@@ -271,23 +283,51 @@ class NftController extends base_controller_1.BaseController {
271
283
  }
272
284
  // Remember max number of urls this allows is 250
273
285
  const sanitizedNftMetadata = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_bulkSanitizeNftMetadata).call(this, nfts.map((nft) => nft.nftMetadata));
286
+ // Resolve network client IDs per item up front. Items that fail (e.g.,
287
+ // the user removes a network during detection) are skipped individually
288
+ // so the rest of the batch is unaffected. Resolved data is bundled into
289
+ // one object per NFT to avoid index-alignment issues between the two loops.
290
+ const resolvedNfts = [];
274
291
  for (const [index, nft] of nfts.entries()) {
275
- const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(nft.tokenAddress);
276
- const hexChainId = (0, controller_utils_1.toHex)(nft.nftMetadata.chainId);
277
- const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', hexChainId);
278
- const newNftContracts = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContract).call(this, networkClientId, {
279
- tokenAddress: checksumHexAddress,
280
- userAddress: addressToSearch,
281
- source,
282
- nftMetadata: sanitizedNftMetadata[index],
283
- });
284
- // If NFT contract was not added, do not add individual NFT
285
- const nftContract = newNftContracts.find((contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase());
286
- // If NFT contract information, add individual NFT
292
+ try {
293
+ const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(nft.tokenAddress);
294
+ const hexChainId = (0, controller_utils_1.toHex)(nft.nftMetadata.chainId);
295
+ const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', hexChainId);
296
+ resolvedNfts.push({
297
+ contractToAdd: {
298
+ networkClientId,
299
+ tokenAddress: checksumHexAddress,
300
+ source,
301
+ nftMetadata: sanitizedNftMetadata[index],
302
+ },
303
+ tokenId: nft.tokenId,
304
+ checksumHexAddress,
305
+ hexChainId,
306
+ sanitizedMetadata: sanitizedNftMetadata[index],
307
+ });
308
+ }
309
+ catch (error) {
310
+ console.error('Failed to resolve network for NFT', nft.tokenAddress, error);
311
+ }
312
+ }
313
+ const { contracts: newNftContracts } = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addNftContracts).call(this, addressToSearch, resolvedNfts.map((item) => item.contractToAdd));
314
+ const nftsToAdd = [];
315
+ for (const { checksumHexAddress, tokenId, hexChainId, sanitizedMetadata, } of resolvedNfts) {
316
+ const nftContract = newNftContracts[hexChainId]?.find((contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase());
287
317
  if (nftContract) {
288
- await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addIndividualNft).call(this, checksumHexAddress, nft.tokenId, sanitizedNftMetadata[index], nftContract, hexChainId, addressToSearch, source);
318
+ nftsToAdd.push({
319
+ tokenAddress: checksumHexAddress,
320
+ tokenId,
321
+ nftMetadata: sanitizedMetadata,
322
+ nftContract,
323
+ chainId: hexChainId,
324
+ source,
325
+ });
289
326
  }
290
327
  }
328
+ if (nftsToAdd.length > 0) {
329
+ await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_addMultipleNfts).call(this, addressToSearch, nftsToAdd);
330
+ }
291
331
  }
292
332
  /**
293
333
  * Refetches NFT metadata and updates the state
@@ -870,43 +910,16 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ
870
910
  };
871
911
  // Sanitize the metadata by checking external links against phishing protection
872
912
  return await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, metadata);
873
- }, _NftController_getNftContractInformationFromContract =
874
- /**
875
- * Request NFT contract information from the contract itself.
876
- *
877
- * @param contractAddress - Hex address of the NFT contract.
878
- * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
879
- * @returns Promise resolving to the current NFT name and image.
880
- */
881
- async function _NftController_getNftContractInformationFromContract(
882
- // TODO for calls to blockchain we need to explicitly pass the currentNetworkClientId since its relying on the provider
883
- contractAddress, networkClientId) {
884
- const [name, symbol] = await Promise.all([
885
- this.messenger.call('AssetsContractController:getERC721AssetName', contractAddress, networkClientId),
886
- this.messenger.call('AssetsContractController:getERC721AssetSymbol', contractAddress, networkClientId),
887
- ]);
888
- return {
889
- collection: { name },
890
- symbol,
891
- address: contractAddress,
892
- };
893
- }, _NftController_getNftContractInformation =
894
- /**
895
- * Request NFT contract information from Blockchain and aggregate with received data from NFTMetadata.
896
- *
897
- * @param contractAddress - Hex address of the NFT contract.
898
- * @param nftMetadataFromApi - Received NFT information to be aggregated with blockchain contract information.
899
- * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
900
- * @returns Promise resolving to the NFT contract name, image and description.
901
- */
902
- async function _NftController_getNftContractInformation(contractAddress, nftMetadataFromApi, networkClientId) {
903
- const blockchainContractData = await (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftContractInformationFromContract).call(this, contractAddress, networkClientId));
904
- if (blockchainContractData ||
913
+ }, _NftController_getNftContractInformation = function _NftController_getNftContractInformation(contractAddress, nftMetadataFromApi) {
914
+ const name = nftMetadataFromApi.collection?.name;
915
+ const symbol = nftMetadataFromApi.collection?.symbol;
916
+ if (name !== undefined ||
917
+ symbol !== undefined ||
905
918
  !Object.values(nftMetadataFromApi).every((value) => value === null)) {
906
919
  return {
907
920
  address: contractAddress,
908
- ...blockchainContractData,
909
921
  schema_name: nftMetadataFromApi?.standard ?? null,
922
+ ...(symbol !== undefined && { symbol }),
910
923
  collection: {
911
924
  name: null,
912
925
  image_url: nftMetadataFromApi?.collection?.image ??
@@ -914,7 +927,7 @@ async function _NftController_getNftContractInformation(contractAddress, nftMeta
914
927
  null,
915
928
  tokenCount: nftMetadataFromApi?.collection?.tokenCount ?? null,
916
929
  ...nftMetadataFromApi?.collection,
917
- ...blockchainContractData?.collection,
930
+ ...(name !== undefined && { name }),
918
931
  },
919
932
  };
920
933
  }
@@ -930,122 +943,157 @@ async function _NftController_getNftContractInformation(contractAddress, nftMeta
930
943
  external_link: null,
931
944
  collection: { name: null, image_url: null },
932
945
  };
933
- }, _NftController_addIndividualNft =
946
+ }, _NftController_addMultipleNfts =
934
947
  /**
935
- * Adds an individual NFT to the stored NFT list.
948
+ * Adds multiple NFTs to the stored NFT list for a given user.
936
949
  *
937
- * @param tokenAddress - Hex address of the NFT contract.
938
- * @param tokenId - The NFT identifier.
939
- * @param nftMetadata - NFT optional information (name, image and description).
940
- * @param nftContract - An object containing contract data of the NFT being added.
941
- * @param chainId - The chainId of the network where the NFT is being added.
942
- * @param userAddress - The address of the account where the NFT is being added.
943
- * @param source - Whether the NFT was detected, added manually or suggested by a dapp.
944
- * @returns A promise resolving to `undefined`.
950
+ * @param userAddress - The address of the account where the NFTs are being added.
951
+ * @param nfts - Array of NFT objects to add.
952
+ * @param nfts[].tokenAddress - Hex address of the NFT contract.
953
+ * @param nfts[].tokenId - The NFT identifier.
954
+ * @param nfts[].nftMetadata - NFT optional information (name, image and description).
955
+ * @param nfts[].nftContract - An object containing contract data of the NFT being added.
956
+ * @param nfts[].chainId - The chainId of the network where the NFT is being added.
957
+ * @param nfts[].source - Whether the NFT was detected, added manually or suggested by a dapp.
945
958
  */
946
- async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadata, nftContract, chainId, userAddress, source) {
959
+ async function _NftController_addMultipleNfts(userAddress, nfts) {
947
960
  const releaseLock = await __classPrivateFieldGet(this, _NftController_mutex, "f").acquire();
948
961
  try {
949
- const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
950
962
  const { allNfts } = this.state;
951
- const nfts = [...(allNfts[userAddress]?.[chainId] ?? [])];
952
- const existingEntry = nfts.find((nft) => nft.address.toLowerCase() === checksumHexAddress.toLowerCase() &&
953
- nft.tokenId === tokenId);
954
- if (existingEntry) {
955
- const differentMetadata = (0, assetsUtil_1.compareNftMetadata)(nftMetadata, existingEntry);
956
- const hasNewFields = (0, assetsUtil_1.hasNewCollectionFields)(nftMetadata, existingEntry);
957
- if (!differentMetadata &&
958
- existingEntry.isCurrentlyOwned &&
959
- !hasNewFields) {
960
- return;
963
+ const allNftsForUser = allNfts[userAddress] || {};
964
+ const allNftsForUserPerChain = {};
965
+ const modifiedChainIds = new Set();
966
+ const pendingCallbacks = [];
967
+ for (const { tokenAddress, tokenId, nftMetadata, nftContract, chainId, source, } of nfts) {
968
+ try {
969
+ const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
970
+ if (!allNftsForUserPerChain[chainId]) {
971
+ allNftsForUserPerChain[chainId] = [
972
+ ...(allNftsForUser?.[chainId] ?? []),
973
+ ];
974
+ }
975
+ const existingEntry = allNftsForUserPerChain[chainId].find((nft) => nft.address.toLowerCase() === checksumHexAddress.toLowerCase() &&
976
+ nft.tokenId === tokenId);
977
+ if (existingEntry) {
978
+ const differentMetadata = (0, assetsUtil_1.compareNftMetadata)(nftMetadata, existingEntry);
979
+ const hasNewFields = (0, assetsUtil_1.hasNewCollectionFields)(nftMetadata, existingEntry);
980
+ if (!differentMetadata &&
981
+ existingEntry.isCurrentlyOwned &&
982
+ !hasNewFields) {
983
+ continue;
984
+ }
985
+ const indexToUpdate = allNftsForUserPerChain[chainId].findIndex((nft) => nft.address.toLowerCase() ===
986
+ checksumHexAddress.toLowerCase() && nft.tokenId === tokenId);
987
+ if (indexToUpdate !== -1) {
988
+ allNftsForUserPerChain[chainId][indexToUpdate] = {
989
+ ...existingEntry,
990
+ ...nftMetadata,
991
+ };
992
+ }
993
+ }
994
+ else {
995
+ const newEntry = {
996
+ address: checksumHexAddress,
997
+ tokenId,
998
+ favorite: false,
999
+ isCurrentlyOwned: true,
1000
+ ...nftMetadata,
1001
+ };
1002
+ allNftsForUserPerChain[chainId].push(newEntry);
1003
+ }
1004
+ modifiedChainIds.add(chainId);
1005
+ if (__classPrivateFieldGet(this, _NftController_onNftAdded, "f")) {
1006
+ pendingCallbacks.push({
1007
+ address: checksumHexAddress,
1008
+ symbol: nftContract.symbol,
1009
+ tokenId: tokenId.toString(),
1010
+ standard: nftMetadata.standard,
1011
+ source,
1012
+ });
1013
+ }
961
1014
  }
962
- const indexToUpdate = nfts.findIndex((nft) => nft.address.toLowerCase() === checksumHexAddress.toLowerCase() &&
963
- nft.tokenId === tokenId);
964
- if (indexToUpdate !== -1) {
965
- nfts[indexToUpdate] = {
966
- ...existingEntry,
967
- ...nftMetadata,
968
- };
1015
+ catch (error) {
1016
+ console.error('Failed to add NFT', tokenAddress, tokenId, error);
969
1017
  }
970
1018
  }
971
- else {
972
- const newEntry = {
973
- address: checksumHexAddress,
974
- tokenId,
975
- favorite: false,
976
- isCurrentlyOwned: true,
977
- ...nftMetadata,
978
- };
979
- nfts.push(newEntry);
1019
+ for (const chainId of modifiedChainIds) {
1020
+ __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNestedNftState).call(this, allNftsForUserPerChain[chainId], ALL_NFTS_STATE_KEY, { chainId, userAddress });
980
1021
  }
981
- __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNestedNftState).call(this, nfts, ALL_NFTS_STATE_KEY, {
982
- chainId,
983
- userAddress,
984
- });
985
- if (__classPrivateFieldGet(this, _NftController_onNftAdded, "f")) {
986
- __classPrivateFieldGet(this, _NftController_onNftAdded, "f").call(this, {
987
- address: checksumHexAddress,
988
- symbol: nftContract.symbol,
989
- tokenId: tokenId.toString(),
990
- standard: nftMetadata.standard,
991
- source,
992
- });
1022
+ for (const callbackData of pendingCallbacks) {
1023
+ __classPrivateFieldGet(this, _NftController_onNftAdded, "f")?.call(this, callbackData);
993
1024
  }
994
1025
  }
995
1026
  finally {
996
1027
  releaseLock();
997
1028
  }
998
- }, _NftController_addNftContract =
1029
+ }, _NftController_addNftContracts =
999
1030
  /**
1000
- * Adds an NFT contract to the stored NFT contracts list.
1031
+ * Adds multiple NFT contracts to the stored NFT contracts list for a given user.
1001
1032
  *
1002
- * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
1003
- * @param options - options.
1004
- * @param options.tokenAddress - Hex address of the NFT contract.
1005
- * @param options.userAddress - The address of the account where the NFT is being added.
1006
- * @param options.nftMetadata - The retrieved NFTMetadata from API.
1007
- * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp.
1008
- * @returns Promise resolving to the current NFT contracts list.
1033
+ * @param userAddress - The address of the account where the NFT contracts are being added.
1034
+ * @param contracts - Array of contract objects to add.
1035
+ * @param contracts[].networkClientId - The networkClientId used to identify the network client for the request.
1036
+ * @param contracts[].tokenAddress - Hex address of the NFT contract.
1037
+ * @param contracts[].nftMetadata - The retrieved NFT metadata from the API.
1038
+ * @param contracts[].source - Whether the NFT was detected, added manually or suggested by a dapp.
1039
+ * @returns Promise resolving to an object mapping chainIds to their updated NFT contract arrays.
1009
1040
  */
1010
- async function _NftController_addNftContract(networkClientId, { tokenAddress, userAddress, source, nftMetadata, }) {
1041
+ async function _NftController_addNftContracts(userAddress, contracts) {
1011
1042
  const releaseLock = await __classPrivateFieldGet(this, _NftController_mutex, "f").acquire();
1012
1043
  try {
1013
- const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
1014
1044
  const { allNftContracts } = this.state;
1015
- const { configuration: { chainId }, } = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
1016
- const nftContracts = allNftContracts[userAddress]?.[chainId] || [];
1017
- const existingEntry = nftContracts.find((nftContract) => nftContract.address.toLowerCase() ===
1018
- checksumHexAddress.toLowerCase());
1019
- if (existingEntry) {
1020
- return nftContracts;
1045
+ const allNftContractsForUser = allNftContracts[userAddress] || {};
1046
+ const nftContractsForUserPerChain = {};
1047
+ const modifiedChainIds = new Set();
1048
+ for (const { networkClientId, tokenAddress, source, nftMetadata, } of contracts) {
1049
+ try {
1050
+ const checksumHexAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
1051
+ const { configuration: { chainId }, } = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
1052
+ // Initialised before the existingEntry check so pre-existing contracts
1053
+ // are still present in the returned map for callers to look up.
1054
+ if (!nftContractsForUserPerChain[chainId]) {
1055
+ nftContractsForUserPerChain[chainId] = [
1056
+ ...(allNftContractsForUser?.[chainId] ?? []),
1057
+ ];
1058
+ }
1059
+ const existingEntry = nftContractsForUserPerChain[chainId].find((nftContract) => nftContract.address.toLowerCase() ===
1060
+ checksumHexAddress.toLowerCase());
1061
+ if (existingEntry) {
1062
+ continue;
1063
+ }
1064
+ // this doesn't work currently for detection if the user switches networks while the detection is processing
1065
+ // will be fixed once detection uses networkClientIds
1066
+ // get name and symbol if ERC721 then put together the metadata
1067
+ const contractInformation = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftContractInformation).call(this, checksumHexAddress, nftMetadata);
1068
+ // If the nft is auto-detected we want some valid metadata to be present
1069
+ if (source === constants_1.Source.Detected &&
1070
+ 'address' in contractInformation &&
1071
+ typeof contractInformation.address === 'string' &&
1072
+ 'collection' in contractInformation &&
1073
+ contractInformation.collection.name === null &&
1074
+ 'image_url' in contractInformation.collection &&
1075
+ contractInformation.collection.image_url === null &&
1076
+ Object.entries(contractInformation).every(([key, value]) => {
1077
+ return key === 'address' || key === 'collection' || !value;
1078
+ })) {
1079
+ continue;
1080
+ }
1081
+ const { asset_contract_type, created_date, symbol, description, external_link, schema_name, collection: { name, image_url, tokenCount }, } = contractInformation;
1082
+ /* istanbul ignore next */
1083
+ const newEntry = Object.assign({}, { address: checksumHexAddress }, description && { description }, name && { name }, image_url && { logo: image_url }, symbol && { symbol }, tokenCount !== null &&
1084
+ typeof tokenCount !== 'undefined' && { totalSupply: tokenCount }, asset_contract_type && { assetContractType: asset_contract_type }, created_date && { createdDate: created_date }, schema_name && { schemaName: schema_name }, external_link && { externalLink: external_link });
1085
+ nftContractsForUserPerChain[chainId].push(newEntry);
1086
+ modifiedChainIds.add(chainId);
1087
+ }
1088
+ catch (error) {
1089
+ console.error('Failed to add NFT contract', tokenAddress, error);
1090
+ }
1021
1091
  }
1022
- // this doesn't work currently for detection if the user switches networks while the detection is processing
1023
- // will be fixed once detection uses networkClientIds
1024
- // get name and symbol if ERC721 then put together the metadata
1025
- const contractInformation = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftContractInformation).call(this, checksumHexAddress, nftMetadata, networkClientId);
1026
- const { asset_contract_type, created_date, symbol, description, external_link, schema_name, collection: { name, image_url, tokenCount }, } = contractInformation;
1027
- // If the nft is auto-detected we want some valid metadata to be present
1028
- if (source === constants_1.Source.Detected &&
1029
- 'address' in contractInformation &&
1030
- typeof contractInformation.address === 'string' &&
1031
- 'collection' in contractInformation &&
1032
- contractInformation.collection.name === null &&
1033
- 'image_url' in contractInformation.collection &&
1034
- contractInformation.collection.image_url === null &&
1035
- Object.entries(contractInformation).every(([key, value]) => {
1036
- return key === 'address' || key === 'collection' || !value;
1037
- })) {
1038
- return nftContracts;
1092
+ // Loops once per chain (not once per NFT contract)
1093
+ for (const chainId of modifiedChainIds) {
1094
+ __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNestedNftState).call(this, nftContractsForUserPerChain[chainId], ALL_NFTS_CONTRACTS_STATE_KEY, { chainId, userAddress });
1039
1095
  }
1040
- /* istanbul ignore next */
1041
- const newEntry = Object.assign({}, { address: checksumHexAddress }, description && { description }, name && { name }, image_url && { logo: image_url }, symbol && { symbol }, tokenCount !== null &&
1042
- typeof tokenCount !== 'undefined' && { totalSupply: tokenCount }, asset_contract_type && { assetContractType: asset_contract_type }, created_date && { createdDate: created_date }, schema_name && { schemaName: schema_name }, external_link && { externalLink: external_link });
1043
- const newNftContracts = [...nftContracts, newEntry];
1044
- __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNestedNftState).call(this, newNftContracts, ALL_NFTS_CONTRACTS_STATE_KEY, {
1045
- chainId,
1046
- userAddress,
1047
- });
1048
- return newNftContracts;
1096
+ return { contracts: nftContractsForUserPerChain };
1049
1097
  }
1050
1098
  finally {
1051
1099
  releaseLock();