Non-Fungible Tokens on Tezos Using FA2 · Digital Assets on Tezos
This tutorial shows how to originate and interact with the FA2 NFT contract implementation. The tutorial uses a pre-compiled FA2 NFT contract written in 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 (Carthagenet).
Disclaimer: We highly recommend users read the additional resources above and take necessary precautions before following this tutorial and interacting with experimental technology. Use this tutorial at your own risk.
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.
An NFT (non-fungible token) is a special type of cryptographic token which 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 individual non-fungible token, the FA2 assigns a unique token ID and associates it with the token owner 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.
You will need to install
tznft
CLI tool. After the installation, you can invoke various commands in the form of tznft [options]
. tznft
provides the following commands:- mint (contract origination) NFT with metadata command
- token inspection commands
- NFT transfer command
- Configuration commands to bootstrap 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.
- 1.Create a new local directory to keep your tutorial configuration:$ mkdir nft-tutorial$ cd nft-tutorial
- 2.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/[email protected]added 3 packages from 1 contributor and updated 145 packages in 11.538sThe command installstznft
CLI tool. - 3.Initialize tutorial config:$ tznft init-configtznft.json config file created
- 4.Check that the default active network is
sandbox
:$ tznft show-networkactive network: sandbox - 5.Bootstrap Tezos network:$ tznft bootstrapebb03733415c6a8f6813a7b67905a448556e290335c5824ca567badc32757cf4starting sandbox...sandbox startedoriginating balance inspector contract...originated balance inspector KT1Pezr7JjgmrPcPhpkbkH1ytG7saMZ34sfdIf 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 aliasesbob
andalice
that can be used for token minting and transferring.
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:- alias or address of the new tokens owner
--tokens
new tokens metadata. Each token metadata is represented as comma delimited string:',,'
:
$ tznft mint --tokens `
Example:
$ tznft mint bob --tokens '0, T1, My Token One' '1, T2, My Token Two'
originating new NFT contract...
originated NFT collection KT1XP3RE6S9t44fKR9Uo5rAfqHvHXu9Cy7fh
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).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 --signer <alias> --tokens
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: { }
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 --signer <alias> --owner <alias> --tokens
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
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 which references an image on IPFS.
- 1.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.
- 2.Copy the IPFS file hash code (
CID
). For this example we will useQmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
- 3.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
- 1.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 }
- 1.You can inspect the file on the web by opening a URL
https://ipfs.io/ipfs/
. For our example, the URL would be https://ipfs.io/ipfs/QmRyTc9KbD7ZSkmEf4e7fk6A44RPciW5pM4iyqRGrhbyvj
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:,,
. We do not need to specify amount of the transfer for NFTs since we can only transfer a single token for any NFT type.
$ tznft transfer --nft --signer --batch `
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
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 transfer any tokens on behalf of alice
.update-ops
command has the following parameters:- 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 --nft --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
.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
.set-network
select specified pre-configured network as an active one. All subsequent commands will operate on the active networkExample:$ tznft set-network sandboxnetwork sandbox is selectedshow-network [--all]
show currently selected network. If--all
flag is specified, show all pre-configured networksExample:$ tznft show-network --all* sandboxtestnetbootstrap
bootstrap selected network and deploy helper balance inspector contract. If selected network issandbox
this command needs to be run each time sandbox is restarted, for other public networks liketestnet
is it enough to run this command once.Example:$ tznft bootstrap366b9f3ead158a086e8c397d542b2a2f81111a119f3bd6ddbf36574b325f1f03starting sandbox...sandbox startedoriginating balance inspector contract...originated balance inspector KT1WDqPuRFMm2HwDRBotGmnWdkWm1WyG4TYEkill-sandbox
stop Flextesa sandbox process if selected network issandbox
. This command has no effect on other network types.Example:$ tznft kill-sandboxflextesa-sandboxkilled 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 an 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.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 bobbob tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU edsk3RFgDiCt7tWB2oe96w1eRw72iYiiqZPLu9nnEY23MYRp2d8Kkx$ tznft show-aliasbob tz1YPSCGWXwBdTncK2aCctSZAXWvGsGwVJqU edsk3RFgDiCt7tWB2oe96w1eRw72iYiiqZPLu9nnEY23MYRp2d8Kkxalice tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbqadd-alias
add alias using its private key. Aliases that 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 edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbqalias jane has been added$ tznft show-alias janejane tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbqadd-alias
add alias using Tezos address (public key hash). Such aliases do not have associated private key and cannot be used to sign Tezos operations.Example:$ tznft add-alias michael tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjbalias michael has been added$ tznft show-alias michaelmichael tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjbadd-alias-faucet
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.jsonactivating faucet account...faucet account activatedalias john has been added$ tznft show-alias johnjohn tz1NfTBQM9QpZpEY6GSvdw3XBpyEjLLGhcEU edskRzaCrGEDr1Ras1U55U73dXoLfQQJyuwE95rSkqbydxUS4oS3fGmWywbaVcYw7DLH34zedoJzwMQxzAXQdixi5QzYC5pGJ6remove-alias
remove alias from the selected network configuration.Example:$ tznft remove-alias johnalias john has been deleted