How to mint Cardano NFT using Smart Contract — Part I

Edward Tam
6 min readOct 27, 2021

--

Current Status

The Cardano smart contract (SC) feature has been available since12 Sep 2021 with the launch of Alonzo Hard Fork. In my opinion, it is the most important milestone of the Cardano roadmap. It makes the development of decentralization applications (Dapps) possible. And only with them, we can make real use of the Cardano blockchain.

Although the infrastructure is ready, developers still have a lot to do building their Dapps. The first thing is to write smart contracts and deploy them on the network. Then the Dapps has to interact with the smart contracts in order generate the outputs they want.

As the smart contract feature is very new, good documentations or tutorials are hard to find. So, in the following, we will talk about our experience of:

  1. write a simple SC
  2. deploy it on the testnet
  3. interact with the SC to produce the expected output

Since it involves quite lot of steps, I will break them down into 2 parts. 1st part is to familiarize ourselves on how to deploy a simple SC and interact with it. 2nd part is to extend what we’ve learned and make a SC to mint NFT.

Smart Contract Basics

Cardano blockchain is adopting the Extended unspent transaction output (EUTXO) accounting model for its ledger which is in contrast to the account-based model of Ethereum. Please refer to this article for more details.

Simply speaking, smart contract of Cardano is a validating script that can determine whether to release the native assets (i.e. can be ADA or fungible/non-fungible tokens) stored inside based on the following inputs:

  • Datum
  • Redeemer
  • Script context

Plutus is the smart contract platform of the Cardano blockchain. It allows us to write applications that interact with the Cardano blockchain. (that’s why Cardano SC is also called Plutus script). More explanations can be found here. Haskell is the programming language for Plutus. So we need to learn Haskell in order to write Cardano SC. Haskell programming is out of our discussion here but there is an excellent online tutorial available.

Try out Plutus Transactions

For new tech knowledge like this, the best way to learn is by trial and error. So, let’s try out a simplest contract that checks nothing (always return true), deploy it on the testnet to store some ADA and finally send a transaction to release the ADA stored inside.

Step 1 — Run Cardano Node

Before we can interact with the Cardano blockchain, we need to run our Cardano node. Fortunately, the hardware requirements are not too high (Linux/Mac/Windows with 2CPU, 8GB RAM and 10GB disk space is enough) and the installation procedure is quite straight forward. Please refer to this guide for details. Since this is for testing purpose, we will setup one for the testnet. We can get some tAda from the faucet.

Also to compile Haskell SC, we need to have cabal, nix & ghc.

Also, please note that it will need a long time to compile the Haskell source code but this can be speeded up by setting up the binary cache maintained by IOHK

Step 2 — Develop “alwayssucceeds” SC

As this article doesn’t focus on Haskell programming, we just use a simple “alwayssucceeds” SC here provided by IOHK.

{-# INLINABLE mkValidator #-}
mkValidator :: BuiltinData -> BuiltinData -> BuiltinData -> ()mkValidator _ _ _ = ()

As you can see the above, this validator (Plutus script) will pass checking regardless of the values of “Datum”, “Redeemer” & “Script context”.

Step 3— Build and Run SC

Build the smart contract by typing the following under the folder

Alonzo-testnet/resources/plutus-sources/plutus-alwayssucceeds

cabal update
cabal build

Then we can run the script by passing in the value of datum (just pick a random no) and the output file name (for the compiled script)

cabal run plutus-alwayssucceeds -- 30 alwayssucceeds.plutus

If things get smooth, you will see something like

Up to dateWriting output to: alwayssucceeds.plutus"Log output"[]"Ex Budget"ExBudget {exBudgetCPU = ExCPU 297830, exBudgetMemory = ExMemory 1100}

And the output plutus script (alwayssucceeds.plutus) will be generated.

Step 4 — generate payment addresses

In this exercise, we need 2 payment addresses. One(payment1.addr) is used to fund the SC and the other one (payment2.addr) is used to unlock the ADA inside the SC.

The commands used to create payment address are:

cardano-cli address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey
cardano-cli stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey
cardano-cli address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--out-file payment.addr \
--testnet-magic 1097911063

Step 5 — Lock Fund on SC

Using the Plutus script (alwayssucceeds.plutus) in step 2, we generate the script address

cardano-cli address build \
--payment-script-file alwayssucceeds.plutus \
--testnet-magic 1097911063 \
--out-file script.addr

With this we can lock ADA (1379280 lovelace) on this script by building a transaction

cardano-cli transaction build \
--alonzo-era \
--testnet-magic 1097911063 \
--change-address $(cat payment.addr) \
--tx-in 250a2d35fcb6bd1a19f1fbdc35d760947ef17f02ff758f79dd0074a31523048f#0 \
--tx-out $(cat script.addr)+1379280 \
--tx-out-datum-hash ${scriptdatumhash} \
--protocol-params-file pparams.json \
--out-file tx-script.build

where tx-in is the utxo from payment.addr and pparams.json is the configuration files for testnet which can be generated by

cardano-cli query protocol-parameters \
--testnet-magic 1097911063 \
--out-file pparams.json

and $scriptdatumhash is the hash value of datum which can be generated by

cardano-cli transaction hash-script-data --script-data-value 30

After transaction is built, we can sign and submit

cardano-cli transaction sign \
--tx-body-file tx-script.build \
--signing-key-file payment.skey \
--testnet-magic 1097911063 \
--out-file tx-script.signed
cardano-cli transaction submit --testnet-magic 1097911063 --tx-file tx-script.signed

After submission, we can query the script address

cardano-cli query utxo --address $(cat script.addr) --testnet-magic 1097911063

and we will get something like

TxHash                                                            TxIx  Amount
--------------------------------------------------------------------------------------
f5a618d579bc66e6199ae2a1ab4a73e2d8a73cba61a324c939346e9cf32bb33a 1 1379280 lovelace + TxOutDatumHash ScriptDataInAlonzoEra "7c7c0bf83e0ed45faf3976a5ee19b4ef8bd069baab4275425161ac89d492bf82"

We can see that this script has a utxo f5a618d579bc66e6199ae2a1ab4a73e2d8a73cba61a324c939346e9cf32bb33a#1 with 1379280 lovelace and the datum is 7c7c0bf83e0ed45faf3976a5ee19b4ef8bd069baab4275425161ac89d492bf82 (hash value of 30)

Step 6— Unlock Fund on SC

Finally, we will try to unlock the fund stored by executing the plutus script.

We will build the transaction using another payment address (payment2.addr):

cardano-cli transaction build \
--alonzo-era \
--testnet-magic 1097911063 \
--tx-in ${plutusutxotxin} \
--tx-in-script-file alwayssucceeds.plutus \
--tx-in-datum-value 30 \
--tx-in-redeemer-value 30 \
--tx-in-collateral ${txCollateral} \
--change-address $(cat payment2.addr) \
--protocol-params-file pparams.json \
--out-file test-alonzo.tx

where: $plutusutxotxin is the utxo of plutus script and $txCollateral is the utxo of payment2.addr used for collateral.

If successful, can sign and submit the transaction:

cardano-cli transaction sign \
--tx-body-file test-alonzo.tx \
--signing-key-file payment2.skey \
--testnet-magic 1097911063 \
--out-file tx-script.signed
cardano-cli transaction submit --testnet-magic 1097911063 --tx-file tx-script.signed

We can check whether the ADA is released by querying payment2.addr

cardano-cli query utxo --address $(cat payment2.addr) --testnet-magic 1097911063

The below shows ADA 1209070 (1379280 with txn fee deduced) is released to payment2.addr

TxHash                                 TxIx        Amount--------------------------------------------------------------------------------------41c3196da3a24b78fc3daac41603d2cd18e082a590f057b933dfbf0e356a8ed1     0        500000000 lovelace + TxOutDatumHashNonec82067621e41e6a10f8393a468df62186755d8b380bfd7a98c19fdc5c81a3e67     0        1209070 lovelace + TxOutDatumHashNone

To be Continued

To summarize, what we’ve done so far:

  1. build and run a SC
  2. send some native assets (i.e. ADA) to this SC
  3. execute this SC to release the native assets

The above shows how Cardano SC works in practice. But this SC virtually has no use as it checks nothing and releases assets whenever it’s called. In the next article, we are going to write a SC that has real usage (i.e. mint non-fungible tokens). Please stay tuned.

References

Cardano EUTXO model
What’s plutus script
Introduction to Haskell
Installation of Cardano node
Testnet Faucet
Intallation of cabal, nix & ghc
IOHK binary cache
Always succeeds Plutus script

--

--

Edward Tam

Owner of IT companies. Blockchain/Crypto enthusiast. Interested in Cardano, Chainlink, Ethereum, Hyperledger,……