Making NFTs with Plutus

Recently, the Cardano cryptocurrency project released their Alonzo version with support for smart contracts. Cardano is primarily built on Haskell and their smart contract system, Plutus, is as well.

Cardano is a large cryptocurrency project, currently the third-largest by market capitalization after Bitcoin and Ethereum. Beyond market capitalization, the project is possibly the most actively developed Haskell project. It is designed to compete with Ethereum’s smart contract abilities, but because Cardano uses Proof of Stake, instead of Proof of Work, it is more energy efficient and requires dramatically lower fees.

It is common for Ethereum smart contract transactions to cost over $60 dollars in fees. However, this has not stopped adoption of Ethereum, which has surged this year as NFTs have become more mainstream.

Cardano promises to do all the things Ethereum can do, but better and cheaper, and it uses Haskell. Ethereum uses Solidity, I don’t know Solidity, but with 12 years of Haskell I thought I would check it out.

As quickly discovered, there was little written on how to deploy an Plutus smart contract from start to finish. Hopefully, this blog post helps fill that gap.

Buy Ada

To actually deploy and interact with a Plutus smart contract, you need to create a transaction on the Cardano blockchain. This will require spending the native currency Ada. You don’t need a lot to play around with Plutus (actually you don’t need any to use the testnet … but how is that fun?). A few bucks in Ada will be enough.

The easiest way to buy Ada is with Coinbase. If you don’t have a Coinbase account yet, you’ll want to install the app and set one up. Have fun.

Install Nix

The first thing you are going to want to do is install the Nix package manager system.

You can do that with this command:

sh <(curl -L https://nixos.org/nix/install) --daemon

If you have Nix installed already, you will see instructions on how to uninstall it.

I’m on a Mac and thus required a few extra steps to uninstall that were not listed, sadly.

First, I had to use the Disk Utility to remove my Nix partition. Then I needed to delete the /nix line from the /etc/exports file.

After restarting my computer, I could install Nix … yay.

🚨🚨🚨 VERY IMPORTANT 🚨🚨🚨

You must configure the IOHK nix caches, otherwise you will end up building GHC from source. Follow the instructions here: https://github.com/input-output-hk/plutus#iohk-binary-cache

Install Daedalus

Daedalus is the Cardano GUI wallet system. You could probably use cardano-node instead, but I got things to work with Daedalus so … that is direction we will go.

Download it here: https://daedaluswallet.io/en/download/.

After installing, start it and let it sync. This will take like half a day.

Build the cardano-cli

We are going to need the cardano-cli to interact with the Cardano Block chain. These instructions are based on the documentation here: https://docs.cardano.org/plutus/Plutus-transactions.

Clone the repo and build the cardano-cli

git clone https://github.com/input-output-hk/cardano-node
cd cardano-node
git fetch --all --recurse-submodules --tags
git checkout tags/1.29.0

Create a plutux-tutorial.nix file with the following content:

{ version ? "mainnet", pkgs ? import <nixpkgs> { }}:
let
  cardano-node-repo = import ./. { };

in pkgs.mkShell {
  buildInputs = with pkgs; [
    libsodium
    cabal-install
    zlib
    haskell.compiler.ghc8107
    haskellPackages.haskell-language-server

    cardano-node-repo.scripts."${version}".node
    cardano-node-repo.cardano-cli
  ];

  CARDANO_NODE_SOCKET_PATH = "${builtins.toString ./.}/state-node-${version}/node.socket";

}

Call

nix-shell plutus-tutorial.nix

Wait ten minutes.

Once everything finishes building, it’s time to create a wallet address.

Run the following in the currently active nix shell.

cardano-cli address key-gen --verification-key-file ~/plutus_test.vkey --signing-key-file ~/plutus_test.skey
cardano-cli address build --payment-verification-key-file ~/plutus_test.vkey --out-file ~/plutus_test.addr

Now transfer 2 Ada to this address.

Creating a NFT

NFTs are a way to make unique digital assets. Cardano has had the ability to create a limited form of NFTs before Alonzo, but with Plutus, Cardano can make NFTs that rival the Ethereum-based NFTs.

I’ve extracted the NFT creation code from the Lobster Challenge repo. The NFT code is located here: https://github.com/jfischoff/plutus-nft.

The NFT smart contract enforces uniqueness by requiring the transaction input is a specific UTxO on the blockchain. UTxO’s are blockchain nodes that can be used as inputs to new transactions. However, once they are used in a transaction, they cannot be used again (this would allow “double spending”).

What this means is the smart contract must be recompiled for every NFT. It can only be used once.

To compile the smart contract, we must first get the ID for the UTxO where our Ada is stored.

First, make sure Daedalus is running, and it is synced up with the network. Then set the

export CARDANO_NODE_SOCKET_PATH="/Users/YOUR_USER_NAME/Library/Application Support/Daedalus Mainnet/cardano-node.socket"

Now we can get the UTxO associated with our wallet address.

cardano-cli query utxo --address $(cat ~/plutus_test.addr) --mainnet

                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
ea2fe8519d45c33c21de2fc95664075c1dfd42af232f3a51809ed0ffad223164     0        4200000 lovelace + TxOutDatumHashNone

In this example the id we need is ea2fe8519d45c33c21de2fc95664075c1dfd42af232f3a51809ed0ffad223164#0.

We can pass this along with the “token name” of our NFT :

cabal run create-nft-script -- ea2fe8519d45c33c21de2fc95664075c1dfd42af232f3a51809ed0ffad223164#0 AwesomeNFT

This will write the nft-mint-policy.plutus to the scripts directory.

We can now create our NFT minting transaction.

scripts/mint_nft.sh ea2fe8519d45c33c21de2fc95664075c1dfd42af232f3a51809ed0ffad223164#0 $(cat ~/plutus_test.addr) ~/plutus_test.skey AwesomeNFT

After about 30 seconds query UTxO at your wallet address

cardano-cli query utxo --address $(cat ~/plutus_test.addr) --mainnet
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
8579dd0a31cd1b54e5b91320ff90806e1bdd65ebe3278a93e638429b262ecd8b     0        2070331 lovelace + TxOutDatumHashNone
8579dd0a31cd1b54e5b91320ff90806e1bdd65ebe3278a93e638429b262ecd8b     1        1724100 lovelace + 1 369e5bad71475274d99a1c3c8272df1b159e677b49b86d220961e3c4.AwesomeNFT + TxOutDatumHash ScriptDataInAlonzoEra "45b0cfc220ceec5b7c1c62c4d4193d38e4eba48e8815729ce75f9c0ab0e4c1c0"

Which should show your NFT.

Congrats!

Closing Thoughts

We created an NFT, but it is not that useful. The NFTs on Ethereum also store metadata, like urls to IPFS which we need to do as well if we wanted to make something more useful. I believe this would require a validator script to preserve the metadata in addition to the minting policy script.

However, it is a start. I’m pretty busy building my third attempt at a consumer social app (only 10 more attempts to go before one of them might work), but I might try extending the NFT functionality. Pull requests welcome ;).

Home