Minting Native Tokens on Cardano Testnet

Today we are going to mint some native tokens on the Cardano blockchain. Cardano differs from Ethereum in that tokens are treated as native assets versus extra add-ons from smart contracts.

Today we are going to mint some native tokens on the Cardano blockchain.  Cardano differs from Ethereum in that tokens are treated as native assets versus extra add-ons from smart contracts.

Normally in the Ethereum world you would write an ERC20, ERC721, or ERC1155 type token contract and publish it to the blockchain in order to interact with your fancy tokens or NFTs.  That means you also need to spend Ether on gas to deploy the token contracts, or interact with them.

Side note for Ethereum, don't write your own contracts and just use OpenZeppelin's token contracts as they are audited for security [https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token].

As of right now token issuance and interaction needs ADA tokens for deployment and interaction with your native assets, but in the future that should be migrated to a new system [see https://iohk.io/en/blog/posts/2021/02/25/babel-fees/]

We are going to work with the Cardano testnet as we do not want to spend any actual money today to learn a new skill.

Note: This post is made possible by smarter people, I just stand on the shoulders of giants.  I worked through the script multiple times to figure this stuff out.  The original script was taken from a GitHub gist linked here, go give it a star:

Mint native tokens on Cardano testnet using the node and cli that come with Daedalus
Mint native tokens on Cardano testnet using the node and cli that come with Daedalus - minting-native-tokens-on-cardano.sh

Also here is the official docs:

Minting Native Assets | Cardano Developer Portal
How to mint native tokens on Cardano.

First, go out and download the testnet version of Daedalus:

https://developers.cardano.org/en/testnets/cardano/get-started/wallet/

Once installed and synced with the testnet blockchain, create a wallet

[REMEMBER TO SAVE YOUR SEED PHRASE AND SPENDING PASSWORD]

^^^ Learning from past experience  ^^^

Now that you have a wallet and are synced up, you can get some test ADA and Testcoin from the IOHK faucet:

https://developers.cardano.org/en/testnets/cardano/tools/faucet/

They applied a daily faucet limit of 1000 test ADA a day for each testnet user to ensure everyone has access to funds. Please don't be greedy, you only need a few for today's session.


Next, we will interact with the cardano-cli to create our token.

I am on a MacBook, so your config might be different on Linux or Windows

We will be using the command line to create a new wallet address and native tokens as that functionality is not included in Daedalus yet.  I had you grab some test ADA with your Daedalus wallet as that is the easy way to get up and running.  

We will fund our new wallet after we create it below.

First let's setup some environmental variables:

# By default Daedalus installs in the users Library
# We need a var for the socket in order to talk to the chain nicely
export CARDANO_NODE_SOCKET_PATH=~/Library/Application\ Support/Daedalus\ Testnet/cardano-node.socket

# We want to make sure we are on the testnet
export TESTNET_ID=1097911063

# We create a nice alias so that we can be lazy in our typing
alias cardano-cli="/Applications/Daedalus\ Testnet.app/Contents/MacOS/cardano-cli"

# Today we are going to create 1024 marble tokens
# Think of this like an ERC20 contract on  Ethereum
# Techincally the name should be up to 5 characters [MRBL?]
export TOKEN_NAME=MARBLE
export TOKEN_AMOUNT=1024

Note: I was able to get the testnet ID by looking at the config's published by IOHK:
https://hydra.iohk.io/build/5822084/download/1/index.html

Click the button by "shelleyGenesis" and search for "networkMagic" near the bottom of the JSON.

# As of this writing the JSON returned the following and that is what we use under TESTNET_ID
"networkMagic": 1097911063,

OK, ready? let's go!

Step 1

Check that everything is working so far

This command should return a list of all transaction on the network, I pipe it to more as to not spend all day looking at transactions.

cardano-cli query utxo --testnet-magic $TESTNET_ID --mary-era | more

Note: You will see --testnet-magic $TESTNET_ID --mary-era on almost all our commands as we will be only working with the testnet and the Mary Era of the chain.

Step 2

We are going to create a wallet that will be issuing our marble tokens, so we need some keys.

Generate our stake keys

cardano-cli stake-address key-gen --verification-key-file stake.vkey --signing-key-file stake.skey

Generate our payment keys

cardano-cli address key-gen --verification-key-file payment.vkey --signing-key-file payment.skey

Use payment and stake verification keys to generate a new payment address

cardano-cli address build --payment-verification-key-file payment.vkey --stake-verification-key-file stake.vkey --out-file payment.addr --testnet-magic $TESTNET_ID

OK we have a new payment address so we can send some ADA to it.

Side note: If you wanted, you could use this address in the faucet to skip a step, but you will miss out on the GUI ;)

Let's check to see if that transaction posted and your new wallet has some tokens:

Check the address balance

cardano-cli query utxo --address $(< payment.addr) --testnet-magic $TESTNET_ID --mary-era

You should see something like the following:

                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
806ba02a2d643467c435b8554d4ece8110222268e3ef37701b9dc79e4e8d6d87     0        50000000 lovelace

Note: I sent 50 ADA to the address, but by default the cli only shows the smallest denomination which is lovelace, so we have 50,000,000 lovelace in our wallet.

Export Protocol Parameters

Finally, we export the protocol parameters for later use in determining the fees for our token transactions.  We save this into a file called protocol.json

cardano-cli query protocol-parameters --testnet-magic $TESTNET_ID --mary-era --out-file protocol.json

Step 3

Next we need to create our token policy, and then mint some tokens.

Create directory for our policy files

mkdir policy

Generate policy signing and verification keys

We will need a set of policy keys to generate our policy ID

cardano-cli address key-gen --verification-key-file policy/policy.vkey --signing-key-file policy/policy.skey

Create an empty policy script file

We do this to make sure the next command doesn't fail because of some shell shenanigans.

touch policy/policy.script && echo "" > policy/policy.script 

Generate policy script and put it into the newly created file

This creates a very basic token policy.

Note: If you wanted to you could lock down the policy so that after a certain number of epochs, no more tokens could be minted.  You can also add multi signatures in if you needed authorization of more than one key to generate.  You can read more here Multisignatures

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 

Generate policy ID

Finally we generate our policy ID, this will be one way we can verify tokens on chain.

cardano-cli transaction policyid --script-file ./policy/policy.script >> policy/policyId

Next we build our raw transaction, figure out the fee, sign it and send it to the testnet.

First we need our original transaction for the amount of ADA.  We need this as ADA is based on the UTXO model.  We will need to burn that one down to create our tokens.

Check the address balance again

Hopefully everything is the same and we can keep rolling.

cardano-cli query utxo --address $(< payment.addr) --testnet-magic $TESTNET_ID --mary-era

You should see something like the following:

                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
806ba02a2d643467c435b8554d4ece8110222268e3ef37701b9dc79e4e8d6d87     0        50000000 lovelace

Save this data as environment variables

Let's setup some environment variables to make math and commands easier.

Note: Your tx_hash will be different.

export TX_HASH=806ba02a2d643467c435b8554d4ece8110222268e3ef37701b9dc79e4e8d6d87
export TX_IX=0
export AVAILABLE_LOVELACE=50000000

The next set of commands are a bit longer so I will be throwing "\" for breaks.  You should still be able to copy and paste.

Build up our raw transaction using this data

cardano-cli transaction build-raw \
  --mary-era \
  --fee 0 \
  --tx-in $TX_HASH#$TX_IX \
  --tx-out $(< payment.addr)+$AVAILABLE_LOVELACE+"$TOKEN_AMOUNT $(< policy/policyId).$TOKEN_NAME" \
  --mint="$TOKEN_AMOUNT $(< policy/policyId).$TOKEN_NAME" \
  --out-file matx.raw

Calculate out the minimum fee

cardano-cli transaction calculate-min-fee \
  --tx-body-file matx.raw \
  --tx-in-count 1 \
  --tx-out-count 1 \
  --witness-count 1 \
  --testnet-magic $TESTNET_ID \
  --protocol-params-file protocol.json

The terminal should return something around the following:

175049 Lovelace

Save your fee as an environment variable

Note: Replace it with value returned by the previous command

export TX_FEE=175049

Use the calculated fee to build the transaction with proper fee amount

We exported the fee so that we could do some inline math for the transaction

cardano-cli transaction build-raw \
  --mary-era \
  --fee $TX_FEE \
  --tx-in $TX_HASH#$TX_IX \
  --tx-out $(< payment.addr)+$(($AVAILABLE_LOVELACE - $TX_FEE))+"$TOKEN_AMOUNT $(< policy/policyId).$TOKEN_NAME" \
  --mint="$TOKEN_AMOUNT $(< policy/policyId).$TOKEN_NAME" \
  --out-file matx.raw

Sign our transaction

Sign our transaction with our private key

cardano-cli transaction sign \
  --signing-key-file payment.skey \
  --signing-key-file policy/policy.skey \
  --script-file policy/policy.script \
  --testnet-magic $TESTNET_ID \
  --tx-body-file matx.raw \
  --out-file matx.signed

Submit the transaction

Send our transaction to the blockchain to

cardano-cli transaction submit --tx-file  matx.signed --testnet-magic $TESTNET_ID

Check the address balance again for tokens

Once the transaction has been accepted your new transaction will now have a little less ADA and 1024 MARBLE tokens, WOOT!

cardano-cli query utxo --address $(< payment.addr) --testnet-magic $TESTNET_ID --mary-era

You should see something like the following:

                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
03a52facd61d7bffe9beddb1cbc0b24638f692a51fe7a78a0178b164ed64de2c     0        49824951 lovelace + 1024 8e7eda99acf9e33f287bb1717e2d261726b2b936c8d2a2a6bed937a6.MARBLE

If everything goes as planned you will now have a bunch of MARBLEs to play with.


Next, we would like to see those tokens in the Daedalus wallet as that is more fun!

Step 4

Sending tokens via command line to our Daedalus wallet.

Go to Daedalus and grab an address from our wallet

Save that address as a new environment variable.

export recipient=addr_test1qrkh980j5mkmtqgxx652u66hcpzshs5sk4y0x72jjcgmduulf32cmjct85j7yns9gked9tp5a7c5alyzv5cvatlmh2vqeh4ar9

Update our TX variables

You will notice that there is a new TX_HASH and a lower amount of ADA, so let's update our variables accordingly.

export TX_HASH=03a52facd61d7bffe9beddb1cbc0b24638f692a51fe7a78a0178b164ed64de2c
export TX_IX=0
export AVAILABLE_LOVELACE=49824951

How many tokens to send?

How many tokens do you want to send to your Daedalus wallet?

WE WANT ALL OF THE MARBLES!!!!

export TOKEN_AMOUNT=1024

Reset the TX fee back to 0

export TX_FEE=0

Copy address from Daedalus wallet into recipientpay.addr

For convenience in scripting.

echo $recipient > recipientpay.addr

Build out the new raw transaction

You can build a more complicated transaction that sends some ADA back to a change address, but I wanted to keep this simple folks.

cardano-cli transaction build-raw \
  --mary-era \
  --fee 0 \
  --tx-in $TX_HASH#$TX_IX \
  --tx-out "$(< recipientpay.addr)+$(($AVAILABLE_LOVELACE - $TX_FEE))+1024 $(< policy/policyId).$TOKEN_NAME" \
  --out-file recipient_matx.raw

Calculate the min TX fee

cardano-cli transaction calculate-min-fee \
  --tx-body-file recipient_matx.raw \
  --tx-in-count 1 \
  --tx-out-count 1 \
  --witness-count 1 \
  --testnet-magic $TESTNET_ID \
  --protocol-params-file protocol.json

Hopefully we did it correctly and the terminal returns the needed fee:

173157 Lovelace

Save your fee as an environment variable

Note: Replace it with value returned by the previous command.

export TX_FEE=173157

Build out the raw transaction again with the correct fee

cardano-cli transaction build-raw \
  --mary-era \
  --fee $TX_FEE \
  --tx-in $TX_HASH#$TX_IX \
  --tx-out "$(< recipientpay.addr)+$(($AVAILABLE_LOVELACE - $TX_FEE))+1024 $(< policy/policyId).$TOKEN_NAME" \
  --out-file recipient_matx.raw

Sign the transaction using the keys generated earlier

cardano-cli transaction sign \
  --signing-key-file payment.skey \
  --signing-key-file policy/policy.skey \
  --testnet-magic $TESTNET_ID \
  --tx-body-file recipient_matx.raw \
  --out-file recipient_matx.signed

Submit the transaction to the chain.

cardano-cli transaction submit --tx-file  recipient_matx.signed --testnet-magic $TESTNET_ID

After a few moments you will now have your ADA returned and some new tokens.

You can view my transaction on the testnet explorer here:

https://explorer.cardano-testnet.iohkdev.io/en/transaction?id=4caab598a6be39fb269eafa170d2c1210589a4412fb8c513031f18c49f4f486f

You say:  

"BUT, we want NFTs!!!1!! They are soooo hot!"

No worries, we will tackle that in another post as this one has gone on for far too long.