-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: NFT details new design (#25524)
## **Description** PR that updates the design for the NFT page. Designs are here: https://www.figma.com/design/TfVzSMJA8KwpWX8TTWQ2iO/Asset-list-and-details?node-id=3717-47944&m=dev [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25524?quickstart=1) ## **Related issues** Fixes: Related: MetaMask/core#4443 ## **Manual testing steps** 1. Go to NFT tab 2. Click on any NFT you have 3. You should be able to see the new NFT page with no errors even if some data is not available. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/MetaMask/metamask-extension/assets/10994169/dc6ca128-5f2a-45e2-9289-d84f5403d772 ### **After** https://github.com/MetaMask/metamask-extension/assets/10994169/58fd8cd4-24d9-42e9-931a-c33f8ae812d6 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension 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. --------- Co-authored-by: MetaMask Bot <[email protected]>
- Loading branch information
1 parent
7b3450a
commit b9de828
Showing
47 changed files
with
1,907 additions
and
764 deletions.
There are no files selected for viewing
236 changes: 236 additions & 0 deletions
236
.yarn/patches/@metamask-assets-controllers-npm-34.0.0-ea790e90a1.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
diff --git a/dist/chunk-354SINOH.js b/dist/chunk-354SINOH.js | ||
index 7f87776370b755bf04765b8a0ae0145bf3a0b5e6..e0b47123b31b3c7779e903180afc6c692953b6c2 100644 | ||
--- a/dist/chunk-354SINOH.js | ||
+++ b/dist/chunk-354SINOH.js | ||
@@ -12,7 +12,8 @@ var _basecontroller = require('@metamask/base-controller'); | ||
|
||
|
||
|
||
- | ||
+var MAX_GET_COLLECTION_BATCH_SIZE = 20; | ||
+var _chunkNYVA7ZTQjs = require('./chunk-NYVA7ZTQ.js'); | ||
|
||
var _controllerutils = require('@metamask/controller-utils'); | ||
var _utils = require('@metamask/utils'); | ||
@@ -134,6 +135,60 @@ var NftDetectionController = class extends _basecontroller.BaseController { | ||
apiNfts = resultNftApi.tokens.filter( | ||
(elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) | ||
); | ||
+ const collections = apiNfts.reduce((acc, currValue) => { | ||
+ if (!acc.includes(currValue.token.contract) && currValue.token.contract === currValue?.token?.collection?.id) { | ||
+ acc.push(currValue.token.contract); | ||
+ } | ||
+ return acc; | ||
+ }, []); | ||
+ if (collections.length !== 0) { | ||
+ const collectionResponse = await _chunkNYVA7ZTQjs.reduceInBatchesSerially.call(void 0, { | ||
+ values: collections, | ||
+ batchSize: MAX_GET_COLLECTION_BATCH_SIZE, | ||
+ eachBatch: async (allResponses, batch) => { | ||
+ const params = new URLSearchParams( | ||
+ batch.map((s) => ["contract", s]) | ||
+ ); | ||
+ params.append("chainId", "1"); | ||
+ const collectionResponseForBatch = await _controllerutils.fetchWithErrorHandling.call(void 0, | ||
+ { | ||
+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${params.toString()}`, | ||
+ options: { | ||
+ headers: { | ||
+ Version: _controllerutils.NFT_API_VERSION | ||
+ } | ||
+ }, | ||
+ timeout: _controllerutils.NFT_API_TIMEOUT | ||
+ } | ||
+ ); | ||
+ return { | ||
+ ...allResponses, | ||
+ ...collectionResponseForBatch | ||
+ }; | ||
+ }, | ||
+ initialResult: {} | ||
+ }); | ||
+ if (collectionResponse.collections?.length) { | ||
+ apiNfts.forEach((singleNFT) => { | ||
+ const found = collectionResponse.collections.find( | ||
+ (elm) => elm.id?.toLowerCase() === singleNFT.token.contract.toLowerCase() | ||
+ ); | ||
+ if (found) { | ||
+ singleNFT.token = { | ||
+ ...singleNFT.token, | ||
+ collection: { | ||
+ ...singleNFT.token.collection ? singleNFT.token.collection : {}, | ||
+ creator: found?.creator, | ||
+ openseaVerificationStatus: found?.openseaVerificationStatus, | ||
+ contractDeployedAt: found.contractDeployedAt, | ||
+ ownerCount: found.ownerCount, | ||
+ topBid: found.topBid | ||
+ } | ||
+ }; | ||
+ } | ||
+ }); | ||
+ } | ||
+ } | ||
const addNftPromises = apiNfts.map(async (nft) => { | ||
const { | ||
tokenId, | ||
diff --git a/dist/chunk-7JWDWDXT.js b/dist/chunk-7JWDWDXT.js | ||
index af5d78416658763da52305f9e08b286733310898..5f1d7268ed8b102e0aab9f09c3896ea6fba6a0a8 100644 | ||
--- a/dist/chunk-7JWDWDXT.js | ||
+++ b/dist/chunk-7JWDWDXT.js | ||
@@ -881,6 +881,18 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { | ||
} | ||
} | ||
}); | ||
+ const getCollectionParams = new URLSearchParams({ | ||
+ chainId: "1", | ||
+ id: `${nftInformation?.tokens[0]?.token?.collection?.id}` | ||
+ }).toString(); | ||
+ const collectionInformation = await _controllerutils.fetchWithErrorHandling.call(void 0, { | ||
+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${getCollectionParams}`, | ||
+ options: { | ||
+ headers: { | ||
+ Version: _controllerutils.NFT_API_VERSION | ||
+ } | ||
+ } | ||
+ }); | ||
if (!nftInformation?.tokens?.[0]?.token) { | ||
return { | ||
name: null, | ||
@@ -918,7 +930,16 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { | ||
}, | ||
rarityRank && { rarityRank }, | ||
rarity && { rarity }, | ||
- collection && { collection } | ||
+ (collection || collectionInformation) && { | ||
+ collection: { | ||
+ ...collection || {}, | ||
+ creator: collection?.creator || collectionInformation?.collections[0].creator, | ||
+ openseaVerificationStatus: collectionInformation?.collections[0].openseaVerificationStatus, | ||
+ contractDeployedAt: collectionInformation?.collections[0].contractDeployedAt, | ||
+ ownerCount: collectionInformation?.collections[0].ownerCount, | ||
+ topBid: collectionInformation?.collections[0].topBid | ||
+ } | ||
+ } | ||
); | ||
return nftMetadata; | ||
}; | ||
@@ -1095,7 +1116,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont | ||
nftMetadata, | ||
existingEntry | ||
); | ||
- if (!differentMetadata && existingEntry.isCurrentlyOwned) { | ||
+ const hasNewFields = hasNewCollectionFields(nftMetadata, existingEntry); | ||
+ if (!differentMetadata && existingEntry.isCurrentlyOwned && !hasNewFields) { | ||
return; | ||
} | ||
const indexToUpdate = nfts.findIndex( | ||
diff --git a/dist/chunk-NYVA7ZTQ.js b/dist/chunk-NYVA7ZTQ.js | ||
index f31fdabedc067227407a6320e57a670f86b972f4..c0ff7ece56dc5f3e68149d114ff16f7d10eb1741 100644 | ||
--- a/dist/chunk-NYVA7ZTQ.js | ||
+++ b/dist/chunk-NYVA7ZTQ.js | ||
@@ -27,6 +27,11 @@ function compareNftMetadata(newNftMetadata, nft) { | ||
}, 0); | ||
return differentValues > 0; | ||
} | ||
+function hasNewCollectionFields(newNftMetadata, nft) { | ||
+ const keysNewNftMetadata = Object.keys(newNftMetadata.collection || {}); | ||
+ const keysExistingNft = new Set(Object.keys(nft.collection || {})); | ||
+ return keysNewNftMetadata.some((key) => !keysExistingNft.has(key)); | ||
+} | ||
var aggregatorNameByKey = { | ||
aave: "Aave", | ||
bancor: "Bancor", | ||
@@ -205,5 +210,5 @@ async function fetchTokenContractExchangeRates({ | ||
|
||
|
||
|
||
-exports.TOKEN_PRICES_BATCH_SIZE = TOKEN_PRICES_BATCH_SIZE; exports.compareNftMetadata = compareNftMetadata; exports.formatAggregatorNames = formatAggregatorNames; exports.formatIconUrlWithProxy = formatIconUrlWithProxy; exports.SupportedTokenDetectionNetworks = SupportedTokenDetectionNetworks; exports.isTokenDetectionSupportedForNetwork = isTokenDetectionSupportedForNetwork; exports.isTokenListSupportedForNetwork = isTokenListSupportedForNetwork; exports.removeIpfsProtocolPrefix = removeIpfsProtocolPrefix; exports.getIpfsCIDv1AndPath = getIpfsCIDv1AndPath; exports.getFormattedIpfsUrl = getFormattedIpfsUrl; exports.addUrlProtocolPrefix = addUrlProtocolPrefix; exports.ethersBigNumberToBN = ethersBigNumberToBN; exports.divideIntoBatches = divideIntoBatches; exports.reduceInBatchesSerially = reduceInBatchesSerially; exports.fetchTokenContractExchangeRates = fetchTokenContractExchangeRates; | ||
+exports.TOKEN_PRICES_BATCH_SIZE = TOKEN_PRICES_BATCH_SIZE; exports.compareNftMetadata = compareNftMetadata; exports.hasNewCollectionFields = hasNewCollectionFields; exports.formatAggregatorNames = formatAggregatorNames; exports.formatIconUrlWithProxy = formatIconUrlWithProxy; exports.SupportedTokenDetectionNetworks = SupportedTokenDetectionNetworks; exports.isTokenDetectionSupportedForNetwork = isTokenDetectionSupportedForNetwork; exports.isTokenListSupportedForNetwork = isTokenListSupportedForNetwork; exports.removeIpfsProtocolPrefix = removeIpfsProtocolPrefix; exports.getIpfsCIDv1AndPath = getIpfsCIDv1AndPath; exports.getFormattedIpfsUrl = getFormattedIpfsUrl; exports.addUrlProtocolPrefix = addUrlProtocolPrefix; exports.ethersBigNumberToBN = ethersBigNumberToBN; exports.divideIntoBatches = divideIntoBatches; exports.reduceInBatchesSerially = reduceInBatchesSerially; exports.fetchTokenContractExchangeRates = fetchTokenContractExchangeRates; | ||
//# sourceMappingURL=chunk-NYVA7ZTQ.js.map | ||
\ No newline at end of file | ||
diff --git a/dist/types/NftController.d.ts b/dist/types/NftController.d.ts | ||
index b663e265475fee486f1e570736a08f2c06ce5479..0252b138bb4f1cbcbfab7c6eadc8ba28fe5af674 100644 | ||
--- a/dist/types/NftController.d.ts | ||
+++ b/dist/types/NftController.d.ts | ||
@@ -7,7 +7,7 @@ import type { PreferencesControllerStateChangeEvent } from '@metamask/preference | ||
import type { Hex } from '@metamask/utils'; | ||
import type { AssetsContractController } from './AssetsContractController'; | ||
import { Source } from './constants'; | ||
-import type { Collection, Attributes, LastSale } from './NftDetectionController'; | ||
+import type { Collection, Attributes, LastSale, TopBid } from './NftDetectionController'; | ||
type NFTStandardType = 'ERC721' | 'ERC1155'; | ||
type SuggestedNftMeta = { | ||
asset: { | ||
@@ -110,9 +110,10 @@ export type NftMetadata = { | ||
tokenURI?: string | null; | ||
collection?: Collection; | ||
address?: string; | ||
- attributes?: Attributes; | ||
+ attributes?: Attributes[]; | ||
lastSale?: LastSale; | ||
rarityRank?: string; | ||
+ topBid?: TopBid; | ||
}; | ||
/** | ||
* @type NftControllerState | ||
diff --git a/dist/types/NftDetectionController.d.ts b/dist/types/NftDetectionController.d.ts | ||
index c645b3ada1ad9dd862428e94adb788f7892c99ad..ad2df53b8225c105b67245f6498702920f882f95 100644 | ||
--- a/dist/types/NftDetectionController.d.ts | ||
+++ b/dist/types/NftDetectionController.d.ts | ||
@@ -227,7 +227,43 @@ export type Attributes = { | ||
topBidValue?: number | null; | ||
createdAt?: string; | ||
}; | ||
-export type Collection = { | ||
+ | ||
+export type GetCollectionsResponse = { | ||
+ collections: CollectionResponse[]; | ||
+ }; | ||
+ | ||
+export type CollectionResponse = { | ||
+ id?: string; | ||
+ openseaVerificationStatus?: string; | ||
+ contractDeployedAt?: string; | ||
+ creator?: string; | ||
+ ownerCount?: string; | ||
+ topBid?: TopBid & { | ||
+ sourceDomain?: string; | ||
+ }; | ||
+}; | ||
+ | ||
+export type FloorAskCollection = { | ||
+ id?: string; | ||
+ price?: Price; | ||
+ maker?: string; | ||
+ kind?: string; | ||
+ validFrom?: number; | ||
+ validUntil?: number; | ||
+ source?: SourceCollection; | ||
+ rawData?: Metadata; | ||
+ isNativeOffChainCancellable?: boolean; | ||
+}; | ||
+ | ||
+export type SourceCollection = { | ||
+ id: string; | ||
+ domain: string; | ||
+ name: string; | ||
+ icon: string; | ||
+ url: string; | ||
+}; | ||
+ | ||
+export type TokenCollection = { | ||
id?: string; | ||
name?: string; | ||
slug?: string; | ||
@@ -243,7 +279,10 @@ export type Collection = { | ||
floorAskPrice?: Price; | ||
royaltiesBps?: number; | ||
royalties?: Royalties[]; | ||
-}; | ||
+ floorAsk?: FloorAskCollection; | ||
+ }; | ||
+ | ||
+export type Collection = TokenCollection & CollectionResponse; | ||
export type Royalties = { | ||
bps?: number; | ||
recipient?: string; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.