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:
Also here is the official docs:
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:
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.