Skip to main content

FA 2.0 - Tezos NFT Standard

Similarly to ERC-1155 standard, FA 2.0 can hold multiple assets, while it is mostly used as a ledger for Non-Fungible tokens.

The standards was initially proposed in TZIP-12 where TZIP stands for "Tezos Improvement Process".

The standard describes the major features of the contract the community members should respect to seamlessly view, display, transfer, create and destroy the tokens.

The requirements were conveniently summarized by a Tezos team member Adam Shindler in his Medieum blog "Tezos Token Standards Simply Explained". The table below is a slightly modified version of Adam's ideas.


The main contract requirements

FunctionPresenceDescription
TokenNameoptionalCollection name
SymboloptionalToken ticker
DecimaloptionalDenomination in 0-18 decimals
TransferobligatoryEnables token transfers between users and contracts
OperatorsobligatorySets multiple wallets for multiple assets that third party wallets can manage on behalp of the Owner
MetadataobligatoryThe pointer to the asset metadata JSON, regulated by the TZIP-016
BalanceOfobligatoryReturns the user's balance, returns multiple token pairs, if specified
TotalSupplyobligatoryReturns the total number of tokens in the collection
AllTokensobligatoryReturns the collection token IDs

FA2 Storage Structure

Variable nameVariable TypeDescription
administratorADDRESS\color{blue}ADDRESSadmin/owner of the contract
all_tokensNAT\color{blue}NATtotal number of tokens
ledgerBIGMAP\color{blue}BIGMAPdictionary of the asset owners
metadataBIGMAP\color{blue}BIGMAPcontract's metadata
operatorsBIGMAP\color{blue}BIGMAPdictionary of the operators
pausedBOOLEAN\color{blue}BOOLEANa flag whether the contract is paused
token_metaBIGMAP\color{blue}BIGMAPmetadata of individual tokens

Smart contract in SmartPy

The tutorial suggested by the standard suggests a contract in LOGO. We're suggesting our implementation of the contract in SmartPy, another high level language that compiles to Michelson, the assembly language developed for Tezos blockchain smart contracts.

The contract below inherits from the contract implementation in SmartPy from the official languege site and implements additionally the minting & burning functionaluty required for the bridge.


The contract below is used as a default recipient of the NFTs on the Tezos side in case only one or few NFTs are transferred. For migrating an entire collection it is a better idea to deploy a new smart contract bearing the collection name and ticker and additional business logic relevant to the project.

import os
import dotenv
import smartpy as sp

# The original token standard implemented in SmartPy
FA2 = sp.io.import_script_from_url('https://smartpy.io/templates/FA2.py')


class XPNFT(FA2.FA2):

# MINTING
@sp.entry_point
def mint(self, params):
sp.set_type(
params,
sp.TRecord(
token_id=sp.TNat,
address=sp.TAddress,
metadata=sp.TMap(
sp.TString, sp.TBytes
),
amount=sp.TNat
)
)

sp.verify(self.is_administrator(sp.sender),
message=self.error_message.not_admin())

# If Fungible tokens
if self.config.single_asset:
sp.verify(params.token_id == 0,
message="single-asset: token-id <> 0")
# If non-fungible tokens
if self.config.non_fungible:
sp.verify(params.amount == 1, message="NFT-asset: amount <> 1")
sp.verify(
~ self.token_id_set.contains(
self.data.all_tokens, params.token_id),
message="NFT-asset: cannot mint the same token twice"
)
user = self.ledger_key.make(params.address, params.token_id)
with sp.if_(self.data.ledger.contains(user)):
self.data.ledger[user].balance += params.amount
with sp.else_():
self.data.ledger[user] = FA2.Ledger_value.make(params.amount)
with sp.if_(~ self.token_id_set.contains(self.data.all_tokens, params.token_id)):
self.token_id_set.add(self.data.all_tokens, params.token_id)
self.data.token_metadata[params.token_id] = sp.record(
token_id=params.token_id,
token_info=params.metadata
)
if self.config.store_total_supply:
self.data.total_supply[params.token_id] = params.amount + \
self.data.total_supply.get(params.token_id, default_value=0)


# BURNING
@sp.entry_point
def burn(self, params):
sp.set_type(params, sp.TRecord(token_id=sp.TNat, address=sp.TAddress, amount=sp.TNat))
sp.verify(self.is_administrator(sp.sender), message = self.error_message.not_admin())
# We don't check for pauseness because we're the admin.
if self.config.single_asset:
sp.verify(params.token_id == 0, message = "single-asset: token-id <> 0")
if self.config.non_fungible:
sp.verify(params.amount == 1, message = "NFT-asset: amount <> 1")
# sp.verify(
# ~ self.token_id_set.contains(self.data.all_tokens, params.token_id),
# message = "NFT-asset: cannot mint twice same token"
# )
user = self.ledger_key.make(params.address, params.token_id)
with sp.if_(self.data.ledger.contains(user)):
self.data.ledger[user].balance = sp.as_nat(self.data.ledger[user].balance - params.amount)
with sp.else_():
self.data.ledger[user] = FA2.Ledger_value.make(params.amount)

sp.verify(self.token_id_set.contains(self.data.all_tokens, params.token_id), "token-id doesn't exists.")

if self.config.store_total_supply:
self.data.total_supply[params.token_id] = sp.as_nat(self.data.total_supply.get(params.token_id, default_value = 0) - params.amount)

Interacting with the contract on Tezos

Our JavaScript library has a helper for interactions with the Bradge smart contract on Tezos. It is available in our GitHub.

Deploying a target contract on TEZOS

1. Install Smartpy-cli

bash <(curl -s https://smartpy.io/cli/install.sh)
~/smartpy-cli/SmartPy.sh --version

Example output when the library is installed:

SmartPy Version: 0.9.1

2. Create Python envorinment

python3 -m venv venv
source venv/bin/activate
touch requirements.txt

Populte the requirements.txt file with the library names:

autopep8
python-dotenv
git+https://github.com/xp-network/smartpy.git
pip install -r requirements.txt

3. Create Unit Tests & Compile Smart Contracts

mkdir compilation/
~/smartpy-cli/SmartPy.sh test contracts/xpnft.py compilation/
~/smartpy-cli/SmartPy.sh compile contracts/xpnft.py compilation/

4. Deploy Tezos SCs (XPNFT - Example code above)

~/smartpy-cli/SmartPy.sh originate-contract --code compilation/xpnft_comp/step_000_cont_0_contract.tz --storage compilation/xpnft_comp/step_000_cont_0_storage.tz --rpc https://hangzhounet.smartpy.io --private-key <Replace with your private key>

Example output:

[INFO] - Contract <the address of your smart contract> originated!!!