Tutorials
Create an NFT
Last Updated: 5th June 2023
This guide shows how to originate and interact with the FA2 NFT contract implementation. We use a pre-compiled FA2 NFT contract written in the LIGO smart contract language and a command line interface (CLI) to originate and interact with the NFT contracts either on the Flextesa sandbox or Tezos testnet.
Introduction
What is FA2 (TZIP-12)?
FA2 refers to a token standard (TZIP-12) on Tezos. FA2 proposes a unified token contract interface, supporting a wide range of token types. The FA2 provides a standard API to transfer tokens, check token balances, manage operators (addresses that are permitted to transfer tokens on behalf of the token owner), and manage token metadata.
What is a Non-Fungible Token (NFT)?
An NFT (non-fungible token) is a special type of cryptographic token that represents something unique; non-fungible tokens are thus not mutually interchangeable. NFTs can represent ownership over digital or physical assets like virtual collectibles or unique artwork.
For each non-fungible token, the FA2 assigns a unique token ID and associates it with the token owner's address. The FA2 API enables the inspection of token balances for the specific token ID and token owner address. For NFTs the balance can be either 0 (which means that the address does not own this particular token) or 1 (the address owns the token).
The FA2 contract also associates some metadata with each token. This tutorial supports token symbol and token name metadata attributes. However, the implementation can be easily extended to support custom metadata attributes such as an associated image or document URL and its crypto-hash.
Tutorial
Prerequisites
Node.js must be installed. The Node installation must also include
npm
(Node package manager).Docker must be installed. You need docker to run Flextesa sandbox. You might skip Docker installation if you plan to run this tutorial on the testnet (Mumbainet) only.
The CLI Tool
You will need to install tznft
CLI tool. After the installation, you can invoke various commands in the form of tznft <command> [options]
. tznft
provides the following commands:
- mint (contract origination) NFT with metadata command
- token inspection commands
- NFT transfer command
- configuration commands to bootstrap the Tezos network and configure address aliases
The commands will be explained in more detail below. You can always run
$ tznft --help
to list all available commands.
Initial Setup
Create a new local directory to keep your tutorial configuration:
$ mkdir nft-tutorial $ cd nft-tutorial
Install
@tqtezos/nft-tutorial
npm package:$ npm install -g https://github.com/tqtezos/nft-tutorial.git /usr/local/bin/tznft -> /usr/local/lib/node_modules/@tqtezos/nft-tutorial/lib/tznft.js + @tqtezos/nft-tutorial@1.0.0 added 3 packages from 1 contributor and updated 145 packages in 11.538s
The command installs
tznft
CLI tool.Initialize tutorial config:
$ tznft init-config tznft.json config file created
Check that the default active network is
sandbox
:$ tznft show-network active network: sandbox
Bootstrap Tezos network:
$ tznft bootstrap ebb03733415c6a8f6813a7b67905a448556e290335c5824ca567badc32757cf4 starting sandbox... sandbox started originating balance inspector contract... originated balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd
If you are bootstrapping a
sandbox
network for the first time, Docker will download the Flextesa Docker image as well.The default configuration comes with two account aliases
bob
andalice
that can be used for token minting and transferring.
Mint NFT Token(s)
This tutorial uses an NFT collection contract. Each time the user mints a new set (collection) of tokens, a new NFT contract is created. The user cannot add more tokens or remove (burn) existing tokens within the contract. However, tokens can be transferred to other owners.
mint
command requires the following parameters:
- <owner> alias or address of the new token's owner
--tokens
new tokens metadata. Each token metadata is represented as comma delimited string:'<token_id>, <token_symbol>, <token_name>'
:
$ tznft mint <owner_alias> --tokens <token_meta_list>`
Example:
$ tznft mint bob --tokens '0, T1, My Token One' '1, T2, My Token Two' originating new NFT contract... originated NFT collection KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh
Inspecting The NFT Contract
Using KT1...
address of the NFT contract created by the mint
command, we can inspect token metadata and balances (i. e. which addresses own the tokens).
Inspect Token Metadata
show-meta
command requires the following parameters:
--nft
address of the FA2 NFT contract to inspect--signer
alias on behalf of which contract is inspected--tokens
a list of token IDs to inspect
$ tznft show-meta --nft <nft_address> --signer <alias> --tokens <token_id_list>
Example:
$ tznft show-meta --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --tokens 0 1 token_id: 0 symbol: T1 name: My Token One extras: { } token_id: 1 symbol: T2 name: My Token Two extras: { }
Inspect Token Balances
show-balance
command requires the following parameters:
--nft
address of the FA2 NFT contract to inspect--signer
alias on behalf of which contract is inspected--owner
alias of the token owner to check balances--tokens
a list of token IDs to inspect
$ tznft show-balance --nft <nft_address> --signer <alias> --owner <alias> --tokens <token_id_list>
Example 1, check bob
's balances:
$ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner bob --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 0 balance: 1 owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 1 balance: 1
Example 2, check alice
balances:
$ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner alice --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 0 balance: 0 owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 1 balance: 0
Tokens With External Metadata
Token metadata can store a reference to some external document and/or image. This tutorial supports storing external data on IPFS and keeping an IPFS hash as a part of the token metadata.
Let's create a single NFT token that references an image on IPFS.
Upload your image to IPFS and obtain an image file hash. There are multiple ways to do that. One of the possible solutions is to install the IPFS Companion web plugin and upload an image file from there. You can upload multiple images and/or documents if you plan to create a collection of multiple NFTs.
Copy the IPFS file hash code (
CID
). For this example, we will useQmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
Execute
tznft mint
command adding IPFS hash as a fourth parameter in the token description.
$ tznft mint bob -t '0, TZT, Tezos Token, QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj' originating new NFT contract... originated NFT collection KT1SgzbcfTtdHRV8qHNG3hd3w1x23oiC31B8
- Now we can inspect new token metadata and see that the IPFS hash (
ipfs_cid
) is there.
$ tznft show-meta -s bob --nft KT1SgzbcfTtdHRV8qHNG3hd3w1x23oiC31B8 --tokens 0 token_id: 0 symbol: TZT name: Tezos Token extras: { ipfs_cid=QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj }
- You can inspect the file on the web by opening a URL
https://ipfs.io/ipfs/<ipfs_cid>
. For our example, the URL would be https://ipfs.io/ipfs/QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
Transferring Tokens
transfer
command requires the following parameters:
--nft
address of the FA2 NFT contract that holds tokens to be transferred--signer
alias or address that initiates the transfer operation--batch
a list of individual transfers. Each individual transfer is represented as a comma-delimited string:<from_address_or_alias>, <to_address_or_alias>, <token_id>
. We do not need to specify the amount of the transfer for NFTs since we can only transfer a single token for any NFT type.
$ tznft transfer --nft <nft_address> --signer <signer> --batch <batch_list>`
Example, bob
transfers his own tokens 0
and 1
to alice
:
$ tznft transfer --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --batch 'bob, alice, 0' 'bob, alice, 1' transferring tokens... tokens transferred
Now, we can check token balances after the transfer:
$ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner bob --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 0 balance: 0 owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 1 balance: 0 $ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner alice --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 0 balance: 1 owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 1 balance: 1
Operator Transfer
It is also possible to transfer tokens on behalf of the owner.
bob
is trying to transfer one of alice
's tokens back:
$ tznft transfer --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --batch 'alice, bob, 1' transferring tokens... Tezos operation error: FA2_NOT_OPERATOR
As we can see, this operation has failed. The default behavior of the FA2 token contract is to allow only token owners to transfer their tokens. In our example, Bob (as an operator) tries to transfer token 1
that belongs to alice
.
However, alice
can add bob
as an operator to allow him to transfer any tokens on behalf of alice
.
update-ops
command has the following parameters:
<owner>
alias or address of the token owner to update operators for--nft
address of the FA2 NFT contract--add
list of pairs aliases or addresses and token id to add to the operator set--remove
list of aliases or addresses and token id to remove from the operator set
$ tznft update-ops <owner> --nft <nft_address> --add [add_operators_list] --remove [add_operators_list]
Example, alice
adds bob
as an operator:
$ tznft update-ops alice --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --add 'bob, 1' updating operators... updated operators
Now bob
can transfer a token on behalf of alice
again:
$ tznft transfer --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --batch 'alice, bob, 1' transferring tokens... tokens transferred
Inspecting balances after the transfer:
$ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner bob --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 0 balance: 0 owner: tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU token: 1 balance: 1 $ tznft show-balance --nft KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh --signer bob --owner alice --tokens 0 1 querying NFT contract KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh using balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfd requested NFT balances: owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 0 balance: 1 owner: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb token: 1 balance: 0
Token 1
now belongs to bob
.
Configuration
tznft
can be configured to interact with different Tezos networks. The user can also configure address aliases to sign Tezos operations and/or use them as command parameters when addresses are required. The default configuration that is created by tznft init-config
command includes two pre-configured networks: sandbox
and testnet
(Carthagenet). Each pre-configured network has two bootstrap aliases: bob
and alice
.
Network Configuration Commands
set-network <network>
selects a specified pre-configured network as an active one. All subsequent commands will operate on the active networkExample:
$ tznft set-network sandbox network sandbox is selected
show-network [--all]
show currently selected network. If--all
flag is specified, show all pre-configured networksExample:
$ tznft show-network --all * sandbox testnet
bootstrap
bootstrap selected network and deploy helper balance inspector contract. If the selected network issandbox
, this command needs to be run each time the sandbox is restarted, for other public networks liketestnet
it is enough to run this command once.Example:
$ tznft bootstrap 366b9f3ead158a086e8c397d542b2a2f81111a119f3bd6ddbf36574b325f1f03 starting sandbox... sandbox started originating balance inspector contract... originated balance inspector KT1WDqPuRFMm2HwDRBotGmnWdkWm1WyG4TYE
kill-sandbox
stop Flextesa sandbox process if the selected network issandbox
. This command has no effect on other network types.Example:
$ tznft kill-sandbox flextesa-sandbox killed sandbox.
The sandbox network (selected by default) is configured to bake new Tezos blocks every 5 seconds. It makes running the commands that interact with the network faster. However, all originated contracts will be lost after the sandbox is stopped.
If you are using testnet
, your originated contracts will remain on the blockchain and you can inspect them afterwards using a block explorer like BCD.
Note: Although testnet
configuration already has two bootstrap aliases bob
and alice
, it is a good practice to create your own alias from the faucet file (see tznft add-alias-faucet
command described below) and use it as a signer for the commands like mint
, transfer
and show_balance
. In this way, your Tezos operations will not interfere with the operations initiated by other users.
Alias Configuration Commands
tznft
allows user to configure and use short names (aliases) instead of typing in full Tezos addresses when invoking tznft
commands. Each network comes with two pre-configured aliases bob
and alice
. The user can manage aliases by directly editing tznft.json
file or using the following commands:
show-alias [alias]
show address and private key (if configured) of the specified[alias]
. If[alias]
option is not specified, show all configured aliases.Example:
$ tznft show-alias bob bob tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU edsk3RFgDiCt7tWB2oe96w1eRw72iYiiqZPLu9nnEY23MYRp2d8Kkx $ tznft show-alias bob tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU edsk3RFgDiCt7tWB2oe96w1eRw72iYiiqZPLu9nnEY23MYRp2d8Kkx alice tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq
add-alias <alias> <private_key>
add alias using its private key. Aliases that are configured with the private key can be used to sign operations that originate or call smart contracts on-chain.tznft
commands that require Tezos operation signing have--signer
option.Example:
$ tznft add-alias jane edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq alias jane has been added $ tznft show-alias jane jane tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq
add-alias <alias> <address>
add alias using Tezos address (public key hash). Such aliases do not have an associated private key and cannot be used to sign Tezos operations.Example:
$ tznft add-alias michael tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb alias michael has been added $ tznft show-alias michael michael tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb
add-alias-faucet <alias> <faucet_json_file_path>
add alias with private key from the faucet file (see Tezos Faucet). This command will not work onsandbox
network. An alias configured from the faucet has the private key and can be used to sign Tezos operations.Example:
$ tznft add-alias-faucet john ~/Downloads/tz1NfTBQM9QpZpEY6GSvdw3XBpyEjLLGhcEU.json activating faucet account... faucet account activated alias john has been added $ tznft show-alias john john tz1NfTBQM9QpZpEY6GSvdw3XBpyEjLLGhcEU edskRzaCrGEDr1Ras1U55U73dXoLfQQJyuwE95rSkqbydxUS4oS3fGmWywbaVcYw7DLH34zedoJzwMQxzAXQdixi5QzYC5pGJ6
remove-alias <alias>
remove the alias from the selected network configuration.Example:
$ tznft remove-alias john alias john has been deleted
This guide was created by Oxhead Alpha and can be found here.