5.1 Non-Fungible Token Tutorial Part 1
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/a21087ad-b22c-4981-b49e-17297e916fa6
The tutorial will ask you to take various actions to interact with this code.
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.
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:
- Deploy a basic 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 capabilities 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.
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_11access(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:
- Create a smart contract with the NFT resource type.
- Declare an ID field, a metadata field and an initializer in the NFT resource.
- 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.
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
Open Account 0x01
to see BasicNFT.cdc
.
BasicNFT.cdc
should contain the following code:
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 next tutorial.
Initializers​
_10init() {_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_10self.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.
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.
Open the NFT Exists
transaction, select account 0x01
as the only signer, and send the transaction.
NFT Exists
should look like this:
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:
_16import BasicNFT from 0x01_16_16/// Basic transaction for two accounts to authorize_16/// to transfer an NFT_16_16transaction {_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.
_20import BasicNFT from 0x01_20_20/// Basic transaction for two accounts to authorize_20/// to transfer an NFT_20_20transaction {_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}
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.