Minting Native Assets
In this section, we will be minting native assets - not NFTs.
It is strongly advised to work through this section to understand how transactions and minting works.
Minting NFTs will follow the same process, with only a few tweaks. If you want to jump to NFTs, please visit Minting NFTs.
Prerequisites
- A running and synced Cardano node - accessible through the
cardano-cli
command. This guide is written withcardano-cli
v 1.27.0. Some commands may be subject to change. - You have some knowledge in Linux as to navigation between directories, creating and editing files, and setting and inspecting variables via Linux shell.
Overview
This tutorial will give you a copy & pastable walk through the complete token lifecycle:
These will be the steps we need to take to complete the whole lifecycle:
- Set everything up
- Build a new address and keys
- Generate a minting policy
- Draft a minting transaction
- Calculate fees
- Send the transaction and mint tokens (to ourselves)
- Send the tokens to a Daedalus wallet
- Burn some token
Directory structure
We'll be working in a new directory. Here is an overview of every file we will be generating:
├── burning.raw # Raw transaction to burn token
├── burning.signed # Signed transaction to burn token
├── matx.raw # Raw transaction to mint token
├── matx.signed # Signed transaction to mint token
├── metadata.json # Metadata to specify NFT attributes
├── payment.addr # Address to send / receive
├── payment.skey # Payment signing key
├── payment.vkey # Payment verification key
├── policy # Folder which holds everything policy-wise
│ ├── policy.script # Script to genereate the policyID
│ ├── policy.skey # Policy signing key
│ ├── policy.vkey # Policy verification key
│ └── policyID # File which holds the policy ID
└── protocol.json # Protocol parameters
Token architecture
Before minting native assets, you need to ask yourself at least those four questions:
- What will be the name of my custom token(s)?
- How many do I want to mint?
- Will there be a time constraint for interaction (minting or burning token?)
- Who should be able to mint them?
Number 1, 3, and 4 will be defined in a so-called monetary policy script, whereas the actual amount will only be defined on the minting transaction.
For this guide, we will use:
- What will be the name of my custom token(s)?
--> We are going to callTesttoken
andSecondTesttoken
- How many do I want to mint?
--> 10000000 each (10MTesttoken
and 10MSecondTesttoken
) - Will there be a time constraint for interaction (minting or burning token?)
---> No (we will, however, when making NFTs), we want to mint and burn them however we like. - Who should be able to mint them?
--> only one signature (which we possess) should be able to sign the transaction and therefore be able to mint the token
Setup
Cardano node socket path
To work with the cardano-cli
we need to export an environment variable called CARDANO_NODE_SOCKET_PATH
. Please note that the variable name is all uppercase.
The variable needs to hold the absolute path to the socket file of your running Cardano node installation.
If you're unsure or do not know where to find your socket path, please check the command on how you start/run your Cardano node.
For example - if you start your node with this command
$HOME/.local/bin/cardano-node run \
--topology config/testnet-topology.json \
--database-path db \
--socket-path $HOME/TESTNET_NODE/socket/node.socket \
--port 3001 \
--config config/testnet-config.json
You need to set the variable to the corresponding path of the --socket-path
parameter:
export CARDANO_NODE_SOCKET_PATH="$HOME/TESTNET_NODE/socket/node.socket"
You need to adjust the path on your setup and your socket path accordingly.
Improve readability
Since we've already answered all of the questions above, we will set variables on our terminal/bash to make readability a bit easier. First, we will specify a testnet, either Pre-production:
testnet="--testnet-magic 1"
... or Preview:
testnet="--testnet-magic 2"
When minting native assets on Mainnet, the only difference will be to use the option --mainnet
instead of --testnet-magic <testnetID>
.
Since cardano-cli version 1.31.0, token names must be base16 encoded, so here we use the xxd
tool to encode the token names:
tokenname1=$(echo -n "Testtoken" | xxd -ps | tr -d '\n')
tokenname2=$(echo -n "SecondTesttoken" | xxd -ps | tr -d '\n')
tokenamount="10000000"
output="0"
Check your node status
We also want to check if our Node is up to date. To do that, we check the current epoch/block and compare it to the current value displayed in one of the Cardano Testnet Explorers: https://preprod.cardanoscan.io/ https://preview.cardanoscan.io/
cardano-cli query tip $testnet
Should give you an output like this
{
"epoch": 282,
"hash": "82cfbbadaaec1a6204442b91de1535505b6482ae9858f3f0bd9c4bb9c8a2c12b",
"slot": 36723570,
"block": 6078639,
"era": "Mary"
}
Epoch and slot number should match when being compared to the data shown by Cardano Testnet explorers like
- testnet.cardanoscan.io is a Pre-Production and Preview block explorer by Cardanoscan.
- testnet.cexplorer.io is a Pre-Production and Preview block explorer by Cexplorer.
Set up your workspace
We will start with a clean slate. So let's make a new directory and navigate into it.
mkdir tokens
cd tokens/
Generate keys and address
If you already have a payment address and keys and you want to use those, you can skip this step.
If not - we need to generate those to submit transactions and to send and receive ada or native assets.
Payment verification and signing keys are the first keys we need to create.
cardano-cli address key-gen --verification-key-file payment.vkey --signing-key-file payment.skey
Those two keys can now be used to generate an address.
cardano-cli address build --payment-verification-key-file payment.vkey --out-file payment.addr $testnet
We will save our address hash in a variable called address
.
address=$(cat payment.addr)
Fund the address
Submitting transactions always require you to pay a fee. Sending native assets also requires sending at least 1 ada.
So make sure the address you are going to use as the input for the minting transaction has sufficient funds.
For testnets, you can request funds through the Testnet Faucet.
Export protocol parameters
For our transaction calculations, we need some of the current protocol parameters. The parameters can be saved in a file called protocol.json with this command:
cardano-cli query protocol-parameters $testnet --out-file protocol.json
Minting native assets
Generate the policy
Policies are the defining factor under which tokens can be minted. Only those in possession of the policy keys can mint or burn tokens minted under this specific policy. We'll make a separate sub-directory in our work directory to keep everything policy-wise separated and more organized. For further reading, please check the official docs or the github page about multi-signature scripts.
mkdir policy
We don't navigate into this directory, and everything is done from our working directory.
First of all, we — again — need some key pairs:
cardano-cli address key-gen \
--verification-key-file policy/policy.vkey \
--signing-key-file policy/policy.skey
Use the echo
command to create a policy.script
(the first line uses >
instead of >>
, to create the file if not present and clear any existing contents):
echo "{" > policy/policy.script
echo " \"keyHash\": \"$(cardano-cli address key-hash --payment-verification-key-file policy/policy.vkey)\"," >> policy/policy.script
echo " \"type\": \"sig\"" >> policy/policy.script
echo "}" >> policy/policy.script
The second echo uses a sub-shell command to generate the so-called key-hash. But, of course, you could also do that by hand.
We now have a simple script file that defines the policy verification key as a witness to sign the minting transaction. There are no further constraints such as token locking or requiring specific signatures to successfully submit a transaction with this minting policy.
Asset minting
To mint the native assets, we need to generate the policy ID from the script file we created.
cardano-cli transaction policyid --script-file ./policy/policy.script > policy/policyID
The output gets saved to the file policyID
as we need to reference it later on.
Build the raw transaction to send to oneself
To mint the tokens, we will make a transaction using our previously generated and funded address.
A quick word about transactions in Cardano
Each transaction in Cardano requires the payment of a fee which — as of now — will mostly be determined by the size of what we want to transmit. The more bytes get sent, the higher the fee.
That's why making a transaction in Cardano is a three-way process.
- First, we will build a transaction, resulting in a file. This will be the foundation of how the transaction fee will be calculated.
- We use this
raw
file and our protocol parameters to calculate our fees - Then we need to re-build the transaction, including the correct fee and the adjusted amount we're able to send. Since we send it to ourselves, the output needs to be the amount of our fundings minus the calculated fee.
Another thing to keep in mind is the model of how transactions and "balances" are designed in Cardano. Each transaction has one (or multiple) inputs (the source of your funds, like which bill you'd like to use in your wallet to pay) and one or multiple outputs. In our minting example, the input and output will be the same - our own address.
Before we start, we will again need some setup to make the transaction building easier. First, query your payment address and take note of the different values present.
cardano-cli query utxo --address $address $testnet
Your output should look something like this (fictional example):
TxHash TxIx Amount
--------------------------------------------------------------------------------------
b35a4ba9ef3ce21adcd6879d08553642224304704d206c74d3ffb3e6eed3ca28 0 1000000000 lovelace
Since we need each of those values in our transaction, we will store them individually in a corresponding variable.
txhash="insert your txhash here"
txix="insert your TxIx here"
funds="insert Amount here"
policyid=$(cat policy/policyID)
For our fictional example, this would result in the following output - please adjust your values accordingly:
$ txhash="b35a4ba9ef3ce21adcd6879d08553642224304704d206c74d3ffb3e6eed3ca28"
$ txix="0"
$ funds="1000000000"
$ policyid=$(cat policy/policyID)
Also, transactions only used to calculate fees must still have a fee set, though it doesn't have to be exact. The calculated fee will be included the second time this transaction is built (i.e. the transaction to sign and submit). This first time, only the fee parameter length matters, so here we choose a maximum value (note):
$ fee="300000"
Now we are ready to build the first transaction to calculate our fee and save it in a file called matx.raw. We will reference the variables in our transaction to improve readability because we saved almost all of the needed values in variables. This is what our transaction looks like:
cardano-cli transaction build-raw \
--fee $fee \
--tx-in $txhash#$txix \
--tx-out $address+$output+"$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
--mint "$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
--minting-script-file policy/policy.script \
--out-file matx.raw
In later versions of cardano-cli (at least from >1.31.0) the tokennames must be base16 encoded or you will receive an error
option --tx-out:
unexpected 'T'
expecting alphanumeric asset name, white space, "+" or end of input
You can fix this by redefining the tokennames. In this tutorial the equivalent base16 token names are:
tokenname1="54657374746F6B656E"
tokenname2="5365636F6E6454657374746F6B656E"
Syntax breakdown
Here's a breakdown of the syntax as to which parameters we define in our minting transaction:
--fee: $fee
The network fee we need to pay for our transaction. Fees will be calculated through the network parameters and depending on the size (in bytes) our transaction will have. The bigger the file size, the higher the fee.
--tx-in $txhash#$txix \
The hash of our address we use as the input for the transaction needs sufficient funds. So the syntax is: the hash, followed by a hashtag, followed by the value of TxIx of the corresponding hash.
--tx-out $address+$output+"$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
Here is where part one of the magic happens. For the --tx-out, we need to specify which address will receive our transaction. In our case, we send the tokens to our own address.
The syntax is very important, so here it is word for word. There are no spaces unless explicitly stated:
- address hash
- a plus sign
- the output in Lovelace (ada) (output = input amount — fee)
- a plus sign
- quotation marks
- the amount of the token
- a blank/space
- the policy id
- a dot
- the token name (optional if you want multiple/different tokens: a blank, a plus, a blank, and start over at 6.)
- quotation marks
--mint "$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
Again, the same syntax as specified in --tx-out but without the address and output.
--out-file matx.raw
We save our transaction to a file that you can name however you want. Just be sure to reference the correct filename in upcoming commands. I chose to stick with the official docs and declared it as matx.raw.
Based on this raw transaction we can calculate the minimal transaction fee and store it in the variable $fee. We get a bit fancy here and only store the value so we can use the variable for terminal based calculations:
fee=$(cardano-cli transaction calculate-min-fee --tx-body-file matx.raw --tx-in-count 1 --tx-out-count 1 --witness-count 2 $testnet --protocol-params-file protocol.json | cut -d " " -f1)
Remember, the transaction input and the output of ada must be equal, or otherwise, the transaction will fail. There can be no leftovers. To calculate the remaining output we need to subtract the fee from our funds and save the result in our output variable.
output=$(expr $funds - $fee)
We now have every value we need to re-build the transaction, ready to be signed. So we reissue the same command to re-build, the only difference being our variables now holding the correct values.
cardano-cli transaction build-raw \
--fee $fee \
--tx-in $txhash#$txix \
--tx-out $address+$output+"$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
--mint "$tokenamount $policyid.$tokenname1 + $tokenamount $policyid.$tokenname2" \
--minting-script-file policy/policy.script \
--out-file matx.raw
Transactions need to be signed to prove the authenticity and ownership of the policy key.
cardano-cli transaction sign \
--signing-key-file payment.skey \
--signing-key-file policy/policy.skey \
$testnet --tx-body-file matx.raw \
--out-file matx.signed
Now we are going to submit the transaction, therefore minting our native assets:
cardano-cli transaction submit --tx-file matx.signed $testnet
Congratulations, we have now successfully minted our own token. After a couple of seconds, we can check the output address
cardano-cli query utxo --address $address $testnet
and should see something like this (fictional example):
TxHash TxIx Amount
--------------------------------------------------------------------------------------
d82e82776b3588c1a2c75245a20a9703f971145d1ca9fba4ad11f50803a43190 0 999824071 lovelace + 10000000 45fb072eb2d45b8be940c13d1f235fa5a8263fc8ebe8c1af5194ea9c.5365636F6E6454657374746F6B656E + 10000000 45fb072eb2d45b8be940c13d1f235fa5a8263fc8ebe8c1af5194ea9c.54657374746F6B656E
Sending token to a wallet
To send tokens to a wallet, we need to build another transaction - this time only without the minting parameter. We will set up our variables accordingly.
fee="0"
receiver="Insert wallet address here"
receiver_output="10000000"
txhash=""
txix=""
funds="Amount of lovelace"
Again - here is an example of how it would look if we use our fictional example:
$ fee="0"
$ receiver="addr_test1qp0al5v8mvwv9mzn77ls0tev3t838yp9ghvgxf9t5qa4sqlua2ywcygl3d356c34576elq5mcacg88gaevceyc5tulwsmk7s8v"
$ receiver_output="10000000"
$ txhash="d82e82776b3588c1a2c75245a20a9703f971145d1ca9fba4ad11f50803a43190"
$ txix="0"
$ funds="999824071"
You should still have access to the other variables from the minting process. Please check if those variables are set:
echo Tokenname 1: $tokenname1
echo Tokenname 2: $tokenname2
echo Address: $address
echo Policy ID: $policyid
We will be sending 2 of our first tokens, Testtoken
, to the foreign address.
A few things worth pointing out:
- We are forced to send at least a minimum of 1 ada (1000000 Lovelace) to the foreign address. We can not send tokens only. So we need to factor this value into our output. We will reference the output value of the remote address with the variable
receiver_output
. - Apart from the receiving address, we also need to set our own address as an additional output. Since we don't want to send everything we have to the remote address, we're going to use our own address to receive everything else coming from the input.
- Our own address, therefore, needs to receive our funds, subtracted by the transaction fee as well as the minimum of 1 ada we need to send to the other address and all of the tokens the
txhash
currently holds, subtracted by the tokens we send.
Check the Cardano ledger docs for further reading
Since we will send 2 of our first tokens to the remote address we are left with 999998 of the Testtoken
as well as the additional 1000000 SecondTesttoken
.
Here’s what the raw
transaction looks like:
cardano-cli transaction build-raw \
--fee $fee \
--tx-in $txhash#$txix \
--tx-out $receiver+$receiver_output+"2 $policyid.$tokenname1" \
--tx-out $address+$output+"9999998 $policyid.$tokenname1 + 10000000 $policyid.$tokenname2" \
--out-file rec_matx.raw
Again we are going to calculate the fee and save it in a variable.
fee=$(cardano-cli transaction calculate-min-fee --tx-body-file rec_matx.raw --tx-in-count 1 --tx-out-count 2 --witness-count 1 $testnet --protocol-params-file protocol.json | cut -d " " -f1)
As stated above, we need to calculate the leftovers that will get sent back to our address.
The logic being:
TxHash Amount
— fee
— min Send 10 ada in Lovelace
= the output for our own address
output=$(expr $funds - $fee - 10000000)
Let’s update the transaction:
cardano-cli transaction build-raw \
--fee $fee \
--tx-in $txhash#$txix \
--tx-out $receiver+$receiver_output+"2 $policyid.$tokenname1" \
--tx-out $address+$output+"9999998 $policyid.$tokenname1 + 10000000 $policyid.$tokenname2" \
--out-file rec_matx.raw
Sign it:
cardano-cli transaction sign --signing-key-file payment.skey $testnet --tx-body-file rec_matx.raw --out-file rec_matx.signed
Send it:
cardano-cli transaction submit --tx-file rec_matx.signed $testnet
After a few seconds, you, the receiver, should have your tokens. For this example, a Daedalus testnet wallet is used.
Burning token
In the last part of our token lifecycle, we will burn 5000 of our newly made tokens SecondTesttoken, thereby destroying them permanently.
You won't be surprised that this — again — will be done with a transaction. If you've followed this guide up to this point, you should be familiar with the process, so let's start over.
Set everything up and check our address:
cardano-cli query utxo --address $address $testnet
Let's set our variables accordingly (if not already set). Variables like address and the token names should also be set.
txhash="insert your txhash here"
txix="insert your TxIx here"
funds="insert Amount only in here"
burnfee="0"
policyid=$(cat policy/policyID)
burnoutput="0"
Burning tokens is fairly straightforward. You will issue a new minting action, but this time with a negative input. This will essentially subtract the amount of token.
cardano-cli transaction build-raw \
--fee $burnfee \
--tx-in $txhash#$txix \
--tx-out $address+$burnoutput+"9999998 $policyid.$tokenname1 + 9995000 $policyid.$tokenname2" \
--mint="-5000 $policyid.$tokenname2" \
--minting-script-file policy/policy.script \
--out-file burning.raw
We also need to specify the amount of tokens left after destroying. The math is: amount of input token — amount of destroyed token = amount of output token
As usual, we need to calculate the fee. To make a better differentiation, we named the variable burnfee:
burnfee=$(cardano-cli transaction calculate-min-fee --tx-body-file burning.raw --tx-in-count 1 --tx-out-count 1 --witness-count 2 $testnet --protocol-params-file protocol.json | cut -d " " -f1)
Calculate the correct output value
burnoutput=$(expr $funds - $burnfee)
Re-build the transaction with the correct amounts
cardano-cli transaction build-raw \
--fee $burnfee \
--tx-in $txhash#$txix \
--tx-out $address+$burnoutput+"9999998 $policyid.$tokenname1 + 9995000 $policyid.$tokenname2" \
--mint="-5000 $policyid.$tokenname2" \
--minting-script-file policy/policy.script \
--out-file burning.raw
Sign the transaction:
cardano-cli transaction sign \
--signing-key-file payment.skey \
--signing-key-file policy/policy.skey \
$testnet \
--tx-body-file burning.raw \
--out-file burning.signed
Send it:
cardano-cli transaction submit --tx-file burning.signed $testnet
Check your address:
cardano-cli query utxo --address $address $testnet
You should now have 5000 tokens less than before:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
f56e2800b7b5980de6a57ebade086a54aaf0457ec517e13012571b712cf53fb3 1 989643170 lovelace + 9995000 45fb072eb2d45b8be940c13d1f235fa5a8263fc8ebe8c1af5194ea9c.5365636F6E6454657374746F6B656E + 9999998 45fb072eb2d45b8be940c13d1f235fa5a8263fc8ebe8c1af5194ea9c.54657374746F6B656E