How to create NFTs on Cardano

Requirements

First, this tutorial will assume the following:

  • You own (or have access to) a full Cardano-node
  • You are at least a little bit familiar with the cardano-cli and its concepts
  • You have an Ada wallet with at least 5 Ada.

Address and key setup

First, you have to create a new payment address and for that you need two keys so let’s generate them:

cardano-cli address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey
cardano-cli address build \
--payment-verification-key-file payment.vkey \
--out-file payment.addr \
--mainnet
cat payment.addr
addr1vyaen9j2c2tkqwa3np8leruh2ykcxn9q5prwjyktupm0d0cg2ymdc
cardano-cli query utxo --address $(cat payment.addr) --mainnet
TxHash                                 TxIx        Amount
-------------------------------------------------------------------------
cardano-cli query utxo --address $(cat payment.addr) --mainnet
cardano-cli query utxo --address $(cat payment.addr) --mainnet 
TxHash TxIx Amount
--------------------------------------------------------------------------------------
58b7d31015482e4aefa834c5ec4911bd6952ef86bec6d77689f9b7a6bf4e9305 0 5000000 lovelace
cardano-cli query protocol-parameters \
--mainnet \
--out-file protocol.json

The Policy

Policies are the defining factor under which tokens can be minted. A policy can create and burn tokens. A token is always identified by the policy id and a token name. The token name is unique for the policy id but can be used with another policy id.

cardano-cli address key-gen \
--verification-key-file policy.vkey \
--signing-key-file policy.skey
  • policy.vkey (the public verification key)
  • policy.skey (the private signing key)
cardano-cli address key-hash --payment-verification-key-file policy.vkey 
86c4c595371738281d374fa4fa7180b0d3adf56a7eb9ea2d9cbab109
cardano-cli query tip --mainnet
{
"epoch": 267,
"hash": "ac54780aa50aa6680c682851af87af6b0af7790fe96429d200ac4d60b3737b1c",
"slot": 30106142,
"block": 5750431
}
cat policy.script
{
"type": "all",
"scripts": [
{
"keyHash": "86c4c595371738281d374fa4fa7180b0d3adf56a7eb9ea2d9cbab109",
"type": "sig"
},
{
"type": "before",
"slot": 30106442
}
]
}
cardano-cli transaction policyid --script-file policy.script 
6596e958394d65d378086b7cdff00ff823d2463dd9d3f0739c73275b
{
"721": {
"<policy_id>": {
"<asset_name>": {
"name": "<name>",
"image": "<uri>",
"description": "<description>"

"type": "<mime_type>",
"src": "<uri>"

<other properties>
},
...
},
...,
"version": "1.0"
}
}
cat metadata.json
{
"721": {
"fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7": {
"TangoNFT": {
"name": "Tango 0001",
"image": "ipfs://QmY9gydScXwQA4wzhFo1vqVpDkgi7sP4gH5TFmz8GYPF98"
}
}
}

The Transactions

Each transaction in Cardano requires the payment of a fee which will mostly be determined by the size of what we want to transmit. If we sent more bytes in the metadata, then we pay more fee.

  1. First, we will build a transaction with 0 fees, for that we have to create a new transaction file with a .raw extension. This is used to calculate the fee of the transaction.
  2. Then we use the file and the blockchain protocol parameters to calculate our fees.
  3. Then we create the transaction again but this time including the correct fee. Since we send it to ourselves the output needs to be the number of our funds in the UTXO minus the calculated fee.
  4. And last, sign the transaction and submit it.
cardano-cli transaction build-raw \
--fee 0 \
--tx-in 58b7d31015482e4aefa834c5ec4911bd6952ef86bec6d77689f9b7a6bf4e9305#0 \
--tx-out $(cat payment.addr)+5000000+"1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--mint="1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--metadata-json-file metadata.json \
--out-file matx.raw
  • — tx-in: Transaction hash (TxHash) of the UTXO in the address that you are going to use for the transaction and the transaction index (TxIx). This is the syntax.
--tx-in <TxHash>#<TxIx>
  • — tx-out: We need to specify which address will receive our transaction. In our case we send the tokens to our address. This is the syntax:
--tx-out <Bech32-encoded_source_address>+<lovelace amount>+"<token amount> <policy ID>.<TokenName>"
--mint "<token amount> <policy ID>.TokenName"
  • — metadata-json-file: The path to our metadata.json which we will attach to our transaction.
  • — out-file: We save our transaction to a file which you can name however you want. Just be sure to reference the correct filename in upcoming commands. Here we are using the same as the official docs and declared it as matx.raw.
cardano-cli transaction calculate-min-fee \
--tx-body-file matx.raw \
--tx-in-count 1 \
--tx-out-count 1 \
--witness-count 2 \
--mainnet \
--protocol-params-file protocol.json
187809 Lovelace
expr 5000000 - 187809
4812191
cardano-cli transaction build-raw \
--mary-era \
--fee 187809 \
--tx-in 58b7d31015482e4aefa834c5ec4911bd6952ef86bec6d77689f9b7a6bf4e9305#0 \
--tx-out $(cat payment.addr)+4812191+"1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--mint="1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--metadata-json-file metadata.json \
--invalid-hereafter=30106442\
--out-file matx.raw
cardano-cli transaction sign \
--signing-key-file payment.skey \
--signing-key-file policy.skey \
--script-file policy.script \
--mainnet \
--tx-body-file matx.raw \
--out-file matx.signed
cardano-cli transaction submit --tx-file  matx.signed --mainnet
cardano-cli query utxo --address $(cat payment.addr) --mainnet 
TxHash TxIx Amount
--------------------------------------------------------------------------------------
4be19689d92e95087f29cd325388b1dcf084134a567b274f102b8e64373d4a08 0 4812191 lovelace + 1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT

Send the new native asset to another address

In order to send the NFT to a wallet we need to create a transaction with 0 fees again.

cardano-cli transaction build-raw \
--mary-era \
--fee 0 \
--tx-in 4be19689d92e95087f29cd325388b1dcf084134a567b274f102b8e64373d4a08#0 \
--tx-out addr1q9e56ctpw0580099eepjwr7zzylv9fr58drncp6vrq8jnhc0qrxl65dfu4tlsjnt434y9n4np4erdxrv7jtru2kc0xvqfveu50+0+"1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--tx-out $(cat payment.addr)+4812191 \
--invalid-hereafter 0 \
--out-file sendFTtx.raw
cardano-cli transaction calculate-min-fee \
--tx-body-file sendFTtx.raw \
--tx-in-count 1 \
--tx-out-count 2 \
--witness-count 1 \
--mainnet \
--protocol-params-file protocol.json
177249 Lovelace
expr 4812191 - 177249 - 1555554
3079388
cardano-cli query tip --mainnet
{
"epoch": 267,
"hash": "b0aa033d25918da905c5e79453bd6f1bc2db067b7ae7116f7b94fa438bf20820",
"slot": 30356205,
"block": 5762935
}
expr 30356205 + 12000
30368205
cardano-cli transaction build-raw \
--mary-era \
--fee 177249 \
--tx-in 4be19689d92e95087f29cd325388b1dcf084134a567b274f102b8e64373d4a08#0 \
--tx-out addr1q9e56ctpw0580099eepjwr7zzylv9fr58drncp6vrq8jnhc0qrxl65dfu4tlsjnt434y9n4np4erdxrv7jtru2kc0xvqfveu50+1555554+"1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
--tx-out $(cat payment.addr)+3079388 \
--invalid-hereafter 30368205 \
--out-file sendFTtx.raw
cardano-cli transaction sign \
--tx-body-file sendFTtx.raw \
--signing-key-file payment.skey \
--mainnet \
--out-file sendFTtx.signed
cardano-cli transaction submit --tx-file  sendFTtx.signed --mainnet
cardano-cli query utxo --address $(cat payment.addr) --mainnet 
TxHash TxIx Amount
--------------------------------------------------------------------------------------
fab57c1a89050e4469c2aaf5dc22ae1413a6551d9a8781b879a497a1d296781a 1 3079388 lovelace

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store