Skip to content
On this page

FA2 - legacy template โ€‹


The template will become obsolete in favor of the new, introduced on March 4, 2022.

Template: view, download.
This is the only authoritative source and must be read in order to dive into details.
Example: FA2 contract is a sample boilerplate.
TZIP specifications: TZIP-12 and TZIP-16.

FA2/TZIP-12 is a standard for a unified token contract interface, supporting a wide range of token types and implementations.

A token contract can be designed to support a single token type (e.g. ERC-20 or ERC-721) or multiple token types (e.g. ERC-1155), to optimize batch transfers and atomic swaps of the tokens.

Tokens can be fungible tokens or non-fungible tokens (NFTs).

SmartPy provides a FA2 template that can be configured and adapted with custom logic to support a very wide range of your needs.

Simple FA2 contract โ€‹

Import โ€‹

To create a FA2 contract you need to import SmartPy and the FA2 template.

import smartpy as sp
FA2 ="")

You can then create your FA2 contract by extending the template FA2.FA2 class.

class ExampleFA2(FA2.FA2):

At this stage you have a complete FA2 contract with a lot of entry points and helpers. We'll see how to choose more precisely what functionalities to keep, how to modify them and how to modify the storage later on. For now, let's compile it.

Compilation target โ€‹

In order to instantiate the FA2 you need to give three parameters:

  • admin: the admin address
  • config: a dictionary that lets you tweak the contract
  • metadata: the TZIP-16 metadata of the contract.

The config can be built by calling FA2.FA2_config(). See config.

The metadata is explained in detail in metadata.

        admin   = sp.address("tz1M9CMEtsXm3QxA7FmMU2Qh7xzsuGXVbcDr"),
        config  = FA2.FA2_config(),
        metadata = sp.utils.metadata_of_url("")

Test โ€‹

You can now write a very basic test with your token contract.

@sp.add_test(name="FA2 tokens")
def test():
    sc = sp.test_scenario()
    FA2_admin = sp.test_account("FA2_admin")
    exampleToken = ExampleFA2(
        admin = FA2_admin.address,
        metadata = sp.utils.metadata_of_url("")
    sc += exampleToken

FA2 customisation โ€‹

entrypoints and mixins โ€‹

The FA2 template is divided into small classes that you can inherit separately if you don't need all the features.

In this example we don't implement the mint entrypoint.

class ExampleFA2(
        # FA2_mint,

Here are all the available mixins:

FA2_coreImplements the strict standard.
FA2_administratoris_administrator method and set_administrator entrypoint.
FA2_change_metadataset_metadata entrypoint.
FA2_mintmint entrypoint.
FA2_pauseis_paused method and set_pause entrypoint.
FA2_token_metadataset_token_metadata_view and make_metadata _methods.

The FA2.FA2 class is implemented by inheriting from all the mixins.

Init and storage โ€‹

You can reimplement the __init__ method and call the one of FA2_core.

class ExampleFA2(FA2.FA2_core):
    def __init__(self, config, admin):
        FA2.FA2_core.__init__(self, config,
                            paused = False,
                            administrator = admin)

This __init__ method takes additional storage fields. They'll be added in the contract storage.

FA2.FA2_core.__init__(self, config, paused = False, administrator = admin,
                    my_custom_bigmap = sp.big_map(
                        tkey = sp.TAddress,
                        tvalue = sp.TNat,
                        l = {}
                    ) # Add a bigmap into the final storage

You can also call use self.update_initial_storage() to update the storage.

class ExampleFA2(FA2.FA2):
    def __init__(self, config, admin):
        FA2.FA2_core.__init__(self, config,
                            paused = False,
                            administrator = admin)
            x = 0,
            y =

Custom entrypoints โ€‹

You can reimplement the provided entrypoints or add yours exactly like you add entrypoints in SmartPy contracts.

In this example, we replace the mint entrypoint by our implementation.

class ExampleFA2(FA2.FA2):
    def mint(self, params):
        """ A very simple implementation of the mint entrypoint"""
        sp.verify(self.is_administrator(sp.sender), message = self.error_message.not_admin())
        with sp.if_(
  [user].balance += params.amount
        with sp.else_():
  [user] = Ledger_value.make(params.amount)
        with sp.if_(~ self.token_id_set.contains(, params.token_id)):
            self.token_id_set.add(, params.token_id)
  [params.token_id] = sp.record(
                token_id    = params.token_id,
                token_info  = params.metadata

Basic usage โ€‹

Mint โ€‹

The mint entrypoint that we provide doesn't let you modify

the token_metadata after the initial mint. If you want to change this, see custom entrypoints. :::

Let's defined the metadata of the token we want to mint.

example_md = FA2.FA2.make_metadata(
    decimals = 0,
    name     = "Example FA2",
    symbol   = "DFA2" )

This is equivalent to

python = {
    # Remember that michelson wants map already in ordered
    "decimals" : sp.utils.bytes_of_string("%d" % 0),
    "name" : sp.utils.bytes_of_string("Example FA2"),
    "symbol" : sp.utils.bytes_of_string("DFA2")

You can also add an icon url or other custom metadata by creating a custom map.

Example in a scenario:

    example_md = FA2.FA2.make_metadata(
        name     = "Example FA2",
        decimals = 0,
        symbol   = "DFA2" )
        address  = FA2_admin.address, # Who will receive the original mint
        token_id = 0,
        amount   = 100_000_000_000,
        metadata = example_md
    ).run(sender = FA2_admin)

Transfer โ€‹

Transfers are a list of batches. A batch is a list of transactions from one sender.

Batch items can be created by the helper contract.batch_transfer.item.


        c1.batch_transfer.item(from_ = alice.address,
                            txs = [
                                sp.record(to_ = bob.address,
                                            amount = 10,
                                            token_id = 0),
                                sp.record(to_ = bob.address,
                                            amount = 10,
                                            token_id = 1)]),
        c1.batch_transfer.item(from_ = bob.address,
                            txs = [
                                sp.record(to_ = alice.address,
                                            amount = 11,
                                            token_id = 0)])
    ]).run(sender = admin)

Operators โ€‹

Operators can be modified by calling update_operators with a list of variants that remove or add operators.


    sp.variant("remove_operator", c1.operator_param.make(
        owner = alice.address,
        operator = op1.address,
        token_id = 0)),
    sp.variant("add_operator", c1.operator_param.make(
        owner = alice.address,
        operator = op2.address,
        token_id = 0))
]).run(sender = alice)

Ledger keys โ€‹

All the info about how many tokens are held by an address are in the ledger bigmap.

The keys of the bigmap can be created by calling contract.ledger_key.make(address, token_id).


scenario.verify([c1.ledger_key.make(bob.address, 0)].balance == 10)

Config โ€‹

The config dictionary contains the meta-programming configuration. It is used to modify global logic in the FA2 contract that potentially affects multiple entrypoints. All values of the dictionary are boolean values.

add_mutez_transferFalseAdd an entrypoint for the admin to transfer tez from the contract's balance.
allow_self_transferFalseThis contract is as an operator for all addresses/tokens it contains.
assume_consecutive_token_idsTrueIf true don't use a set of token ids, only keep how many there are.
debug_modeFalseUse maps instead of big-maps to simplify the contract's state inspection.
lazy_entrypointsFalseadd flag lazy-entrypoints
non_fungibleFalseEnforce the non-fungibility of the tokens (i.e. total supply has to be 1).
single_assetFalseSave some gas and storage by working only for the token-id 0.
store_total_supplyTrueStore the total-supply for each token (next to the token-metadata).
support_operatorTrueIf False, remove operator logic (maintain the presence of the entrypoint).
use_token_metadata_offchain_viewFalseInclude offchain view for accessing the token metadata (requires TZIP-016 contract metadata).

The config is returned by instantiating the class FA2_config.


FA2.FA2_config(assume_consecutive_token_ids = False, debug_mode = True)