Creating an NFT Contract
This guide is an in-depth tutorial on launching NFT contracts from scratch. To launch in 2 minutes using a tool check out Touchstone
What are NFTs
NFTs, or Non-Fungible Tokens, represent a unique digital asset verified using blockchain technology. Unlike cryptocurrencies such as Bitcoin, which are fungible and can be exchanged on a one-for-one basis, NFTs are distinct and cannot be exchanged on a like-for-like basis. This uniqueness and indivisibility make them ideal for representing rare and valuable items like art, collectibles, tickets and even real estate. Their blockchain-backed nature ensures the authenticity and ownership of these digital assets.
Setting Up a Project
To start creating an NFT on the Flow blockchain, you'll first need some tools and configurations in place.
Installing Flow CLI
The Flow CLI (Command Line Interface) provides a suite of tools that allow developers to interact seamlessly with the Flow blockchain.
If you haven't installed the Flow CLI yet and have Homebrew installed,
you can run brew install flow-cli
. If you don't have Homebrew,
please follow the installation guide here.
Initializing a New Project
💡 Note: Here is a link to the completed code if you want to skip ahead or reference as you follow along.
Once you have the Flow CLI installed, you can set up a new project using the flow init
command. This command initializes the necessary directory structure and a flow.json
configuration file (a way to configure your project for contract sources, deployments, accounts, and more):
Note: Select "No" when it asks you to install core contracts for the purposes of this tutorial.
Upon execution, the command will generate the following directory structure:
Now, navigate into the project directory:
To begin, let's create a contract file named FooBar
for the FooBar
token,
which will be the focus of this tutorial. To do this, we can use the boilerplate generate
command from the Flow CLI:
This will create a new file at cadence/contracts/FooBar.cdc
with the following contents:
Now, add these contracts to your flow.json
.
These are important contracts that your contract will import that
are pre-deployed to the emulator.
Setting Up Our NFT on the Contract
Understanding Resources
On the Flow blockchain, "Resources" are a key feature of the Cadence programming language. They represent unique, non-duplicable assets, ensuring that they can only exist in one place at a time. This concept is crucial for representing NFTs on Flow, as it guarantees their uniqueness.
To begin, let's define a basic NFT
resource.
This resource requires an init
method, which is invoked when the resource is instantiated:
Every resource in Cadence has a unique identifier assigned to it. We can use it to set an ID for our NFT. Here's how you can do that:
To control the creation of NFTs, it's essential to have a mechanism
that restricts their minting. This ensures that not just anyone can create an NFT and inflate its supply.
To achieve this, you can introduce an NFTMinter
resource that contains a createNFT
function:
In this example, the NFTMinter
resource will be stored on the contract account's storage.
This means that only the contract account will have the ability to mint new NFTs.
To set this up, add the following line to the contract's init
function:
Setting Up an NFT Collection
Storing individual NFTs directly in an account's storage can cause issues, especially if you want to store multiple NFTs. Instead, it's required to create a collection that can hold multiple NFTs. This collection can then be stored in the account's storage.
Start by creating a new resource named Collection
.
This resource will act as a container for your NFTs, storing them in a dictionary indexed by their IDs.
Fitting the Flow NFT Standard
To ensure compatibility and interoperability within the Flow ecosystem, it's crucial that your NFT contract adheres to the Flow NFT standard. This standard defines the events, functions, resources, metadata and other elements that a contract should have. By following this standard, your NFTs will be compatible with various marketplaces, apps, and other services within the Flow ecosystem.
Applying the Standard
To start, you need to inform the Flow blockchain that your contract will implement the NonFungibleToken
standard.
Since it's a standard, there's no need for deployment.
It's already available on the Emulator, Testnet, and Mainnet for the community's benefit.
Begin by importing the token standard into your contract and adding the correct interface conformances to FooBar, NFT, and Collection:
As you can see, we also added standard paths for the Collection and Minter
These interface conformances for NFT and Collection inherit from other interfaces that provide important functionality and restrictions for your NFT and Collection types.
To allow accounts to create their own collections, add a function
in the main contract that creates a new Collection
and returns it.
This function takes a nftType: Type
argument that allows the caller
to specify which type of Collection
they want to create.
Contracts that implement multiple NFT
and/or Collection
types can use this argument,
but since your contract is only implementing one NFT
and Collection
type,
it can ignore the argument.
You'll also want to add a simpler one directly
to the NFT
and Collection
definitions
so users can directly create a collection from an existing collection:
To manage the NFTs within a collection, you'll need functions to deposit and withdraw NFTs. Here's how you can add a deposit
function:
Similarly, you can add a withdraw
function to remove an NFT from the collection:
As you can see, this function has an access(NonFungibleToken.Withdraw)
access modifier.
This is an example of entitlements in Cadence.
Entitlements
are a way for developers to restrict access to privileged fields and functions
in a composite type like a resource when a reference is created for it.
In this example, the withdraw()
function is always accessible to code that
controls the full Collection
object, but if a reference is created for it,
the withdraw()
function can only be called if the reference
is authorized by the owner with NonFungibleToken.Withdraw
,
which is a standard entitlement
defined by the NonFungibleToken
contract:
Entitlements are important to understand because they are what protects privileged functionality in your resource objects from being accessed by third-parties. It is recommended to read the entitlements documentation to understand how to use the feature properly.
References can be freely up-casted and down-casted in Cadence, so it is important for privileged functionality to be protected by an entitlement so that it can only be accessed if it is authorized.
Standard NFT Events
Many projects rely on events the signal when withdrawals or deposits happen.
Luckily, the NonFungibleToken
standard handles the definition and emission
of events for projects, so there is no need for you to add any events
to your implementation for withdraw and deposit.
Here are the FungibleToken
event definitions:
These events are emitted by the Collection
interface
in the NonFungibleToken
contract whenever the relevant function is called on any implementation.
There is also a NonFungibleToken.NFT.ResourceDestroyed
event that is emitted every time an NFT is destroyed:
ResourceDestroyed
events are standard events that can be added to any resource definition
to be emitted when the resource is destroyed. Learn more about them in the Cadence docs.
Additionally, check out the optional Burner
contract,
which is the standard that all projects should use for handling the destruction of any resource.
Lastly, there is a standard NonFungibleToken.Updated
event
that your contract can emit if the NFT is updated in any way.
This is optional though, so no need to include support for it in your implementation.
To facilitate querying, you'll also want a function to retrieve important information from the collection, like what types it supports and all the NFT IDs within a collection:
Supporting NFT Metadata
The Non-Fungible Token standard also enforces that implementations provide functionality to return a set of standard views about the tokens via the ViewResolver and MetadataViews definitions. (You will need to add these imports to your contract) These provide developers with standard ways of representing metadata about a given token such as token symbols, images, royalties, editions, website links, and standard account paths and types that third-parties can access in a standard way. You can see the metadata views documentation for a more thorough guide using a NFT contract as an example.
For now, you can add this code to your contract to support the important metadata:
If you ever plan on making your NFTs more complex, you should look into
adding views for Edition
, EVMBridgedMetadata
, Traits
, and Royalties
.
These views make it much easier for third-party sites like marketplaces
and NFT information aggregators to clearly display information
about your projects on their apps and websites and are critical
for every project to include if we want to have a vibrant and interoperable
ecosystem.
Deploying the Contract
With your contract ready, it's time to deploy it.
First, add the FooBar
contract to the flow.json
configuration file:
When prompted, enter the following name and location (press Enter
to skip alias questions):
Next, configure the deployment settings by running the following command:
Choose the emulator
for the network and emulator-account
for the account to deploy to.
Then, select the FooBar
contract (you may need to scroll down).
This will update your flow.json
configuration.
After that, you can select No
when asked to deploy another contract.
To start the Flow emulator, run (you may need to approve a prompt to allow connection the first time):
In a separate terminal or command prompt, deploy the contract:
You'll then see a message that says All contracts deployed successfully
.
Creating an NFTCollection
To manage multiple NFTs, you'll need an NFT collection.
Start by creating a transaction file for this purpose (we can use the generate
command again):
This creates a transaction file at cadence/transactions/setup_foobar_collection.cdc
.
Transactions, on the other hand, are pieces of Cadence code that can mutate the state of the blockchain. Transactions need to be signed by one or more accounts, and they can have multiple phases, represented by different blocks of code.
In this file, import the necessary contracts and define a transaction to create a new collection, storing it in the account's storage. Additionally, the transaction creates a capability that allows others to get a public reference to the collection to read from its methods.
This capability ensures secure, restricted access to specific functionalities or information within a resource.
There are also examples of generic transactions that you can use to setup an account for ANY non-fungible token using metadata views! You should check those out and try to use generic transactions whenever it is possible.
To store this new NFT collection, create a new account:
Name it test-acct
and select emulator
as the network. Then, using the Flow CLI, run the transaction:
Congratulations! You've successfully created an NFT collection for the test-acct
.
Get an Account's NFTs
To retrieve the NFTs associated with an account, you'll need a script. Scripts are read-only operations that allow you to query the blockchain. They don't modify the blockchain's state, and therefore, they don't require gas fees or signatures (read more about scripts here).
Start by creating a script file using the generate
command again:
In this script, import the necessary contracts and define a function that retrieves the NFT IDs associated with a given account:
To check the NFTs associated with the test-acct
, run the script (note: replace 0x123
with the address for test-acct
from flow.json
):
Since you haven't added any NFTs to the collection yet, the result will be an empty array.
Minting and Depositing an NFT to a Collection
To mint and deposit an NFT into a collection, create a new transaction file:
In this file, define a transaction that takes a recipient's address as an argument. This transaction will borrow the minting capability from the contract account, borrow the recipient's collection capability, create a new NFT using the minter, and deposit it into the recipient's collection:
To run this transaction, use the Flow CLI. Remember, the contract account
(which has the minting resource) should be the one signing the transaction.
Pass the test account's address (from the flow.json
file) as the recipient argument
(note: replace 0x123
with the address for test-acct
from flow.json
):
After executing the transaction, you can run the earlier script to verify
that the NFT was added to the test-acct
's collection (remember to replace 0x123
):
You should now see a value in the test-acct
's collection array!
Transferring an NFT to Another Account
To transfer an NFT to another account, create a new transaction file using generate
:
In this file, define a transaction that takes a recipient's address and the ID of the NFT you want to transfer as arguments. This transaction will borrow the sender's collection, get the recipient's capability, withdraw the NFT from the sender's collection, and deposit it into the recipient's collection:
To transfer the NFT, first create a new account:
Name it test-acct-2
and select Emulator
as the network. Next, create a collection for this new account:
Now, run the transaction to transfer the NFT from test-acct
to test-acct-2
using the addresses from the flow.json
file (replace 0x124
with test-acct-2
's address.
Also note that 0
is the id
of the NFT
we'll be transferring):
To verify the transfer, you can run the earlier script for test-acct-2
(replace 0x124
):
The transfer transaction also has a generic version that developers are encouraged to use!
Congrats, you did it! You're now ready to launch the next fun NFT project on Flow.
More
- Explore an example NFT repository
- Dive into the details of the NFT Standard
- Check out the
Burner
contract, which is the standard that all projects should use for handling the destruction of any resource. - For a deeper dive into
MetadataViews
, consult the introduction guide or the FLIP that introduced this feature. - Use a no code tool for creating NFT projects on Flow