Skip to main content
Version: 1.0

5.1 Non-Fungible Token Tutorial Part 1

In this tutorial, we're going to deploy, store, and transfer Non-Fungible Tokens (NFTs).


tip

Open the starter code for this tutorial in the Flow Playground:

https://play.onflow.org/a21087ad-b22c-4981-b49e-17297e916fa6

The tutorial will ask you to take various actions to interact with this code.

info

The playground code that is linked uses Cadence 0.42, but the examples use Cadence 1.0 to show how each contract, transaction and script is implemented in Cadence 1.0. You can access a Cadence 1.0-compatible playground by going to https://v1.play.flow.com/. The project link will still work with the current version of the playground, but when the playground is updated to Cadence 1.0, the link will be replaced with a 1.0-compatible version.

info

Instructions that require you to take action are always included in a callout box like this one. These highlighted actions are all that you need to do to get your code running, but reading the rest is necessary to understand the language's design.

The NFT is an integral part of blockchain technology. An NFT is a digital asset that represents ownership of a unique asset. NFTs are also indivisible, you can't trade part of an NFT. Possible examples of NFTs include: CryptoKitties, Top Shot Moments, tickets to a really fun concert, or a horse.

Instead of being represented in a central ledger, like in most smart contract languages, Cadence represents each NFT as a resource object that users store in their accounts.

This allows NFTs to benefit from the resource ownership rules that are enforced by the type system - resources can only have a single owner, they cannot be duplicated, and they cannot be lost due to accidental or malicious programming errors. These protections ensure that owners know that their NFT is safe and can represent an asset that has real value.

NFTs in a real-world context make it possible to trade assets and prove who the owner of an asset is. On Flow, NFTs are interoperable - so the NFTs in an account can be used in different smart contracts and app contexts. All NFTs on Flow implement the Flow NFT Standard which defines a basic set of properties for NFTs on Flow. This tutorial, will teach you a basic method of creating NFTs to illustrate important language concepts, but will not use the NFT Standard for the sake of simplicity and learning.

After completing the NFT tutorials, readers should visit the NFT Guide and the NFT standard github repository to learn how full, production-ready NFTs are created.

To get you comfortable using NFTs, this tutorial will teach you to:

  1. Deploy a basic NFT contract and type definitions.
  2. Create an NFT object and store it in your account storage.
  3. Create an NFT collection object to store multiple NFTs in your account.
  4. Create an NFTMinter and use it to mint an NFT.
  5. Create capabilities to your collection that others can use to send you tokens.
  6. Set up another account the same way.
  7. Transfer an NFT from one account to another.
  8. Use a script to see what NFTs are stored in each account's collection.
warning

It is important to remember that while this tutorial implements a working non-fungible token, it has been simplified for educational purposes and is not what any project should use in production. See the Flow Fungible Token standard for the standard interface and example implementation.

Before proceeding with this tutorial, we highly recommend following the instructions in Getting Started, Hello, World!, Resources, and Capabilities to learn how to use the Playground tools and to learn the fundamentals of Cadence. This tutorial will build on the concepts introduced in those tutorials.

Non-Fungible Tokens on the Flow Emulator


In Cadence, each NFT is represented by a resource with an integer ID:


_11
// The most basic representation of an NFT
_11
access(all) resource NFT {
_11
// The unique ID that differentiates each NFT
_11
access(all)
_11
let id: UInt64
_11
_11
// Initialize both fields in the initializer
_11
init(initID: UInt64) {
_11
self.id = initID
_11
}
_11
}

Resources are a perfect type to represent NFTs because resources have important ownership rules that are enforced by the type system. They can only have one owner, cannot be copied, and cannot be accidentally or maliciously lost or duplicated. These protections ensure that owners know that their NFT is safe and can represent an asset that has real value. For more information about resources, see the resources tutorial

An NFT is also usually represented by some sort of metadata like a name or a picture. Historically, most of this metadata has been stored off-chain, and the on-chain token only contains a URL or something similar that points to the off-chain metadata. In Flow, this is possible, but the goal is to make it possible for all the metadata associated with a token to be stored on-chain. This is out of the scope of this tutorial though. This paradigm has been defined by the Flow community and the details are contained in the NFT metadata guide.

When users on Flow want to transact with each other, they can do so peer-to-peer and without having to interact with a central NFT contract by calling resource-defined methods in both users' accounts.

Adding an NFT Your Account

We'll start by looking at a basic NFT contract that adds an NFT to an account. The contract will:

  1. Create a smart contract with the NFT resource type.
  2. Declare an ID field, a metadata field and an initializer in the NFT resource.
  3. Create an initializer for the contract that saves an NFT to an account.

This contract relies on the account storage API to save NFTs in the account.

info

First, you'll need to follow this link to open a playground session with the Non-Fungible Token contracts, transactions, and scripts pre-loaded:

https://play.onflow.org/ae2f2a83-6698-4e03-93cf-70d35627e28e

info

Open Account 0x01 to see BasicNFT.cdc. BasicNFT.cdc should contain the following code:

BasicNFT.cdc

_27
access(all) contract BasicNFT {
_27
_27
// Declare the NFT resource type
_27
access(all) resource NFT {
_27
// The unique ID that differentiates each NFT
_27
access(all) let id: UInt64
_27
_27
// String mapping to hold metadata
_27
access(all) var metadata: {String: String}
_27
_27
// Initialize both fields in the initializer
_27
init(initID: UInt64) {
_27
self.id = initID
_27
self.metadata = {}
_27
}
_27
}
_27
_27
// Function to create a new NFT
_27
access(all) fun createNFT(id: UInt64): @NFT {
_27
return <-create NFT(initID: id)
_27
}
_27
_27
// Create a single new NFT and save it to account storage
_27
init() {
_27
self.account.storage.save(<-create NFT(initID: 1), to: /storage/BasicNFTPath)
_27
}
_27
}

In the above contract, the NFT is a resource with an integer ID and a field for metadata.

Each NFT resource should have a unique ID, so they cannot be combined or duplicated unless the smart contract allows it.

Another unique feature of this design is that each NFT can contain its own metadata. In this example, we use a simple String-to-String mapping, but you could imagine a much more rich version that can allow the storage of complex file formats and other such data.

An NFT could even own other NFTs! This functionality is shown in a later tutorial.

Initializers


_10
init() {
_10
// ...

All composite types like contracts, resources, and structs can have an optional initializer that only runs when the object is initially created. Cadence requires that all fields in a composite type must be explicitly initialized, so if the object has any fields, this function has to be used to initialize them.

Contracts also have read and write access to the storage of the account that they are deployed to by using the built-in self.account field. This is an account reference (&Account), authorized and entitled to access and manage all aspects of the account, such as account storage, capabilities, keys, and contracts.

In the contract's initializer, we create a new NFT object and move it into the account storage.


_10
// put it in storage
_10
self.account.storage.save(<-create NFT(initID: 1), to: /storage/BasicNFTPath)

Here we access the storage object of the account that the contract is deployed to and call its save method. We also create the NFT in the same line and pass it as the first argument to save. We save it to the /storage/ domain, where objects are meant to be stored.

info

Deploy BasicNFT by clicking the Deploy button in the top right of the editor.

You should now have an NFT in your account. Let's run a transaction to check.

info

Open the NFT Exists transaction, select account 0x01 as the only signer, and send the transaction.
NFT Exists should look like this:

NFTExists.cdc

_13
import BasicNFT from 0x01
_13
_13
// This transaction checks if an NFT exists in the storage of the given account
_13
// by trying to borrow from it. If the borrow succeeds (returns a non-nil value), the token exists!
_13
transaction {
_13
prepare(acct: auth(BorrowValue) &Account) {
_13
if acct.storage.borrow<&BasicNFT.NFT>(from: /storage/BasicNFTPath) != nil {
_13
log("The token exists!")
_13
} else {
_13
log("No token found!")
_13
}
_13
}
_13
}

Here, we are trying to directly borrow a reference from the NFT in storage. If the object exists, the borrow will succeed and the reference optional will not be nil, but if the borrow fails, the optional will be nil.

You should see something that says "The token exists!".

Great work! You have your first NFT in your account. Let's move it to another account!

Performing a Basic Transfer

With these powerful assets in your account, you'll probably want to move them around to other accounts. There are many ways to transfer objects in Cadence, but we'll show the simplest one first.

This will also be an opportunity for you to try to write some of your own code!

info

Open the Basic Transfer transaction.
Basic Transfer should look like this:


_16
import BasicNFT from 0x01
_16
_16
/// Basic transaction for two accounts to authorize
_16
/// to transfer an NFT
_16
_16
transaction {
_16
prepare(
_16
signer1: auth(LoadValue) &Account,
_16
signer2: auth(SaveValue) &Account
_16
) {
_16
_16
// Fill in code here to load the NFT from signer1
_16
// and save it into signer2's storage
_16
_16
}
_16
}

We've provided you with a blank transaction with two signers.

While a transaction is open, you can select one or more accounts to sign a transaction. This is because, in Flow, multiple accounts can sign the same transaction, giving access to their private storage. If multiple accounts are selected as signers, this needs to be reflected in the signature of the transaction to show multiple signers, as is shown in the "Basic Transfer" transaction.

All you need to do is load() the NFT from signer1's storage and save() it into signer2's storage. You have used both of these operations before, so this hopefully shouldn't be too hard to figure out. Feel free to go back to earlier tutorials to see examples of these account methods.

You can also scroll down a bit to see the correct code:










Here is the correct code to load the NFT from one account and save it to another account.


_20
import BasicNFT from 0x01
_20
_20
/// Basic transaction for two accounts to authorize
_20
/// to transfer an NFT
_20
_20
transaction {
_20
prepare(
_20
signer1: auth(LoadValue) &Account,
_20
signer2: auth(SaveValue) &Account
_20
) {
_20
_20
// Load the NFT from signer1's account
_20
let nft <- signer1.storage.load<@BasicNFT.NFT>(from: /storage/BasicNFTPath)
_20
?? panic("Could not load NFT from the first signer's storage")
_20
_20
// Save the NFT to signer2's account
_20
signer2.storage.save(<-nft, to: /storage/BasicNFTPath)
_20
_20
}
_20
}

info

Select both Account 0x01 and Account 0x02 as the signers. Make sure account 0x01 is the first signer.
Click the "Send" button to send the transaction.

Now, the NFT should be stored in the storage of Account 0x02! You should be able to run the "NFT Exists" transaction again with 0x02 as the signer to confirm that it is in their account.

Enhancing the NFT Experience

Hopefully by now, you have an idea of how NFTs can be represented by resources in Cadence. You might have noticed by now that if we required users to remember different paths for each NFT and to use a multisig transaction for transfers, we would not have a very friendly developer and user experience.

This is where the true utility of Cadence is shown. Continue on to the next tutorial to find out how we can use capabilities and resources owning other resources to enhance the ease of use and safety of our NFTs.