NFT Catalog
The NFT Catalog is an on chain registry listing NFT collections that exists on Flow which adhere to the NFT metadata standard. This empowers dApp developers to easily build on top of and discover interoperable NFT collections on Flow.
Live Site
Checkout the catalog site to submit your NFT collection both on testnet and mainnet.
Contract Addresses
NFTCatalog.cdc
: This contract contains the NFT Catalog
Network | Address |
---|---|
Mainnet | 0x49a7cda3a1eecc29 |
Testnet | 0x324c34e1c517e4db |
NFTRetrieval.cdc
: This contract contains helper functions to make it easier to discover NFTs within accounts and from the catalog
Network | Address |
---|---|
Mainnet | 0x49a7cda3a1eecc29 |
Testnet | 0x324c34e1c517e4db |
Submitting a Collection to the NFT Catalog
- Visit here
- Enter the address containing the NFT contract which contains the collection and select the contract.
-
Enter the storage path where the NFTs are stored and enter an address that holds a sample NFT or log in if you have access to an account that owns the NFT.
-
The application will verify that your NFT collection implements the required Metadata views.
- The required metadata views include…
- NFT Display
- How to display an individual NFT part of the collection
- External URL
- A website for the NFT collection
- Collection Data
- Information needed to store and retrieve an NFT
- Collection Display
- How to display information about the NFT collection the NFT belongs to
- Royalties
- Any royalties that should be accounted for during marketplace transactions
- NFT Display
- You can find sample implementations of all these views in this example NFT contract.
- If you are not implementing a view, the app will communicate this and you can update your NFT contract and try resubmitting.
- The required metadata views include…
-
Submit proposal transaction to the NFT catalog by entering a unique url safe identifier for the collection and a message including any additional context (like contact information).
- Once submitted you can view all proposals here to track the review of your NFT.
If you would like to make a proposal manually, you may submit the following transaction with all parameters filled in: https://github.com/dapperlabs/nft-catalog/blob/main/cadence/transactions/propose_nft_to_catalog.cdc
Proposals should be reviewed and approved within a few days. Reasons for a proposal being rejected may include:
- Providing duplicate path or name information of an existing collection on the catalog
- Providing a not url safe or inaccurate name as the identifier
Using the Catalog (For marketplaces and other NFT applications)
All of the below examples use the catalog in mainnet, you may replace the imports to the testnet address when using the testnet network.
Example 1 - Retrieve all NFT collections on the catalog
_12import NFTCatalog from 0x49a7cda3a1eecc29_12_12/*_12 The catalog is returned as a `String: NFTCatalogMetadata`_12 The key string is intended to be a unique identifier for a specific collection._12 The NFTCatalogMetadata contains collection-level views corresponding to each_12 collection identifier._12*/_12pub fun main(): {String : NFTCatalog.NFTCatalogMetadata} {_12 return NFTCatalog.getCatalog()_12_12}
Example 2 - Retrieve all collection names in the catalog
_10import NFTCatalog from 0x49a7cda3a1eecc29_10_10pub fun main(): [String] {_10 let catalog: {String : NFTCatalog.NFTCatalogMetadata} = NFTCatalog.getCatalog()_10 let catalogNames: [String] = []_10 for collectionIdentifier in catalog.keys {_10 catalogNames.append(catalog[collectionIdentifier]!.collectionDisplay.name)_10 }_10 return catalogNames_10}
Example 3 - Retrieve NFT collections and counts owned by an account
_29import MetadataViews from 0x1d7e57aa55817448_29import NFTCatalog from 0x49a7cda3a1eecc29_29import NFTRetrieval from 0x49a7cda3a1eecc29_29_29pub fun main(ownerAddress: Address) : {String : Number} {_29 let catalog = NFTCatalog.getCatalog()_29 let account = getAuthAccount(ownerAddress)_29 let items : {String : Number} = {}_29_29 for key in catalog.keys {_29 let value = catalog[key]!_29 let tempPathStr = "catalog".concat(key)_29 let tempPublicPath = PublicPath(identifier: tempPathStr)!_29 account.link<&{MetadataViews.ResolverCollection}>(_29 tempPublicPath,_29 target: value.collectionData.storagePath_29 )_29 let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)_29 if !collectionCap.check() {_29 continue_29 }_29 let count = NFTRetrieval.getNFTCountFromCap(collectionIdentifier : key, collectionCap : collectionCap)_29 if count != 0 {_29 items[key] = count_29 }_29 }_29_29 return items_29}
Sample Response...
_10{_10 "schmoes_prelaunch_token": 1_10}
Example 4 - Retrieve all NFTs including metadata owned by an account
_116import MetadataViews from 0x1d7e57aa55817448_116import NFTCatalog from 0x49a7cda3a1eecc29_116import NFTRetrieval from 0x49a7cda3a1eecc29_116_116pub struct NFT {_116 pub let id : UInt64_116 pub let name : String_116 pub let description : String_116 pub let thumbnail : String_116 pub let externalURL : String_116 pub let storagePath : StoragePath_116 pub let publicPath : PublicPath_116 pub let privatePath: PrivatePath_116 pub let publicLinkedType: Type_116 pub let privateLinkedType: Type_116 pub let collectionName : String_116 pub let collectionDescription: String_116 pub let collectionSquareImage : String_116 pub let collectionBannerImage : String_116 pub let royalties: [MetadataViews.Royalty]_116_116 init(_116 id: UInt64,_116 name : String,_116 description : String,_116 thumbnail : String,_116 externalURL : String,_116 storagePath : StoragePath,_116 publicPath : PublicPath,_116 privatePath : PrivatePath,_116 publicLinkedType : Type,_116 privateLinkedType : Type,_116 collectionIdentifier: String,_116 collectionName : String,_116 collectionDescription : String,_116 collectionSquareImage : String,_116 collectionBannerImage : String,_116 royalties : [MetadataViews.Royalty]_116 ) {_116 self.id = id_116 self.name = name_116 self.description = description_116 self.thumbnail = thumbnail_116 self.externalURL = externalURL_116 self.storagePath = storagePath_116 self.publicPath = publicPath_116 self.privatePath = privatePath_116 self.publicLinkedType = publicLinkedType_116 self.privateLinkedType = privateLinkedType_116 self.collectionIdentifier = collectionIdentifier_116 self.collectionName = collectionName_116 self.collectionDescription = collectionDescription_116 self.collectionSquareImage = collectionSquareImage_116 self.collectionBannerImage = collectionBannerImage_116 self.royalties = royalties_116 }_116}_116_116pub fun main(ownerAddress: Address) : { String : [NFT] } {_116 let catalog = NFTCatalog.getCatalog()_116 let account = getAuthAccount(ownerAddress)_116 let items : [NFTRetrieval.BaseNFTViewsV1] = []_116_116 let data : {String : [NFT] } = {}_116_116 for key in catalog.keys {_116 let value = catalog[key]!_116 let tempPathStr = "catalog".concat(key)_116 let tempPublicPath = PublicPath(identifier: tempPathStr)!_116 account.link<&{MetadataViews.ResolverCollection}>(_116 tempPublicPath,_116 target: value.collectionData.storagePath_116 )_116 let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)_116 if !collectionCap.check() {_116 continue_116 }_116 let views = NFTRetrieval.getNFTViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)_116_116 let items : [NFT] = []_116 for view in views {_116 let displayView = view.display_116 let externalURLView = view.externalURL_116 let collectionDataView = view.collectionData_116 let collectionDisplayView = view.collectionDisplay_116 let royaltyView = view.royalties_116 if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {_116 // This NFT does not have the proper views implemented. Skipping...._116 continue_116 }_116_116 items.append(_116 NFT(_116 id: view.id,_116 name : displayView!.name,_116 description : displayView!.description,_116 thumbnail : displayView!.thumbnail.uri(),_116 externalURL : externalURLView!.url,_116 storagePath : collectionDataView!.storagePath,_116 publicPath : collectionDataView!.publicPath,_116 privatePath : collectionDataView!.providerPath,_116 publicLinkedType : collectionDataView!.publicLinkedType,_116 privateLinkedType : collectionDataView!.providerLinkedType,_116 collectionIdentifier: key,_116 collectionName : collectionDisplayView!.name,_116 collectionDescription : collectionDisplayView!.description,_116 collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),_116 collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),_116 royalties : royaltyView!.getRoyalties()_116 )_116 )_116 }_116 data[key] = items_116 }_116 return data_116}
Sample Response...
_23{_23 "FlovatarComponent": [],_23 "schmoes_prelaunch_token": [_23 s.aa16be98aac20e8073f923261531cbbdfae1464f570f5be796b57cdc97656248.NFT(_23 id: 1006,_23 name: "Schmoes Pre Launch Token #1006",_23 description: "",_23 thumbnail: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",_23 externalURL: "https://schmoes.io",_23 storagePath: /storage/SchmoesPreLaunchTokenCollection,_23 publicPath: /public/SchmoesPreLaunchTokenCollection,_23 privatePath: /private/SchmoesPreLaunchTokenCollection,_23 publicLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A. 1d7e57aa55817448.NonFungibleToken.Receiver,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),_23 privateLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A.1d7e57aa55817448.NonFungibleToken.Provider,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),_23 collectionName: "Schmoes Pre Launch Token",_23 collectionDescription: "",_23 collectionSquareImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",_23 collectionBannerImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",_23 royalties: []_23 )_23 ],_23 "Flovatar": []_23}
Example 5 - Retrieve all NFTs including metadata owned by an account for large wallets
For Wallets that have a lot of NFTs you may run into memory issues. The common pattern to get around this for now is to retrieve just the ID's in a wallet by calling the following script
_33import MetadataViews from 0x1d7e57aa55817448_33import NFTCatalog from 0x49a7cda3a1eecc29_33import NFTRetrieval from 0x49a7cda3a1eecc29_33_33pub fun main(ownerAddress: Address) : {String : [UInt64]} {_33 let catalog = NFTCatalog.getCatalog()_33 let account = getAuthAccount(ownerAddress)_33_33 let items : {String : [UInt64]} = {}_33_33 for key in catalog.keys {_33 let value = catalog[key]!_33 let tempPathStr = "catalogIDs".concat(key)_33 let tempPublicPath = PublicPath(identifier: tempPathStr)!_33 account.link<&{MetadataViews.ResolverCollection}>(_33 tempPublicPath,_33 target: value.collectionData.storagePath_33 )_33_33 let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)_33 if !collectionCap.check() {_33 continue_33 }_33_33 let ids = NFTRetrieval.getNFTIDsFromCap(collectionIdentifier : key, collectionCap : collectionCap)_33_33 if ids.length > 0 {_33 items[key] = ids_33 }_33 }_33 return items_33_33}
and then use the ids to retrieve the full metadata for only those ids by calling the following script and passing in a map of collectlionIdentifer -> [ids]
_120import MetadataViews from 0x1d7e57aa55817448_120import NFTCatalog from 0x49a7cda3a1eecc29_120import NFTRetrieval from 0x49a7cda3a1eecc29_120_120pub struct NFT {_120 pub let id : UInt64_120 pub let name : String_120 pub let description : String_120 pub let thumbnail : String_120 pub let externalURL : String_120 pub let storagePath : StoragePath_120 pub let publicPath : PublicPath_120 pub let privatePath: PrivatePath_120 pub let publicLinkedType: Type_120 pub let privateLinkedType: Type_120 pub let collectionName : String_120 pub let collectionDescription: String_120 pub let collectionSquareImage : String_120 pub let collectionBannerImage : String_120 pub let royalties: [MetadataViews.Royalty]_120_120 init(_120 id: UInt64,_120 name : String,_120 description : String,_120 thumbnail : String,_120 externalURL : String,_120 storagePath : StoragePath,_120 publicPath : PublicPath,_120 privatePath : PrivatePath,_120 publicLinkedType : Type,_120 privateLinkedType : Type,_120 collectionName : String,_120 collectionDescription : String,_120 collectionSquareImage : String,_120 collectionBannerImage : String,_120 royalties : [MetadataViews.Royalty]_120 ) {_120 self.id = id_120 self.name = name_120 self.description = description_120 self.thumbnail = thumbnail_120 self.externalURL = externalURL_120 self.storagePath = storagePath_120 self.publicPath = publicPath_120 self.privatePath = privatePath_120 self.publicLinkedType = publicLinkedType_120 self.privateLinkedType = privateLinkedType_120 self.collectionName = collectionName_120 self.collectionDescription = collectionDescription_120 self.collectionSquareImage = collectionSquareImage_120 self.collectionBannerImage = collectionBannerImage_120 self.royalties = royalties_120 }_120}_120_120pub fun main(ownerAddress: Address, collections: {String : [UInt64]}) : {String : [NFT] } {_120 let data : {String : [NFT] } = {}_120_120 let catalog = NFTCatalog.getCatalog()_120 let account = getAuthAccount(ownerAddress)_120 for collectionIdentifier in collections.keys {_120 if catalog.containsKey(collectionIdentifier) {_120 let value = catalog[collectionIdentifier]!_120 let tempPathStr = "catalog".concat(collectionIdentifier)_120 let tempPublicPath = PublicPath(identifier: tempPathStr)!_120 account.link<&{MetadataViews.ResolverCollection}>(_120 tempPublicPath,_120 target: value.collectionData.storagePath_120 )_120_120 let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)_120_120 if !collectionCap.check() {_120 return data_120 }_120_120 let views = NFTRetrieval.getNFTViewsFromIDs(collectionIdentifier : collectionIdentifier, ids: collections[collectionIdentifier]!, collectionCap : collectionCap)_120_120 let items : [NFT] = []_120_120 for view in views {_120 let displayView = view.display_120 let externalURLView = view.externalURL_120 let collectionDataView = view.collectionData_120 let collectionDisplayView = view.collectionDisplay_120 let royaltyView = view.royalties_120 if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {_120 // Bad NFT. Skipping...._120 continue_120 }_120_120 items.append(_120 NFT(_120 id: view.id,_120 name : displayView!.name,_120 description : displayView!.description,_120 thumbnail : displayView!.thumbnail.uri(),_120 externalURL : externalURLView!.url,_120 storagePath : collectionDataView!.storagePath,_120 publicPath : collectionDataView!.publicPath,_120 privatePath : collectionDataView!.providerPath,_120 publicLinkedType : collectionDataView!.publicLinkedType,_120 privateLinkedType : collectionDataView!.providerLinkedType,_120 collectionName : collectionDisplayView!.name,_120 collectionDescription : collectionDisplayView!.description,_120 collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),_120 collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),_120 royalties : royaltyView!.getRoyalties()_120 )_120 )_120 }_120_120 data[collectionIdentifier] = items_120 }_120 }_120_120_120 return data_120}
Example 6 - Retrieve all MetadataViews for NFTs in a wallet
If you're looking for some MetadataViews that aren't in the core view list you can leverage this script to grab all the views each NFT supports. Note: You lose some typing here but get more data.
_68import MetadataViews from 0x1d7e57aa55817448_68import NFTCatalog from 0x49a7cda3a1eecc29_68import NFTRetrieval from 0x49a7cda3a1eecc29_68_68pub struct NFTCollectionData {_68 pub let storagePath : StoragePath_68 pub let publicPath : PublicPath_68 pub let privatePath: PrivatePath_68 pub let publicLinkedType: Type_68 pub let privateLinkedType: Type_68_68 init(_68 storagePath : StoragePath,_68 publicPath : PublicPath,_68 privatePath : PrivatePath,_68 publicLinkedType : Type,_68 privateLinkedType : Type,_68 ) {_68 self.storagePath = storagePath_68 self.publicPath = publicPath_68 self.privatePath = privatePath_68 self.publicLinkedType = publicLinkedType_68 self.privateLinkedType = privateLinkedType_68 }_68}_68_68pub fun main(ownerAddress: Address) : { String : {String : AnyStruct} } {_68 let catalog = NFTCatalog.getCatalog()_68 let account = getAuthAccount(ownerAddress)_68 let items : [MetadataViews.NFTView] = []_68_68 let data : { String : {String : AnyStruct} } = {}_68_68 for key in catalog.keys {_68 let value = catalog[key]!_68 let tempPathStr = "catalog".concat(key)_68 let tempPublicPath = PublicPath(identifier: tempPathStr)!_68 account.link<&{MetadataViews.ResolverCollection}>(_68 tempPublicPath,_68 target: value.collectionData.storagePath_68 )_68 let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)_68 if !collectionCap.check() {_68 continue_68 }_68_68 var views = NFTRetrieval.getAllMetadataViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)_68_68 if views.keys.length == 0 {_68 continue_68 }_68_68 // Cadence doesn't support function return types, lets manually get rid of it_68 let nftCollectionDisplayView = views[Type<MetadataViews.NFTCollectionData>().identifier] as! MetadataViews.NFTCollectionData?_68 let collectionDataView = NFTCollectionData(_68 storagePath : nftCollectionDisplayView!.storagePath,_68 publicPath : nftCollectionDisplayView!.publicPath,_68 privatePath : nftCollectionDisplayView!.providerPath,_68 publicLinkedType : nftCollectionDisplayView!.publicLinkedType,_68 privateLinkedType : nftCollectionDisplayView!.providerLinkedType,_68 )_68 views.insert(key: Type<MetadataViews.NFTCollectionData>().identifier, collectionDataView)_68_68 data[key] = views_68 }_68_68 return data_68}
Example 7 - Setup a user’s account to receive a specific collection
- Run the following script to retrieve some collection-level information for an NFT collection identifier from the catalog
_58import MetadataViews from 0x1d7e57aa55817448_58import NFTCatalog from 0x49a7cda3a1eecc29_58import NFTRetrieval from 0x49a7cda3a1eecc29_58_58pub struct NFTCollection {_58 pub let storagePath : StoragePath_58 pub let publicPath : PublicPath_58 pub let privatePath: PrivatePath_58 pub let publicLinkedType: Type_58 pub let privateLinkedType: Type_58 pub let collectionName : String_58 pub let collectionDescription: String_58 pub let collectionSquareImage : String_58 pub let collectionBannerImage : String_58_58 init(_58 storagePath : StoragePath,_58 publicPath : PublicPath,_58 privatePath : PrivatePath,_58 publicLinkedType : Type,_58 privateLinkedType : Type,_58 collectionName : String,_58 collectionDescription : String,_58 collectionSquareImage : String,_58 collectionBannerImage : String_58 ) {_58 self.storagePath = storagePath_58 self.publicPath = publicPath_58 self.privatePath = privatePath_58 self.publicLinkedType = publicLinkedType_58 self.privateLinkedType = privateLinkedType_58 self.collectionName = collectionName_58 self.collectionDescription = collectionDescription_58 self.collectionSquareImage = collectionSquareImage_58 self.collectionBannerImage = collectionBannerImage_58 }_58}_58_58pub fun main(collectionIdentifier : String) : NFT? {_58 let catalog = NFTCatalog.getCatalog()_58_58 assert(catalog.containsKey(collectionIdentifier), message: "Invalid Collection")_58_58 return NFTCollection(_58 storagePath : collectionDataView!.storagePath,_58 publicPath : collectionDataView!.publicPath,_58 privatePath : collectionDataView!.providerPath,_58 publicLinkedType : collectionDataView!.publicLinkedType,_58 privateLinkedType : collectionDataView!.providerLinkedType,_58 collectionName : collectionDisplayView!.name,_58 collectionDescription : collectionDisplayView!.description,_58 collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),_58 collectionBannerImage : collectionDisplayView!.bannerImage.file.uri()_58 )_58 }_58_58 panic("Invalid Token ID")_58}
- This script result can then be used to form a transaction by inserting the relevant variables from above into a transaction template like the following:
_26import NonFungibleToken from 0x1d7e57aa55817448_26import MetadataViews from 0x1d7e57aa55817448_26{ADDITIONAL_IMPORTS}_26_26transaction {_26_26 prepare(signer: AuthAccount) {_26 // Create a new empty collection_26 let collection <- {CONTRACT_NAME}.createEmptyCollection()_26_26 // save it to the account_26 signer.save(<-collection, to: {STORAGE_PATH})_26_26 // create a public capability for the collection_26 signer.link<&{PUBLIC_LINKED_TYPE}>(_26 {PUBLIC_PATH},_26 target: {STORAGE_PATH}_26 )_26_26 // create a private capability for the collection_26 signer.link<&{PRIVATE_LINKED_TYPE}>(_26 {PRIVATE_PATH},_26 target: {STORAGE_PATH}_26 )_26 }_26}
Developer Usage
1. Install the Flow CLI
2. Install Node
3. Clone the project
_10git clone --depth=1 https://github.com/onflow/nft-catalog.git
4. Install packages
- Run
npm install
in the root of the project
5. Run Test Suite
- Run
npm test
in the root of the project
License
The works in these files:
- FungibleToken.cdc
- NonFungibleToken.cdc
- ExampleNFT.cdc
- MetadataViews.cdc
- NFTCatalog.cdc
- NFTCatalogAdmin.cdc
- NFTRetrieval.cdc
are under the Unlicense.