Skip to main content
Version: main branch

Define Token Types

This first tutorial demonstrates how to create new fungible and non-fungible token types and how token type inheritance works. It includes examples of different kinds of predicates used on tokens and provides guidance on how to supply the necessary inputs for these predicates.

Known Issue

The wallet token list-types command is not yet implemented in the new JSON-RPC-based validator APIs. This feature will be introduced in a future release. For now, users need to manually track their token type IDs.

Prerequisites

Before you begin, make sure you have completed the following steps:

Define Fungible Token Types

  1. The CLI wallet has a separate command for interaction with the User Token Partition. Go to the alphabill-wallet/build directory and open the help output for the wallet token command to view available subcommands and options:

    ./abwallet wallet token -h
    create and manage fungible and non-fungible tokens

    Usage:
    abwallet wallet token [command]

    Available Commands:
    collect-dust join fungible tokens into one unit
    list lists all available tokens
    list-types lists token types
    lock locks a fungible or non-fungible token
    new mint new token
    new-type create new token type
    send send a token
    unlock unlocks a fungible or non-fungible token
    update update the data field on a non-fungible token

    Flags:
    -h, --help help for token
    --max-fee string maximum fee per transaction (in tema) (default "10")
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    Use "abwallet wallet token [command] --help" for more information about a command.

    As seen in the help output, you can create new token types with the wallet token new-type subcommand.

  2. Start with the fungible token type, but first check the available options:

    ./abwallet wallet token new-type fungible -h
    create new fungible token type

    Usage:
    abwallet wallet token new-type fungible [flags]

    Flags:
    --decimals uint32 token decimal (default 8)
    -h, --help help for fungible
    --icon-file string icon file name for the token type (optional)
    --inherit-bearer-clause string predicate that will be inherited by subtypes into their bearer clauses. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "true")
    -k, --key uint which key to use for sending the transaction (default 1)
    --mint-clause string predicate to control minting of this type. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "ptpkh")
    --name string full name of the token type (optional)
    --parent-type hex unit identifier of a parent type in hexadecimal format
    --subtype-clause string predicate to control sub typing. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "true")
    --subtype-input strings input to satisfy the parent type creation clause (mandatory with --parent-type)
    --symbol string symbol (short name) of the token type (mandatory)

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    --max-fee string maximum fee per transaction (in tema) (default "10")
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    The output shows that this subcommand has only one mandatory option: --symbol, short name of the token type.

    Let's take a look at some of the token type default properties:

    • --decimals: defaults to 8 decimal places.
    • --inherit-bearer-clause: defaults to true. This means no additional bearer restrictions are placed on types that inherit from this type. Just because it's a default, doesn't mean it doesn't exist⁠—this fact will come into play later.
    • --mint-clause: defaults to ptpkh. This means that only this wallet can be used to mint or create new tokens of this type. The default key is key #1, but other keys can be indicated using the syntax: ptpkh:2, for example. Mint clauses are also inherited! This means that if a token type inherits from a parent, the transaction to mint tokens of the subtype must first satisfy the parent's mint clause and then the subtype's. This is an important concept, as you will see later. Remember, just because it's a default doesn't mean it doesn't exist.
    • --parent-type: defaults to 00, or no parent. If you want to inherit, you need to pass this flag and indicate the ID of the type you want to inherit from. This token has no parent.
    • --subtype-clause: this flag is only required if the token is inheriting from a parent type. If you do inherit, you need to provide an input to the parent types subtype clause.
    • --symbol: mandatory, short name of the token type.
  3. Run the following command to create a new type, only the mandatory --symbol option provided (in this example, FT_TYPE):

    ./abwallet wallet token new-type fungible \
    --symbol FT_TYPE
    Example response:
    Sent request for new fungible token type with id=04F82501E3D227CFAE7743BA092E8AFF916D378380945A946594B338F19DDE1901
    Paid 0.000'000'01 fees for transaction(s).
    info

    The default value for the --rpc-url flag is used here. Each wallet subcommand requires a connection to the partition's RPC node. For the User Token Partition, this node runs on localhost:28866.

Parent Fungible Token Type

Create a new type, but place some restrictions on how it can be subtyped:

./abwallet wallet token new-type fungible \
--subtype-clause ptpkh:2 \
--symbol FT_PARENT_TYPE
Example response:
Sent request for new fungible token type with id=8181CBB70CF397CEEE9590DB3C7D8A0F7A940C0C8988F99DBF1230643EE05DA301
Paid 0.000'000'01 fees for transaction(s).
Important

Note the created token type ID, as you need this in the following steps.

What is happening here is that the parent token types can only be subtyped if the transaction to create the subtype includes an input that satisfies the ptpkh predicate/clause for this wallet's key #2.

Effectively, this means that only this wallet can subtype this parent type. At first, it seems not too useful, however, you could imagine that there is an application that might allow other users to request the ability to subtype your token types, and it could facilitate you signing their transaction hashes to do it.

This is an effective form of copyright for token types. Alternatively, the syntax also allows you to create clauses using public keys that are not in your wallet using the following syntax:

ptpkh:0x026b2c214fdcd96c6a246e5ab13309e3d95772d50b2add414a2b48bc2bb9b8ef15

Here the public key is prefixed by 0x as indicated. This allows one user to create a type for another user to use, which is effectively delegation of a responsibility or right.

Fungible Token Subtype

  1. Next, create a subtype from the parent type. Remember to use the actual ID of your parent type:

    ./abwallet wallet token new-type fungible \
    --parent-type FT_PARENT_TYPE_ID \
    --subtype-clause false \
    --subtype-input ptpkh:2 \
    --symbol FT_SUBTYPE
    Example response:
    Sent request for new fungible token type with id=0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01
    Paid 0.000'000'02 fees for transaction(s).

    The properties of this token are:

    • The parent type is the FT_PARENT_TYPE token created earlier.
    • It cannot, itself, be subtyped any further.
    • Its symbol is FT_SUBTYPE.
  2. Create one more subtype. Again, use the actual ID of your parent type:

    ./abwallet wallet token new-type fungible \
    --inherit-bearer-clause ptpkh:2 \
    --parent-type FT_PARENT_TYPE_ID \
    --subtype-clause false \
    --subtype-input ptpkh:2 \
    --symbol FT_SUBTYPE2
    Example response:
    Sent request for new fungible token type with id=D63CB010C3B633928D75D8BF3A9485AE222B9595A1D0F274BE9C8FE92E4392F701
    Paid 0.000'000'02 fees for transaction(s).

    The properties of this new subtype token type are similar to the previous type, except for one addition—all tokens of this token type will require a signature from this wallet's key #2 in order to be transferred.

    Again, this might not seem too useful, however, it could be used to create a permission system, where tokens can only be transferred if approved by an authority.

    While this is not what we usually think of when using cryptocurrency, it is exactly how accounting procedures at a corporation, or risk control procedures at a financial institution, work.

Define Non-Fungible Token Types

Create some non-fungible token (NFT) types as well. Open the help output to see what options are available:

./abwallet wallet token new-type non-fungible -h
create new non-fungible token type

Usage:
abwallet wallet token new-type non-fungible [flags]

Flags:
--data-update-clause string data update predicate. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "true")
-h, --help help for non-fungible
--icon-file string icon file name for the token type (optional)
--inherit-bearer-clause string predicate that will be inherited by subtypes into their bearer clauses. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "true")
-k, --key uint which key to use for sending the transaction (default 1)
--mint-clause string predicate to control minting of this type. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "ptpkh")
--name string full name of the token type (optional)
--parent-type hex unit identifier of a parent type in hexadecimal format
--subtype-clause string predicate to control sub typing. Valid values are either one of the predicate template name [ true | false | ptpkh | ptpkh:n | ptpkh:0x<hex-string> ] or @<filename> to load predicate from given file. (default "true")
--subtype-input strings input to satisfy the parent type creation clause (mandatory with --parent-type)
--symbol string symbol (short name) of the token type (mandatory)

Global Flags:
--config string config file URL (default is $AB_HOME/config.props)
--home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
--log-file string log file path or one of the special values: stdout, stderr, discard
--log-format string log format, one of: text, json, console
--log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
--max-fee string maximum fee per transaction (in tema) (default "10")
-p, --password password (interactive from prompt)
--pn string password (non-interactive from args)
--proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
-r, --rpc-url string rpc node url (default "localhost:28866")
-w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
-l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

The options are similar to those available for fungible types, with two differences:

  • There is no --decimals option. Since these tokens are non-fungible, they cannot be divided at all.
  • There is a new --data-update-clause option. This is a new predicate which is unique to Alphabill NFTs. It is the clause that all tokens of this type (and of subtypes) inherit into their data update predicates. It defaults to true.

Parent Non-Fungible Token Type

Start with creating the parent type first and then the subtype:

./abwallet wallet token new-type non-fungible \
--subtype-clause ptpkh:2 \
--symbol NFT_PARENT_TYPE
Example response:
Sent request for new NFT type with id=56CDD3256612207A0910FC5CC93FB1F41294AC4FA85C9A90553443115CA65EB102
Paid 0.000'000'01 fees for transaction(s).
Important

Note the created token type ID, as you need this in the following steps.

Non-Fungible Token Subtype

Create a subtype of the parent type you just created, but remember to use the actual ID of your parent NFT type:

./abwallet wallet token new-type non-fungible \
--mint-clause ptpkh:2 \
--parent-type NFT_PARENT_TYPE_ID \
--subtype-clause false \
--subtype-input ptpkh:2 \
--symbol NFT_SUBTYPE
Example response:
Sent request for new NFT type with id=66EE618CE91B8D78DFA7AEED9B7C9FCBA990D92E9FB5C21B20ADEE3FD297FFEB02
Paid 0.000'000'02 fees for transaction(s).

Notice one difference from the previous fungible examples—it has a non-default --mint-clause on the subtype. As a result, it requires a ptpkh:2 input instead of the default, which is ptpkh.

info

These examples haven't covered any non-default --data-update-clause usage yet. You can see it later when minting a non-fungible token. However, the full functionality of this clause will only be realized once a fully programmable predicate language is introduced in the future.

Conclusion

The token types you have created will come into use when you start minting actual tokens and transfer them from one account to another. Here are key points to keep in mind:

  • Token types are immutable and cannot be altered after creation.
  • Token types are not owned and cannot be transferred. This follows from the above.
  • Token types can be subtyped and specify additional bearer clauses, which will be inherited by descendant types.
  • Some token types may exist solely as parent types, never intended to be used to actually mint tokens from.
  • Token type inheritance can be multilevel, with as many levels as needed.
  • Non-fungible token types have a mutable data field that can be updated if the data update predicate is satisfied. This mutability enables implementing a state machine and enforcing business processes or procedures.
  • Predicate rules:
    • true predicates mean there are no restrictions.
    • false predicates can never be satisfied.
    • ptpkh predicates could refer to keys in your wallet, or keys in someone else's wallet.