Base classes
The FA2 library has three base classes that you can use as a foundation to develop your own smart contracts. Each of these classes cater to different token standards and behaviors, thus, offering the flexibility to design a contract that suits your specific needs.
The base classes
Upon inheriting from any of the base classes you're provided with a standard FA2 contract.
main.Nft
This class is for Non-Fungible Tokens (NFTs). In this case each token is distinct and thus not interchangeable ("fungible") with another. This makes it suitable for unique assets like digital artwork, real estate, or unique digital goods.
The main.Nft
class utilizes a ledger of type sp.big_map[sp.nat, sp.address]
, mapping where the key represents the token id and the value signifies the adddress of the token owner. Thus every NFT is associated with a unique owner.
Use case: An artist wants to release a collection of digital art pieces as NFTs. Each artwork will be represented as a unique token on the blockchain.
main.Fungible
This class is designed for fungible tokens, which are interchangeable ("fungible") with each other. This type of token is useful when the individual units are essentially identical, such as with cryptocurrencies or utility tokens. If you're aiming to create a token where each unit holds the same value and properties, main.Fungible
could be your base class of choice.
Use case: A gaming company wants to issue multiple in-game currencies. For each currency, each unit holds the same value and properties.
main.SingleAsset
This class is a specialized version of the main.Fungible
class. It is used when your use-case only involves a single fungible token. main.SingleAsset
is optimized for this specific scenario and can provide performance benefits over the more general main.Fungible
. If you're creating a smart contract for a single cryptocurrency, this class could be the right choice.
Use case: A gaming company wants to issue a single in-game currency. Each unit of this currency holds the same value and properties.
Origination
Apart from the specialized ledger
the three classes share a common interface (see entrypoints) and their constructor takes the following parameters:
metadata
(sp.big_map[sp.string, sp.bytes]
): This is the contract's metadata bigmap. The metadata bigmap should never be empty. See contract metadata.ledger
: Used to create the ledger bigmap, it contains the pre-minted tokens. The ledger type varies based on the class - it'ssp.map[sp.nat, sp.address]
forNft
,sp.map[sp.pair[sp.address, sp.nat], sp.address]
forFungible
, andsp.map[sp.address, sp.nat]
forSingleAsset
. Most of the time, this will be empty:{}
.token_metadata
: Used to create the token metadata bigmap, see token metadata. It contains the pre-minted tokens' metadata. It's a list of metadata map: (sp.list[sp.map[sp.string, sp.bytes]]
) forNft
andFungible
, or the unique metadata map (sp.map[sp.string, sp.bytes]
) forSingleAsset
. Most of the time, your contract is empty at the beginning so its value is[]
or{}
.
Examples with empty contract:
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("Nft", [fa2.t, fa2.main])
sc.h2("Without pre-minted tokens")
c1 = fa2.main.Nft(sp.big_map(), {}, [])
sc += c1
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("Fungible", [fa2.t, fa2.main])
sc.h2("Without pre-minted tokens")
c1 = fa2.main.Fungible(sp.big_map(), {}, [])
sc += c1
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("SingleAsset", [fa2.t, fa2.main])
sc.h2("Without pre-minted tokens")
c1 = fa2.main.SingleAsset(sp.big_map(), {}, {})
sc += c1
Example with pre-minted tokens
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("Nft", [fa2.t, fa2.main])
sc.h2("With pre-minted tokens")
alice = sp.test_account("alice")
bob = sp.test_account("bob")
tok0_md = fa2.make_metadata(name="Token Zero", decimals=1, symbol="Tok0")
tok1_md = fa2.make_metadata(name="Token One", decimals=1, symbol="Tok1")
tok2_md = fa2.make_metadata(name="Token Two", decimals=1, symbol="Tok2")
c1 = fa2.main.Nft(
metadata = sp.big_map(),
ledger = {
0: alice.address,
1: bob.address,
2: alice.address,
},
token_metadata = [tok0_md, tok1_md, tok2_md]
)
sc += c1
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("Fungible", [fa2.t, fa2.main])
sc.h2("With pre-minted tokens")
alice = sp.test_account("alice")
tok0_md = fa2.make_metadata(name="Token Zero", decimals=1, symbol="Tok0")
tok1_md = fa2.make_metadata(name="Token One", decimals=1, symbol="Tok1")
tok2_md = fa2.make_metadata(name="Token Two", decimals=1, symbol="Tok2")
c1 = fa2.main.Fungible(
metadata = sp.big_map(),
ledger = {
(alice.address, 0): 42,
(alice.address, 1): 42,
(alice.address, 2): 42,
},
token_metadata = [tok0_md, tok1_md, tok2_md]
)
sc += c1
import smartpy as sp
from templates import fa2_lib as fa2
@sp.add_test()
def test():
sc = sp.test_scenario("SingleAsset", [fa2.t, fa2.main])
sc.h2("With pre-minted tokens")
alice = sp.test_account("alice")
tok0_md = fa2.make_metadata(name="Token Zero", decimals=1, symbol="Tok0")
c1 = fa2.main.SingleAsset(
metadata=sp.big_map(),
token_metadata=tok0_md,
ledger={alice.address: 42}
)
sc += c1
Inheritance
In practice you should create your own class and inherit from one of the base classes. This allows you to add storage fields and override methods, thus modifying the behaviour of your tokens.
Example:
class ExampleNft(main.Nft):
def __init__(self, metadata, ledger, token_metadata):
main.Nft.__init__(self, metadata, ledger, token_metadata)
# ...
class ExampleFungible(main.Fungible):
def __init__(self, metadata, ledger, token_metadata):
main.Fungible.__init__(self, metadata, ledger, token_metadata)
# ...
class ExampleSingleAsset(main.SingleAsset):
def __init__(self, metadata, ledger, token_metadata):
main.SingleAsset.__init__(self, metadata, ledger, token_metadata)
# ...