Introduction
Native tokens are a novel feature on Novo that facilitate multi-asset transactions. Users can transact with Novo and an unlimited number of user-defined (custom) tokens natively.
Offering native support provides distinct advantages for developers. There is no need to create smart contracts specifically for handling custom tokens. Instead, smart contracts can focus on executing business functions. This eliminates an extra layer of complexity and reduces the potential for manual errors, as the ledger manages all token-related operations. Additionally, transaction fees are lower when minting and utilizing tokens.
Custom-created native tokens represent a certain value and function as an accounting unit. They can be employed for payments, transactions, and integration with smart contracts.
To make tokens equivalent to Novo, the structure of UTXO is extended.
Each transaction output (TxOut) includes an optional contract section that records information pertinent to the native token. The fields are as follows:
ContractType (uint64, Defined with: FT, NFT, FT_MINT, NFT_MINT)
ContractID (txid+vout 36 bytes, the outpoint when the contract is published)
ContractValue (uint256, the value of FT, the tokenID of NFT, and the total issued amount of MINT.)
ContractMaxSupply (uint256, the FT/NFT maximum circulation)
ContractMetadata (string n bytes, any data less than 1KB, initialized once when created, and then read-only)
The recently incorporated token data is optional and maintains compatibility with the original structure. An output may contain either no tokens or only one type of token.
Once the contract is established, the ContractID, which is globally unique, serves to differentiate various tokens.
In the process of transferring tokens between addresses, transaction fees must be paid using Novo, and a minimum of 5 NOVO (dust limit) is required for the token output.
Usage
Example: Creating Your Own Fungible Token (FT)
Creating a fungible token with the node command tool is as straightforward as sending Novo to an address. Keep in mind that creating tokens necessitates Novo for transaction fees.
Initially, ensure that the wallet balance is sufficient. To create a total of 21,000,000 FT and designate it as TESTCOIN, execute the following command. The creator's address is miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6:
./novo-cli createtoken FT 21000000 '{"name":"TESTCOIN","symbol":"TC","decimal":8}' miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6
c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0
The token's unique identifier is c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0, which represents the ContractID.
When tokens are first created, they have no supply. Let's mint some – Here we mint 1,000,000 and 50 tokens into the address.
./novo-cli minttoken FT c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0 1000000 msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
./novo-cli minttoken FT c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0 50 miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6
Example: Transferring Tokens to Another User
To receive tokens, the recipient generates their wallet address by executing the command 'novo-cli getnewaddress' and shares it with the sender. The sender then proceeds by running:
./novo-cli transfertoken FT c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0 100 msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
Example: Creating a Non-Fungible Token (NFT)
By employing various types in the 'createtoken' command, we can generate NFTs. For instance, here we create an NFT with a total circulation of 100, designated as TESTCARD, with the creator's address miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6:
./novo-cli createtoken NFT 100 '{"name":"TESTCARD",description:""}' miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6
d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0
The token's unique identifier is d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0, representing the ContractID.
When non-fungible tokens are initially created, they have no supply. To mint them individually, we will add 3 tokens to the address, each with distinct metadata (D1/D2/D3):
./novo-cli minttoken NFT d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0 '{"name":"D1",description:""}' msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
./novo-cli minttoken NFT d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0 '{"name":"D2",description:""}' msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
./novo-cli minttoken NFT d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0 '{"name":"D3",description:""}' msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
The auto-incremented number is used as the tokenID for NFT minting.
Example: Transferring an NFT to Another User
To transfer a specific NFT, both the contractID and tokenID must be specified.
The recipient generates their wallet address by executing the command 'novo-cli getnewaddress' and shares it with the sender. The sender then proceeds by running:
./novo-cli transfertoken NFT d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0 1 n1RZhxvsNRVXFgB1GKbE9XtscYUKMMcSGx
Example: Viewing All Tokens You Own
After receiving the token, you can utilize the 'listcontractunspent' command to obtain the UTXO of all tokens.
./novo-cli listcontractunspent
[
{
"txid": "7e305ff908f3251967b5026ed38311bf513f86873368389f1f083dbfb0d8f601",
"vout": 0,
"address": "n1RZhxvsNRVXFgB1GKbE9XtscYUKMMcSGx",
"account": "",
"scriptPubKey": "76a914da5d94a04ada05253448c6cee108c67cfdc25d0588ac",
"amount": 0.4368,
"contractType": "NFT",
"contractID": "d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0",
"contractValue": "0",
"contractMaxSupply": "100",
"contractMetadata": '{"name":"D1",description:""}',
"confirmations": 63,
"spendable": true,
"solvable": true
},
{
"txid": "df8194645691a3d65613ed3b2cae8c9b8797121d53d384d2e0d65c8ca7c4f908",
"vout": 1,
"address": "miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6",
"account": "",
"scriptPubKey": "76a91421aab58a9e80ca6a7f28c5ebcbcc94286d410f6988ac",
"amount": 0.4368,
"contractType": "NFT_MINT",
"contractID": "d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0",
"contractValue": "3",
"contractMaxSupply": "100",
"contractMetadata": '{"name":"NFT_CARD",description:""}',
"confirmations": 63,
"spendable": true,
"solvable": true
},
{
"txid": "478ef48c0e753c2956c681c92a02ed101059b11044f59dbdf1254b3c84711e53",
"vout": 1,
"address": "miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6",
"account": "",
"scriptPubKey": "76a91421aab58a9e80ca6a7f28c5ebcbcc94286d410f6988ac",
"amount": 0.4368,
"contractType": "FT_MINT",
"contractID": "c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0",
"contractValue": "4300",
"contractMaxSupply": "21000000",
"contractMetadata": '{"name":"TESTCOIN","symbol":"TC","decimal":8}',
"confirmations": 63,
"spendable": true,
"solvable": true
},
{
"txid": "29506301f26f6fe593440d197e427d9212875c32203e3629e1235260f61c417b",
"vout": 1,
"address": "msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx",
"account": "",
"scriptPubKey": "76a91485487c71ae9a6ab0652df0a95dfd681d6285236388ac",
"amount": 0.4368,
"contractType": "FT",
"contractID": "c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0",
"contractValue": "100",
"contractMaxSupply": "21000000",
"contractMetadata": '{"name":"TESTCOIN","symbol":"TC","decimal":8}',
"confirmations": 63,
"spendable": true,
"solvable": true
},
...
Furthermore, custom creation of 'rawtransactions' can be employed to accomplish these objectives. At this point, it is essential to understand the output structure of the native token.
'createrawtransaction'
'signrawtransaction'
'sendrawtransaction'
'decoderawtransaction'
Contracts
FT_MINT
When using the 'createtoken FT' command, a minting contract of the FT_MINT type is created. The FT_MINT contract can only be used for minting FTs and is not an FT itself. The contract rules are as follows:
ContractType:
It is set as FT_MINT and is immutable.
ContractID:
This is unique and immutable.
ContractValue:
This represents the amount of tokens minted, which increases as more tokens are issued.
ContractMaxSupply:
This refers to the maximum circulation and is immutable.
ContractMetadata:
This is the metadata, which is immutable. Please refer to the Token Standard.
FT
An output of the FT type indicates that an FT is attached to the output. When using the 'minttoken FT' command, the UTXO of FT_MINT will be spent, generating a new UTXO of FT_MINT and a UTXO of the newly minted FT. The contract rules for the FT are as follows:
ContractType:
It is set as FT and is immutable.
ContractID:
This is unique and immutable. It is inherited from FT_MINT and remains consistent with it.
ContractValue:
This represents the amount of this token, which changes when a transfer occurs.
ContractMaxSupply:
This refers to the maximum circulation and is immutable. It is inherited from FT_MINT and remains consistent with it.
ContractMetadata:
This is the metadata, which is immutable. It is inherited from FT_MINT and remains consistent with it.
NFT_MINT
When using the 'createToken NFT' command, a minting contract of the NFT_MINT type is created. The NFT_MINT contract can only be used for minting NFTs and is not an NFT itself. The contract rules are as follows:
ContractType:
It is set as NFT_MINT and is immutable.
ContractID:
This is unique and immutable.
ContractValue:
This represents the number of minted NFTs, which increases as more NFTs are issued.
ContractMaxSupply:
This refers to the maximum circulation and is immutable.
ContractMetadata:
This is the metadata of the series of NFTs and is optional. Please refer to the Token Standard.
NFT
An output of the NFT type indicates that an NFT is attached to the output. When using the 'minttoken' command, the UTXO of NFT_MINT will be spent, generating a new UTXO of NFT_MINT and a UTXO of the newly minted NFT. The contract rules for the NFT are as follows:
ContractType:
It is set as NFT and is immutable.
ContractID:
This is unique and immutable. It is inherited from NFT_MINT and remains consistent with it.
ContractValue:
This represents the tokenID of this NFT and is immutable. It starts from 1.
ContractMaxSupply:
This refers to the maximum circulation and is immutable. It is inherited from NFT_MINT and remains consistent with it.
ContractMetadata:
This is the metadata of this NFT. Please refer to the Token Standard.
Fungible Token (FT) Standard
It's recommended to use a serialized JSON with the following structure:
Field
Type
Description
name
string
Name of the Token
symbol
string
Symbol of the Token
decimal
number
Decimal of the Token
Example:
./novo-cli createtoken FT 21000000 '{"name":"TESTCOIN","symbol":"TC","decimal":8}' miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6
c841d5322f7553517e19e833420048bf55473be7faea7b54ce00ca1c8afcdc2a:0
Non-Fungible Token (NFT) Standard
It's recommended that the metadata for NFTs be either a URI (e.g., https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/1) or a serialized JSON with the following structure:
Field
Type
Description
name
string
Name of the NFT
description
string
Description of the NFT
image
string
URI pointing to the NFT
Example:
./novo-cli minttoken NFT d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0 '{"name":"TESTCARD-01",description:""}' msfgzzQqGeQi2nakiiqh42MS1ueELzbyNx
NFT Collection Standard
When creating a collection of NFTs, a general message can be set to display the basic information. We recommend using a serialized JSON with the following structure:
Field
Type
Description
name
string
Name of the Collection
description
string
Description of the Collection
image
string
URI pointing to the Collection
Example:
./novo-cli createtoken NFT 100 '{"name":"TESTCARD",description:""}' miay6TrfZgGfbCuMrJtCmUe9sPCbFurng6
d6692f2b87c2aac24fd0f512aad1b92430ae33778f2c21b5505ac82246eceebd:0
Developer Guide
To support native tokens, developers should pay attention to the following points:
TxOutput
The transaction output structure has changed. A specific output now includes a native token, meaning that in addition to the value and script, there is also a token data segment within the output.
SignatureHash
During transaction serialization, the transaction output containing the native token will serialize the token data before serializing the value. When calculating the signed SignatureHash, if the spent UTXO contains the native token, the token data will also be added before the serialized value. Please refer to BIP143, where sections 6. value and 8. hashOutputs may be affected by the token data.