Giter Site home page Giter Site logo

nft-catalog's Introduction

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.

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. The module can be found here with installation instructions below: https://www.npmjs.com/package/flow-catalog

Methods

Method signatures and their associating parameters/responses can be found in the cadence/ folder of this repo.

Scripts

checkForRecommendedV1Views
genTx
getAllNftsAndViewsInAccount
getAllNftsInAccount
getExamplenftCollectionLength
getExamplenftType
getNftAndViewsInAccount
getNftCatalog
getNftCatalogCount
getNftCatalogIdentifiers
getNftCatalogProposals
getNftCatalogProposalsCount
getNftCollectionsForNftType
getNftIdsInAccount
getNftInAccount
getNftInAccountFromPath
getNftMetadataForCollectionIdentifier
getNftProposalForId
getNftsCountInAccount
getNftsInAccount
getNftsInAccountFromIds
getNftsInAccountFromPath
getSupportedGeneratedScripts
getSupportedGeneratedTransactions
hasAdminProxy
isCatalogAdmin

Transactions

addToNftCatalog
addToNftCatalogAdmin
approveNftCatalogProposal
mintExampleNft
mintNonstandardNft
proposeNftToCatalog
rejectNftCatalogProposal
removeFromNftCatalog
removeNftCatalogProposal
sendAdminCapabilityToProxy
setupExamplenftCollection
setupNftCatalogAdminProxy
setupNonstandardnftCollection
setupStorefront
transferExamplenft
updateNftCatalogEntry
withdrawNftProposalFromCatalog

Installation

npm install flow-catalog

or

yarn add flow-catalog

Usage

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));
};

main();

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 -

[
  {
    BILPlayerCollection: {
      contractName: 'Player',
      contractAddress: '0x9e6cdb88e34fa1f3',
      nftType: [Object],
      collectionData: [Object],
      collectionDisplay: [Object]
    },
    ...
    SoulMadeComponent: {
      contractName: 'SoulMadeComponent',
      contractAddress: '0x421c19b7dc122357',
      nftType: [Object],
      collectionData: [Object],
      collectionDisplay: [Object]
    }
  },
  null
]

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

  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

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
    collection identifier.
    Due to the large size of the response, only the first 10 entries are returned.
*/
pub fun main(): {String: NFTCatalog.NFTCatalogMetadata} {
    let catalogKeys = NFTCatalog.getCatalogKeys()
    let keys = catalogKeys.slice(from: 0, upTo: 10)
    let collections: {String: NFTCatalog.NFTCatalogMetadata} = {}

    for key in keys {
        collections[key] = catalog[key]
    }

    return collections
}

Example 2 - Retrieve all collection names in the catalog

import NFTCatalog from 0x49a7cda3a1eecc29

pub fun main(): [String] {
    let catalogKeys: {String: NFTCatalog.NFTCatalogMetadata} = NFTCatalog.getCatalogKeys()
    let catalogNames: [String] = []

    for collectionIdentifier in catalogKeys {
        catalogNames.append(catalog[collectionIdentifier]!.collectionDisplay.name)
    }

    return catalogNames
}

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 account = getAuthAccount(ownerAddress)
    let items: {String: Number} = {}

    NFTCatalog.forEachCatalogKey(fun (collectionIdentifier: String):Bool {
        let value = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!
        let keyHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(collectionIdentifier.utf8))
        let tempPathStr = "catalog".concat(keyHash)
        let tempPublicPath = PublicPath(identifier: tempPathStr)!

        account.link<&{MetadataViews.ResolverCollection}>(
            tempPublicPath,
            target: value.collectionData.storagePath
        )

        let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)

        if !collectionCap.check() {
            return true
        }

        let count = NFTRetrieval.getNFTCountFromCap(collectionIdentifier: collectionIdentifier, collectionCap: collectionCap)

        if count != 0 {
            items[collectionIdentifier] = count
        }
        return true
    })

    return items
}

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 struct NFT {
    pub let id: UInt64
    pub let name: String
    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 collectionExternalURL: String
    pub let royalties: [MetadataViews.Royalty]

    init(
        id: UInt64,
        name: String,
        description: String,
        thumbnail: String,
        externalURL: String,
        storagePath: StoragePath,
        publicPath: PublicPath,
        privatePath: PrivatePath,
        publicLinkedType: Type,
        privateLinkedType: Type,
        collectionName: String,
        collectionDescription: String,
        collectionSquareImage: String,
        collectionBannerImage: String,
        collectionExternalURL: String,
        royalties: [MetadataViews.Royalty]
    ) {
        self.id = id
        self.name = name
        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.collectionExternalURL = collectionExternalURL
        self.royalties = royalties
    }
}

pub fun main(ownerAddress: Address): {String: [NFT]} {
    let account = getAuthAccount(ownerAddress)
    let items: [MetadataViews.NFTView] = []
    let data: {String: [NFT]} = {}

    NFTCatalog.forEachCatalogKey(fun (collectionIdentifier: String):Bool {
        let value = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!
        let keyHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(collectionIdentifier.utf8))
        let tempPathStr = "catalog".concat(keyHash)
        let tempPublicPath = PublicPath(identifier: tempPathStr)!

        account.link<&{MetadataViews.ResolverCollection}>(
            tempPublicPath,
            target: value.collectionData.storagePath
        )

        let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)

        if !collectionCap.check() {
            return true
        }

        let views = NFTRetrieval.getNFTViewsFromCap(collectionIdentifier: collectionIdentifier, collectionCap: collectionCap)
        let items: [NFT] = []

        for view in views {
            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) {
                // Bad NFT. Skipping....
                return true
            }

            items.append(
                NFT(
                    id: view.id,
                    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(),
                    collectionExternalURL: collectionDisplayView!.externalURL.url,
                    royalties: royaltyView!.getRoyalties()
                )
            )
        }

        data[collectionIdentifier] = items
        return true
    })

    return data
}

Sample Response...

{
    "FlovatarComponent": [],
    "schmoes_prelaunch_token": [
        s.aa16be98aac20e8073f923261531cbbdfae1464f570f5be796b57cdc97656248.NFT(
            id: 1006,
            name: "Schmoes Pre Launch Token #1006",
            description: "",
            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",
            royalties: []
        )
    ],
    "Flovatar": []
}

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 account = getAuthAccount(ownerAddress)
    let items: {String: [UInt64]} = {}

    NFTCatalog.forEachCatalogKey(fun (key: String):Bool {
        let value = NFTCatalog.getCatalogEntry(collectionIdentifier: key)!
        let keyHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(key.utf8))
        let tempPathStr = "catalogIDs".concat(keyHash)
        let tempPublicPath = PublicPath(identifier: tempPathStr)!

        account.link<&{MetadataViews.ResolverCollection}>(
            tempPublicPath,
            target: value.collectionData.storagePath
        )

        let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)

        if !collectionCap.check() {
            return true
        }

        let ids = NFTRetrieval.getNFTIDsFromCap(collectionIdentifier: key, collectionCap: collectionCap)

        if ids.length > 0 {
            items[key] = ids
        }
        return true
    })

    return items
}

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 struct NFT {
    pub let id: UInt64
    pub let name: String
    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 collectionExternalURL: String
    pub let royalties: [MetadataViews.Royalty]

    init(
        id: UInt64,
        name: String,
        description: String,
        thumbnail: String,
        externalURL: String,
        storagePath: StoragePath,
        publicPath: PublicPath,
        privatePath: PrivatePath,
        publicLinkedType: Type,
        privateLinkedType: Type,
        collectionName: String,
        collectionDescription: String,
        collectionSquareImage: String,
        collectionBannerImage: String,
        collectionExternalURL: String,
        royalties: [MetadataViews.Royalty]
    ) {
        self.id = id
        self.name = name
        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.collectionExternalURL = collectionExternalURL
        self.royalties = royalties
    }
}

pub fun main(ownerAddress: Address, collections: {String: [UInt64]}): {String: [NFT]} {
    let data: {String: [NFT]} = {}
    let account = getAuthAccount(ownerAddress)

    for collectionIdentifier in collections.keys {
        if NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier) != nil {
            let value = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!
            let identifierHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(collectionIdentifier.utf8))
            let tempPathStr = "catalog".concat(identifierHash)
            let tempPublicPath = PublicPath(identifier: tempPathStr)!

            account.link<&{MetadataViews.ResolverCollection}>(
                tempPublicPath,
                target: value.collectionData.storagePath
            )

            let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)

            if !collectionCap.check() {
                return data
            }

            let views = NFTRetrieval.getNFTViewsFromIDs(collectionIdentifier: collectionIdentifier, ids: collections[collectionIdentifier]!, collectionCap: collectionCap)

            let items: [NFT] = []

            for view in views {
                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) {
                    // Bad NFT. Skipping....
                    continue
                }

                items.append(
                    NFT(
                        id: view.id,
                        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(),
                        collectionExternalURL: collectionDisplayView!.externalURL.url,
                        royalties: royaltyView!.getRoyalties()
                    )
                )
            }

            data[collectionIdentifier] = items
        }
    }

    return data
}

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

    init(
        storagePath: StoragePath,
        publicPath: PublicPath,
        privatePath: PrivatePath,
        publicLinkedType: Type,
        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 account = getAuthAccount(ownerAddress)
    let items: [MetadataViews.NFTView] = []
    let data: {String: {String: AnyStruct}} = {}

    NFTCatalog.forEachCatalogKey(fun (collectionIdentifier: String):Bool {
        let value = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!
        let keyHash = String.encodeHex(HashAlgorithm.SHA3_256.hash(collectionIdentifier.utf8))
        let tempPathStr = "catalog".concat(keyHash)
        let tempPublicPath = PublicPath(identifier: tempPathStr)!

        account.link<&{MetadataViews.ResolverCollection}>(
            tempPublicPath,
            target: value.collectionData.storagePath
        )

        let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)

        if !collectionCap.check() {
            return true
        }

        var views = NFTRetrieval.getAllMetadataViewsFromCap(collectionIdentifier: collectionIdentifier, collectionCap: collectionCap)

        if views.keys.length == 0 {
            return true
        }

        // 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)

        data[collectionIdentifier] = views

        return true
    })

    return data
}

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
// Assuming file name is `get_nft_collection_data.cdc`
import MetadataViews from 0x1d7e57aa55817448
import NFTCatalog from 0x49a7cda3a1eecc29
import NFTRetrieval from 0x49a7cda3a1eecc29

pub struct NFTCollection {
    pub let contractName: String
    pub let contractAddress: 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

    init(
        contractName: String,
        contractAddress: String,
        storagePath: StoragePath,
        publicPath: PublicPath,
        privatePath: PrivatePath,
        publicLinkedType: Type,
        privateLinkedType: Type,
        collectionName: String,
        collectionDescription: String,
        collectionSquareImage: String,
        collectionBannerImage: String
    ) {
        self.contractName = contractName
        self.contractAddress = contractAddress
        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) : NFTCollection? {
    pre {
        NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier) != nil : "Invalid collection identifier"
    }

    let contractView = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!
    let collectionDataView = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!.collectionData
    let collectionDisplayView = NFTCatalog.getCatalogEntry(collectionIdentifier: collectionIdentifier)!.collectionDisplay

    return NFTCollection(
        contractName: contractView.contractName,
        contractAddress: contractView.contractAddress.toString(),
        storagePath: collectionDataView!.storagePath,
        publicPath: collectionDataView!.publicPath,
        privatePath: collectionDataView!.privatePath,
        publicLinkedType: collectionDataView!.publicLinkedType,
        privateLinkedType: collectionDataView!.privateLinkedType,
        collectionName: collectionDisplayView!.name,
        collectionDescription: collectionDisplayView!.description,
        collectionSquareImage: collectionDisplayView!.squareImage.file.uri(),
        collectionBannerImage: collectionDisplayView!.bannerImage.file.uri()
    )
}
  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:
// Assuming file name is `setup_collection_template.cdc`
import NonFungibleToken from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
import {CONTRACT_NAME} from {CONTRACT_ADDRESS}

transaction {

    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}>(
            {PUBLIC_PATH},
            target: {STORAGE_PATH}
        )

        // create a private capability for the collection
        signer.link<&{PRIVATE_LINKED_TYPE}>(
            {PRIVATE_PATH},
            target: {STORAGE_PATH}
        )
    }
}

We can achieve this with JavaScript, for example:

import { readFileSync, writeFileSync } from "fs";
import * as fcl from "@onflow/fcl";

fcl.config()
    .put("accessNode.api", "https://rest-mainnet.onflow.org")
    .put("flow.network", "mainnet");

const args = process.argv.slice(2);

const collectionIdentifier = args[0];
if (collectionIdentifier === undefined) {
    console.error("You need to pass the Collection identifier as an argument.");
    process.exit(1);
}

try {
    const scriptPath = new URL("get_nft_collection_data.cdc", import.meta.url);
    const scriptCode = readFileSync(scriptPath, { encoding: "utf8" });

    const nftCollection = await fcl.query({
        cadence: scriptCode,
        args: (arg, t) => [arg(collectionIdentifier, t.String)],
    });

    console.log(nftCollection);

    const filePath = new URL("setup_collection_template.cdc", import.meta.url);
    const transactionTemplate = readFileSync(filePath, { encoding: "utf8" });

    const transaction = transactionTemplate
        .replaceAll("{CONTRACT_NAME}", nftCollection.contractName)
        .replaceAll("{CONTRACT_ADDRESS}", nftCollection.contractAddress)
        .replaceAll(
            "{STORAGE_PATH}",
            `/${nftCollection.storagePath.domain}/${nftCollection.storagePath.identifier}`
        )
        .replaceAll(
            "{PUBLIC_PATH}",
            `/${nftCollection.publicPath.domain}/${nftCollection.publicPath.identifier}`
        )
        .replaceAll(
            "{PRIVATE_PATH}",
            `/${nftCollection.privatePath.domain}/${nftCollection.privatePath.identifier}`
        )
        .replaceAll(
            "{PUBLIC_LINKED_TYPE}",
            nftCollection.publicLinkedType.typeID.replace(/A\.\w{16}\./g, "")
        )
        .replaceAll(
            "{PRIVATE_LINKED_TYPE}",
            nftCollection.privateLinkedType.typeID.replace(/A\.\w{16}\./g, "")
        );

    const transactionPath = `setup_${nftCollection.contractName}_collection.cdc`;
    writeFileSync(transactionPath, transaction);

    console.log(transaction);
} catch (err) {
    console.error(err.message);
}

Running the above with:

node setupCollection.mjs "ChainmonstersRewards"

will generate a file called setup_ChainmonstersRewards_collection.cdc, containing:

import NonFungibleToken from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
import ChainmonstersRewards from 0x93615d25d14fa337

transaction {

    prepare(signer: AuthAccount) {
        // Create a new empty collection
        let collection <- ChainmonstersRewards.createEmptyCollection()

        // save it to the account
        signer.save(<-collection, to: /storage/ChainmonstersRewardCollection)

        // create a public capability for the collection
        signer.link<&ChainmonstersRewards.Collection{ChainmonstersRewards.ChainmonstersRewardCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(
            /public/ChainmonstersRewardCollection,
            target: /storage/ChainmonstersRewardCollection
        )

        // create a private capability for the collection
        signer.link<&ChainmonstersRewards.Collection{ChainmonstersRewards.ChainmonstersRewardCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(
            /private/ChainmonstersRewardsCollectionProvider,
            target: /storage/ChainmonstersRewardCollection
        )
    }
}

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

License

The works in these files:

are under the Unlicense.

nft-catalog's People

Contributors

aishairzay avatar albeethekid avatar austinkline avatar bshahid331 avatar bthaile avatar franklywatson avatar gregsantos avatar justinbarry avatar justjoolz avatar m-peter avatar nialexsan avatar poiati avatar prpatel05 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nft-catalog's Issues

Improve performance by deprecating getCatalog, and preferring alternatives for accessing the catalog.

As #135 points out, we need a way to deprecate use of the getCatalog function, and have all consumers update to a new way to pull all information from the catalog because as the catalog grows, we are nearing execution limits from simply calling getCatalog() because it copies over the entire catalog map to the memory of the executing script or transaction rather than using the map reference.

I think the best way to do this will be to add support for new ways to iterate over the the catalog similar to how the following PR does #135

But, rather than entirely removing the getCatalog function, we can make a snapshot of the current catalog, and have the getCatalog function return the snapshotted catalog. This will make it so all existing use of getCatalog will not be affected short-term, and allows for all consumers to switch to the new functions to iterate over the catalog in a way that will not reach execution limits.

cannot get catalog info, and cannot get mainnet nft info

the code is:

const addressMap = await flowCatalog.getAddressMaps();
    const catalog = await flowCatalog.scripts.getNftCatalog(addressMap);
    console.log(catalog);

the result is:

[
  null,
  HTTPRequestError [HTTP Request Error]:
        HTTP Request Error: An error occurred when interacting with the Access API.
        error=Invalid Flow argument: failed to execute the script on the execution node execution-5f6c73a22445d7d958c6a37c1f3be99c72cacd39894a3e46d6647a9adb007b4d@execution-001.devn
et38.nodes.onflow.org:3569=100: rpc error: code = InvalidArgument desc = failed to execute script: failed to execute script at block (3c0d1eef596167778723c5d746b98882273683f77687026
94cd2ede2c2d71842): [Error Code: 1106] error caused by: [Error Code: 1101] cadence runtime error: Execution failed:
  error: internal error: get program failed: cannot check account freeze status: failed to load account status for the account (e666c53e1758dec6): [Error Code: 1106] max interaction
 with storage has exceeded the limit (used: 20577720 bytes, limit 20000000 bytes)
  goroutine 1795848883 [running]:
  runtime/debug.Stack()
        /usr/local/go/src/runtime/debug/stack.go:24 +0x65
  github.com/onflow/cadence/runtime/err ... 3 +0xcfe
  google.golang.org/grpc.(*Server).handleStream(0xd3932eaa80, {0x2813f40, 0xd439561860}, 0xd3d31837a0, 0x0)
        /go/pkg/mod/google.golang.org/[email protected]/server.go:1620 +0xa2f
  google.golang.org/grpc.(*Server).serveStreams.func1.2()
        /go/pkg/mod/google.golang.org/[email protected]/server.go:922 +0x98
  created by google.golang.org/grpc.(*Server).serveStreams.func1
        /go/pkg/mod/google.golang.org/[email protected]/server.go:920 +0x28a

  --> 9083cbc90646dfd00b4ea79eca542ef1aa86149b8b0b720a3e6ed878ec26a9d2

        hostname=https://rest-testnet.onflow.org
        path=/v1/scripts?block_height=sealed
        method=POST
        requestBody={"script":"CmltcG9ydCBORlRDYXRhbG9nIGZyb20gMHgzMjRjMzRlMWM1MTdlNGRiCgpwdWIgZnVuIG1haW4oKToge1N0cmluZyA6IE5GVENhdGFsb2cuTkZUQ2F0YWxvZ01ldGFkYXRhfSB7CiAgICByZXR1cm
4gTkZUQ2F0YWxvZy5nZXRDYXRhbG9nKCkKfQo=","arguments":[]}
        responseBody={"code":400,"message":"Invalid Flow argument: failed to execute the script on the execution node execution-5f6c73a22445d7d958c6a37c1f3be99c72cacd39894a3e46d6647
[email protected]:3569=100: rpc error: code = InvalidArgument desc = failed to execute script: failed to execute script at block (3c0d1eef596167778
723c5d746b98882273683f7768702694cd2ede2c2d71842): [Error Code: 1106] error caused by: [Error Code: 1101] cadence runtime error: Execution failed:\nerror: internal error: get program
 failed: cannot check account freeze status: failed to load account status for the account (e666c53e1758dec6): [Error Code: 1106] max interaction with storage has exceeded the limit
 (used: 20577720 bytes, limit 20000000 bytes)\ngoroutine 1795848883 [running]:\nruntime/debug.Stack()\n\t/usr/local/go/src/runtime/debug/stack.go:24 +0x65\ngithub.com/onflow/cadence
/runtime/err ... 3 +0xcfe\ngoogle.golang.org/grpc.(*Server).handleStream(0xd3932eaa80, {0x2813f40, 0xd439561860}, 0xd3d31837a0, 0x0)\n\t/go/pkg/mod/google.golang.org/[email protected]/se
rver.go:1620 +0xa2f\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2()\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:922 +0x98\ncreated by google.golang.org/grpc.(*S
erver).serveStreams.func1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:920 +0x28a\n\n--> 9083cbc90646dfd00b4ea79eca542ef1aa86149b8b0b720a3e6ed878ec26a9d2\n"}
        responseStatusText=Bad Request
        statusCode=400

      at _callee$ (webpack://flowCatalog/./node_modules/@onflow/transport-http/dist/sdk-send-http.module.js?:146:33)
      at tryCatch (webpack://flowCatalog/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js?:86:17)
      at Generator.eval [as _invoke] (webpack://flowCatalog/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js?:66:24)
      at Generator.eval [as next] (webpack://flowCatalog/./node_modules/@babel/runtime/helpers/regeneratorRuntime.js?:117:21)
      at asyncGeneratorStep (webpack://flowCatalog/./node_modules/@babel/runtime/helpers/asyncToGenerator.js?:3:24)
      at _next (webpack://flowCatalog/./node_modules/@babel/runtime/helpers/asyncToGenerator.js?:25:9)
      at processTicksAndRejections (internal/process/task_queues.js:93:5) {
    statusCode: 400,
    errorMessage: 'Invalid Flow argument: failed to execute the script on the execution node execution-5f6c73a22445d7d958c6a37c1f3be99c72cacd39894a3e46d6647a9adb007b4d@execution-001
.devnet38.nodes.onflow.org:3569=100: rpc error: code = InvalidArgument desc = failed to execute script: failed to execute script at block (3c0d1eef596167778723c5d746b98882273683f776
8702694cd2ede2c2d71842): [Error Code: 1106] error caused by: [Error Code: 1101] cadence runtime error: Execution failed:\n' +
      'error: internal error: get program failed: cannot check account freeze status: failed to load account status for the account (e666c53e1758dec6): [Error Code: 1106] max intera
ction with storage has exceeded the limit (used: 20577720 bytes, limit 20000000 bytes)\n' +
      'goroutine 1795848883 [running]:\n' +
      'runtime/debug.Stack()\n' +
      '\t/usr/local/go/src/runtime/debug/stack.go:24 +0x65\n' +
      'github.com/onflow/cadence/runtime/err ... 3 +0xcfe\n' +
      'google.golang.org/grpc.(*Server).handleStream(0xd3932eaa80, {0x2813f40, 0xd439561860}, 0xd3d31837a0, 0x0)\n' +
      '\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1620 +0xa2f\n' +
      'google.golang.org/grpc.(*Server).serveStreams.func1.2()\n' +
      '\t/go/pkg/mod/google.golang.org/[email protected]/server.go:922 +0x98\n' +
      'created by google.golang.org/grpc.(*Server).serveStreams.func1\n' +
      '\t/go/pkg/mod/google.golang.org/[email protected]/server.go:920 +0x28a\n' +
      '\n' +
      '--> 9083cbc90646dfd00b4ea79eca542ef1aa86149b8b0b720a3e6ed878ec26a9d2\n'
  }
]

With the new UX, hard to find proposals that need review

With new UX, it is hard to find the latest proposals that need review. There are multiple issues with the new UX:

  1. Infinite scrolling requires multiple clicks to get to the bottom
  2. There is no easy way to discern if there is an updated proposal for a proposal under review.

Suggestions:

  1. Add a filter to show only proposals that require review
  2. Hide proposals whose updated version has been submitted for review

Update NFTCatalog Cadence code for Cadence 1.0

Description

These contracts will need to be updated in preparation for Stable Cadence.

Context

While not directly used in NFTStorefront contracts, it looks like integration is useful and demonstrated in example transactions. Not currently a blocker, but flagging as I came across the contracts in onflow/nft-storefront#89

Fill in collection identifier

In step 5 of the wizard we are asked to fill inn the collection identifier. Since we have already submitted an NFT that has a collectionDisplay view, can we prefil in the value from the name there? The intent when that view was created was that the name should be an url safe identifier and the description a long form one. (Atleast that was my intent)

Support for pagination

Some users have large collections, so large infact that even a single collection beeing resolved against a given capability/collection will blow the limit. A method that can take a cap and a set of ids you want to resolve will mitigate this.

NFTRetrival is out of date

A lot of the code in NFTRetrieval is in MetadataViews now, It is best to just create a new version that uses the new NFTView before this gets too much of a traction?

GetNftCatalog (maybe also other functions) return only testnet nfts

Thanks for developing the lib 👍

Issue:

  • no matter what I do, I get only testnet collections

Possible cause:

Solution:

  • IMO it's pretty strange that getAddressMaps returns costs for both mainnet and testnet, it should at least receive an argument mainnet | testnet and return only network-specific address maps.

https://www.flow-nft-catalog.com/catalog/testnet crashed

image

I think it's because the data returned from getCatalog is too large?

The Code:

import NFTCatalog from 0x324c34e1c517e4db
// import NFTCatalog from 0x49a7cda3a1eecc29

  pub fun main(): {String : NFTCatalog.NFTCatalogMetadata} {
      return NFTCatalog.getCatalog()
  }

The error:

error: internal error: get program failed: cannot check account freeze status: failed to load account status for the account (683564e46977788a): [Error Code: 1106] max interaction with storage has exceeded the limit (used: 20081770 bytes, limit 20000000 bytes)

Use IPFS gateway to load IPFS assets

We noticed that IPFS images aren't displayed in the catalog web UI. Here's an example: https://nft-catalog.vercel.app/nfts/testnet/0xf1e7849ad48c52ca/athlete_studio_testnet/110265932

The image tag includes an ipfs:// URL (the result of calling ipfsFile.uri() in Cadence), but most browsers won't be able to resolve that.

<img src="ipfs://bafybeiefc2s5hkjfzbbwhehv2qe6cz3elik3plqv4g5kbdk6dqzv5lbfh4" width="50" height="50">

Instead, I'd recommend that the catalog use an IPFS gateway to convert the CID + path to an HTTP URL:

<img src="https://nftstorage.link/ipfs/bafybeiefc2s5hkjfzbbwhehv2qe6cz3elik3plqv4g5kbdk6dqzv5lbfh4" width="50" height="50">

Public IPFS gateways: https://ipfs.github.io/public-gateway-checker/

Invalid character in catalog's dictionary key leads to broken scripts

Most catalog scripts rely on temporary linking to return data (like this):

let tempPathStr = "catalog".concat(key)
let tempPublicPath = PublicPath(identifier: tempPathStr)!
account.link<&{MetadataViews.ResolverCollection}>(
            tempPublicPath,
            target: value.collectionData.storagePath
        )

This leads to errors if the key contains characters like '. This is now true on mainnet catalog.

As a workaround we can hash the key instead, but the downside is more computation:

pub fun cleanStringForPath(_ input: String): String {
  return "catalog".concat(String.encodeHex(HashAlgorithm.SHA3_256.hash(input.utf8)))
}


let tempPublicPath = PublicPath(identifier: cleanStringForPath(key))!

Emit event when proposals are accepted

Can we have an event that says when a proposal is approved that contains proposer address and some more fields. In order to automate systems we need this link that «one of our proposals» had been approved.

Why aren't catalog keys generated using the contract name and address?

Currently, the catalog keys appear to be generated by the user.
It is difficult to obtain information about a contract without the key.
The only way to get the key for a contract in the current implementation appears to be to loop through the entire catalog at once or in batches and compare contract name and address.

A simpler approach would have been to generate the key from the contract name.

Example for TopShot:

Address: 0x0b2a3299cc857e29
Contract: TopShot
Resource: NFT

So the key would be

Key:  0b2a3299cc857e29_TopShot_NFT

"Visit Website" hyperlink is reading wrong externalsite url from contract

https://www.flow-nft-catalog.com/catalog/mainnet/DisruptArt

"Visit Website" hyperlink was supposed to be taken from line 147 externalURL

//
 return MetadataViews.NFTCollectionDisplay(
                        name: "DisruptArt Collection",
                        description: "Discover amazing NFT collections from various disruptor creators. Disrupt.art Marketplace's featured and spotlight NFTs",
                        externalURL: MetadataViews.ExternalURL("https://disrupt.art"),
                        squareImage: media,
                        bannerImage: media,
                        socials: {
                            "twitter": MetadataViews.ExternalURL("https://twitter.com/DisruptArt"),
                            "instagram": MetadataViews.ExternalURL("https://www.instagram.com/disrupt.art/"),
                            "discord" : MetadataViews.ExternalURL("https://discord.io/disruptart")
                        }
                    )
                    //
                But it is taking wrongly from sample wallet address's token's externalURL from line 126. 

Nft catalog not working in web enviroment

Issue:

  • when importing the npm package into a react app, the app does not load at all
  • the app crashes with Uncaught (in promise) ReferenceError: global is not defined

Possible cause:

Again, thanks for developing this package 👍 helps a lot :)

Flow NFT Catalog and Testnet

  • I created public and private keys: flow keys generate.
  • I visited the site https://testnet-faucet.onflow.org/ and paste the Public Key into the appropriate field. Аnd click the create account button.
  • I published copies of the contracts (FungibleToken.cdc, NonFungibleToken.cdc, ExampleNFT.cdc, MetadataViews.cdc) on the testnet.
  • I visited the site https://nft-catalog.vercel.app/v and add my ExampleNFT.cdc in form.
  • On the second step I see an error - The provided contract, ExampleNFT, is not an NFT. Your contract must implement the standard NonFungibleToken contract.

What am I doing wrong (how to fix the error)? Why can't I add a contact from the test network? How do I add a test contract to a directory on the testnet?

Update transactions to avoid DUC leakage

@satyamakgec has done some excellent work to avoid DUC leakage in the "StorefrontListItem" and "StorefrontBuyItem" transactions:

https://github.com/onflow/TransactionMix/tree/main/transactions/nft-storefront-v2

Can we update the corresponding nft-catalog transactions to include these checks to avoid DUC leakage?

I'm not sure if there are wider implications or some additional context to consider.

I'd also be happy to open a PR to do this myself.

I think it'd be a good thing if we start considering the nft-catalog transaction templates as exemplar/standard.

i'm currently working on setting up some re-usable/multi-project workflow functionality around marketplace/storefront transactions and I'd like to depend more on nft-catalog.

I don't think I can start to rely on nft-catalog yet, until we can resolve this issue

Server error when using library, max interaction with storage has exceeded the limit. Also can't send parameters to methods.

Greetings, I'm a web3 dev who makes multichain wallet viewers. This project seemed perfect for what I want to do but I'm having some trouble following the instructions to get it to work.

If I run the following code, I get two different errors:

import flowcat from "flow-catalog";
import fc from "@onflow/flow-cadut";

app.get("/test2", async (req, res) => {
    try {
        const addressMap = await flowcat.getAddressMaps();
        fc.setEnvironment("mainnet");
        const result = await flowcat.scripts.getNftCatalog(addressMap);
        const result2 = await flowcat.scripts.getNftIdsInAccount('0x3b77a836b3b67081');
        res.send(result);
    } catch (ex) {
        res.render("error", { exception: ex.response?.data ? ex.response.data : ex })
    }
});

"result" contains this error:

[null,{"name":"HTTP Request Error","statusCode":400,"errorMessage":"Invalid Flow argument: failed to execute the script on the execution node execution-0ca407c1da940952ebcc02283b60cd97c9a008e111a48ea6cf1ce8f36f1e0153@execution-004.mainnet21.nodes.onflow.org:3569=100: rpc error: code = InvalidArgument desc = failed to execute script: failed to execute script at block (90e7f5552850e08c962c9846950b0699d00c758701784e8254785977de01811e): [Error Code: 1106] error caused by: [Error Code: 1101] cadence runtime error: Execution failed:\nerror: internal error: get program failed: cannot check account freeze status: failed to load account status for the account (30cf5dcf6ea8d379): [Error Code: 1106] max interaction with storage has exceeded the limit (used: 20147306 bytes, limit 20000000 bytes)\ngoroutine 42950176864 [running]:\nruntime/debug.Stack()\n\t/usr/local/go/src/runtime/debug/stack.go:24 +0x65\ngithub.com/onflow/cadence/runtime/er ... golang.org/[email protected]/server.go:1283 +0xcfe\ngoogle.golang.org/grpc.(*Server).handleStream(0x10ca2a048c0, {0x28a2600, 0x10d65a364e0}, 0x12599562240, 0x0)\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1620 +0xa2f\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2()\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:922 +0x98\ncreated by google.golang.org/grpc.(*Server).serveStreams.func1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:920 +0x28a\n\n--> 30cf5dcf6ea8d379.AeraNFT\n"}]

And "result2" is this error getNftIdsInAccount => Incorrect number of arguments: found 0 of 1 even though I tried sending the address multiple ways, it never changes.

I would like to use this library to do two simple things:

  1. Load a list of collections that are in a wallet address
  2. Load the NFTs with metadata for those NFTs either all at once or per collection.

I have an idea for the upcoming hackathon so I would appreciate any assistance. Thank you.

P.S.
I have a typical empty nodejs app using express. I installed node 16.13.1 and I also installed the following packages:

    "@onflow/fcl": "^1.2.0",
    "@onflow/types": "^1.0.2",
    "@onflow/util-actor": "^0.0.2",
    "@onflow/util-invariant": "^1.0.2",
    "@onflow/util-uid": "^0.0.1",
    "axios": "^0.27.2",
    "body-parser": "^1.20.0",
    "bootstrap": "^5.2.3",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express-jwt": "^7.7.5",
    "flow-catalog": "^0.1.1",
    "jsonwebtoken": "^8.5.1",
    "node-cron": "^3.0.2",
    "pug": "^3.0.0",
    "random-number-csprng": "^1.0.2",
    "sanitize": "^2.1.2",
    "winston": "^3.8.2"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.