How to create NFTs on Cardano

In this guide we’ll show you how to create an NFT (Non Fungible Tokens) on Cardano. The steps will guide you through the minting process to finally sending it to your wallet.

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.

If you don’t have a Cardano node installed you can follow this guide https://cardano-node-installation.stakepool247.eu/

You can get all the commands in this article from our repo https://github.com/tango-crypto/create-nft-cli/blob/main/create-nft-cli.txt

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

Then generate the payment address:

cardano-cli address build \
--payment-verification-key-file payment.vkey \
--out-file payment.addr \
--mainnet

The payment address is something like this. You can use the cat command to see the content of the file:

cat payment.addr
addr1vyaen9j2c2tkqwa3np8leruh2ykcxn9q5prwjyktupm0d0cg2ymdc

Now check the current UTXOs (Unspent Transaction Outputs) of your address:

cardano-cli query utxo --address $(cat payment.addr) --mainnet 

This should output something like this:

TxHash                                 TxIx        Amount
-------------------------------------------------------------------------


This means you did not have any transactions on your address. Now you need to fund your address, for that go to your wallet and transfer the amount you like to this address, in this case I’m going to transfer 5 Ada. After you sent the Ada check the UTXO again to see if you have it.

cardano-cli query utxo --address $(cat payment.addr) --mainnet 

Not the output should output something like this:

cardano-cli query utxo --address $(cat payment.addr) --mainnet 
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
58b7d31015482e4aefa834c5ec4911bd6952ef86bec6d77689f9b7a6bf4e9305     0        5000000 lovelace

I transferred 5 Ada to my address therefore I have 5000000 Lovelace. The amount is always displayed in Lovelace where 1 Ada = 1,000,000 Lovelace.

To build a transaction and calculate the fees to mint the tokens, we need the current protocol parameters. Let’s query them and save them in a file called protocol.json to reference later on.

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.

Only those in possession of the policy keys can mint or burn tokens, minted under this specific policy.

First generate a payment key pair, you will need two keys two create your policy, so let’s do that:

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

This creates two files:

  • policy.vkey (the public verification key)
  • policy.skey (the private signing key)

Now you are able to obtain the hash of the public verification key:

cardano-cli address key-hash --payment-verification-key-file policy.vkey 
86c4c595371738281d374fa4fa7180b0d3adf56a7eb9ea2d9cbab109

We have to query the mainnet to know the slot we are right now

cardano-cli query tip --mainnet
{
    "epoch": 267,
    "hash": "ac54780aa50aa6680c682851af87af6b0af7790fe96429d200ac4d60b3737b1c",
    "slot": 30106142,
    "block": 5750431
}

With the hash and the slot we can to create the policy.script, this script indicates the type of signing that is required to issue tokens, the hash of the public verification key and a time locking. With this we are saying that the signature is going to be valid before slot 30106442, this will give us aprox 5 min to create the NFT, after that slot we can’t mint it even if we use the same key.

cat policy.script
{
    "type": "all",
    "scripts": [
      {
        "keyHash": "86c4c595371738281d374fa4fa7180b0d3adf56a7eb9ea2d9cbab109",
        "type": "sig"
      },
      {
        "type": "before",
        "slot": 30106442
      }
    ]
}

Here you indicate with sig that only a single signature is needed to issue new tokens, meaning the policy key signing.

This minting policy grants the right to mint tokens to a single key, it any transaction that mints tokens to be witnessed by the key with the hash 86c4c595371738281d374fa4fa7180b0d3adf56a7eb9ea2d9cbab109.

Now, let’s get the policy ID out of your policy script:

cardano-cli transaction policyid --script-file policy.script 
6596e958394d65d378086b7cdff00ff823d2463dd9d3f0739c73275b

Right now, the official wallets don’t provide support of images, luckily some community tools like pool.pm are able to provide that functionality by adding metadata in a specific format. Metadata helps us to display things like image URIs and links to the artist producing the NFT.

The structure is the following, it allows for multiple token mints even using different policies, in a single transaction.

{
    "721": {
      "<policy_id>": {
        "<asset_name>": {
          "name": "<name>",
          "image": "<uri>",
          "description": "<description>"
  
          "type": "<mime_type>",
          "src": "<uri>"
  
          <other properties>
        },
        ...
      },
      ...,
      "version": "1.0"
    }
}

For more info visit https://github.com/Berry-Pool/NFT-Metadata-Standard-CIP/blob/main/CIP-NFTMetadataStandard.md

One thing to to take into consideration is the name is case sensitive and need to match the names in the minting transaction, in our case the name is “TangoNFT” . For the image, we are using the IPFS (InterPlanetary File System) service Pinata (https://pinata.cloud/). You can create an account in a few steps and upload the image for your NFT. Once uploaded it will generate a CID (Content Identifier) this is a hash that will identify your file in the distributed file system. You don’t have to worry about the inner details of how this works, just upload an image and copy the hash in your metadata file.

Here’s an example of my metadata.json which we’ll use for this guide, you can see the policy ID at the beginning and image tag pointing to the hash in the IPFS:

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.

Making a transaction in Cardano is a four-step process.

  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.

Let’s see what we have to do in each one of these steps:

1. Build the transaction with 0 fees

In Cardano each transaction has one or multiple inputs (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 our address.

Here the code we are going to use to create the raw transaction:

cardano-cli transaction build-raw \
  --fee 0 \
  --tx-in 58b7d31015482e4aefa834c5ec4911bd6952ef86bec6d77689f9b7a6bf4e9305#0 \
  --tx-out $(cat payment.addr)+5000000+"1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
  --mint="1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT" \
  --minting-script-file \
  --metadata-json-file metadata.json \
  --out-file matx.raw

Let’s get under the hood and understand the syntax for each parameter:

  • --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>

And as we saw earlier the data the data for filling this comes from the output of the command cardano-cli query utxo --address $(cat payment.addr) --mainnet

  • --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: specifies the value to be minted or burnt. The same syntax as specified in — tx-out but without the address and output

--mint "<token amount> <policy ID>.TokenName"
  • --mint: Amount of tokens to mint. In this case we are just minting 1 token.
  • --minting-script-file: The path to our policy.script file.
  • --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.

2. Calculate the minimum fee

Now we have the raw transaction, the next step is to calculate the transaction fees:

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

The calculation needs the raw transaction, the number of transaction inputs, the number of transaction outputs, the number of keys needed to sign the transaction, and the network parameters.

3. Build the transaction again including the fee

To calculate the remaining output, we need to subtract the fee from our funds and save the result in our output variable.

expr 5000000 - 187809
4812191

The transaction will now include the fee and the UTXO inputs must match outputs and all inputs must be fully spent, therefore we consume the 5000000 Lovelace and get in return a new UTXO with 4812191 Lovelace because of the transaction fee.

It is important to include the --invalid-hereafter parameter with the same slot we choose when we defined the policy.script:

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" \
 --minting-script-file policy.script \ 
 --metadata-json-file metadata.json \
  --invalid-hereafter=30106442\
  --out-file matx.raw

4. Sign the transaction and submit it

Transactions need to be signed to prove authenticity and ownership of the policy private signing key. We need witnesses from two keys – one to spend the input UTXO, and one to satisfy the minting policy script. Here, payment.skey is the key that allows spending from UTXO, and policy.skey is the key that hashes to the value specified in the policy.script.

cardano-cli transaction sign \
  --signing-key-file payment.skey \
  --signing-key-file policy.skey \
  --mainnet \
  --tx-body-file matx.raw \
  --out-file matx.signed

Note: the signed transaction will be saved in a new file called matx.signed.

And finally submit the transaction, which is basically minting the tokens, if you don’t get any response, that is usually a sign that everything worked.

cardano-cli transaction submit --tx-file  matx.signed --mainnet

Let’s check the address again and now we can see the token there:

cardano-cli query utxo --address $(cat payment.addr) --mainnet 
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
4be19689d92e95087f29cd325388b1dcf084134a567b274f102b8e64373d4a08     0        4812191 lovelace + 1 fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT

5. Check that everything worked out as intended

If you go to https://pool.pm/tokens you’ll see your NFT there, for this example the link is this:

https://pool.pm/fcd7249ddd5bae5d98351bedd56eca999541589c872ef20b77d04ad7.TangoNFT

And that’s it we have created our NFT 😊.

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

Let’s calculate the minimum fee.

  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

The minimum fee was 177249 Lovelace.

It is impossible to make outputs containing only custom tokens therefore we need to take into consideration the minimum amount of Ada required in a transaction. For 1 policy ID and one 32-character asset name, the minimum is 1555554 Lovelace. For more details on how to calculate this value check this link https://github.com/input-output-hk/cardano-ledger-specs/blob/master/doc/explanations/min-utxo.rst

Our address has 4812191 Lovelace and we need to build the transaction with the amount of Lovelace we are getting back to our sending address. That would be the actual amount we have on the UTXO minus the fee and the minimum minus the minimum amount of Ada required in a transaction.

expr 4812191 - 177249 - 1555554
3079388

Let’s see the current slot and add some time extra.

cardano-cli query tip --mainnet
  {
      "epoch": 267,
      "hash": "b0aa033d25918da905c5e79453bd6f1bc2db067b7ae7116f7b94fa438bf20820",
      "slot": 30356205,
      "block": 5762935
  }

  expr 30356205 + 12000
  30368205

Then we build the transaction with all the calculated parameters.

  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

Sign the transaction with the private signing key.

  cardano-cli transaction sign \
  --tx-body-file sendFTtx.raw \
  --signing-key-file payment.skey \
  --mainnet \
  --out-file sendFTtx.signed

And finally we submit the transaction.

cardano-cli transaction submit --tx-file  sendFTtx.signed --mainnet

If we check the UTXO for the address we created the NFT we’ll see it’s not there anymore.

cardano-cli query utxo --address $(cat payment.addr) --mainnet 
                           TxHash                               TxIx   Amount
--------------------------------------------------------------------------------------
fab57c1a89050e4469c2aaf5dc22ae1413a6551d9a8781b879a497a1d296781a  1    3079388 lovelace

Here we can see the transaction in cardano explorer

And finally we can check we received the NFT in our Daedalus Wallet.

And that’s it, we minted an NFT and send it to our wallet. We hope you like this tutorial and if you have any doubt just email us and we are going to help you in anything you need.