Mint your NFT on Stacks

Satyam
7 min readNov 14, 2022

--

First of all, what is Clarinet?

Clarinet is a Clarity runtime packaged as a command line tool designed to facilitate smart contract understanding, development, testing and deployment.

Basically, Clarinet is a primary development environment for building and testing Clarity smart contracts. Built for Stacks Blockchain. You can also check out this article to start with Clarity and Stacks.

Stacks

Lets deep dive into it without any further due,

Installing Clarinet

For macOS, just this command will do using homebrew,

brew install clarinet

And for Windows, this command will do this using the winget,

winget install clarinet

You can look at this article to install Clarinet with more detailed steps.

Writing NFT Contract

  1. First, we will create a project using the following command in the terminal and enter the project directory:
clarinet new clarity-nft && cd clarity-nft

This creates a basic clarity smart contract project directory for you to start working on ASAP.

2. Now, enter the editor (in my case, vs code) with the given command.

code .

it might look something like this,

As NFTs on Stacks rely on a trait called SIP-009, we will need two contracts. One defines the trait, and the other defines your specific NFT, which is dependent on the trait.

3. Execute this command on your project terminal to create the contracts.

clarinet contract new nft-trait; clarinet contract new my-nft

If you see the contracts folder, you will have two contracts named nft-trait and my-nft created.

You can also install the Clarity VS Code extension to help you from here.

4. We will update the nft-trait contract with SIP-009 trait from this repo.

(define-trait sip009
(
;; Last token ID, limited to uint range
(get-last-token-id () (response uint uint))

;; URI for metadata associated with the token
(get-token-uri (uint) (response (optional (string-ascii 256)) uint))

;; Owner of a given token identifier
(get-owner (uint) (response (optional principal) uint))

;; Transfer from the sender to a new principal
(transfer (uint principal principal) (response bool uint))
)
)

5. Also copy the following code to my-nft contract. We will go through it line by line after this.

(impl-trait .nft-trait.sip009)

(define-non-fungible-token my-nft uint)

(define-constant mint_price u100)
(define-constant wallet_1 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) ;; creator
(define-constant wallet_2 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) ;; artist

(define-data-var last-token-id uint u0)

(define-read-only (get-last-token-id)
(ok (var-get last-token-id))
)

(define-read-only (get-token-uri (id uint))
(ok none)
)

(define-read-only (get-owner (id uint))
(ok (nft-get-owner? my-nft id))
)

(define-public (transfer (id uint) (sender principal) (recipient principal))
(nft-transfer? my-nft id sender recipient)
)

(define-public (mint (recipient principal))
(let
(
(id (+ (var-get last-token-id) u1))
(portion-of-total (/ mint_price u2))
)
(try! (stx-transfer? portion-of-total recipient wallet_1) )
(try! (stx-transfer? portion-of-total recipient wallet_2) )
(try! (nft-mint? my-nft id recipient))
(var-set last-token-id id)
(ok id)
)
)

Explaining my-nft contract

First, implement (basically, importing) the trait contract we defined previously,

(impl-trait .nft-trait.sip009)

Then, define the nft using pre-set define-non-fungible-token function.

(define-non-fungible-token my-nft uint)

Define some constants using define-constant.

(define-constant mint_price u100)
(define-constant wallet_1 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) ;; creator
(define-constant wallet_2 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) ;; artist

The first constant sets the mint price to a value of 100. The other two constant defines the wallet id of the creator and artist, which will be used to give a portion of minting to each as earnings. (The address are taken from settings/Devnet.toml, the first two)

Define a variable to store the last token-id, which will increment whenever a new token is created.

(define-data-var last-token-id uint u0)

Define a read-only function to get the token-id of the last token. This function is defined concerning the SIP-009 trait (its first function).

(define-read-only (get-last-token-id)
(ok (var-get last-token-id))
)

And the read-only function doesn't take any parameters as input.

now, define the second function in the nft trait.

(define-read-only (get-token-uri (id uint))
(ok none)
)

You can also give input using (ok (var-get URI)) if you have an ipfs URI of your metadata and define the variable like this,

(define-data-var URI (string-ascii 256) "ipfs://QmdQ6pqBmiYbZydStLavTw66bPMcGN3hEdYYtWqjJn4g7q")

Define the third read-only function with the built-in return function nft-get-owner.

(define-read-only (get-owner (id uint))
(ok (nft-get-owner? my-nft id))
)

Now the last function concerning the SIP-009. Define a public function which helps us to make changes on the chain.

(define-public (transfer (id uint) (sender principal) (recipient principal)) 
(nft-transfer? my-nft id sender recipient)
)

Done. You can run clarinet check command in your terminal to check the code. You might get some warnings, but you can ignore them right now.

But wait, what about minting NFT? The minting function is not included in the standardised contract (SIP-009), but most people want to mint NFT immediately. So here is the function.

(define-public (mint (recipient principal)) 
(let
(
(id (+ (var-get last-token-id) u1))
(portion-of-total (/ mint_price u2))
)

(try! (stx-transfer? portion-of-total recipient wallet_1) )
(try! (stx-transfer? portion-of-total recipient wallet_2) )
(try! (nft-mint? my-nft id recipient))
(var-set last-token-id id)
(ok id)
)
)
  1. let → define two local variables. One, the token-id (1 + last-token-id) and the next one, the portion given to each artist and creator (Taking it simply, we divided the total by 2, i.e. 50 STX each).
  2. try! → runs the inside function and proceeds to the next line only if the output is “ok” or else terminates the whole function.
  3. stx-transfer? → pre-defined function to transfer STX. (Transfers STX to artist and creator)
  4. nft-mint? → pre-defined function to mint NFT.
  5. var-set → sets the new global last-token-id to the current id.

Sheesh, that was a lot. Now we can have our hands dirty and interact with contracts using clarinet. XD.

Interacting with Contract using Clarinet

Run clarinet console in your terminal, and you will get an output like this.

Inside the Contract identifier, you can see our contracts are deployed on the local Devnet.

And we have ten addresses to work with. Previously, we defined the artist and creator wallet as the first two addresses.

Inside the console, define the address of the transaction executer using, (the last address from the above addresses)

::set_tx_sender STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6

Now, call the contract using the,

(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.my-nft mint tx-sender)

Here contract-call? is used to call the contract, and the first parameter given is the contract address from the Contract Identifier.

We call themint function, which takes the recipient address as an input (here, tx-sender as defined in the past).

If you run,

::get_assets_maps 

you will get this output.

Here you can see one NFT is minted for the tx-sender, and the recipient spends 100 STX as defined in the contract. Also, the artist and creator are incentivised with 50–50 STX each.

You can also use the transfer function to transfer the token as defined in the contract.

(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.my-nft transfer u1 tx-sender 'ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ)

This transfers the NFT with id u1 to the second last address in the given addresses.

Again if you run,

::get_assets_maps

the output will be,

Here you can see the NFT is transferred to the last address without any fuel, as it was not defined in the contract.

Congrats on completing this tutorial. LFG 🚀🚀

Here is the reference code for this tutorial on GitHub.

Thank you for reading. If this was helpful, follow me on Medium or Twitter.

Some Resources:

  1. Hiro’s Tutorial: https://docs.hiro.so/tutorials/clarity-nft
  2. Official SIP: https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md#trait
  3. Clarity Book: https://book.clarity-lang.org/ch10-00-stacks-improvement-proposals.html

Want to join in a journey to learn Clarity? Do it here.

--

--

Satyam

A Dilettante Writer / Blockchain Developer. (satyam.btc, satyvm.eth)