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

1
import NFTCatalog from 0x49a7cda3a1eecc29
2
3
/*
4
The catalog is returned as a `String: NFTCatalogMetadata`
5
The key string is intended to be a unique identifier for a specific collection.
6
The NFTCatalogMetadata contains collection-level views corresponding to each
7
collection identifier.
8
*/
9
pub fun main(): {String : NFTCatalog.NFTCatalogMetadata} {
10
return NFTCatalog.getCatalog()
11
12
}

Example 2 - Retrieve all collection names in the catalog

1
import NFTCatalog from 0x49a7cda3a1eecc29
2
3
pub fun main(): [String] {
4
let catalog: {String : NFTCatalog.NFTCatalogMetadata} = NFTCatalog.getCatalog()
5
let catalogNames: [String] = []
6
for collectionIdentifier in catalog.keys {
7
catalogNames.append(catalog[collectionIdentifier]!.collectionDisplay.name)
8
}
9
return catalogNames
10
}

Example 3 - Retrieve NFT collections and counts owned by an account

1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub fun main(ownerAddress: Address) : {String : Number} {
6
let catalog = NFTCatalog.getCatalog()
7
let account = getAuthAccount(ownerAddress)
8
let items : {String : Number} = {}
9
10
for key in catalog.keys {
11
let value = catalog[key]!
12
let tempPathStr = "catalog".concat(key)
13
let tempPublicPath = PublicPath(identifier: tempPathStr)!
14
account.link<&{MetadataViews.ResolverCollection}>(
15
tempPublicPath,
16
target: value.collectionData.storagePath
17
)
18
let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)
19
if !collectionCap.check() {
20
continue
21
}
22
let count = NFTRetrieval.getNFTCountFromCap(collectionIdentifier : key, collectionCap : collectionCap)
23
if count != 0 {
24
items[key] = count
25
}
26
}
27
28
return items
29
}

Sample Response...

1
{
2
"schmoes_prelaunch_token": 1
3
}

Example 4 - Retrieve all NFTs including metadata owned by an account

1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub struct NFT {
6
pub let id : UInt64
7
pub let name : String
8
pub let description : String
9
pub let thumbnail : String
10
pub let externalURL : String
11
pub let storagePath : StoragePath
12
pub let publicPath : PublicPath
13
pub let privatePath: PrivatePath
14
pub let publicLinkedType: Type
15
pub let privateLinkedType: Type
16
pub let collectionName : String
17
pub let collectionDescription: String
18
pub let collectionSquareImage : String
19
pub let collectionBannerImage : String
20
pub let royalties: [MetadataViews.Royalty]
21
22
init(
23
id: UInt64,
24
name : String,
25
description : String,
26
thumbnail : String,
27
externalURL : String,
28
storagePath : StoragePath,
29
publicPath : PublicPath,
30
privatePath : PrivatePath,
31
publicLinkedType : Type,
32
privateLinkedType : Type,
33
collectionIdentifier: String,
34
collectionName : String,
35
collectionDescription : String,
36
collectionSquareImage : String,
37
collectionBannerImage : String,
38
royalties : [MetadataViews.Royalty]
39
) {
40
self.id = id
41
self.name = name
42
self.description = description
43
self.thumbnail = thumbnail
44
self.externalURL = externalURL
45
self.storagePath = storagePath
46
self.publicPath = publicPath
47
self.privatePath = privatePath
48
self.publicLinkedType = publicLinkedType
49
self.privateLinkedType = privateLinkedType
50
self.collectionIdentifier = collectionIdentifier
51
self.collectionName = collectionName
52
self.collectionDescription = collectionDescription
53
self.collectionSquareImage = collectionSquareImage
54
self.collectionBannerImage = collectionBannerImage
55
self.royalties = royalties
56
}
57
}
58
59
pub fun main(ownerAddress: Address) : { String : [NFT] } {
60
let catalog = NFTCatalog.getCatalog()
61
let account = getAuthAccount(ownerAddress)
62
let items : [NFTRetrieval.BaseNFTViewsV1] = []
63
64
let data : {String : [NFT] } = {}
65
66
for key in catalog.keys {
67
let value = catalog[key]!
68
let tempPathStr = "catalog".concat(key)
69
let tempPublicPath = PublicPath(identifier: tempPathStr)!
70
account.link<&{MetadataViews.ResolverCollection}>(
71
tempPublicPath,
72
target: value.collectionData.storagePath
73
)
74
let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)
75
if !collectionCap.check() {
76
continue
77
}
78
let views = NFTRetrieval.getNFTViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)
79
80
let items : [NFT] = []
81
for view in views {
82
let displayView = view.display
83
let externalURLView = view.externalURL
84
let collectionDataView = view.collectionData
85
let collectionDisplayView = view.collectionDisplay
86
let royaltyView = view.royalties
87
if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {
88
// This NFT does not have the proper views implemented. Skipping....
89
continue
90
}
91
92
items.append(
93
NFT(
94
id: view.id,
95
name : displayView!.name,
96
description : displayView!.description,
97
thumbnail : displayView!.thumbnail.uri(),
98
externalURL : externalURLView!.url,
99
storagePath : collectionDataView!.storagePath,
100
publicPath : collectionDataView!.publicPath,
101
privatePath : collectionDataView!.providerPath,
102
publicLinkedType : collectionDataView!.publicLinkedType,
103
privateLinkedType : collectionDataView!.providerLinkedType,
104
collectionIdentifier: key,
105
collectionName : collectionDisplayView!.name,
106
collectionDescription : collectionDisplayView!.description,
107
collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),
108
collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),
109
royalties : royaltyView!.getRoyalties()
110
)
111
)
112
}
113
data[key] = items
114
}
115
return data
116
}

Sample Response...

1
{
2
"FlovatarComponent": [],
3
"schmoes_prelaunch_token": [
4
s.aa16be98aac20e8073f923261531cbbdfae1464f570f5be796b57cdc97656248.NFT(
5
id: 1006,
6
name: "Schmoes Pre Launch Token #1006",
7
description: "",
8
thumbnail: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
9
externalURL: "https://schmoes.io",
10
storagePath: /storage/SchmoesPreLaunchTokenCollection,
11
publicPath: /public/SchmoesPreLaunchTokenCollection,
12
privatePath: /private/SchmoesPreLaunchTokenCollection,
13
publicLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A. 1d7e57aa55817448.NonFungibleToken.Receiver,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),
14
privateLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A.1d7e57aa55817448.NonFungibleToken.Provider,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),
15
collectionName: "Schmoes Pre Launch Token",
16
collectionDescription: "",
17
collectionSquareImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
18
collectionBannerImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
19
royalties: []
20
)
21
],
22
"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

1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub fun main(ownerAddress: Address) : {String : [UInt64]} {
6
let catalog = NFTCatalog.getCatalog()
7
let account = getAuthAccount(ownerAddress)
8
9
let items : {String : [UInt64]} = {}
10
11
for key in catalog.keys {
12
let value = catalog[key]!
13
let tempPathStr = "catalogIDs".concat(key)
14
let tempPublicPath = PublicPath(identifier: tempPathStr)!
15
account.link<&{MetadataViews.ResolverCollection}>(
16
tempPublicPath,
17
target: value.collectionData.storagePath
18
)
19
20
let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)
21
if !collectionCap.check() {
22
continue
23
}
24
25
let ids = NFTRetrieval.getNFTIDsFromCap(collectionIdentifier : key, collectionCap : collectionCap)
26
27
if ids.length > 0 {
28
items[key] = ids
29
}
30
}
31
return items
32
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]

1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub struct NFT {
6
pub let id : UInt64
7
pub let name : String
8
pub let description : String
9
pub let thumbnail : String
10
pub let externalURL : String
11
pub let storagePath : StoragePath
12
pub let publicPath : PublicPath
13
pub let privatePath: PrivatePath
14
pub let publicLinkedType: Type
15
pub let privateLinkedType: Type
16
pub let collectionName : String
17
pub let collectionDescription: String
18
pub let collectionSquareImage : String
19
pub let collectionBannerImage : String
20
pub let royalties: [MetadataViews.Royalty]
21
22
init(
23
id: UInt64,
24
name : String,
25
description : String,
26
thumbnail : String,
27
externalURL : String,
28
storagePath : StoragePath,
29
publicPath : PublicPath,
30
privatePath : PrivatePath,
31
publicLinkedType : Type,
32
privateLinkedType : Type,
33
collectionName : String,
34
collectionDescription : String,
35
collectionSquareImage : String,
36
collectionBannerImage : String,
37
royalties : [MetadataViews.Royalty]
38
) {
39
self.id = id
40
self.name = name
41
self.description = description
42
self.thumbnail = thumbnail
43
self.externalURL = externalURL
44
self.storagePath = storagePath
45
self.publicPath = publicPath
46
self.privatePath = privatePath
47
self.publicLinkedType = publicLinkedType
48
self.privateLinkedType = privateLinkedType
49
self.collectionName = collectionName
50
self.collectionDescription = collectionDescription
51
self.collectionSquareImage = collectionSquareImage
52
self.collectionBannerImage = collectionBannerImage
53
self.royalties = royalties
54
}
55
}
56
57
pub fun main(ownerAddress: Address, collections: {String : [UInt64]}) : {String : [NFT] } {
58
let data : {String : [NFT] } = {}
59
60
let catalog = NFTCatalog.getCatalog()
61
let account = getAuthAccount(ownerAddress)
62
for collectionIdentifier in collections.keys {
63
if catalog.containsKey(collectionIdentifier) {
64
let value = catalog[collectionIdentifier]!
65
let tempPathStr = "catalog".concat(collectionIdentifier)
66
let tempPublicPath = PublicPath(identifier: tempPathStr)!
67
account.link<&{MetadataViews.ResolverCollection}>(
68
tempPublicPath,
69
target: value.collectionData.storagePath
70
)
71
72
let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)
73
74
if !collectionCap.check() {
75
return data
76
}
77
78
let views = NFTRetrieval.getNFTViewsFromIDs(collectionIdentifier : collectionIdentifier, ids: collections[collectionIdentifier]!, collectionCap : collectionCap)
79
80
let items : [NFT] = []
81
82
for view in views {
83
let displayView = view.display
84
let externalURLView = view.externalURL
85
let collectionDataView = view.collectionData
86
let collectionDisplayView = view.collectionDisplay
87
let royaltyView = view.royalties
88
if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {
89
// Bad NFT. Skipping....
90
continue
91
}
92
93
items.append(
94
NFT(
95
id: view.id,
96
name : displayView!.name,
97
description : displayView!.description,
98
thumbnail : displayView!.thumbnail.uri(),
99
externalURL : externalURLView!.url,
100
storagePath : collectionDataView!.storagePath,
101
publicPath : collectionDataView!.publicPath,
102
privatePath : collectionDataView!.providerPath,
103
publicLinkedType : collectionDataView!.publicLinkedType,
104
privateLinkedType : collectionDataView!.providerLinkedType,
105
collectionName : collectionDisplayView!.name,
106
collectionDescription : collectionDisplayView!.description,
107
collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),
108
collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),
109
royalties : royaltyView!.getRoyalties()
110
)
111
)
112
}
113
114
data[collectionIdentifier] = items
115
}
116
}
117
118
119
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.

1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub struct NFTCollectionData {
6
pub let storagePath : StoragePath
7
pub let publicPath : PublicPath
8
pub let privatePath: PrivatePath
9
pub let publicLinkedType: Type
10
pub let privateLinkedType: Type
11
12
init(
13
storagePath : StoragePath,
14
publicPath : PublicPath,
15
privatePath : PrivatePath,
16
publicLinkedType : Type,
17
privateLinkedType : Type,
18
) {
19
self.storagePath = storagePath
20
self.publicPath = publicPath
21
self.privatePath = privatePath
22
self.publicLinkedType = publicLinkedType
23
self.privateLinkedType = privateLinkedType
24
}
25
}
26
27
pub fun main(ownerAddress: Address) : { String : {String : AnyStruct} } {
28
let catalog = NFTCatalog.getCatalog()
29
let account = getAuthAccount(ownerAddress)
30
let items : [MetadataViews.NFTView] = []
31
32
let data : { String : {String : AnyStruct} } = {}
33
34
for key in catalog.keys {
35
let value = catalog[key]!
36
let tempPathStr = "catalog".concat(key)
37
let tempPublicPath = PublicPath(identifier: tempPathStr)!
38
account.link<&{MetadataViews.ResolverCollection}>(
39
tempPublicPath,
40
target: value.collectionData.storagePath
41
)
42
let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)
43
if !collectionCap.check() {
44
continue
45
}
46
47
var views = NFTRetrieval.getAllMetadataViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)
48
49
if views.keys.length == 0 {
50
continue
51
}
52
53
// Cadence doesn't support function return types, lets manually get rid of it
54
let nftCollectionDisplayView = views[Type<MetadataViews.NFTCollectionData>().identifier] as! MetadataViews.NFTCollectionData?
55
let collectionDataView = NFTCollectionData(
56
storagePath : nftCollectionDisplayView!.storagePath,
57
publicPath : nftCollectionDisplayView!.publicPath,
58
privatePath : nftCollectionDisplayView!.providerPath,
59
publicLinkedType : nftCollectionDisplayView!.publicLinkedType,
60
privateLinkedType : nftCollectionDisplayView!.providerLinkedType,
61
)
62
views.insert(key: Type<MetadataViews.NFTCollectionData>().identifier, collectionDataView)
63
64
data[key] = views
65
}
66
67
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
1
import MetadataViews from 0x1d7e57aa55817448
2
import NFTCatalog from 0x49a7cda3a1eecc29
3
import NFTRetrieval from 0x49a7cda3a1eecc29
4
5
pub struct NFTCollection {
6
pub let storagePath : StoragePath
7
pub let publicPath : PublicPath
8
pub let privatePath: PrivatePath
9
pub let publicLinkedType: Type
10
pub let privateLinkedType: Type
11
pub let collectionName : String
12
pub let collectionDescription: String
13
pub let collectionSquareImage : String
14
pub let collectionBannerImage : String
15
16
init(
17
storagePath : StoragePath,
18
publicPath : PublicPath,
19
privatePath : PrivatePath,
20
publicLinkedType : Type,
21
privateLinkedType : Type,
22
collectionName : String,
23
collectionDescription : String,
24
collectionSquareImage : String,
25
collectionBannerImage : String
26
) {
27
self.storagePath = storagePath
28
self.publicPath = publicPath
29
self.privatePath = privatePath
30
self.publicLinkedType = publicLinkedType
31
self.privateLinkedType = privateLinkedType
32
self.collectionName = collectionName
33
self.collectionDescription = collectionDescription
34
self.collectionSquareImage = collectionSquareImage
35
self.collectionBannerImage = collectionBannerImage
36
}
37
}
38
39
pub fun main(collectionIdentifier : String) : NFT? {
40
let catalog = NFTCatalog.getCatalog()
41
42
assert(catalog.containsKey(collectionIdentifier), message: "Invalid Collection")
43
44
return NFTCollection(
45
storagePath : collectionDataView!.storagePath,
46
publicPath : collectionDataView!.publicPath,
47
privatePath : collectionDataView!.providerPath,
48
publicLinkedType : collectionDataView!.publicLinkedType,
49
privateLinkedType : collectionDataView!.providerLinkedType,
50
collectionName : collectionDisplayView!.name,
51
collectionDescription : collectionDisplayView!.description,
52
collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),
53
collectionBannerImage : collectionDisplayView!.bannerImage.file.uri()
54
)
55
}
56
57
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:
1
import NonFungibleToken from 0x1d7e57aa55817448
2
import MetadataViews from 0x1d7e57aa55817448
3
{ADDITIONAL_IMPORTS}
4
5
transaction {
6
7
prepare(signer: AuthAccount) {
8
// Create a new empty collection
9
let collection <- {CONTRACT_NAME}.createEmptyCollection()
10
11
// save it to the account
12
signer.save(<-collection, to: {STORAGE_PATH})
13
14
// create a public capability for the collection
15
signer.link<&{PUBLIC_LINKED_TYPE}>(
16
{PUBLIC_PATH},
17
target: {STORAGE_PATH}
18
)
19
20
// create a private capability for the collection
21
signer.link<&{PRIVATE_LINKED_TYPE}>(
22
{PRIVATE_PATH},
23
target: {STORAGE_PATH}
24
)
25
}
26
}

Developer Usage

3. Clone the project

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