Skip to main content
Version: Next

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

NetworkAddress
Mainnet0x49a7cda3a1eecc29
Testnet0x324c34e1c517e4db

NFTRetrieval.cdc: This contract contains helper functions to make it easier to discover NFTs within accounts and from the catalog

NetworkAddress
Mainnet0x49a7cda3a1eecc29
Testnet0x324c34e1c517e4db

Submitting a Collection to the NFT Catalog

  1. Visit here
  2. Enter the address containing the NFT contract which contains the collection and select the contract.
Screen Shot 2023-02-08 at 9 40 01 AM
  1. 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.

    Screen Shot 2023-02-08 at 9 42 54 AM
  2. The application will verify that your NFT collection implements the required Metadata views.

    1. The required metadata views include…
      1. NFT Display
        1. How to display an individual NFT part of the collection
      2. External URL
        1. A website for the NFT collection
      3. Collection Data
        1. Information needed to store and retrieve an NFT
      4. Collection Display
        1. How to display information about the NFT collection the NFT belongs to
      5. Royalties
        1. Any royalties that should be accounted for during marketplace transactions
    2. You can find sample implementations of all these views in this example NFT contract.
    3. If you are not implementing a view, the app will communicate this and you can update your NFT contract and try resubmitting.
    Screen Shot 2023-02-08 at 9 46 56 AM
  3. 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).

Screen Shot 2023-02-08 at 9 48 45 AM
  1. 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


_12
import 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
*/
_12
pub fun main(): {String : NFTCatalog.NFTCatalogMetadata} {
_12
return NFTCatalog.getCatalog()
_12
_12
}

Example 2 - Retrieve all collection names in the catalog


_10
import NFTCatalog from 0x49a7cda3a1eecc29
_10
_10
pub 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


_29
import MetadataViews from 0x1d7e57aa55817448
_29
import NFTCatalog from 0x49a7cda3a1eecc29
_29
import NFTRetrieval from 0x49a7cda3a1eecc29
_29
_29
pub 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


_116
import MetadataViews from 0x1d7e57aa55817448
_116
import NFTCatalog from 0x49a7cda3a1eecc29
_116
import NFTRetrieval from 0x49a7cda3a1eecc29
_116
_116
pub 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
_116
pub 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


_33
import MetadataViews from 0x1d7e57aa55817448
_33
import NFTCatalog from 0x49a7cda3a1eecc29
_33
import NFTRetrieval from 0x49a7cda3a1eecc29
_33
_33
pub 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]


_120
import MetadataViews from 0x1d7e57aa55817448
_120
import NFTCatalog from 0x49a7cda3a1eecc29
_120
import NFTRetrieval from 0x49a7cda3a1eecc29
_120
_120
pub 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
_120
pub 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.


_68
import MetadataViews from 0x1d7e57aa55817448
_68
import NFTCatalog from 0x49a7cda3a1eecc29
_68
import NFTRetrieval from 0x49a7cda3a1eecc29
_68
_68
pub 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
_68
pub 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

  1. Run the following script to retrieve some collection-level information for an NFT collection identifier from the catalog

_58
import MetadataViews from 0x1d7e57aa55817448
_58
import NFTCatalog from 0x49a7cda3a1eecc29
_58
import NFTRetrieval from 0x49a7cda3a1eecc29
_58
_58
pub 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
_58
pub 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
}

  1. 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:

_26
import NonFungibleToken from 0x1d7e57aa55817448
_26
import MetadataViews from 0x1d7e57aa55817448
_26
{ADDITIONAL_IMPORTS}
_26
_26
transaction {
_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


_10
git 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:

are under the Unlicense.