Create True NFT with Plutus

Juhel Phanju

NFT (Non-Fungible Tokens) are the unique token whose amount is one, i.e. there can only exists one NFT in a network. The NFTs can be minted only once and amount can only one to prevent duplicity.

The NFT is minted similar to the native assets but has amount as one and minted only once. So, first let's talk about the native assets.
Native assets are simply assets that has:

  1. PolicyID (currency symbol):  It is the hash of the policy script which defines the rules of the asset.
  2. Name: It is simply a name that the user have given.
  3. Amount (Value): It is the amount of the token.

The cardano's ada(lovelace) is also one the native assets with name 'lovelace' and importantly PolicyID as "" i.e. empty/nothing.

A transaction can be created which mints the native assets in which we can define a amount , policyID and name. The amount defines the amount of native assets being minted. The amount can be negative values which means destroying the native assets.

As said earlier the NFT is minted similar to native assets, its because the NFT is one special native assets. The NFT has following characteristics:

  1. NFT has a unique policyID i.e. unique currency symbol
  2. Amount must be 1

Now, you may have idea about what an NFT is. But you may be wondering what is policy. Let's me elaborate.

Cardano Token Policy

Cardano's token policy is the set of rules and parameters that govern the creation, distribution, and management of native tokens on the Cardano blockchain. It includes features such as the ability to create custom tokens, multi-asset support, native token locking, and other functionalities that enable a variety of use cases and applications. The policy is designed to be flexible, scalable, and interoperable, allowing for a wide range of token-based applications to be built on the Cardano platform.

NFT policy

Now how to create NFT policy. The idea of NFT is minting only once and only once. So, there comes a problem a transaction can be submitted any amount of time. NFT's existence can be checked before creating it this can work if the only one transaction could be done in one slot, this is not the case.

So, to tackle this problem we use concept of unique UTxOs. As the UTxOs are unique after consumption of the UTxOs the existence will be invalid. The reason of UTxOs being unique is fees. UTxOs hash is calculated from the input UTxO, script. So, there will always some changes in input. The transaction can completely identical but input cannot. This makes UTxOs unique. So, he consumption of UTxOs is checked in the policy.

The specific UTxOs is provied as a parameter to the minting policy of NFT. The policy checks if the transaction consumes this UTxO provided. So, if can be consumed the NFT is created and UTxOs is consumed so that it cannot be consumed again. And if cannot be consumed the NFT doesn't get created preserving the uniqueness.

Writing NFT Policy

Now, you may have the idea about the policy used in NFT. Now, let's write the policy:

{-# LANGUAGE NoImplicitPrelude  #-}
{-# LANGUAGE TemplateHaskell    #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}


module Contract
where


import PlutusTx hiding( txOutDatum)
import PlutusTx.Prelude
import Plutus.V2.Ledger.Api
import Plutus.V2.Ledger.Contexts
import qualified Plutus.V1.Ledger.Value as Value
import Plutus.V1.Ledger.Address ()
import Ledger.Address (unPaymentPubKeyHash, PaymentPubKeyHash (..))
import Ledger.Typed.Scripts as Scripts
import Data.Aeson (Value(Bool))

{-# INLINABLE mkValidator #-}
mkValidator :: TxOutRef -> () -> ScriptContext  -> Bool
mkValidator txUtxo () ctx = traceIfFalse "No utxo" hasUtxo && traceIfFalse "value should be one" checkMintedValue
    where
            info :: TxInfo
            info = scriptContextTxInfo ctx

            hasUtxo :: Bool
            hasUtxo = any (\i -> txInInfoOutRef i == txUtxo) $ txInfoInputs info

            allMintedToken :: [(CurrencySymbol, TokenName, Integer)]
            allMintedToken = Value.flattenValue (txInfoMint info)

            checkMintedValue :: Bool
            checkMintedValue = all (\(_, _, amt) -> amt == 1) allMintedToken


{-# INLINABLE mkWrappedValidator #-}
mkWrappedValidator :: TxOutRef -> BuiltinData  -> BuiltinData -> ()
mkWrappedValidator txUtxo _ c = check $ mkValidator txUtxo () (parseData c "wrong input")
    where parseData a s = case fromBuiltinData a of
            Just x  -> x
            Nothing -> traceError s


validator' :: TxOutRef -> Validator
validator' txUtxo  = Validator $ unMintingPolicyScript $ mkMintingPolicyScript  $ $$(PlutusTx.compile [|| mkWrappedValidator ||]) `applyCode` liftCode txUtxo


validator = validator' $ TxOutRef "0b08a0bc189ab87a6cb14ddc505c86a10e931889bf648cb1ed08b65565bd46eb" 1

Here in the upper code two validation is done.

  1. Checked if the UTxO is consumed or not
  2. Checked if the minted amount(value) is 1 or not

Now you have the policy. How to deploy it. You can deploy the policy easily using the Kuber IDE.

Just open up the kuber IDE write the contract and compile it.

After compiling you will get the script and script hash.

This is how you deploy a NFT policy in Kuber IDE.

Creation of transaction using Kuber

You have deployed the NFT policy let's create an NFT. To create the NFT you need to just write the json associated with kuber api. You can see the documentation of how to write json for kuber api in https://github.com/dQuadrant/kuber/blob/master/docs/json-api-reference.md.

Let's write the json:

{
    "inputs": [
        "0b08a0bc189ab87a6cb14ddc505c86a10e931889bf648cb1ed08b65565bd46eb#1"
    ],
    "output":[
       {
            "address": "addr_test1qplrdfxgm2wxl74ggttfgvkrqz9hdht6xwq3r326h62cmn989k5m8uprgt299v2080aga7uqqkutycf97stwtskk40nskerus4",
            "value": "1   2882f1b08420b2e86d37c1a46df8b5e579cac484a4cfeff60824d829.546f6b656e4e616d65"
       }
    ],
    "mint": [
        {
            "script": {"type":"PlutusScriptV2","description":"","cborHex":"590ff5590ff201000033233223322323233223232323232323232323232323232323232323232322223232325335323253355335333553009120013233500b2233350032200200200135001220011233001225335002102610010232325335333573466e3cd400488008d4028880080940904ccd5cd19b8735001220013500a220010250241024350012200235500122222222222200c102313357389201074e6f207574786f0002215335333553009120013233500b223335003220020020013500122001123300122533500210011025024235001222333573466e1c005200202702632330215026001355001222222222222008102313357389211376616c75652073686f756c64206265206f6e650002210221350012200253353333333574800846666ae68cdc39aab9d5004480008cccd55cfa8021280f91999aab9f500425020233335573e6ae89401494cd4c8c8c8c8c8c8c8c8c8c8c8c8c8c8ccccccd5d200711999ab9a3370e6aae7540392000233335573ea01c4a06446666aae7d4038940cc8cccd55cfa8071281a11999aab9f500e25035233335573ea01c4a06c46666aae7d4038940dc8cccd55cfa8071281c11999aab9f500e25039233335573ea01c4a07446666aae7d4038940ec8cccd55cfa8071281e11999aab9f500e2503d233335573e6ae89403c94cd4cd40ac0b0d5d0a80d90a99a99a8160169aba1501b21533533502d02f35742a03642a66a666aa064078a0626ae85406c854cd4ccd540cc0f540c8d5d0a80d90a99a99a81801d1aba1501b2153353335503503c03d35742a03642a66a646464646666666ae900108cccd5cd19b8735573aa008900011999aab9f50042504c233335573ea0084a09a46666aae7cd5d128029299a991919191999999aba400423333573466e1cd55cea8022400046666aae7d4010941548cccd55cfa8021282b11999aab9f35744a00a4a66a66a0a60a06ae85401c854cd4c154d5d0a803909a82d09198008018010a82c0a82b9282b82a82a0299282a0289282992829928299282982889aba25001135573ca00226ea8004d5d0a80390a99a991919191999999aba400423333573466e1cd55cea8022400046666aae7d4010941588cccd55cfa8021282b91999aab9f35744a00a4a66a66a0a80a26ae85401c854cd4c158d5d0a803909a82d89198008018010a82c8a82c1282c02b02a82a1282a8291282a1282a1282a1282a02909aba25001135573ca00226ea8004d5d0a803909a82889198008018010a8278a82712827026025825128258241282512825128251282502409aba25001135573ca00226ea8004d5d0a80d90a99a99a8198209aba1501b2153353335503803b504835742a03642a66a666aa072086a0926ae85406c854cd4c0fcd5d0a80d909a825891999999999998008068060058050048040038030028020018010a8248a8240a8238a8230a8228a8220a8218a8210a8208a8200a81f8a81f1281f01e01d81d01c81c01b81b01a81a019819018818128188171281812818128181281801709aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d55cf280089baa00135742a00e42a66a602c6ae85401c84d409048cc00400c00854088540849408407c0780749407806c9407494074940749407406c840044c98c806ccd5ce2490b77726f6e6720696e7075740001b101b13263201b3357389201035054350001b135744a00226aae7940044dd5000990009aa80e9108911299a80089a80191000910999a802910011802001199aa9803890008028020008910919800801801091911999999aba400125017250172300337580044a02e4a02e02a640026aa038446666aae7c0048d4061407894cd4c010d5d080110a99a98021aba2003213501a33501f002001150181501701523232323333333574800846666ae68cdc39aab9d5004480008cccd55cfa8021280c91999aab9f50042501a233335573e6ae89401494cd4c040d5d0a80390a99a98059aba15007213501e12330010030021501c1501b2501b0190180172501801525017250172501725017015135744a00226aae7940044dd5000919191919191999999aba400623333573466e1cd55cea8032400046666aae7d4018940688cccd55cfa8031280d91999aab9f50062501c233335573ea00c4a03a46666aae7cd5d128039299a991919191999999aba400423333573466e1cd55cea8022400046666aae7d4010940948cccd55cfa8021281311999aab9f35744a00a4a66a60426ae85401c854cd4cd406c080d5d0a803909a81509198008018010a8140a81392813812812011928120109281192811928119281181089aba25001135573ca00226ea8004d5d0a80590a99a999aa80800d28079aba1500b215335323232323333333574800846666ae68cdc3a8012400846666aae7d40109409c8cccd55cf9aba25005235029321222300200435742a00c4a05004c04a46666ae68cdc3a801a400446666aae7d4014940a08cccd55cf9aba2500625335302435742a00e426a056244460020082a0524a05204e04c46666ae68cdc3a8022400046666aae7d40188d40a8488800c940a409c940a009409008c9409494094940949409408c4d55cea80109aab9e5001137540026ae85402c854cd4cd4050070d5d0a805909a8118919998008028020018010a8108a8100a80f8a80f1280f00e00d80d00c80c1280c80b1280c1280c1280c1280c00b09aba25001135744a00226ae8940044d55cf280089baa0011335500100b009112232233333335748002aa00a4a66a60066eac00884d405800454051540155401554014048c8004d5406488c8cccd55cf80111a80b280e1299a98031aab9d5002215335300635573ca00642a66a600c6ae8801484d4064cd407848cc00401000c0045405c540585405404c4d5d080088928078911919191999999aba400423333573466e1d40092000233335573ea0084a02a46666aae7cd5d128029299a98049aba15006213501835018001150162501601401323333573466e1d400d2002233335573ea00a46a02ea02c4a02c0284a02a0240224a0264a0264a0264a02602226aae7540084d55cf280089baa00123232323333333574800846666ae68cdc3a8012400c46666aae7d40109404c8cccd55cf9aba2500525335300b35742a00c426a02c24444600800a2a0284a02802402246666ae68cdc3a801a400846666aae7d4014940508cccd55cf9aba2500625335300d35742a00e426a02e24444600400a2a02a4a02a02602446666ae68cdc3a8022400446666aae7d4018940548cccd55cf9aba2500725335300b35742a010426a03024444600200a2a02c4a02c02802646666ae68cdc3a802a400046666aae7d401c940588cccd55cf9aba2500825335301235742a012426a03224444600600a2a02e4a02e02a0284a02a02402202001e4a0224a0224a0224a02201e26aae7540084d55cf280089baa00123232323333333574800846666ae68cdc39aab9d5004480008cccd55cfa8021280911999aab9f500425013233335573e6ae89401494cd4c028d5d0a80390a99a98071aba15007213501712330010030021501515014250140120110102501100e2501025010250102501000e135744a00226aae7940044dd5000919191999999aba400323333573466e1cd55cea801a400046666aae7d400c940408cccd55cf9aba2500425335300c35742a00a426a0260022a0224a02201e01c4a01e0184a01c4a01c4a01c4a01c01826aae7940044dd500091919191919191999999aba400723333573466e1d4009200c233335573ea00e46a028244444440064a02602246666ae68cdc3a801a401446666aae7d40208d405448888888010940500488cccd5cd19b875004480208cccd55cfa8049280a91999aab9f500725016233335573e6ae89402094cd4c048d5d0a80610a99a98089aba1500a213501a122222223300100900815018150172501701501401323333573466e1d40152006233335573ea0144a02c46666aae7d40249405c8cccd55cf9aba2500a25335301335742a01a42a66a60286ae85403084d406c48888888cc0080240205406454060940600580540508cccd5cd19b875006480108cccd55cfa8059280b91999aab9f500b25018233335573e6ae89403094cd4c044d5d0a80710a99a980a9aba1500e213501c12222222330060090081501a150192501901701601523333573466e1d401d2002233335573ea0184a03046666aae7cd5d128069299a98089aba1500e213501b122222223007008150192501901701623333573466e1d40212000233335573ea01a4a03246666aae7cd5d128071299a98091aba1500f213501c1222222230050081501a2501a0180172501801501401301201101000f2501125011250112501100f135573aa00a26ae89400c4d5d1280109aba25001135573ca00226ea80048c8c8c8c8c8ccccccd5d200311999ab9a3370ea004900111999aab9f500625011233335573ea00c4a02446666aae7d40189404c8cccd55cf9aba2500725335300d35742a01442a66a601c6ae854028854cd4c03cd5d0a805109a80c0911998008028020018a80b0a80a8a80a1280a00900880800791999ab9a3370ea006900011999aab9f500725012233335573e6ae89402094cd4c034d5d0a804909a80a89118010018a80992809808808128088070069280792807928079280780689aab9d5004135744a00226ae8940044d55cf280089baa0012333333357480024a0124a0124a01246a0146eb40089402401c8c8c8c8ccccccd5d200211999ab9a3370ea004900111999aab9f50042500d233335573e6ae89401494cd4c024d5d0a803109a80809118008018a8071280700600591999ab9a3370ea006900011999aab9f50052500e233335573e6ae89401894cd4c028d5d0a803909a80889118010018a80792807806806128068050049280592805928059280580489aab9d5002135573ca00226ea80048ccccccd5d20009280392803928039280391a8041bae0020051223232323333333574800846666ae68cdc3a8012400846666aae7d40108d40344888004940300288cccd5cd19b875003480088cccd55cfa8029280691999aab9f35744a00c4a66a60146ae85401c84d40404888c00c010540389403803002c8cccd5cd19b875004480008cccd55cfa80311a807891100112807006128068050048041280512805128051280500409aab9d5002135573ca00226ea80048c8c8c8ccccccd5d200211999ab9a3370ea004900111999aab9f500423500b00f2500a00823333573466e1d400d2000233335573ea00a46a01801e4a0160124a01400e00c4a0104a0104a0104a01000c26aae7540084d55cf280089baa0014984800448488c00800c4488004c8004d540188894cd40044008884d400888cc01cccc02000801800400cc8004d5401488894cd40044008884d4008894cd4ccd5cd19b87001480000280244ccc02001c01800c4ccc02001ccd402c48ccc00402000c00801800c4880084880044488008488488cc00401000c448c8c00400488cc00cc008008004cd4488cc0092201200b08a0bc189ab87a6cb14ddc505c86a10e931889bf648cb1ed08b65565bd46eb00480088848cc00400c0088005"},
            "redeemer": { "constructor": 0, "fields": []},
            "amount": {
                "TokenName": 1
            }
        }
    ],
    "metadata": {
        "721": {
            "2882f1b08420b2e86d37c1a46df8b5e579cac484a4cfeff60824d829": {
                "TokenName": {
                    "name": "Token name",
                    "image": "ipfs://XXXX",
                    "mediaType": "image/png",
                    "description": [
                        "This is description of token name",
                        "This is another description of token name"
                    ],            
                    "files": [{
                        "name": "Token name image",
                        "mediaType": "image/png",
                        "src": "ipfs://XXXX",
                    }],
                    "artist": "Artist name",
                    "royalties": [
                        {
                            "address": [
                                "address_XXXX"
                            ],
                            "rate": "0.1"
                        }
                    ]
                }
            }
        }
    }
}

This way you can create a json for minting transaction of NFT. Not so hard is it. Let be break it down.

First the input, the input is just the utxo that is to be consumed for minting NFT. It preserves the uniqueness of NFT.

The output is just the address of the wallet who will own the asset after minting. The value is the assets. In value 1 is the amount of asset which is one due to NFT. Another is assets which is policyID.tokenHex . The policyID is the hash of the policy and tokenHex is hex representation of token name.

In mint we got script, redeemer and amount. The amount consists of token name and it's value which must be 1 in case of NFT.

Now, let's talk about metadata. Metadata in cardano works differently than ethereum. In ethereum metadata gets attached to a token through smart contract itself but in cardano metadata is send in transaction which is way to create a link between token and metadata.

Here 721 means NFT. In metadata, the policy id, token name, files, images associated, description are stated. For further information you can go to https://cips.cardano.org/cips/cip25/.

So, this is how NFT is created in Cardano using Kuber IDE.