@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
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- 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;
12
+ 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;
13
13
  function $importDefault(module) {
14
14
  if (module?.__esModule) {
15
15
  return module.default;
@@ -18,7 +18,8 @@ function $importDefault(module) {
18
18
  }
19
19
  import { isAddress } from "@ethersproject/address";
20
20
  import { BaseController } from "@metamask/base-controller";
21
- import { safelyExecute, handleFetch, toChecksumHexAddress, BNToHex, fetchWithErrorHandling, IPFS_DEFAULT_GATEWAY_URL, ERC721, ERC1155, ApprovalType, NFT_API_BASE_URL, NFT_API_VERSION, convertHexToDecimal } from "@metamask/controller-utils";
21
+ import { safelyExecute, handleFetch, toChecksumHexAddress, BNToHex, fetchWithErrorHandling, IPFS_DEFAULT_GATEWAY_URL, ERC721, ERC1155, ApprovalType, NFT_API_BASE_URL, NFT_API_VERSION, convertHexToDecimal, toHex } from "@metamask/controller-utils";
22
+ import { RecommendedAction } from "@metamask/phishing-controller";
22
23
  import { rpcErrors } from "@metamask/rpc-errors";
23
24
  import { remove0x } from "@metamask/utils";
24
25
  import { Mutex } from "async-mutex";
@@ -52,7 +53,6 @@ export class NftController extends 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 @@ export class NftController extends 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 = IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) {
65
+ constructor({ ipfsGateway = 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 @@ export class NftController extends BaseController {
75
75
  _NftController_instances.add(this);
76
76
  _NftController_mutex.set(this, new 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 @@ export class NftController extends 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 @@ export class NftController extends 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 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 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 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: random(),
134
136
  time: Date.now(),
@@ -137,8 +139,8 @@ export class NftController extends 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 @@ export class NftController extends BaseController {
147
149
  },
148
150
  userAddress,
149
151
  source: Source.Dapp,
150
- networkClientId,
151
152
  });
152
153
  }
153
154
  /**
@@ -164,11 +165,10 @@ export class NftController extends 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 @@ export class NftController extends 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 @@ export class NftController extends 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 = Source.Custom, networkClientId, chainId, } = {}) {
225
+ async addNft(tokenAddress, tokenId, networkClientId, { nftMetadata, userAddress, source = 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 = 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 === 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 = convertHexToDecimal(chainIdToAddTo);
252
+ nftMetadata.chainId = 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 @@ export class NftController extends 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: 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', 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]?.[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 = 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, toHex(elm.nft.chainId)));
301
321
  }
302
322
  }
303
323
  finally {
@@ -309,13 +329,13 @@ export class NftController extends 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 = toChecksumHexAddress(address);
320
340
  __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_removeIndividualNft).call(this, checksumHexAddress, tokenId, {
321
341
  chainId,
@@ -336,13 +356,13 @@ export class NftController extends 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 = toChecksumHexAddress(address);
347
367
  __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_removeAndIgnoreIndividualNft).call(this, checksumHexAddress, tokenId, {
348
368
  chainId,
@@ -372,20 +392,18 @@ export class NftController extends 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 @@ export class NftController extends 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 @@ export class NftController extends 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);
@@ -596,12 +614,10 @@ export class NftController extends BaseController {
596
614
  });
597
615
  }
598
616
  }
599
- _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, }) {
600
- const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
601
- __classPrivateFieldSet(this, _NftController_chainId, chainId, "f");
602
- }, _NftController_onPreferencesControllerStateChange =
617
+ _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 =
603
618
  /**
604
619
  * Handles the state change of the preference controller.
620
+ *
605
621
  * @param preferencesState - The new state of the preference controller.
606
622
  * @param preferencesState.ipfsGateway - The configured IPFS gateway.
607
623
  * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used.
@@ -625,6 +641,7 @@ async function _NftController_onPreferencesControllerStateChange({ ipfsGateway,
625
641
  }, _NftController_onSelectedAccountChange =
626
642
  /**
627
643
  * Handles the selected account change on the accounts controller.
644
+ *
628
645
  * @param internalAccount - The new selected account.
629
646
  */
630
647
  async function _NftController_onSelectedAccountChange(internalAccount) {
@@ -847,16 +864,14 @@ async function _NftController_getNftURIAndStandard(contractAddress, tokenId, net
847
864
  * @returns Promise resolving to the current NFT name and image.
848
865
  */
849
866
  async function _NftController_getNftInformation(contractAddress, tokenId, networkClientId) {
850
- const chainId = __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, {
851
- networkClientId,
852
- });
867
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
853
868
  const [blockchainMetadata, nftApiMetadata] = await Promise.all([
854
869
  safelyExecute(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromTokenURI).call(this, contractAddress, tokenId, networkClientId)),
855
870
  __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f") && chainId === '0x1'
856
871
  ? safelyExecute(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromApi).call(this, contractAddress, tokenId))
857
872
  : undefined,
858
873
  ]);
859
- return {
874
+ const metadata = {
860
875
  ...nftApiMetadata,
861
876
  name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null,
862
877
  description: blockchainMetadata?.description ?? nftApiMetadata?.description ?? null,
@@ -864,6 +879,8 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ
864
879
  standard: blockchainMetadata?.standard ?? nftApiMetadata?.standard ?? null,
865
880
  tokenURI: blockchainMetadata?.tokenURI ?? null,
866
881
  };
882
+ // Sanitize the metadata by checking external links against phishing protection
883
+ return await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_sanitizeNftMetadata).call(this, metadata);
867
884
  }, _NftController_getNftContractInformationFromContract =
868
885
  /**
869
886
  * Request NFT contract information from the contract itself.
@@ -872,7 +889,9 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ
872
889
  * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
873
890
  * @returns Promise resolving to the current NFT name and image.
874
891
  */
875
- async function _NftController_getNftContractInformationFromContract(contractAddress, networkClientId) {
892
+ async function _NftController_getNftContractInformationFromContract(
893
+ // TODO for calls to blockchain we need to explicitly pass the currentNetworkClientId since its relying on the provider
894
+ contractAddress, networkClientId) {
876
895
  const [name, symbol] = await Promise.all([
877
896
  this.messagingSystem.call('AssetsContractController:getERC721AssetName', contractAddress, networkClientId),
878
897
  this.messagingSystem.call('AssetsContractController:getERC721AssetSymbol', contractAddress, networkClientId),
@@ -1007,22 +1026,20 @@ async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadat
1007
1026
  /**
1008
1027
  * Adds an NFT contract to the stored NFT contracts list.
1009
1028
  *
1029
+ * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request.
1010
1030
  * @param options - options.
1011
1031
  * @param options.tokenAddress - Hex address of the NFT contract.
1012
1032
  * @param options.userAddress - The address of the account where the NFT is being added.
1013
1033
  * @param options.nftMetadata - The retrieved NFTMetadata from API.
1014
- * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request.
1015
1034
  * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp.
1016
- * @param options.chainIdHex - The chainId to add the NFT contract to.
1017
1035
  * @returns Promise resolving to the current NFT contracts list.
1018
1036
  */
1019
- async function _NftController_addNftContract({ tokenAddress, userAddress, networkClientId, source, nftMetadata, chainIdHex, }) {
1037
+ async function _NftController_addNftContract(networkClientId, { tokenAddress, userAddress, source, nftMetadata, }) {
1020
1038
  const releaseLock = await __classPrivateFieldGet(this, _NftController_mutex, "f").acquire();
1021
1039
  try {
1022
1040
  const checksumHexAddress = toChecksumHexAddress(tokenAddress);
1023
1041
  const { allNftContracts } = this.state;
1024
- // TODO: revisit this with Solana support and instead of passing chainId, make sure chainId is read from nftMetadata when nftMetadata is available
1025
- const chainId = chainIdHex || __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getCorrectChainId).call(this, { networkClientId });
1042
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
1026
1043
  const nftContracts = allNftContracts[userAddress]?.[chainId] || [];
1027
1044
  const existingEntry = nftContracts.find((nftContract) => nftContract.address.toLowerCase() ===
1028
1045
  checksumHexAddress.toLowerCase());
@@ -1116,7 +1133,7 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1116
1133
  userAddress,
1117
1134
  });
1118
1135
  return newNftContracts;
1119
- }, _NftController_validateWatchNft = async function _NftController_validateWatchNft(asset, type, userAddress, { networkClientId } = {}) {
1136
+ }, _NftController_validateWatchNft = async function _NftController_validateWatchNft(asset, type, userAddress, networkClientId) {
1120
1137
  const { address: contractAddress, tokenId } = asset;
1121
1138
  // Validate parameters
1122
1139
  if (!type) {
@@ -1139,7 +1156,7 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1139
1156
  }
1140
1157
  // Check if the user owns the suggested NFT
1141
1158
  try {
1142
- const isOwner = await this.isNftOwner(userAddress, contractAddress, tokenId, { networkClientId });
1159
+ const isOwner = await this.isNftOwner(userAddress, contractAddress, tokenId, networkClientId);
1143
1160
  if (!isOwner) {
1144
1161
  throw rpcErrors.invalidInput('Suggested NFT is not owned by the selected account');
1145
1162
  }
@@ -1151,12 +1168,6 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1151
1168
  }
1152
1169
  throw error;
1153
1170
  }
1154
- }, _NftController_getCorrectChainId = function _NftController_getCorrectChainId({ networkClientId, }) {
1155
- if (networkClientId) {
1156
- const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
1157
- return chainId;
1158
- }
1159
- return __classPrivateFieldGet(this, _NftController_chainId, "f");
1160
1171
  }, _NftController_getAddressOrSelectedAddress = function _NftController_getAddressOrSelectedAddress(address) {
1161
1172
  if (address) {
1162
1173
  return address;
@@ -1164,8 +1175,16 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1164
1175
  // If the address is not defined (or empty), we fallback to the currently selected account's address
1165
1176
  const selectedAccount = this.messagingSystem.call('AccountsController:getAccount', __classPrivateFieldGet(this, _NftController_selectedAccountId, "f"));
1166
1177
  return selectedAccount?.address || '';
1167
- }, _NftController_updateNftUpdateForAccount = async function _NftController_updateNftUpdateForAccount(account) {
1168
- const nfts = this.state.allNfts[account.address]?.[__classPrivateFieldGet(this, _NftController_chainId, "f")] ?? [];
1178
+ }, _NftController_updateNftUpdateForAccount =
1179
+ /**
1180
+ * Updates the all nfts in state for the account.
1181
+ * Nfts will be updated if they don't have a name, description or image.
1182
+ *
1183
+ * @param account - The account to update the NFT metadata for.
1184
+ */
1185
+ async function _NftController_updateNftUpdateForAccount(account) {
1186
+ // get all nfts for the account for all chains
1187
+ const nfts = Object.values(this.state.allNfts[account.address] || {}).flat();
1169
1188
  // Filter only nfts
1170
1189
  const nftsToUpdate = nfts.filter((singleNft) => !singleNft.name && !singleNft.description && !singleNft.image);
1171
1190
  if (nftsToUpdate.length !== 0 &&
@@ -1175,6 +1194,101 @@ async function _NftController_addNftContract({ tokenAddress, userAddress, networ
1175
1194
  userAddress: account.address,
1176
1195
  });
1177
1196
  }
1197
+ }, _NftController_bulkSanitizeNftMetadata =
1198
+ /**
1199
+ * Sanitizes multiple NFT metadata objects by checking external links against PhishingController in a single bulk request
1200
+ *
1201
+ * @param metadataList - Array of NFT metadata objects to sanitize
1202
+ * @returns Array of sanitized NFT metadata objects
1203
+ */
1204
+ async function _NftController_bulkSanitizeNftMetadata(metadataList) {
1205
+ // Create a copy of the metadata list to avoid mutating the input
1206
+ const sanitizedMetadataList = metadataList.map((metadata) => ({
1207
+ ...metadata,
1208
+ }));
1209
+ // Maps URL to a list of {metadataIndex, fieldName} to track where each URL is used
1210
+ const urlMap = {};
1211
+ const fieldsToCheck = [
1212
+ 'externalLink',
1213
+ 'image',
1214
+ 'imagePreview',
1215
+ 'imageThumbnail',
1216
+ 'imageOriginal',
1217
+ 'animation',
1218
+ 'animationOriginal',
1219
+ ];
1220
+ // Collect all URLs from all metadata objects
1221
+ sanitizedMetadataList.forEach((metadata, metadataIndex) => {
1222
+ // Check regular fields
1223
+ for (const field of fieldsToCheck) {
1224
+ const url = metadata[field];
1225
+ if (typeof url === 'string' && url && url.startsWith('http')) {
1226
+ if (!urlMap[url]) {
1227
+ urlMap[url] = [];
1228
+ }
1229
+ urlMap[url].push({ metadataIndex, fieldName: field });
1230
+ }
1231
+ }
1232
+ // Check collection links if they exist
1233
+ if (metadata.collection) {
1234
+ const { collection } = metadata;
1235
+ if ('externalLink' in collection &&
1236
+ typeof collection.externalLink === 'string') {
1237
+ const url = collection.externalLink;
1238
+ if (!urlMap[url]) {
1239
+ urlMap[url] = [];
1240
+ }
1241
+ urlMap[url].push({
1242
+ metadataIndex,
1243
+ fieldName: 'collection.externalLink',
1244
+ });
1245
+ }
1246
+ }
1247
+ });
1248
+ const urlsToCheck = Object.keys(urlMap);
1249
+ if (urlsToCheck.length === 0) {
1250
+ return sanitizedMetadataList;
1251
+ }
1252
+ try {
1253
+ // Use bulkScanUrls to check all URLs at once
1254
+ const bulkScanResponse = await this.messagingSystem.call('PhishingController:bulkScanUrls', urlsToCheck);
1255
+ // Apply scan results to all metadata objects
1256
+ Object.entries(bulkScanResponse.results).forEach(([url, result]) => {
1257
+ if (result.recommendedAction === RecommendedAction.Block) {
1258
+ // Remove this URL from all metadata objects where it appears
1259
+ urlMap[url].forEach(({ metadataIndex, fieldName }) => {
1260
+ if (fieldName === 'collection.externalLink' &&
1261
+ sanitizedMetadataList[metadataIndex].collection // Check if collection exists
1262
+ ) {
1263
+ const { collection } = sanitizedMetadataList[metadataIndex];
1264
+ // Ensure collection is not undefined again just to be safe before using 'in'
1265
+ if (collection && 'externalLink' in collection) {
1266
+ delete collection.externalLink;
1267
+ }
1268
+ }
1269
+ else {
1270
+ delete sanitizedMetadataList[metadataIndex][fieldName];
1271
+ }
1272
+ });
1273
+ }
1274
+ });
1275
+ }
1276
+ catch (error) {
1277
+ console.error('Error during bulk URL scanning:', error);
1278
+ // If bulk scan fails, we fall back to keeping all URLs
1279
+ }
1280
+ return sanitizedMetadataList;
1281
+ }, _NftController_sanitizeNftMetadata =
1282
+ /**
1283
+ * Sanitizes NFT metadata by checking external links against PhishingController
1284
+ *
1285
+ * @param metadata - The NFT metadata to sanitize
1286
+ * @returns Sanitized NFT metadata with potentially dangerous links removed
1287
+ */
1288
+ async function _NftController_sanitizeNftMetadata(metadata) {
1289
+ // Use the bulk sanitize function with just a single metadata object
1290
+ const sanitized = await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_bulkSanitizeNftMetadata).call(this, [metadata]);
1291
+ return sanitized[0];
1178
1292
  };
1179
1293
  export default NftController;
1180
1294
  //# sourceMappingURL=NftController.mjs.map