How to mint an NFT on Cardano Testnet using the command line
Table of contents
- Why do this?
- Alternatives to this method
- Create a Cardano Node
- Start the Cardano Node and Wallet
- Create a folder to hold everything
- Create the digital asset the NFT represents
- Put your NFT onto IPFS
- Generate the payment keys
- Generate an address of the payment keys
- Fund the address
- Export protocol parameters
- Generate the policy keys
- Write the Policy Script
- Generate the PolicyID
- Encode the NFT’s name in base-16
- Create the metadata
- Gather information needed to build the transaction
- Get recipient’s address
- Estimate the fee and slot target
- Make sure all variables are set
- Build the transaction
- Sign the transaction
- Submit the transaction to the blockchain
- Confirm it worked
- Return test ADA to the faucet
- Do it on the Mainnet
This is a step-by-step guide showing how to mint an NFT on the Cardano Testnet blockchain using the command line (as of December 2021).
Why do this?
You have a digital asset that you predict others will want to buy. It can be either unique where each asset has its own digital serial number (meaning it’s non-fungible and can be represented by an NFT), or an asset where each of them have equal value (similar to physical coins like nickles/dimes/quarters) and can be represented by a Native Asset.
Alternatives to this method
This article shows how to create an NFT using the command line. It’s the method that gives you complete (low level) control over every little detail from start to finish. This is good if you need that level of control, or if you’re trying to learn how everything fits together from scratch so you can understand and appreciate the technology. But it involves downloading a copy of the full blockchain and keeping it up-to-date.
An alternative is to use NamiWallet which can be done from your web browser. But at the moment, it’s very limited in features. You can only specify the name, quantity, author, and upload a photo. You don’t have control over the meta data.
Create a Cardano Node
Method 1: Daedalus Wallet
If you’re using an operating system that has a full desktop environment like Windows or Mac (as opposed to one that’s solely commandline), you can install the Daedalus Wallet for Cardano Mainnet and/or Cardano Testnet . It will download the entire blockchain.
The Daedalus Wallet comes with cardano-cli
and cardano-wallet
which we’ll be using
throughout this guide. You’ll need to create an environment variable
for this to work.
Method 2: Cardano Node
If you’re using an operating system that only has a commandline/shell like Ubuntu Server, see this guide: How to create a Cardano Testnet node in Linux
In my case, I use Daedalus Wallet on Windows for the mainnet, and Ubuntu Server 20.04 LTS inside a virtual machine for the testnet. This keeps my testing environment isolated from my production (Mainnet) environment, hopefully preventing accidents.
Start the Cardano Node and Wallet
Method 1: Daedalus Wallet
Open Daedalus Wallet, wait for it to 100% synchronize with the blockchain, and keep it running. As a reminder, you’ll need to create an environment variable for this to work.
Open a terminal/shell or Command Prompt and navigate to the Daedalus Wallet folder.
Method 2: Cardano Node
NOTE: If you just finished the How to create a Cardano Testnet node in Linux guide and already have the Cardano Node and Cardano Wallet running, you can skip this step.
Open three shells. This could be either three Command Prompts (if using Windows) or three terminal windows/shells (if using Linux or Mac).
In the first shell, start the Cardano Node. This is what keeps your local copy of the Testnet blockchain up-to-date.
cardano-node run \
--topology ~/cardano/configuration/testnet/testnet-topology.json \
--database-path ~/cardano/testnet-db \
--socket-path ~/cardano/testnet-db/node.socket \
--host-addr 127.0.0.1 \
--port 3001 \
--config ~/cardano/configuration/testnet/testnet-config.json
In the second shell, start the Cardano Wallet server. This is what lets you interact with the node.
cardano-wallet serve \
--port 8090 \
--database ~/cardano/testnet-db \
--node-socket $CARDANO_NODE_SOCKET_PATH \
--testnet ~/cardano/configuration/testnet/testnet-byron-genesis.json
You’ll use the third shell for everything else point forward. Keep the above two shells open.
Create a folder to hold everything
We’re going to store our keys and metadata in a new folder to keep things
organized. Our NFT will be named TestNFT1234
.
mkdir -p ~/cardano/nft/TestNFT1234/policy
cd ~/cardano/nft/TestNFT1234
Create the digital asset the NFT represents
At some point in the future, NFTs will be more than just a photo of something. For now, this is typically an image. For example: https://ipfs.io/ipfs/QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw
Put your NFT onto IPFS
Put your NFT resource (the image or whatever it is) onto IPFS. See this guide: Types of IPFS links and when to use them and How to Use IPFS: The Backbone of Web3 .
In this example, we’re using the image already uploaded by the person who wrote
the official Cardano NFT guide: ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw
(view image on ipfs.io
).
Generate the payment keys
Run this. Never share the contents of these two files with anyone.
cardano-cli address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey
Generate an address of the payment keys
Run this to generate the address and write it to a file called payment.addr
.
In this example, my payment address ends up being
addr_test1vzcwn6u3fephqacec4g6fr9gpsp9znpr5hsk9ggcfarn23sthac04
.
cardano-cli address build \
--payment-verification-key-file payment.vkey \
--out-file payment.addr \
--testnet-magic 1097911063
To make things easy point-forward, we’ll store the address in a variable. When
we run commands later, we can say $payment_address
instead of typing/pasting
the entire address.
payment_address=$(cat payment.addr)
When you see --testnet-magic 1097911063
, it means we’re intereacting with
the Testnet rather than the Mainnet (which uses --mainnet
without any
numbers). Why is it 1097911063?
It’s the network identifier of the Testnet.
Fund the address
You should have at least 2 ADA in your payment address above to mint a token. If you were on the Mainnet, you’d transfer some funds to that address from your wallet or an exchange.
Since we’re on the Testnet, we can use the faucet to fund this account. Request test tokens using the ADA faucet to immediately get 1,000 tAda (test ADA). Send it to the payment address you just created.
See how much lovelace is in our account (1,000,000 lovelace = 1 ADA).
cardano-cli query utxo \
--address $payment_address \
--testnet-magic 1097911063
Here’s what it might look like:
TxHash TxIx Amount
--------------------------------------------------------------------------------------
0ba233973412b629d8d1bcf6cf219a83c7cf16b59f84c1f2c488d80c5d0404dd 0 1000000000 lovelace + TxOutDatumNone
Export protocol parameters
We need current protocol parameters for when we calculate transactions.
bash
cardano-cli query protocol-parameters \
--testnet-magic 1097911063 \
--out-file protocol.json
This generates a 10KB file called protocol.json
that contains information
like this:
{
"txFeePerByte": 44,
"minUTxOValue": null,
"decentralization": 0,
"utxoCostPerWord": 34482,
"stakePoolDeposit": 500000000,
"poolRetireMaxEpoch": 18,
"extraPraosEntropy": null,
"collateralPercentage": 150,
"stakePoolTargetNum": 500,
"maxBlockBodySize": 73728,
"minPoolCost": 340000000,
"maxTxSize": 16384,
"treasuryCut": 0.2,
"maxBlockExecutionUnits": {
"memory": 50000000,
"steps": 40000000000
},
"maxCollateralInputs": 3,
"maxValueSize": 5000,
"maxBlockHeaderSize": 1100,
"maxTxExecutionUnits": {
"memory": 12500000,
"steps": 10000000000
},
"costModels": {
"PlutusScriptV1": {
"cekConstCost-exBudgetMemory": 100,
"unBData-cpu-arguments": 150000,
"divideInteger-memory-arguments-minimum": 1,
"nullList-cpu-arguments": 150000,
"cekDelayCost-exBudgetMemory": 100,
...
}
},
"protocolVersion": {
"minor": 0,
"major": 6
},
"txFeeFixed": 155381,
"stakeAddressDeposit": 2000000,
"monetaryExpansion": 3.0e-3,
"poolPledgeInfluence": 0.3,
"executionUnitPrices": {
"priceSteps": 7.21e-5,
"priceMemory": 5.77e-2
}
}
Generate the policy keys
Run this. Never share the contents of these two files with anyone.
cardano-cli address key-gen \
--verification-key-file policy/policy.vkey \
--signing-key-file policy/policy.skey
Write the Policy Script
Run the following to generate the policy.script
file.
echo "{" >> policy/policy.script
echo " \"type\": \"all\"," >> policy/policy.script
echo " \"scripts\":" >> policy/policy.script
echo " [" >> policy/policy.script
echo " {" >> policy/policy.script
echo " \"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.script
echo " ]" >> policy/policy.script
echo "}" >> policy/policy.script
It will look something like this:
{
"type": "all",
"scripts": [
{
"type": "sig",
"keyHash": "6d1321490cde0b447794edff9239cf0668ae01f0df33823d0a3bbcf7"
}
]
}
Store the script filename in a variable.
script="policy/policy.script"
Generate the PolicyID
Run this to generate the unique PolicyID hash using the policy script.
cardano-cli transaction policyid \
--script-file ./policy/policy.script >> policy/policyID
In this example, the Policy ID is 155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7c
.
Store the PolicyID in a variable.
policyid=$(cat policy/policyID)
Encode the NFT’s name in base-16
Encode your NFT’s name using base-16 (hexadecimal). You can use the following command in Linux:
echo -n "TestNFT1234" | xxd -ps
Store this in a variable. We’re only creating one NFT in this example so the Token Amount variable is set accordingly.
tokenname="546573744e465431323334"
tokenamount=1
Create the metadata
Create the metadata for your NFT. See https://github.com/cardano-foundation/CIPs/tree/master/CIP-0025 for the latest structure.
Save it as metadata.json
.
nano metadata.json
In the following example:
155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7c
is the PolicyID546573744e465431323334
is the asset name (TestNFT1234
) encoded in base-16 (hexadecimal)721
is the key the community is using to signify this is an NFT
{
"721": {
"155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7c": {
"546573744e465431323334": {
"name": "TestNFT1234",
"image": [
"https://ipfs.io/ipfs/",
"QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw"
],
"mediaType": "image/png",
"description": "Just another fun NFT"
}
},
"version": "1.0"
}
}
If we didn’t split the “image” attribute into an array, we would’ve gotten this error:
Command failed: transaction build Error: Error reading metadata at: “metadata.json” Value out of range within the metadata item 721: [contents of metadata.json here] > Text string metadata value must consist of at most 64 UTF8 bytes, but it consists of 67 bytes.
Gather information needed to build the transaction
Run this.
cardano-cli query utxo \
--address $payment_address \
--testnet-magic 1097911063
Store the value of TxHash and TxIx into variables.
txhash="0ba233973412b629d8d1bcf6cf219a83c7cf16b59f84c1f2c488d80c5d0404dd"
txix="0"
Get recipient’s address
Determine who will be the first recipient of this NFT. It might be your own personal wallet. Whatever the case, get the wallet address for that person.
first_nft_owner_address="addr_test1qqwtrpkkh8a60pjjljn6n90mzdf6fa9wempj8s6t732xh4deudejw0dvq2hyrty7htt02xepkuq65w0peun9wx5kxpuskcgwl9"
Estimate the fee and slot target
We don’t yet know how much Lovelace this will cost. Start with 1400000 lovelace (1.4 ADA), which is the minimum UTxO requirement. If it ends up costing more because the metadata is so large, we’ll be forced to adjust it later.
fee=1400000
Make sure all variables are set
Run the following and look at the output. Make sure everything has a value.
echo [payment_address] NFT will be generated by payment address \"$payment_address\"
echo [first_nft_owner_address] NFT will be sent to address \"$first_nft_owner_address\"
echo [fee] This is estimated to cost \"$fee\" lovelace
echo [policyid] The PolicyID is \"$policyid\"
echo [tokenname] The token name in hexadecimal is \"$tokenname\"
echo [tokenamount] We are going to mint \"$tokenamount\" tokens
echo [script] The policy script is \"$script\"
echo [txhash][txix] This transaction will be funded by TxHash \"$txhash\" using TxIx \"$txix\"
In this example, here’s the output (with the commands removed to make it easier to read):
[payment_address] NFT will be generated by payment address "addr_test1vzcwn6u3fephqacec4g6fr9gpsp9znpr5hsk9ggcfarn23sthac04"
[first_nft_owner_address] NFT will be sent to address "addr_test1qqwtrpkkh8a60pjjljn6n90mzdf6fa9wempj8s6t732xh4deudejw0dvq2hyrty7htt02xepkuq65w0peun9wx5kxpuskcgwl9"
[fee] This is estimated to cost "1400000" lovelace
[policyid] The PolicyID is "155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7c"
[tokenname] The token name in hexadecimal is "546573744e465431323334"
[tokenamount] We are going to mint "1" tokens
[script] The policy script is "policy/policy.script"
[txhash][txix] This transaction will be funded by TxHash "0ba233973412b629d8d1bcf6cf219a83c7cf16b59f84c1f2c488d80c5d0404dd" using TxIx "0"
Build the transaction
We’re now going to build the transaction and store it inside a file called
matx.raw
.
cardano-cli transaction build \
--testnet-magic 1097911063 \
--alonzo-era \
--tx-in $txhash#$txix \
--tx-out $first_nft_owner_address+$fee+"$tokenamount $policyid.$tokenname" \
--change-address $payment_address \
--mint="$tokenamount $policyid.$tokenname" \
--minting-script-file $script --metadata-json-file metadata.json \
--witness-override 2
--out-file matx.raw
IMPORTANT:
If you set the --change-address
value to $first_nft_owner_address
, your entire
payment wallet’s ADA balance will be sent to that address. If you own it, it’s not
a problem.
For example, assume $first_nft_owner_address is your main wallet and you
transferred 10 ADA from $first_nft_owner_address to $payment_address for the sake
of minting an NFT. Minting the NFT might actually cost 1.590889 ADA. If you
want to return the remaining 8.6 ADA to your main wallet, set --change-address
to $first_nft_owner_address. But if you want to mint another NFT and keep the
8.6 ADA in that temporary wallet, set it to $payment_address.
If you get a message saying something like “Minimum required UTxO: Lovelace 1448244
”,
change the fee to that amount. Otherwise, continue without making any changes. In this
example, it says Estimated transaction fee: Lovelace 190889
so we’re all good.
fee=1448244 # Run this only if you got the above warning
If you get a message saying that you don’t have enough funds to cover the cost of
the transaction, you can specify a second --tx-in
entry. For example, when we run
cardano-cli query utxo --address $address --testnet-magic 1097911063
, it shows
the following. By specifying a second --tx-in
entry, I can combine the funds.
TxHash TxIx Amount
--------------------------------------------------------------------------------------
0ba233973412b629d8d1bcf6cf219a83c7cf16b59f84c1f2c488d80c5d0404dd 0 50000000 lovelace + TxOutDatumNone
77300dc0598bdc8268a706705ee7521f76bf29cdd9d0c7580b69c660f0f38bec 0 48220906 lovelace + TxOutDatumNone
For those curious, the file looks like this:
{
"type": "TxBodyAlonzo",
"description": "",
"cborHex": "86a700818258200ba233973412b629d8d1bcf6cf219a83c7cf16b59f84c1f
2c488d80c5d0404dd000d800182825839001cb186d6b9fba78652fca7a995fb1353a4f4ae
cec323c34bf4546bd5b9e373273dac02ae41ac9ebad6f51b21b701aa39e1cf26571a96307
91a3b828397825839001cb186d6b9fba78652fca7a995fb1353a4f4aecec323c34bf4546b
d5b9e373273dac02ae41ac9ebad6f51b21b701aa39e1cf26571a963079821a00155cc0a15
81c155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7ca14b546573744e
46543132333401021a0002e9a90e8009a1581c155f313f864d009d7b0b519b1295c9fab7b
75c17cfc5ba1aed501b7ca14b546573744e465431323334010758206a46c80316d784a2b4
936540adca437283d949a52878616b9494eb60279878779f82008201818200581c6d13214
90cde0b447794edff9239cf0668ae01f0df33823d0a3bbcf7ff8080f5d90103a100a11902
d1a2783831353566333133663836346430303964376230623531396231323935633966616
23762373563313763666335626131616564353031623763a1763534363537333734346534
3635343331333233333334a46b6465736372697074696f6e744a75737420616e6f7468657
22066756e204e465465696d616765827568747470733a2f2f697066732e696f2f69706673
2f782e516d5268545462557250594577336d4a4747685171515354396b383676314450426
95454574a474b444a73564677696d656469615479706569696d6167652f706e67646e616d
656b546573744e4654313233346776657273696f6e63312e30"
}
To see what this says:
- Go to cbor.me
- Paste the value of
cborHex
into the box on the right - Check the four checkboxes above that box (as text, utf8, emb cbor, cborseq)
- Click the green ← arrow next to the word “Bytes”
The left box will now contain something like this:
[{0: [[h'0BA233973412B629D8D1BCF6CF219A83C7CF16B59F84C1F2C488D80C5D0404DD', 0]],
13: [], 1: [[h'001CB186D6B9FBA78652FCA7A995FB1353A4F4AECEC323C34BF4546BD5B9E3732
73DAC02AE41AC9EBAD6F51B21B701AA39E1CF26571A963079', 998409111], [h'001CB186D6B9F
BA78652FCA7A995FB1353A4F4AECEC323C34BF4546BD5B9E373273DAC02AE41AC9EBAD6F51B21B70
1AA39E1CF26571A963079', [1400000, {h'155F313F864D009D7B0B519B1295C9FAB7B75C17CFC
5BA1AED501B7C': {'TestNFT1234': 1}}]]], 2: 190889, 14: [], 9: {h'155F313F864D009
D7B0B519B1295C9FAB7B75C17CFC5BA1AED501B7C': {'TestNFT1234': 1}}, 7: h'6A46C80316
D784A2B4936540ADCA437283D949A52878616B9494EB6027987877'}, [[0, [1, [[0, h'6D1321
490CDE0B447794EDFF9239CF0668AE01F0DF33823D0A3BBCF7']]]]], [], [], true, 259({0:
{721: {"155f313f864d009d7b0b519b1295c9fab7b75c17cfc5ba1aed501b7c": {"546573744e4
65431323334": {"description": "Just another fun NFT", "image": ["https://ipfs.io
/ipfs/", "QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw"], "mediaType": "image/
png", "name": "TestNFT1234"}}, "version": "1.0"}}})]
It’s obvious what all of that means, so I won’t bother explaining it. 🙃 You can view the Cbor.hs source code if you’re interested in going down that rabbit hole.
Sign the transaction
To prove we authorize this transaction, we must sign it using the payment key and policy key we generated earlier. Remember, never share these keys with anyone.
cardano-cli transaction sign \
--signing-key-file payment.skey \
--signing-key-file policy/policy.skey \
--testnet-magic 1097911063 \
--tx-body-file matx.raw \
--out-file matx.signed
Submit the transaction to the blockchain
Up until this point, we’ve just been reading from the Cardano Testnet blockchain. But once we execute the command below, we’re writing to the blockchain. There’s no going back.
cardano-cli transaction submit \
--tx-file matx.signed \
--testnet-magic 1097911063
You should see the message:
Transaction successfully submitted.
Confirm it worked
Go to https://testnet.cardanoscan.io
and search
for the wallet address (the one stored in $first_nft_owner_address
). Use the
dropdown list to view the tokens.
If you don’t see it yet, wait 10-30 seconds and check again.
Return test ADA to the faucet
If you’re done, it’s recommended you return any remaining tAda to the faucet.
Do it on the Mainnet
If everything worked the way you expected, you can now do it on the
Mainnet. Replace --testnet-magic 1097911063
with --mainnet
in all
of the commands throughout this guide.