Minting NFTs
note
There are many ways to realize NFTs with Cardano. However, in this guide, we will concentrate on the most dominant way, to attach storage references of other services like IPFS to our tokens.
#
What's the difference?What is the difference between native assets and NFTs?
From a technical point of view, NFTs are the same as native assets. But some additional characteristics make a native asset truly an NFT:
- As the name states - it must be 'non-fungible. This means you need to have unique identifiers or attributes attached to a token to make it distinguishable from others.
- Most of the time, NFT's should live on the blockchain forever. Therefore we need some mechanism to ensure an NFT stays unique and can not be duplicated.
#
The policyIDNative assets in Cardano feature the following characteristics:
- An amount/value (how much are there?)
- A name
- A unique
policyID
Since asset names are not unique and can be easily duplicated, Cardano NFTs need to be identified by the policyID
.
This ID is unique and attached permanently to the asset.
The policy ID stems from a policy script that defines characteristics such as who can mint tokens and when those actions can be made.
Many NFT projects make the policyID
under which the NFTs were minted publicly available, so anyone can differentiate fraudulent/duplicate NFTs from the original tokens.
Some services even offer to register your policyID
to detect tokens that feature the same attributes as your token but were minted under a different policy.
#
Metadata attributesIn addition to the unique policyID
we can also attach metadata with various attributes to a transaction.
Here is an example from nft-maker.io
{ "721": { "{policy_id}": { "{policy_name}": { "name": "<required>", "description": "<optional>", "sha256": "<required>", "type": "<required>", "image": "<required>", "location": { "ipfs": "<required>", "https": "<optional>", "arweave": "<optional>" } } } }}
Metadata helps us to display things like image URIs and stuff that truly makes it an NFT. With this workaround of attaching metadata, third-party platforms like pool.pm can easily trace back to the last minting transaction, read the metadata, and query images and attributes accordingly. The query would look something like this:
- Get asset name and
policyID
. - Look up the latest minting transaction of this asset.
- Check the metadata for label
721
. - Match the asset name and (in this case) the {policy_name}-entry.
- Query the IPFS hash and all other attributes to the corresponding entry.
note
There is currently no agreed standard as to how an NFT or the metadata is defined. However, there is a Cardano Improvement Proposal if you want to follow the discussion.
#
Time lockingSince NFTs are likely to be traded or sold, they should follow a more strict policy. Most of the time, a value is defined by the (artificial) scarcity of an asset.
You can regulate such factors with multi-signature scripts.
For this guide, we will choose the following constraints:
- There should be only one defined signature allowed to mint (or burn) the NFT.
- The signature will expire in 10000 blocks from now to leave the room if we screw something up.
#
PrerequisitesApart from the same requisites as on the minting native assets guide, we will additionally need:
- Obviously, what / how many NFTs you want to make.
--> We are going to make only one NFT - An already populated
metadata.json
- Know how your minting policy should look like.
--> Only one signature allowed (which we will create in this guide)
--> No further minting or burning of the asset allowed after 10000 blocks have passed since the transaction was made - Hash if uploaded image to IPFS
--> We will use this image
note
We recommend upload images to IPFS as it is the most common decentralized storage service. There are alternatives, but IFPS has the biggest adoption in terms of how many NFTs got minted.
#
SetupSince the creation of native assets is documented extensively in the minting chapter, we won't go into much detail here. Here's a little recap and needed setup
#
Working directoryFirst of all, we are going to set up a new working directory and change into it.
mkdir nftcd nft/
#
Set variablesWe will set important values in a more readable variable for better readability and debugging of failed transactions.
tokenname="NFT1"tokenamount="1"fee="0"output="0"ipfs_hash="please insert your ipfs hash here"
note
The IFPS hash is a key requirement and can be found once you upload your image to IPFS. Here's an example of how the IPFS looks like when an image is uploaded in pinata
#
Generate keys and addressWe will be generating new keys and a new payment address:
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 --mainnet
We will save our address hash in a variable called address.
address=$(cat payment.addr)
#
Fund the addressSubmitting transactions always require you to pay a fee.
Sending native assets 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 our example, the newly generated address was funded with 10 ada.
cardano-cli query utxo --address $address --mainnet
You should see something like this.
TxHash TxIx Amount--------------------------------------------------------------------------------------974e98c4529f8fc75fa8baf5618f7b5ade81aa9ed29ce33cd1c2f2e70838180e 0 10000000 lovelace
#
Export protocol parametersFor 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 --mainnet --out-file protocol.json
#
Creating the policyIDJust as in generating native assets, we will need to generate some policy-related files like key pairs and a policy script.
mkdir policy
note
We don’t change into this directory, and everything is done from our working directory.
Generate a new set of key pairs:
cardano-cli address key-gen \ --verification-key-file policy/policy.vkey \ --signing-key-file policy/policy.skey
Instead of only defining a single signature (as we did in the native asset minting guide), our script file needs to implement the following characteristics (which we defined above):
- Only one signature allowed
- No further minting or burning of the asset allowed after 10000 blocks have passed since the transaction was made
For this specific purpose policy.script
file which will look like this:
{ "type": "all", "scripts": [ { "type": "before", "slot": <insert slot here> }, { "type": "sig", "keyHash": "insert keyHash here" } ]}
As you can see, we need to adjust two values here, the slot
number as well as the keyHash
.
To set everything at once and copy and paste it, use this command(s):
You need to have the jq
installed to parse the tip correctly!
echo "{" >> policy/policy.scriptecho " \"type\": \"all\"," >> policy/policy.script echo " \"scripts\":" >> policy/policy.script echo " [" >> policy/policy.script echo " {" >> policy/policy.script echo " \"type\": \"before\"," >> policy/policy.script echo " \"slot\": $(expr $(cardano-cli query tip --mainnet | jq .slot?) + 10000)" >> policy/policy.scriptecho " }," >> policy/policy.script echo " {" >> policy/policy.scriptecho " \"type\": \"sig\"," >> policy/policy.script echo " \"keyHash\": \"$(cardano-cli address key-hash --payment-verification-key-file policy/policy.vkey)\"" >> policy/policy.script echo " }" >> policy/policy.scriptecho " ]" >> policy/policy.script echo "}" >> policy/policy.script
If this command is not working, please set the key hash and correct slot manually.
To generate the keyHash
, use the following command:
cardano-cli address key-hash --payment-verification-key-file policy/policy.vkey
To calculate the correct slot, query the current block and add 10000 to it:
cardano-cli query tip --mainnet
Make a new file called policy.script in the policy folder
touch policy/policy.script
Paste the JSON from above, populated with your keyHash
and your slot
number into it
nano policy/policy.script
note
Be aware the slot number is defined as an integer and therefore needs no double quotation marks, whereas the keyHash
is defined as a string and needs to be wrapped in double quotation marks.
Please take note of your slot number and save it in a variable.
slotnumber="Replace this with your slot number"
And save the location of the script file into a variable as well.
script="policy/policy.script"
The last step is to generate the policyID:
cardano-cli transaction policyid --script-file ./policy/policy.script >> policy/policyID
#
MetadataSince we now have our policy as well as our policyID
defined, we need to adjust our metadata information.
Here’s an example of the metadata.json which we’ll use for this guide:
{ "721": { "please_insert_policyID_here": { "NFT1": { "description": "This is my first NFT thanks to the Cardano foundation", "name": "Cardano foundation NFT guide token", "id": 1, "image": "" } } }}
note
The third element in the hierarchy needs to have the same name as our NFT native asset.
Save this file as metadata.json
.
If you want to generate it "on the fly," use the following commands:
echo "{" >> metadata.jsonecho " \"721\": {" >> metadata.json echo " \"$(cat policy/policyID)\": {" >> metadata.json echo " \"$(echo $tokenname)\": {" >> metadata.jsonecho " \"description\": \"This is my first NFT thanks to the Cardano foundation\"," >> metadata.jsonecho " \"name\": \"Cardano foundation NFT guide token\"," >> metadata.jsonecho " \"id\": \"1\"," >> metadata.jsonecho " \"image\": \"ipfs://$(echo $ipfs_hash)\"" >> metadata.jsonecho " }" >> metadata.jsonecho " }" >> metadata.json echo " }" >> metadata.json echo "}" >> metadata.json
note
Please make sure the image value / IFPS hash is set with the correct protocol pre-fix ipfs://
(for example "ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw")
#
Crafting the transactionLet's begin building our transaction. Before we start, we will again need some setup to make the transaction building easier. Query your payment address and take note of the different values present.
cardano-cli query utxo --address $address --mainnet
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 in lovelace here"policyid=$(cat policy/policyID)
If you're unsure, check if all of the other needed variables for the transaction are set:
echo $feeecho $addressecho $outputecho $tokenamountecho $policyidecho $tokennameecho $slotnumberecho $script
If everything is set, run this command to generate a raw transaction file.
cardano-cli transaction build-raw \--fee $fee \--tx-in $txhash#$txix \--tx-out $address+$output+"$tokenamount $policyid.$tokenname" \--mint="$tokenamount $policyid.$tokenname" \--minting-script-file $script \--metadata-json-file metadata.json \--invalid-hereafter $slotnumber \--out-file matx.raw
As with every other transaction, we need to calculate the fee and the output and save them in the corresponding variables (currently set to zero).
Use this command to set the $fee variable.
fee=$(cardano-cli transaction calculate-min-fee --tx-body-file matx.raw --tx-in-count 1 --tx-out-count 1 --witness-count 1 --mainnet --protocol-params-file protocol.json | cut -d " " -f1)
And this command calculates the correct value for $output.
output=$(expr $funds - $fee)
With the newly set values, re-issue the building of the raw transaction.
cardano-cli transaction build-raw \--fee $fee \--tx-in $txhash#$txix \--tx-out $address+$output+"$tokenamount $policyid.$tokenname" \--mint="$tokenamount $policyid.$tokenname" \--minting-script-file $script \--metadata-json-file metadata.json \--invalid-hereafter $slotnumber \--out-file matx.raw
Sign the transaction
cardano-cli transaction sign \--signing-key-file payment.skey \--signing-key-file policy/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 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 --mainnet
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 --mainnet
and should see something like this:
#
Displaying your NFTOne of the most adopted NFT browsers is pool.pm. Enter your address in the search bar, hit enter, and your NFT will be displayed with all its attributes and the corresponding image.
You can check it out yourself and see the NFT created for this tutorial here.
#
Burn your tokenIf you messed something up and want to re-start, you can always burn your token if the slot defined in your policy script isn't over yet. Assuming you have still every variable set, you need to re-set:
burnfee="0"burnoutput="0"txhash="Insert your utxo holding the NFT"txix="Insert your txix"
The transaction looks like this:
cardano-cli transaction build-raw --fee $burnfee --tx-in $txhash#$txix --tx-out $address+$burnoutput --mint="-1 $policyid.$tokenname" --minting-script-file $script --invalid-hereafter $slot --out-file burning.raw
note
The minting parameter is now called with a negative value, therefore destroying one token.
Calculate the fee to burn the token.
burnfee=$(cardano-cli transaction calculate-min-fee --tx-body-file burning.raw --tx-in-count 1 --tx-out-count 1 --witness-count 1 --mainnet --protocol-params-file protocol.json | cut -d " " -f1)
Calculate the leftovers/output.
burnoutput=$(expr $funds - $burnfee)
Re-run the transaction build.
cardano-cli transaction build-raw --fee $burnfee --tx-in $txhash#$txix --tx-out $address+$burnoutput --mint="-1 $policyid.$tokenname" --minting-script-file $script --invalid-hereafter 33005389 --out-file burning.raw
Sign the transaction.
cardano-cli transaction sign --signing-key-file payment.skey --signing-key-file policy/policy.skey --mainnet --tx-body-file burning.raw --out-file burning.signed
Full send.
cardano-cli transaction submit --tx-file burning.signed --mainnet