1 Non-Fungible Token Tutorial Part 1
An introduction to NFTs on Cadence
In this tutorial, we're going to deploy, store, and transfer Non-Fungible Tokens (NFTs).
Open the starter code for this tutorial in the Flow Playground:
https://play.onflow.org/ae2f2a83-6698-4e03-93cf-70d35627e28eThe tutorial will ask you to take various actions to interact with this code.
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, and tickets to a really fun concert.
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 NFT Token Standard which defines a basic set of properties for NFTs on Flow. This tutorial, will teach you a basic method of creating an NFT to illustrate important language concepts. After completing the NFT tutorials, readers should visit 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:
- Deploy the NFT contract and type definitions.
- Create an NFT object and store it in your account storage.
- Create an NFT collection object to store multiple NFTs in your account.
- Create an
NFTMinter
and use it to mint an NFT. - Create references to your collection that others can use to send you tokens.
- Set up another account the same way.
- Transfer an NFT from one account to another.
- Use a script to see what NFTs are stored in each account's collection.
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. 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 proposal.
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 each users' account.
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:
- Create a smart contract with the NFT resource type.
- Declare an ID field, a metadata field and an
init()
function in the NFT resource - Create an
init()
function for the contract that saves an NFT to an account
This contract relies on the account storage API to save NFTs in the
AuthAccount
object.
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-70d35627e28eOpen Account 0x01
to see BasicNFT.cdc
.
BasicNFT.cdc
should contain the following code:
pub contract BasicNFT {
// Declare the NFT resource type
pub resource NFT {
// The unique ID that differentiates each NFT
pub let id: UInt64
// String mapping to hold metadata
pub var metadata: {String: String}
// Initialize both fields in the init function
init(initID: UInt64) {
self.id = initID
self.metadata = {}
}
}
// Function to create a new NFT
pub fun createNFT(id: UInt64): @NFT {
return <-create NFT(initID: id)
}
// Create a single new NFT and save it to account storage
init() {
self.account.save<@NFT>(<-create NFT(initID: 1), to: /storage/BasicNFTPath)
}
}
In the above contract, the NFT is a resource with an integer ID and a field for metadata.
Each NFT resource has 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 the next tutorial.
In the contract's init
function, we create a new NFT object and move it into the account storage.
// put it in storage
self.account.save<@NFT>(<-create NFT(initID: 1), to: /storage/BasicNFTPath)
Here we access the AuthAccount
object on the account the contract is deployed to and call its save
method,
specifying @NFT
as the type it is being saved as.
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.
Deploy NFTv1
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.
Open the NFT Exists
transaction, select account 0x01
as the only signer, and send the transaction.
NFT Exists
should look like this:
import BasicNFT from 0x01
// This transaction checks if an NFT exists in the storage of the given account
// by trying to borrow from it. If the borrow succeeds (returns a non-nil value), the token exists!
transaction {
prepare(acct: AuthAccount) {
if acct.borrow<&BasicNFT.NFT>(from: /storage/BasicNFTPath) != nil {
log("The token exists!")
} else {
log("No token found!")
}
}
}
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!
Open the Basic Transfer
transaction.
Basic Transfer
should look like this:
import BasicNFT from 0x01
/// Basic transaction for two accounts to authorize
/// to transfer an NFT
transaction {
prepare(signer1: AuthAccount, signer2: AuthAccount) {
// Fill in code here to load the NFT from signer1
// and save it into signer2's storage
}
}
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.
import BasicNFT from 0x01
/// Basic transaction for two accounts to authorize
/// to transfer an NFT
transaction {
prepare(signer1: AuthAccount, signer2: AuthAccount) {
// Load the NFT from signer1's account
let nft <- signer1.load<@BasicNFT.NFT>(from: /storage/BasicNFTPath)
?? panic("Could not load NFT")
// Save the NFT to signer2's account
signer2.save<@BasicNFT.NFT>(<-nft, to: /storage/BasicNFTPath)
}
}
Select both Account 0x01
and Account 0x02
as the signers.
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.