DEV Community

Cover image for Creating an NFT with Stacks
shabbiryk.eth
shabbiryk.eth

Posted on • Edited on

Creating an NFT with Stacks

What You Will Need

Clarinet installed on your machine (follow the instructions in the link to install it)
IDE or Text Editor (we recommend VSCode)
Hiro Web Wallet (learn more about the installation here)
STX Testnet tokens (you can get some at this faucet)

Overview

Stacks is a layer-1 blockchain that allows for the execution of smart contracts, Proof of Transfer (PoX) consensus mechanism. Stacks is associated with the Bitcoin ecosystem because it aims to enhance Bitcoin’s capabilities by building on top of it. Stacks is also exciting because it supports smart contracts, so developers can build dApps such as decentralised exchanges, DAOs, NFT marketplaces, and more!

This guide will demonstrate how to create and deploy NFT on the Stacks Testnet using Clarity. Clarity is a smart contract programming language that was purpose-built for writing safer, more secure code.

Understanding Clarity

Clarity is a smart contract language with a Lisp style created for the Stacks blockchain. Clarity is intentionally Turing incomplete as it avoids “Turing complexity.” This allows for complete static analysis of the entire call graph of a given smart contract. What does this mean exactly? To make your solidity contract code understandable by the EVM on most EVM-based chains, you must compile it into bytecode. Because you don't have to compile your code, the Stacks blockchain is unique. Instead, the executed code can be easily audited because it is human readable. Another advantage this has for users is that, unlike on EVM-based chains, the source code for a smart contract is always accessible when it is deployed on the Stacks blockchain. Finally, you can analyse Clarity code for runtime cost and data usage. This empowers developers to predict what a given Clarity program will do, and how much it will cost.

Clarity vs Solidity NFT Standards

A comparison between the SIP-009 trait and the analogous EIP 721 standard will help as a starting point. SIP-009 is the Clarity standard for NFT and reads as follows;

(define-trait nft-trait
 (
   ;; 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))
 )
)
Enter fullscreen mode Exit fullscreen mode

The fact that EIP 721 is longer simply demonstrates a portion that the Clarity trait left out and emphasises the differences between the two in terms of who is able to transfer ownership of an NFT.

interface ERC721 /* is ERC165 */ {...
   function approve(address _approved, uint256 _tokenId) external payable;


   function setApprovalForAll(address _operator, bool _approved) external;


   function getApproved(uint256 _tokenId) external view returns (address);


   function isApprovedForAll(address _owner, address _operator) external view returns (bool)
}
Enter fullscreen mode Exit fullscreen mode

You’ll notice that EIP 721 defines the ability for the NFT owner to delegate, to an approved address, the ability to transfer the asset on the owner's behalf.

In Clarity there is no need for an approval mechanism because Clarity leaves this up to the smart contract developer. While this is great for innovation and rapid development it is also a concern from a security perspective as it means the contract builder can insert backdoors which allow any NFT in the contract to be transferred to anyone else — without the consent of the owner.

What is Clarinet?

Now that you have some familiarity with Stacks and Clarity, let us dive into the tool we will use to build our smart contract, Clarinet. Clarinet is a command-line tool useful for efficient smart contract development. It can be used for developing, testing, debugging, and deploying your contracts to local and production environments. It's similar to other popular web3 smart contract libraries such as Hardhat.

Prerequisites

For this tutorial, you should have a local installation of Clarinet. Refer to Installing Clarinet for instructions on how to set up your local environment. You should also have a text editor or IDE to edit the Clarity smart contracts.

If you are using Visual Studio Code, you may want to install the Clarity Visual Studio Code plugin.

Building an NFT Smart Contract

With that high-level description of Clarity, let’s get into how to actually create a smart contract with it.

Hiro Web Wallet Set Up

The Hiro Web Wallet must be set up in order for us to deploy our clarity smart contract to the Stacks Testnet network. You must switch the network configuration of your wallet to Testnet once it has been configured. On the homepage of your wallet, click the ellipses ("...") button, then select Change Network.

Next, you'll want to ensure you have some STX Testnet tokens. You can request some tokens on the Stacks Explorer Faucet page. Just connect your wallet, then click the Request STX button.
Feel free to continue to the next section after you have your wallet and STX Testnet tokens!

Clarity Basics

The syntax of Clarity is LISP-like. As a result, there will be numerous parentheses. You can discover additional parenthesis, symbols, sentences, values, and more within these ones. People that are new to LISP-like languages may feel intimidated as it looks very foreign compared to both natural languages as many other programming languages.

A way to conceptualize Clarity code is to think of lists inside lists. (The technical term for these kinds of expressions is "S-expression".) Expressions do not differentiate between data and code, that makes things a bit easier! Here is an example of a Clarity expression.

For instance, if we wrote 4 + 5 in JS, we would get the result of 9.
The identical expression would be represented as (+ 4 5) in Clarity.
In this case, we are invoking the + function, which has two parameters: 4 and 5. All of Clarity functions this way, with everything being basically a function call with passed parameters.

Create and configure a Clarinet project

Open up a terminal window and navigate into the directory you want this project to live in. Then, run the following command to create a new clarinet project and navigate inside it.

clarinet new nft-project && cd nft-project
Enter fullscreen mode Exit fullscreen mode

The clarinet command above creates a boilerplate project directory called nft-project that we can use to develop smart contracts quickly.

clarinet contract new nft-trait; clarinet contract new nft-sample
Enter fullscreen mode Exit fullscreen mode

The above command creates two .clar files, which are the Clarity file format. One of these files contains the metadata for our NFTs and the other contains the logic for our NFT contracts (for example, nft-sample.clar) (e.g., nft-trait.clar). Two test files in TypeScript format will also be produced in a test directory.

At this point, your project folder setup should look like this:

├── Clarinet.toml
├── contracts
│ ├── nft-sample.clar
│ └── nft-trait.clar
├── settings
│ ├── Devnet.toml
│ ├── Mainnet.toml
│ └── Testnet.toml
└── tests
├── nft-sample_test.ts
└── nft-trait_test.ts

In the next section, we will start configuring and implementing the NFT smart contract.

Create and Define the Clarity Smart Contract

Implementing the SIP-009 Standard

To make sure the contract we are building complies with SIP-009, we must configure some dependencies before we begin writing the smart contract code. Open Clarinet.toml and edit the contracts.nft-sample section (around line 11-12) to match the following configuration:

[contracts.nft-sample]
path = "contracts/nft-sample.clar"
depends_on = ["nft-trait"]
Enter fullscreen mode Exit fullscreen mode

Our smart contract complies with the necessary characteristics specified in the SIP-009 standard thanks to the configuration described above. If we don’t implement the configured traits, our deployment to test and production environments will fail.

Next, open the nft-trait.clar file. Copy the following code and replace it with the content already in the file:

define-trait nft-trait
  (
    ;; 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))
  )
)
Enter fullscreen mode Exit fullscreen mode

Then, save the file. The Clarity code above sets the functions our contract should conform to. These functions should be familiar to you as we discussed them in the previous section.

Implementing the NFT Contract Logic

We will now update the nft-sample.clar file with the NFT smart contract logic. We will be able to mint NFTs using the contract on the Stacks blockchain. Open the file and add the following code to the existing content:

;; using the SIP009 interface (testnet)
;; trait configured and deployed from ./settings/Devnet.toml
(impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait)

;; declare a new NFT
(define-non-fungible-token NFT-SAMPLE uint)

;; store the last issued token ID
(define-data-var last-id uint u0)

;; mint a new NFT
(define-public (claim)
  (mint tx-sender))

;; SIP009: Transfer token to a specified principal
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
     (asserts! (is-eq tx-sender sender) (err u403))
     ;; Make sure to replace NFT-SAMPLE
     (nft-transfer? NFT-SAMPLE token-id sender recipient)))

(define-public (transfer-memo (token-id uint) (sender principal) (recipient principal) (memo (buff 34)))
  (begin
    (try! (transfer token-id sender recipient))
    (print memo)
    (ok true)))

;; SIP009: Get the owner of the specified token ID
(define-read-only (get-owner (token-id uint))
  ;; Make sure to replace NFT-NAME
  (ok (nft-get-owner? NFT-SAMPLE token-id)))

;; SIP009: Get the last token ID
(define-read-only (get-last-token-id)
  (ok (var-get last-id)))

;; SIP009: Get the token URI. You can set it to any other URI
(define-read-only (get-token-uri (token-id uint))
  (ok (some "https://token.stacks.co/{id}.json")))

;; Internal - Mint new NFT
(define-private (mint (new-owner principal))
    (let ((next-id (+ u1 (var-get last-id))))
      (var-set last-id next-id)
      ;; You can replace NFT-SAMPLE with another name if you'd like
      (nft-mint? NFT-SAMPLE next-id new-owner)))

Enter fullscreen mode Exit fullscreen mode

In the next section, we'll demonstrate how to verify your contracts to make sure they are deployable.

Testing the Smart Contract in a Local Environment

Clarity is an interpreted language, which means it doesn't compile code down to a lower level before execution; instead, the code gets executed in the same format at runtime. This makes the code slower but more transparent. To check that our clarity code is syntactically correct, we can run the command clarinet check from the nft-project directory.

Image description

You may get some warnings; however, don't worry about those for the purpose of this guide.

Before deploying to Stacks Testnet, it is good practice to check a few functions to make sure the responses are what we expect. We can initiate the clarinet console to do this:

clarinet console
Enter fullscreen mode Exit fullscreen mode

Once the console is initiated, you'll see your contract address and public functions available in your contract. There will also be a list of test accounts that are associated with your local clarinet console. Run the command below to call the claim function of our smart contract, which will mint an NFT in our local environment.

(contract-call? .nft-sample claim)
Enter fullscreen mode Exit fullscreen mode

You should see a response similar to this:

Image description

If all goes well, move on to the next section where we will deploy our NFT contract to Stacks Testnet 👀

Deploy the NFT Contract to Stacks Testnet

This section will demonstrate how to deploy your NFT contract to Stacks Testnet using the Stacks Explorer Sandbox. Before we get started, make sure you have the Hiro Wallet already installed and set up. Moreover, make sure to have some test STX tokens for gas fees (you can get some at this faucet).

Once your wallet is set up, connect your wallet to the Stacks Explorer Sandbox and navigate to the Write & Deploy page. Copy and paste the code from your nft-sample.clar file, and then fill in the contract name (in our example, it's "nft-sample") if you don't want the randomly generated name provided to you.

Next, click the deploy button. You should get a prompt from the Hiro wallet window with information about the transaction. Verify that the transaction looks correct, then click Confirm

Image description

The mining (transaction/process) can go on for a short while.
On the transactions page of the Stacks explorer or in the activity section of your Hiro wallet, you can keep track of the transaction.
The homepage of our contract can be reached by searching for the contract address or selecting the transaction in our wallet once we have verified that the contract has been mined. The contract page will display your contract's name, deployer address, fees to deploy the contract, the source code, and what block number it was mined in.

Where is the NFT that you recently minted, you might be wondering? Technically speaking, it hasn't yet been minted. We have only developed and deployed the NFT contract thus far. We will walk you through the minting process in the following section, where you can view your NFT in your Hiro wallet and claim it. To take your time and learn more about the explorer page, feel free to do so, then proceed to the next section when you're ready.

Mint the NFT through the Stacks Sandbox

The moment you've been waiting for! It's time to mint it up!

To mint the NFT, we will need to call the claim function of our NFT contract. Navigate to the Call a contract page (the f in the left sidebar), then input your contract's address (i.e., ST12KGMZCKXERR1VG1TFEQQZ3VQXSMVVC3J31S604.nft-sample) and the sandbox should detect the address and name. Click the Get contract button and should see the callable functions listed in the table like so:

Image description

Click the Claim function then click Call function. You'll get a prompt in your Hiro wallet. Verify the claim function is being called, then confirm the transaction. Your transaction may take a couple of minutes to confirm. Feel free to take a quick break; it should be confirmed once you return. If it still takes a while, you can click the Increase fee* icon on your transaction within your Hiro wallets activity tab.

Once the transaction is mined, you can see your NFT within the Balances tab on your Hiro wallet or the Stacks explorer by going to your personal wallet address page and looking at the Collectibles tab.

Image description

Awesome Work! You’ve learned how to create and deploy, and execute an NFT contract to the Stacks Testnet blockchain

Top comments (1)

Collapse
 
syedzubairahmed001 profile image
Syed Zubair Ahmed

This is a well written tutorial, thanks Yashwant!