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. We also will be using the testnet. The only difference between minting native assets in the mainnet will be that you need to substitute the network variable testnet with mainnet.
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.testnet="--testnet-magic 1097911063"
tokenname1=$(echo -n "Testtoken" | xxd -ps | tr -d '\n')
tokenname2=$(echo -n "SecondTesttoken" | xxd -ps | tr -d '\n')
tokenamount="10000000"
output="0"
We will be using this technique of setting variables along the way to make it easier to follow along.
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 the Cardano Explorer for the testnet.
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 Cardano Explorer for testnet
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 requires 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 the testnet, 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
note
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
Create a policy.script
file and fill it with an empty string.
touch policy/policy.script && echo "" > policy/policy.script
Use the echo
command to populate the file:
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
note
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
note
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.
note
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
The signed transaction will be saved in a new file called matx.signed instead of matx.raw.
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="Amout 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.
Depending on the size and amount of native assets you are going to send it might be possible to send more than the minimum requirement of only 1 ada. For this guide, we will be sending 10 ada to be on the safe side.
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
Since we've already sent tokens away, we need to adjust the amount of Testtoken we are going to send.
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
Since we already have multiple transaction files, we will give this transaction a better name and call it 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 tokenAs 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