61 min read
Overview
Flow is a fast, decentralized, and developer-friendly L1 blockchain designed for the next generation of games, apps, and digital assets. Flow’s multi-role architecture is purpose-built for internet-scale performance, with low fees, upgradable smart contracts, and user-friendly tools like the Flow Emulator and flow-cli. Cadence, the next-generation smart contract language for Flow, stores digital assets directly in accounts thus guaranteeing direct ownership of assets held within. Cadence incorporates security and other language native concepts for handling on-chain digital assets, enabling developers to focus their effort on building products. Contracts can call other contracts, even dynamically if required, transactions are ACID, and scripts simplify read-access to on-chain data. Creating complex, decentralized applications which store data and logic together on-chain has never been easier. Flow unlocks productivity gains and true composability for Web3 like never before.
In this guide, you will learn about Flow. Here, you will discover how to create an NFT collection and build a frontend using Cadence, Flow CLI, and NextJS.
While our exploratory journey takes place in Flow Testnet's sandbox, rest assured that the skills you'll acquire are completely transferable to Flow Mainnet. Let's turn the tides and transform the complex into simplicity together!
What You Will Need
- A basic knowledge of Flow
- A basic knowledge of web development experience
- Node.js installed (version 18.16 or higher)
- The Flow Command Line Interface installed (Flow CLI)
- A code editor (e.g. Visual Studio Code)
- A Flow-compatible wallet (For wallet options, you may check Flow's Wallets page)
You can download and install each dependency by clicking the links above and following their instructions.
Dependencies Used in This Guide
Dependency | Version |
---|---|
node.js | 18.16.0 |
flow-cli | 1.4.2 |
What You Will Do
- Learn about the Flow blockchain
- Learn about the Cadence programming language
- Create a Flow wallet using Flow CLI
- Create NFT collection smart contract, transaction, and script files with Cadence
- Deploy the smart contract on the Flow blockchain
- Build a frontend to mint NFTs
Flow Blockchain Overview
Key Features of Flow
Multi-Role Architecture
Flow applies pipelining to transaction processing by separating the jobs of a validator node into four different roles: Collection, Consensus, Execution, and Verification. This unique multi-node architecture allows Flow to scale significantly without compromising the security or long-term decentralization of the network. More details can be found here if needed.
Resource-Oriented Programming
Flow's adoption of Resource-oriented programming is a game-changer in the world of smart contracts. Cadence allows developers to define their own Resource types and enforce custom rules for their use. Cadence's design reduces the risk of programming errors and vulnerabilities in other smart contract languages since Resources cannot be duplicated or destroyed and can only be stored in an account. During code execution, functions will abort if a Resource is not left stored within an Account upon completion.
For developers who are familiar with Solidity, Cadence introduces a lot of new concepts and patterns that differ from what you may be used to. We recommend reviewing Guide for Solidity developers for a more detailed understanding of these differences.
Account Concept
Flow’s account model defines each account to associate with an address, and hold one or more public keys, contracts, and other account storage. More detailed information can be found in Accounts in Flow's developer docs.
- Address: The account's unique identification.
- Public Keys: public keys that have been approved on the account.
- Contracts: Cadence contracts have been deployed to the account.
- Storage: The region of the account where resource assets are kept.
Flow accounts support multiple public keys, granting flexibility in access control, and owners of the corresponding private keys can sign transactions to modify the account's state, offering a departure from Ethereum's single key-pair account model.
Upgradability
EVM-based chains have always enforced contract immutability which pushes developers to work around that limitation using proxy patterns. By contrast Flow allows developers to upgrade smart contracts to a limited degree.
Why create an NFT collection on Flow?
Non-fungible tokens (NFTs) have taken the digital world by storm, revolutionizing ownership and creativity in the digital realm. While NFTs are often associated with Ethereum's ERC-721 and ERC-1155 standards, Flow presents a compelling alternative with its unique advantages. This section explores why creators and collectors should consider creating an NFT collection on Flow.
Developer Tooling
Flow improves developer productivity with easy-to-use developer and testing tooling, SDKs, wallets, the Flow Playground, Cadence support built into VS Code and GoLand, and much more. Together with Flow’s extensive developer docs, the experience of building on Flow can be both productive and enjoyable!
Resource-Oriented Programming and Cadence
Resources exist in Cadence specifically to represent assets or tokens which are supply limited, making them ideal for blockchain. Linear types, as this type concept is known, are when the programming language enforces the constraint that each variable can exist only once and are unique. NFTs are themselves a type of Resource - what makes them a NFT is that they adhere to the standards detailed below. However, Resources have an infinite range of possible tokenization use-cases which may be useful for identity, rights assignment or other uses. Transactions involving an exchange of assets, for example selling a NFT in exchange for Fungible-Token Resources, are handled in a peer-to-peer way - sender assets are deposited directly to the receiver and vice versa, rather than being brokered through a central contract.
Decentralized Ownership: All storage on Flow is handled exclusively inside accounts. Unlike ledger-based blockchains, the Resource owned is not a mapping of asset ID to owner ID. When an asset is within a given account, only that account holder has the ability to take actions to move that Resource elsewhere.
Capability-based Access Control: Cadence uses Capabilities to restrict access to protected functions or objects. A Capability is a key-like entity which is provided to the holding account ahead of time which only allows them to initiate specific operations. Capabilities can be provided directly to specific accounts or otherwise revoked if needed.
The Non-Fungible Token Standard
At the heart of Flow's NFT ecosystem is the NonFungibleToken contract, which defines a set of essential functionality for NFTs. Any implementation of the NonFungibleToken interface on Flow is required to implement two resource interfaces:
1. NFT - A Resource Describing a Single NFT: This resource outlines the structure of an individual NFT, including its unique characteristics and properties.
2. Collection - A Resource for Holding Multiple NFTs of the Same Type: Collections are repositories that can store multiple NFTs of the same type. Users typically maintain one collection per NFT type, which is stored at a predefined location in their account storage.
For instance, imagine a user owning NBA Top Shot Moments NFTs. These NFTs would be stored within a TopShot.Collection in the user's account at the path /storage/MomentCollection
.
Strong NFT Ecosystem
Flow has established itself as a mainstream ready blockchain and is known for sports NFT projects such as NBA TopShot, NFL AllDay and UFC Strike, which allow users to own and trade digital moments of varying rarities from their favorite sports. Many hundreds of other NFTs projects have also launched on Flow, including Doodles, Flovatar, and many others.
Developer Set Up
Accessing Flow with QuickNode
To build on Flow, you'll need an endpoint to connect with the Flow network. You're welcome to use Flow's public endpoints that are powered by QuickNode or deploy and manage your own infrastructure; however, if you'd like faster response times, you can leave the heavy lifting to us. See why Flow chose QuickNode as their public node's provider and sign up for a free account here.
In this guide, we will use public endpoints that are powered by QuickNode.
Setting Up Your Development Environment
You need a terminal emulator (i.e., Terminal, Windows PowerShell) and code editor (i.e., Visual Studio Code) to set up the project.
We assume that you have already installed Flow-CLI since it is mentioned in What You Will Need, but if not, please check the relevant section to install the necessary programs and packages.
Creating a Flow Project
Run the following code in your terminal to set up the project. Our project folder's name will be nft-collection-qn
, but you can modify the name as you wish. The --scaffold
flag is for using provided scaffolds, which are project templates you can use to bootstrap your development.
flow setup nft-collection-qn --scaffold
If the terminal needs your input for scaffold number
, you can select the fifth option [5] FCL Web Dapp. It creates all the necessary files and folders to build a simple TypeScript web application using next.js, FCL, and Cadence. The console output should look like the below one.
Enter the scaffold number: 5
🎉 Congrats! your project was created.
Start development by following these steps:
1. 'cd nft-collection-qn' to change to your new project,
2. 'flow emulator' or run Flowser to start the emulator,
3. 'flow dev' to start developing.
You should also read README.md to learn more about the development process!
Run the command to change your directory.
cd nft-collection-qn
With the project folder created, you can now move on to creating a Flow wallet.
Configuring Your Flow Account
You must create an account on the Flow blockchain using Flow CLI to deploy a smart contract. Luckily, Flow CLI has a useful command to create a wallet and fund it automatically.
To set up the wallet, run the following code in your terminal.
flow accounts create
Then, enter an account name and select the network. We type testnet-account as the account name and select "Testnet" as the network.
The console output should be like the one below.
🎉 New account created with address 0xbcc2fbf2808c44b6 and name testnet-account on Testnet network.
Here’s a summary of all the actions that were taken:
- Added the new account to flow.json.
- Saved the private key to testnet-account.pkey.
- Added testnet-account.pkey to .gitignore.
Save the account address since it will be necessary for the following sections.
As mentioned in the output, this command performs these actions:
- Adds the new account to the
flow.json
file - Saves the private key to the
pkey
file - Adds the
pkey
file to the.gitignore
file
After this step, your account is created and funded.
Checking the Configuration File
Flow configuration file (flow.json
) is used to define networks (, i.e., mainnet, testnet), accounts, deployment targets, and contracts that will be deployed. Thus, the following properties should be included in the configuration file:
networks
which predefines the Flow emulator, testnet, and mainnet connection configurationaccounts
which predefines the Flow emulator account and your newly created accountdeployments
where all deployment targets can be definedcontracts
where all contracts that will be used in the project can be defined
The file's default state may not have all the mentioned properties. It is not a problem; we will walk through to update the file when we are ready for deployment.
When we created a wallet using the flow accounts create
command previously, our account was added to this config file automatically. However, if you had wallet credentials you wanted to import, you would add them here. (See Flow CLI Configuration documentation for details.)
There is no need to update anything in this file for now, but we highly recommend that you review the file for a better understanding of the configuration file.
So far, the folder structure should be like the one below.
├── README.md # Documentation for your app.
├── cadence # Cadence language files (smart contracts, transactions, and scripts).
├── components # React components used in your app.
├── config # Configuration files for your app.
├── constants # Constants used in your app.
├── emulator.key # Emulator key file (for local development).
├── flow.json # Flow blockchain configuration or settings.
├── helpers # Helper functions or utilities for your app.
├── hooks # Custom React hooks used in your app.
├── jest.config.js # Configuration file for Jest testing framework.
├── layouts # Layout components for your app.
├── next.config.js # Configuration file for Next.js.
├── package-lock.json # Automatically generated file to lock dependency versions.
├── package.json # Package configuration file for Node.js.
├── pages # Next.js pages for routing.
├── public # Static files served by Next.js.
├── styles # Stylesheets and CSS for your app.
├── testnet-account.pkey # Testnet account private key (for testing purposes).
├── tsconfig.json # TypeScript configuration for your app.
└── types # TypeScript type declarations for your app.
Creating an NFT Collection on Flow
For an NFT collection dApp, we need the following files:
- a smart contract (
QuickNFT.cdc
) - script files (
GetIDsQuickNFT.cdc
,TotalSupplyQuickNFT.cdc
, andGetMetadataQuickNFT.cdc
) - transaction files (
MintNFT.cdc
andSetUpAccount.cdc
)
Script: A script is executable Cadence code that queries the Flow network but does not modify it. Unlike Flow transactions, they don’t need signing, and they can return a value. You can think of executing scripts as a read-only operation.
Transaction: Transactions are cryptographically signed data messages that contain a set of instructions that update the Flow state. They are a basic unit of computation that gets executed by execution nodes. In order for a transaction to be included in the Flow blockchain, a fee is required from the payer.
However, before jumping into the coding, let's learn more about Flow's native NFT standard.
Flow's Native NFT Standard
Flow's native NFT standard, often referred to as the NonFungibleToken contract, defines a set of essential functionalities that ensure the secure and efficient management of NFTs. This standard offers a streamlined approach to creating, trading, and interacting with NFTs, underlining Flow's commitment to simplicity and user-friendly development.
The core features of the NonFungibleToken contract encompass two essential resource interfaces: NFT, which describes a single NFT's structure, and Collection, designed to hold multiple NFTs of the same type. Users typically organize their NFTs by saving one collection per NFT type in a predefined location within their account storage. For instance, a user might store all their NBA Top Shot Moments in a TopShot.Collection located at /storage/MomentCollection.
To create a new NFT collection, developers can utilize the createEmptyCollection
function, which generates an empty collection void of any NFTs. Users often save these new collections in a recognizable location within their account and make use of the NonFungibleToken.CollectionPublic
interface to establish a public capability for linking.
When it comes to depositing an NFT into a Collection, the deposit function comes into play. This action triggers the Deposit event and is accessible through the NonFungibleToken.CollectionPublic
interface, allowing individuals to deposit NFTs into a collection without needing access to the entire collection.
Creating Smart Contract
Go to the ./cadence/contracts
directory. Then, create a file named QuickNFT.cdc
in it by running the commands below.
echo > QuickNFT.cdc
Then, open the QuickNFT.cdc
file with your code editor. Copy the code below and paste it into the file.
The code may seem complicated, but we'll guide you through it step by step just after the code snippet. Also, many functions are standard and explained in Flow's documentation.
import NonFungibleToken from 0x631e88ae7f1d7c20
import ViewResolver from 0x631e88ae7f1d7c20
import MetadataViews from 0x631e88ae7f1d7c20
pub contract QuickNFT: NonFungibleToken, ViewResolver {
/// Total supply of QuickNFTs in existence
pub var totalSupply: UInt64
/// The event that is emitted when the contract is created
pub event ContractInitialized()
/// The event that is emitted when an NFT is withdrawn from a Collection
pub event Withdraw(id: UInt64, from: Address?)
/// The event that is emitted when an NFT is deposited to a Collection
pub event Deposit(id: UInt64, to: Address?)
/// Storage and Public Paths
pub let CollectionStoragePath: StoragePath
pub let CollectionPublicPath: PublicPath
/// The core resource that represents a Non Fungible Token.
/// New instances will be created using the NFTMinter resource
/// and stored in the Collection resource
///
pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
/// The unique ID that each NFT has
pub let id: UInt64
/// Metadata fields
pub let name: String
pub let description: String
pub let thumbnail: String
access(self) let metadata: {String: AnyStruct}
init(
id: UInt64,
name: String,
description: String,
thumbnail: String,
metadata: {String: AnyStruct},
) {
self.id = id
self.name = name
self.description = description
self.thumbnail = thumbnail
self.metadata = metadata
}
/// Function that returns all the Metadata Views implemented by a Non Fungible Token
///
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
pub fun getViews(): [Type] {
return [
Type<MetadataViews.Display>(),
Type<MetadataViews.Editions>(),
Type<MetadataViews.ExternalURL>(),
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>()
]
}
/// Function that resolves a metadata view for this token.
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.Display>():
return MetadataViews.Display(
name: self.name,
description: self.description,
thumbnail: MetadataViews.HTTPFile(
url: self.thumbnail
)
)
case Type<MetadataViews.Editions>():
// There is no max number of NFTs that can be minted from this contract
// so the max edition field value is set to nil
let editionInfo = MetadataViews.Edition(name: "Example NFT Edition", number: self.id, max: nil)
let editionList: [MetadataViews.Edition] = [editionInfo]
return MetadataViews.Editions(
editionList
)
case Type<MetadataViews.Serial>():
return MetadataViews.Serial(
self.id
)
case Type<MetadataViews.ExternalURL>():
return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
case Type<MetadataViews.NFTCollectionData>():
return MetadataViews.NFTCollectionData(
storagePath: QuickNFT.CollectionStoragePath,
publicPath: QuickNFT.CollectionPublicPath,
providerPath: /private/QuickNFTCollection,
publicCollection: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic}>(),
publicLinkedType: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
providerLinkedType: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
return <-QuickNFT.createEmptyCollection()
})
)
case Type<MetadataViews.NFTCollectionDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
),
mediaType: "image/svg+xml"
)
return MetadataViews.NFTCollectionDisplay(
name: "The Example Collection",
description: "This collection is used as an example to help you develop your next Flow NFT.",
externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"),
squareImage: media,
bannerImage: media,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
}
)
case Type<MetadataViews.Traits>():
// exclude mintedTime and foo to show other uses of Traits
let excludedTraits = ["mintedTime", "foo"]
let traitsView = MetadataViews.dictToTraits(dict: self.metadata, excludedNames: excludedTraits)
// mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it.
let mintedTimeTrait = MetadataViews.Trait(name: "mintedTime", value: self.metadata["mintedTime"]!, displayType: "Date", rarity: nil)
traitsView.addTrait(mintedTimeTrait)
// foo is a trait with its own rarity
let fooTraitRarity = MetadataViews.Rarity(score: 10.0, max: 100.0, description: "Common")
let fooTrait = MetadataViews.Trait(name: "foo", value: self.metadata["foo"], displayType: nil, rarity: fooTraitRarity)
traitsView.addTrait(fooTrait)
return traitsView
}
return nil
}
}
/// Defines the methods that are particular to this NFT contract collection
///
pub resource interface QuickNFTCollectionPublic {
pub fun deposit(token: @NonFungibleToken.NFT)
pub fun getIDs(): [UInt64]
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
pub fun borrowQuickNFT(id: UInt64): &QuickNFT.NFT? {
post {
(result == nil) || (result?.id == id):
"Cannot borrow QuickNFT reference: the ID of the returned reference is incorrect"
}
}
}
/// The resource that will be holding the NFTs inside any account.
/// In order to be able to manage NFTs any account will need to create
/// an empty collection first
///
pub resource Collection: QuickNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
// dictionary of NFT conforming tokens
// NFT is a resource type with an `UInt64` ID field
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
init () {
self.ownedNFTs <- {}
}
/// Removes an NFT from the collection and moves it to the caller
///
/// @param withdrawID: The ID of the NFT that wants to be withdrawn
/// @return The NFT resource that has been taken out of the collection
///
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
emit Withdraw(id: token.id, from: self.owner?.address)
return <-token
}
/// Adds an NFT to the collections dictionary and adds the ID to the id array
///
/// @param token: The NFT resource to be included in the collection
///
pub fun deposit(token: @NonFungibleToken.NFT) {
let token <- token as! @QuickNFT.NFT
let id: UInt64 = token.id
// add the new token to the dictionary which removes the old one
let oldToken <- self.ownedNFTs[id] <- token
emit Deposit(id: id, to: self.owner?.address)
destroy oldToken
}
/// Helper method for getting the collection IDs
///
/// @return An array containing the IDs of the NFTs in the collection
///
pub fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
/// Gets a reference to an NFT in the collection so that
/// the caller can read its metadata and call its methods
///
/// @param id: The ID of the wanted NFT
/// @return A reference to the wanted NFT resource
///
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
}
/// Gets a reference to an NFT in the collection so that
/// the caller can read its metadata and call its methods
///
/// @param id: The ID of the wanted NFT
/// @return A reference to the wanted NFT resource
///
pub fun borrowQuickNFT(id: UInt64): &QuickNFT.NFT? {
if self.ownedNFTs[id] != nil {
// Create an authorized reference to allow downcasting
let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
return ref as! &QuickNFT.NFT
}
return nil
}
/// Gets a reference to the NFT only conforming to the `{MetadataViews.Resolver}`
/// interface so that the caller can retrieve the views that the NFT
/// is implementing and resolve them
///
/// @param id: The ID of the wanted NFT
/// @return The resource reference conforming to the Resolver interface
///
pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
let QuickNFT = nft as! &QuickNFT.NFT
return QuickNFT as &AnyResource{MetadataViews.Resolver}
}
destroy() {
destroy self.ownedNFTs
}
}
/// Allows anyone to create a new empty collection
///
/// @return The new Collection resource
///
pub fun createEmptyCollection(): @NonFungibleToken.Collection {
return <- create Collection()
}
/// Mints a new NFT with a new ID and deposit it in the
/// recipients collection using their collection reference
///
/// @param recipient: A capability to the collection where the new NFT will be deposited
/// @param name: The name for the NFT metadata
/// @param description: The description for the NFT metadata
/// @param thumbnail: The thumbnail for the NFT metadata
///
pub fun mintNFT(
recipient: &{NonFungibleToken.CollectionPublic},
name: String,
description: String,
thumbnail: String,
) {
let metadata: {String: AnyStruct} = {}
// this piece of metadata will be used to show embedding rarity into a trait
metadata["foo"] = "bar"
// create a new NFT
var newNFT <- create NFT(
id: QuickNFT.totalSupply,
name: name,
description: description,
thumbnail: thumbnail,
metadata: metadata,
)
// deposit it in the recipient's account using their reference
recipient.deposit(token: <-newNFT)
QuickNFT.totalSupply = QuickNFT.totalSupply + 1
}
/// Function that resolves a metadata view for this contract.
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
pub fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.NFTCollectionData>():
return MetadataViews.NFTCollectionData(
storagePath: QuickNFT.CollectionStoragePath,
publicPath: QuickNFT.CollectionPublicPath,
providerPath: /private/QuickNFTCollection,
publicCollection: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic}>(),
publicLinkedType: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
providerLinkedType: Type<&QuickNFT.Collection{QuickNFT.QuickNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
return <-QuickNFT.createEmptyCollection()
})
)
case Type<MetadataViews.NFTCollectionDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
),
mediaType: "image/svg+xml"
)
return MetadataViews.NFTCollectionDisplay(
name: "The Example Collection",
description: "This collection is used as an example to help you develop your next Flow NFT.",
externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"),
squareImage: media,
bannerImage: media,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
}
)
}
return nil
}
/// Function that returns all the Metadata Views implemented by a Non Fungible Token
///
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
pub fun getViews(): [Type] {
return [
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>()
]
}
init() {
// Initialize the total supply
self.totalSupply = 0
// Set the named paths
self.CollectionStoragePath = /storage/QuickNFTCollection
self.CollectionPublicPath = /public/QuickNFTCollection
// Create a Collection resource and save it to storage
let collection <- create Collection()
self.account.save(<-collection, to: self.CollectionStoragePath)
// create a public capability for the collection
self.account.link<&QuickNFT.Collection{NonFungibleToken.CollectionPublic, QuickNFT.QuickNFTCollectionPublic, MetadataViews.ResolverCollection}>(
self.CollectionPublicPath,
target: self.CollectionStoragePath
)
emit ContractInitialized()
}
}
You can now access Logs for your RPC endpoints, helping you troubleshoot issues more effectively. If you encounter an issue with your RPC calls, simply check the logs in your QuickNode dashboard to identify and resolve problems quickly. Learn more about log history limits on our pricing page.
Let's explain the smart contract step by step.
Imports:
The code imports three interfaces: NonFungibleToken, ViewResolver, and MetadataViews. These interfaces provide functionalities needed for creating and managing non-fungible tokens (NFTs) on the Flow blockchain.
Since the Flow team has already deployed these interfaces on the testnet, we use their addresses to import these interfaces. Feel free to check the Non-Fungible Token Contract page to see NonFungibleToken address for each network.
When you want to deploy your smart contract to the mainnet, you should change the addresses accordingly.
#### Contract Declaration:
This code defines a contract named QuickNFT, which is used to create and manage NFTs. It declares that the contract implements the NonFungibleToken and ViewResolver interfaces.
Contract State:
-
totalSupply: A variable that tracks the total number of QuickNFTs in existence.
-
Events: The contract defines several events, such as
ContractInitialized
,Withdraw
, andDeposit
, which can be emitted to record important contract actions.
Storage and Paths:
CollectionStoragePath
and CollectionPublicPath
are storage and public paths used for storing and accessing NFT collections and their metadata.
NFT Resource:
NFT
resource represents an individual NFT with various properties like id
, name
, description
, thumbnail
, and metadata
. NFTs are minted and managed using this resource.
Functions in NFT Resource:
-
getViews(): Returns an array of supported metadata views for an NFT.
-
resolveView(_ view: Type): Resolves a specific metadata view for an NFT. The functions create and return metadata views for different aspects of an NFT, such as its display, editions, external URL, collection data, and more.