-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
## **Description** This PR aims to: 1- Enable nft autodetection by default 2- Show a modal only once if the user disables the nft autodetection 3- Make NFT detection tied to the components that use the NFTs instead of the 3mins polling strategy. This PR goes with this core PR: MetaMask/core#4281 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/24547?quickstart=1) ## **Related issues** Related to: MetaMask/core#4281 ## **Manual testing steps** **Test the nft auto detection modal:** 1. Do a fresh import of the wallet 2. Switch to mainnet. 3. Go to settings => Security and privacy and make sure the toggle enable NFT auto detection is ON 4. Turn the nft auto detection toggle OFF 5. Go back to home page and you should see a new modal 6. Clicking on `not right now `button should close the modal and closing on `allow` button should enable the nft auto detection modal. This modal should be seen only once. **Test the removal of the polling in the backgound:** We have the toggle enabled by default now but this should not trigger calls to NFT-API every 3 mins anymore. Instead the calls should be triggered only when you click on the NFT tab. 1. Open the background console and click on Networks tab. 2. Filter by /tokens (so you are able to see only the calls that will fetch user nfts) 3. Notice that as long as you did not click on the NFT tab, you should not be able to see any calls made in the backround. (where the old logic should keep detecting your NFTs every 3 mins as long as you have MM open) 5. Click on the NFT tab and you should be able to see the requests in the background to fetch your nfts. 7. You can also click on Send, and click on asset picker and click on NFT tab, you should be able to see your NFTs there too. 8. Calls should be made only when you click on the NFT tab. **Test new notice banner behavior:** Users should see the NFT Notice banner as long as they are on mainnet + they have NFT detection OFF. Regardless of whether they have NFTs in the state or not. Clicking on the"Enable NFT autodetection" should remove the notice banner and enable the NFT detection without redirecting the user to settings. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> https://github.com/MetaMask/metamask-extension/assets/10994169/0815ac0c-a60c-400a-9ffe-92451dfc3ded ### **After** <!-- [screenshots/recordings] --> https://github.com/MetaMask/metamask-extension/assets/10994169/0b00f1c1-baf1-4205-bca2-991d11e39a2f Notice banner with toast: https://github.com/MetaMask/metamask-extension/assets/10994169/c1aaa168-a29f-4b38-b0c7-74aaa1945ba0 ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
diff --git a/dist/chunk-FMZML3V5.js b/dist/chunk-FMZML3V5.js | ||
index ee6155cd938366918de155e8867c7d359b8ea826..b4dfe838c463a561b0e91532bcb674806fdc52bd 100644 | ||
--- a/dist/chunk-FMZML3V5.js | ||
+++ b/dist/chunk-FMZML3V5.js | ||
@@ -5,6 +5,7 @@ | ||
|
||
|
||
var _controllerutils = require('@metamask/controller-utils'); | ||
+var _utils = require('@metamask/utils'); | ||
var _pollingcontroller = require('@metamask/polling-controller'); | ||
var DEFAULT_INTERVAL = 18e4; | ||
var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { | ||
@@ -14,6 +15,8 @@ var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { | ||
BlockaidResultType2["Malicious"] = "Malicious"; | ||
return BlockaidResultType2; | ||
})(BlockaidResultType || {}); | ||
+const supportedNftDetectionNetworks= [_controllerutils.ChainId.mainnet]; | ||
+var inProcessNftFetchingUpdates; | ||
var NftDetectionController = class extends _pollingcontroller.StaticIntervalPollingControllerV1 { | ||
/** | ||
* Creates an NftDetectionController instance. | ||
@@ -50,6 +53,7 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll | ||
* Name of this controller used during composition | ||
*/ | ||
this.name = "NftDetectionController"; | ||
+ this.inProcessNftFetchingUpdates= {}; | ||
/** | ||
* Checks whether network is mainnet or not. | ||
* | ||
@@ -72,11 +76,6 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll | ||
const { selectedAddress: previouslySelectedAddress, disabled } = this.config; | ||
if (selectedAddress !== previouslySelectedAddress || !useNftDetection !== disabled) { | ||
this.configure({ selectedAddress, disabled: !useNftDetection }); | ||
- if (useNftDetection) { | ||
- this.start(); | ||
- } else { | ||
- this.stop(); | ||
- } | ||
} | ||
}); | ||
onNetworkStateChange(({ selectedNetworkClientId }) => { | ||
@@ -92,34 +91,33 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll | ||
this.setIntervalLength(this.config.interval); | ||
} | ||
getOwnerNftApi({ | ||
+ chainId, | ||
address, | ||
next | ||
}) { | ||
- return `${_controllerutils.NFT_API_BASE_URL}/users/${address}/tokens?chainIds=1&limit=50&includeTopBid=true&continuation=${next ?? ""}`; | ||
+ return `${_controllerutils.NFT_API_BASE_URL}/users/${address}/tokens?chainIds=${chainId}&limit=50&includeTopBid=true&continuation=${next ?? ""}`; | ||
} | ||
- async getOwnerNfts(address) { | ||
- let nftApiResponse; | ||
- let nfts = []; | ||
- let next; | ||
- do { | ||
- nftApiResponse = await _controllerutils.fetchWithErrorHandling.call(void 0, { | ||
- url: this.getOwnerNftApi({ address, next }), | ||
- options: { | ||
- headers: { | ||
- Version: "1" | ||
- } | ||
+ async getOwnerNfts( | ||
+ address, | ||
+ chainId, | ||
+ cursor, | ||
+ ) { | ||
+ // Convert hex chainId to number | ||
+ const convertedChainId = (0, _controllerutils.convertHexToDecimal)(chainId).toString(); | ||
+ const url = this.getOwnerNftApi({ | ||
+ chainId: convertedChainId, | ||
+ address, | ||
+ next: cursor, | ||
+ }); | ||
+ | ||
+ const nftApiResponse = await _controllerutils.handleFetch.call(void 0, url, | ||
+ { | ||
+ headers: { | ||
+ Version: "1" | ||
}, | ||
- timeout: 15e3 | ||
- }); | ||
- if (!nftApiResponse) { | ||
- return nfts; | ||
} | ||
- const newNfts = nftApiResponse.tokens.filter( | ||
- (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) | ||
- ); | ||
- nfts = [...nfts, ...newNfts]; | ||
- } while (next = nftApiResponse.continuation); | ||
- return nfts; | ||
+ ); | ||
+ return nftApiResponse; | ||
} | ||
async _executePoll(networkClientId, options) { | ||
await this.detectNfts({ networkClientId, userAddress: options.address }); | ||
@@ -169,62 +167,103 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll | ||
networkClientId, | ||
userAddress | ||
} = { userAddress: this.config.selectedAddress }) { | ||
- if (!this.isMainnet() || this.disabled) { | ||
+ const { chainId } = this.config; | ||
+ if (!supportedNftDetectionNetworks.includes(chainId) || this.disabled) { | ||
return; | ||
} | ||
if (!userAddress) { | ||
return; | ||
} | ||
- const apiNfts = await this.getOwnerNfts(userAddress); | ||
- const addNftPromises = apiNfts.map(async (nft) => { | ||
- const { | ||
- tokenId: token_id, | ||
- contract, | ||
- kind, | ||
- image: image_url, | ||
- imageSmall: image_thumbnail_url, | ||
- metadata: { imageOriginal: image_original_url } = {}, | ||
- name, | ||
- description, | ||
- attributes, | ||
- topBid, | ||
- lastSale, | ||
- rarityRank, | ||
- rarityScore, | ||
- collection | ||
- } = nft.token; | ||
- let ignored; | ||
- const { ignoredNfts } = this.getNftState(); | ||
- if (ignoredNfts.length) { | ||
- ignored = ignoredNfts.find((c) => { | ||
- return c.address === _controllerutils.toChecksumHexAddress.call(void 0, contract) && c.tokenId === token_id; | ||
- }); | ||
- } | ||
- if (!ignored) { | ||
- const nftMetadata = Object.assign( | ||
- {}, | ||
- { name }, | ||
- description && { description }, | ||
- image_url && { image: image_url }, | ||
- image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, | ||
- image_original_url && { imageOriginal: image_original_url }, | ||
- kind && { standard: kind.toUpperCase() }, | ||
- lastSale && { lastSale }, | ||
- attributes && { attributes }, | ||
- topBid && { topBid }, | ||
- rarityRank && { rarityRank }, | ||
- rarityScore && { rarityScore }, | ||
- collection && { collection } | ||
- ); | ||
- await this.addNft(contract, token_id, { | ||
- nftMetadata, | ||
- userAddress, | ||
- source: "detected" /* Detected */, | ||
- networkClientId | ||
+ | ||
+ const updateKey = `${chainId}:${userAddress}`; | ||
+ if (updateKey in this.inProcessNftFetchingUpdates) { | ||
+ // This prevents redundant updates | ||
+ // This promise is resolved after the in-progress update has finished, | ||
+ // and state has been updated. | ||
+ await this.inProcessNftFetchingUpdates[updateKey]; | ||
+ return; | ||
+ } | ||
+ const { | ||
+ promise: inProgressUpdate, | ||
+ resolve: updateSucceeded, | ||
+ reject: updateFailed | ||
+ } = _utils.createDeferredPromise.call(void 0, { suppressUnhandledRejection: true }); | ||
+ this.inProcessNftFetchingUpdates[updateKey] = inProgressUpdate; | ||
+ | ||
+ let next; | ||
+ let apiNfts= []; | ||
+ let resultNftApi; | ||
+ | ||
+ try{ | ||
+ do { | ||
+ resultNftApi = await this.getOwnerNfts(userAddress, chainId, next) | ||
+ apiNfts = resultNftApi.tokens.filter( | ||
+ (elm) => | ||
+ elm.token.isSpam === false && | ||
+ (elm.blockaidResult?.result_type | ||
+ ? elm.blockaidResult?.result_type === BlockaidResultType.Benign | ||
+ : true), | ||
+ ); | ||
+ const addNftPromises = apiNfts.map(async (nft) => { | ||
+ const { | ||
+ tokenId: token_id, | ||
+ contract, | ||
+ kind, | ||
+ image: image_url, | ||
+ imageSmall: image_thumbnail_url, | ||
+ metadata: { imageOriginal: image_original_url } = {}, | ||
+ name, | ||
+ description, | ||
+ attributes, | ||
+ topBid, | ||
+ lastSale, | ||
+ rarityRank, | ||
+ rarityScore, | ||
+ collection, | ||
+ } = nft.token; | ||
+ | ||
+ let ignored; | ||
+ /* istanbul ignore else */ | ||
+ const { ignoredNfts } = this.getNftState(); | ||
+ if (ignoredNfts.length) { | ||
+ ignored = ignoredNfts.find((c) => { | ||
+ return c.address === _controllerutils.toChecksumHexAddress.call(void 0, contract) && c.tokenId === token_id; | ||
+ }); | ||
+ } | ||
+ /* istanbul ignore else */ | ||
+ if (!ignored) { | ||
+ const nftMetadata = Object.assign( | ||
+ {}, | ||
+ { name }, | ||
+ description && { description }, | ||
+ image_url && { image: image_url }, | ||
+ image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, | ||
+ image_original_url && { imageOriginal: image_original_url }, | ||
+ kind && { standard: kind.toUpperCase() }, | ||
+ lastSale && { lastSale }, | ||
+ attributes && { attributes }, | ||
+ topBid && { topBid }, | ||
+ rarityRank && { rarityRank }, | ||
+ rarityScore && { rarityScore }, | ||
+ collection && { collection } | ||
+ ); | ||
+ await this.addNft(contract, token_id, { | ||
+ nftMetadata, | ||
+ userAddress, | ||
+ source: "detected" /* Detected */, | ||
+ networkClientId | ||
+ }); | ||
+ } | ||
}); | ||
- } | ||
- }); | ||
- await Promise.all(addNftPromises); | ||
+ await Promise.all(addNftPromises); | ||
+ } while ((next = resultNftApi.continuation)); | ||
+ updateSucceeded(); | ||
+ } catch (error){ | ||
+ updateFailed(error); | ||
+ throw error; | ||
+ } finally { | ||
+ delete this.inProcessNftFetchingUpdates[updateKey]; | ||
+ } | ||
} | ||
}; | ||
var NftDetectionController_default = NftDetectionController; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.