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
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.
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
import NFTCatalog from 0x49a7cda3a1eecc29
The catalog is returned as a `String: NFTCatalogMetadata`
The key string is intended to be a unique identifier for a specific collection.
The NFTCatalogMetadata contains collection-level views corresponding to each
pub fun main () : { String : NFTCatalog. NFTCatalogMetadata } {
return NFTCatalog. getCatalog ()
Example 2 - Retrieve all collection names in the catalog
import NFTCatalog from 0x49a7cda3a1eecc29
pub fun main () : [ String ] {
let catalog: { String : NFTCatalog. NFTCatalogMetadata } = NFTCatalog. getCatalog ()
let catalogNames: [ String ] = []
for collectionIdentifier in catalog.keys {
catalogNames. append ( catalog [ collectionIdentifier ] ! . collectionDisplay . name )
Example 3 - Retrieve NFT collections and counts owned by an account
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub fun main ( ownerAddress : Address ) : { String : Number } {
let catalog = NFTCatalog. getCatalog ()
let account = getAuthAccount ( ownerAddress )
let items : { String : Number } = {}
for key in catalog.keys {
let value = catalog [ key ] !
let tempPathStr = " catalog " . concat ( key )
let tempPublicPath = PublicPath ( identifier : tempPathStr ) !
account. link <& { MetadataViews. ResolverCollection } > (
target : value. collectionData . storagePath
let collectionCap = account. getCapability <& AnyResource { MetadataViews. ResolverCollection } > ( tempPublicPath )
if ! collectionCap. check () {
let count = NFTRetrieval. getNFTCountFromCap ( collectionIdentifier : key, collectionCap : collectionCap )
Sample Response...
"schmoes_prelaunch_token": 1
Example 4 - Retrieve all NFTs including metadata owned by an account
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub let description : String
pub let thumbnail : String
pub let externalURL : String
pub let storagePath : StoragePath
pub let publicPath : PublicPath
pub let privatePath: PrivatePath
pub let publicLinkedType: Type
pub let privateLinkedType: Type
pub let collectionName : String
pub let collectionDescription: String
pub let collectionSquareImage : String
pub let collectionBannerImage : String
pub let royalties: [ MetadataViews.Royalty ]
storagePath : StoragePath,
privatePath : PrivatePath,
privateLinkedType : Type,
collectionIdentifier : String ,
collectionDescription : String ,
collectionSquareImage : String ,
collectionBannerImage : String ,
royalties : [ MetadataViews.Royalty ]
self .description = description
self . thumbnail = thumbnail
self . externalURL = externalURL
self . storagePath = storagePath
self . publicPath = publicPath
self . privatePath = privatePath
self . publicLinkedType = publicLinkedType
self . privateLinkedType = privateLinkedType
self . collectionIdentifier = collectionIdentifier
self . collectionName = collectionName
self . collectionDescription = collectionDescription
self . collectionSquareImage = collectionSquareImage
self . collectionBannerImage = collectionBannerImage
self . royalties = royalties
pub fun main ( ownerAddress : Address ) : { String : [NFT] } {
let catalog = NFTCatalog. getCatalog ()
let account = getAuthAccount ( ownerAddress )
let items : [ NFTRetrieval.BaseNFTViewsV1 ] = []
let data : { String : [NFT] } = {}
for key in catalog.keys {
let value = catalog [ key ] !
let tempPathStr = " catalog " . concat ( key )
let tempPublicPath = PublicPath ( identifier : tempPathStr ) !
account. link <& { MetadataViews. ResolverCollection } > (
target : value. collectionData . storagePath
let collectionCap = account. getCapability <& AnyResource { MetadataViews. ResolverCollection } > ( tempPublicPath )
if ! collectionCap. check () {
let views = NFTRetrieval. getNFTViewsFromCap ( collectionIdentifier : key, collectionCap : collectionCap )
let displayView = view. display
let externalURLView = view. externalURL
let collectionDataView = view. collectionData
let collectionDisplayView = view. collectionDisplay
let royaltyView = view. royalties
if ( displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil ) {
// This NFT does not have the proper views implemented. Skipping....
name : displayView ! . name ,
description : displayView ! .description,
thumbnail : displayView ! . thumbnail . uri () ,
externalURL : externalURLView ! .url,
storagePath : collectionDataView ! . storagePath ,
publicPath : collectionDataView ! . publicPath ,
privatePath : collectionDataView ! . providerPath ,
publicLinkedType : collectionDataView ! . publicLinkedType ,
privateLinkedType : collectionDataView ! . providerLinkedType ,
collectionIdentifier : key,
collectionName : collectionDisplayView ! . name ,
collectionDescription : collectionDisplayView ! .description,
collectionSquareImage : collectionDisplayView ! . squareImage . file . uri () ,
collectionBannerImage : collectionDisplayView ! . bannerImage . file . uri () ,
royalties : royaltyView ! . getRoyalties ()
Sample Response...
"schmoes_prelaunch_token": [
s.aa16be98aac20e8073f923261531cbbdfae1464f570f5be796b57cdc97656248.NFT(
name: "Schmoes Pre Launch Token #1006",
thumbnail: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
externalURL: "https://schmoes.io",
storagePath: /storage/SchmoesPreLaunchTokenCollection,
publicPath: /public/SchmoesPreLaunchTokenCollection,
privatePath: /private/SchmoesPreLaunchTokenCollection,
publicLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A. 1d7e57aa55817448.NonFungibleToken.Receiver,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),
privateLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A.1d7e57aa55817448.NonFungibleToken.Provider,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),
collectionName: "Schmoes Pre Launch Token",
collectionDescription: "",
collectionSquareImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
collectionBannerImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",
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
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub fun main ( ownerAddress : Address ) : { String : [ UInt64 ] } {
let catalog = NFTCatalog. getCatalog ()
let account = getAuthAccount ( ownerAddress )
let items : { String : [ UInt64 ] } = {}
for key in catalog.keys {
let value = catalog [ key ] !
let tempPathStr = " catalogIDs " . concat ( key )
let tempPublicPath = PublicPath ( identifier : tempPathStr ) !
account. link <& { MetadataViews. ResolverCollection } > (
target : value. collectionData . storagePath
let collectionCap = account. getCapability <& AnyResource { MetadataViews. ResolverCollection } > ( tempPublicPath )
if ! collectionCap. check () {
let ids = NFTRetrieval. getNFTIDsFromCap ( collectionIdentifier : key, collectionCap : collectionCap )
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]
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub let description : String
pub let thumbnail : String
pub let externalURL : String
pub let storagePath : StoragePath
pub let publicPath : PublicPath
pub let privatePath: PrivatePath
pub let publicLinkedType: Type
pub let privateLinkedType: Type
pub let collectionName : String
pub let collectionDescription: String
pub let collectionSquareImage : String
pub let collectionBannerImage : String
pub let royalties: [ MetadataViews.Royalty ]
storagePath : StoragePath,
privatePath : PrivatePath,
privateLinkedType : Type,
collectionDescription : String ,
collectionSquareImage : String ,
collectionBannerImage : String ,
royalties : [ MetadataViews.Royalty ]
self .description = description
self . thumbnail = thumbnail
self . externalURL = externalURL
self . storagePath = storagePath
self . publicPath = publicPath
self . privatePath = privatePath
self . publicLinkedType = publicLinkedType
self . privateLinkedType = privateLinkedType
self . collectionName = collectionName
self . collectionDescription = collectionDescription
self . collectionSquareImage = collectionSquareImage
self . collectionBannerImage = collectionBannerImage
self . royalties = royalties
pub fun main ( ownerAddress : Address, collections : { String : [ UInt64 ] }) : { String : [NFT] } {
let data : { String : [NFT] } = {}
let catalog = NFTCatalog. getCatalog ()
let account = getAuthAccount ( ownerAddress )
for collectionIdentifier in collections.keys {
if catalog. containsKey ( collectionIdentifier ) {
let value = catalog [ collectionIdentifier ] !
let tempPathStr = " catalog " . concat ( collectionIdentifier )
let tempPublicPath = PublicPath ( identifier : tempPathStr ) !
account. link <& { MetadataViews. ResolverCollection } > (
target : value. collectionData . storagePath
let collectionCap = account. getCapability <& AnyResource { MetadataViews. ResolverCollection } > ( tempPublicPath )
if ! collectionCap. check () {
let views = NFTRetrieval. getNFTViewsFromIDs ( collectionIdentifier : collectionIdentifier, ids : collections [ collectionIdentifier ] ! , collectionCap : collectionCap )
let displayView = view. display
let externalURLView = view. externalURL
let collectionDataView = view. collectionData
let collectionDisplayView = view. collectionDisplay
let royaltyView = view. royalties
if ( displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil ) {
name : displayView ! . name ,
description : displayView ! .description,
thumbnail : displayView ! . thumbnail . uri () ,
externalURL : externalURLView ! .url,
storagePath : collectionDataView ! . storagePath ,
publicPath : collectionDataView ! . publicPath ,
privatePath : collectionDataView ! . providerPath ,
publicLinkedType : collectionDataView ! . publicLinkedType ,
privateLinkedType : collectionDataView ! . providerLinkedType ,
collectionName : collectionDisplayView ! . name ,
collectionDescription : collectionDisplayView ! .description,
collectionSquareImage : collectionDisplayView ! . squareImage . file . uri () ,
collectionBannerImage : collectionDisplayView ! . bannerImage . file . uri () ,
royalties : royaltyView ! . getRoyalties ()
data [ collectionIdentifier ] = items
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.
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub struct NFTCollectionData {
pub let storagePath : StoragePath
pub let publicPath : PublicPath
pub let privatePath: PrivatePath
pub let publicLinkedType: Type
pub let privateLinkedType: Type
storagePath : StoragePath,
privatePath : PrivatePath,
privateLinkedType : Type,
self . storagePath = storagePath
self . publicPath = publicPath
self . privatePath = privatePath
self . publicLinkedType = publicLinkedType
self . privateLinkedType = privateLinkedType
pub fun main ( ownerAddress : Address ) : { String : { String : AnyStruct } } {
let catalog = NFTCatalog. getCatalog ()
let account = getAuthAccount ( ownerAddress )
let items : [ MetadataViews.NFTView ] = []
let data : { String : { String : AnyStruct } } = {}
for key in catalog.keys {
let value = catalog [ key ] !
let tempPathStr = " catalog " . concat ( key )
let tempPublicPath = PublicPath ( identifier : tempPathStr ) !
account. link <& { MetadataViews. ResolverCollection } > (
target : value. collectionData . storagePath
let collectionCap = account. getCapability <& AnyResource { MetadataViews. ResolverCollection } > ( tempPublicPath )
if ! collectionCap. check () {
var views = NFTRetrieval. getAllMetadataViewsFromCap ( collectionIdentifier : key, collectionCap : collectionCap )
if views.keys. length == 0 {
// Cadence doesn't support function return types, lets manually get rid of it
let nftCollectionDisplayView = views [ Type < MetadataViews. NFTCollectionData > () . identifier ] as! MetadataViews. NFTCollectionData ?
let collectionDataView = NFTCollectionData (
storagePath : nftCollectionDisplayView ! . storagePath ,
publicPath : nftCollectionDisplayView ! . publicPath ,
privatePath : nftCollectionDisplayView ! . providerPath ,
publicLinkedType : nftCollectionDisplayView ! . publicLinkedType ,
privateLinkedType : nftCollectionDisplayView ! . providerLinkedType ,
views. insert ( key : Type < MetadataViews. NFTCollectionData > () . identifier , collectionDataView )
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
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29
pub struct NFTCollection {
pub let storagePath : StoragePath
pub let publicPath : PublicPath
pub let privatePath: PrivatePath
pub let publicLinkedType: Type
pub let privateLinkedType: Type
pub let collectionName : String
pub let collectionDescription: String
pub let collectionSquareImage : String
pub let collectionBannerImage : String
storagePath : StoragePath,
privatePath : PrivatePath,
privateLinkedType : Type,
collectionDescription : String ,
collectionSquareImage : String ,
collectionBannerImage : String
self . storagePath = storagePath
self . publicPath = publicPath
self . privatePath = privatePath
self . publicLinkedType = publicLinkedType
self . privateLinkedType = privateLinkedType
self . collectionName = collectionName
self . collectionDescription = collectionDescription
self . collectionSquareImage = collectionSquareImage
self . collectionBannerImage = collectionBannerImage
pub fun main ( collectionIdentifier : String ) : NFT ? {
let catalog = NFTCatalog. getCatalog ()
assert ( catalog. containsKey ( collectionIdentifier ) , message : " Invalid Collection ")
storagePath : collectionDataView ! . storagePath ,
publicPath : collectionDataView ! . publicPath ,
privatePath : collectionDataView ! . providerPath ,
publicLinkedType : collectionDataView ! . publicLinkedType ,
privateLinkedType : collectionDataView ! . providerLinkedType ,
collectionName : collectionDisplayView ! . name ,
collectionDescription : collectionDisplayView ! .description,
collectionSquareImage : collectionDisplayView ! . squareImage . file . uri () ,
collectionBannerImage : collectionDisplayView ! . bannerImage . file . uri ()
panic (" Invalid Token ID ")
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:
import NonFungibleToken from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
prepare ( signer : AuthAccount ) {
// Create a new empty collection
let collection <- { CONTRACT_NAME } . createEmptyCollection ()
// save it to the account
signer. save ( <- collection, to : { STORAGE_PATH })
// create a public capability for the collection
signer. link <& { PUBLIC_LINKED_TYPE } > (
// create a private capability for the collection
signer. link <& { PRIVATE_LINKED_TYPE } > (
Developer Usageβ
3. Clone the projectβ
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
Cadence Generationβ
Using the NFT Catalog, you can generate common scripts and transactions to be run against the Flow Blockchain to support your application.
Generate from Javascriptβ
Installationβ
or
1. Retrieve a list of transactions available for code generation:
NOTE: In order to properly bootstrap the method, you will need to run and await
on the getAddressMaps()
method, passing it into all of the methods as shown below.
import { getAddressMaps, scripts } from "flow-catalog";
import * as fcl from "@onflow/fcl"
const main = async () => {
const addressMap = await getAddressMaps();
console.log(await scripts.getSupportedGeneratedTransactions(addressMap));
2. Provide a Catalog collection identifier to generate code
const getTemplatedTransactionCode = async function() {
const catalogAddressMap = await getAddressMaps()
const result = await cadence.scripts.genTx({
'CollectionInitialization' is one of the available transactions from step 1.
'Flunks' is the collection identifier in this case
'Flow' is a fungible token identifier (if applicable to the transaction being used)
args: ['CollectionInitialization', 'Flunks', 'flow'],
addressMap: catalogAddressMap
3. Use the generated code in a transaction
const txId = await fcl.mutate({
cadence: await getTemplatedTransactionCode()[0],
args: (arg: any, t: any) => []
const transaction = await fcl.tx(txId).onceSealed()
Generate from non-Javascript environmentsβ
Cadence scripts and transactions can be generated directly on-chain via scripts. You will need to be able to run cadence scripts to continue.
1. Retrieve a list of transactions available for code generation
Run the following script to retrieve available code generation methods: https://github.com/dapperlabs/nft-catalog/blob/main/cadence/scripts/get_supported_generated_transactions.cdc
2. Provide a catalog collection identifier to generate code
You may use the following script to generate code: https://github.com/dapperlabs/nft-catalog/blob/main/cadence/scripts/gen_tx.cdc
For example, from the CLI this may be run like the following:
flow -n mainnet scripts execute ./get_tx.cdc CollectionInitialization Flunks flow
In the above example, CollectionInitialization
is one of the supported transactions returned from step 1, Flunks
is the name of an entry on the catalog (https://www.flow-nft-catalog.com/catalog/mainnet/Flunks ), and flow
is a fungible token identifier.
NPM Moduleβ
We exposed an interface to the catalog via a consumable NPM module. This library will expose a number of methods that can be called to interact with the catalog.
Methodsβ
Method signatures and their associating parameters/responses can be found in the cadence/
folder of this repo.
Scriptsβ
checkForRecommendedV1Views
getExamplenftCollectionLength
getNftCollectionsForNftType
getNftMetadataForCollectionIdentifier
getSupportedGeneratedTransactions
Transactionsβ
approveNftCatalogProposal
sendAdminCapabilityToProxy
setupExamplenftCollection
setupNftCatalogAdminProxy
setupNonstandardnftCollection
withdrawNftProposalFromCatalog
Installationβ
or
Methods can be imported as follows, all nested methods live under the scripts
or transactions
variable.
NOTE: In order to properly bootstrap the method, you will need to run and await
on the getAddressMaps()
method, passing it into all of the methods as shown below.
import { getAddressMaps, scripts } from "flow-catalog";
const main = async () => {
const addressMap = await getAddressMaps();
console.log(await scripts.getNftCatalog(addressMap));
The response of any method is a tuple-array, with the first element being the result, and the second being the error (if applicable).
For example, the result of the method above would look like -
contractAddress: '0x9e6cdb88e34fa1f3',
collectionData: [Object],
collectionDisplay: [Object]
contractName: 'SoulMadeComponent',
contractAddress: '0x421c19b7dc122357',
collectionData: [Object],
collectionDisplay: [Object]
Licenseβ
The works in these files:
are under the Unlicense .