DEV Community

Shoaib Sayyed
Shoaib Sayyed

Posted on

Create an NFT with Stacks

Quickly create your own NFT using Clarity with Stacks.

Hello Developers ๐Ÿ‘‹

Welcome to todayโ€™s post, where weโ€™ll break down a blockchain topic into bite-sized pieces to help you learn and apply your new skills in the real world.

Todayโ€™s topic is Creating an NFT with Stacks.

Hereโ€™s a list of what weโ€™ll cover ๐Ÿ—’

  • โœ… Step 1: Set up your project using Clarinet
  • โœ… Step 2: Configure dependencies and define traits
  • โœ… Step 3: Create the NFT smart contract
  • โœ… Step 4: Check the contracts for errors
  • โœ… Step 5: Testing the contract on local chain
  • โœ… Step 6: Deploy the NFT smart contracts

By the end of this post, youโ€™ll be able to create and deploy your own NFT using the Stacks.

Letโ€™s go! ๐Ÿš€

Prerequisites

There are some prerequisite that you should have setup before going into this tutorial โš’๏ธ

  1. You should have Clarinet installed on your system. Follow the installation guide.
  2. Setup your Hiro wallet. Download the wallet.

โœ… Step 1: Set up your project using Clarinet

We will create a project called music-bird using Clarinet. Open up your terminal and type the following in the command:

clarinet new music-bird
Enter fullscreen mode Exit fullscreen mode

Once the project is created Open the folder in your favorite IDE (example: VS Code) and open the built-in terminal emulator by going to the Terminal menu and choosing New Terminal.

Now, Inside our project folder, we will create two new Clarity contracts using the following commands:

clarinet contract new nft-trait
Enter fullscreen mode Exit fullscreen mode
clarinet contract new music-bird
Enter fullscreen mode Exit fullscreen mode

Clarinet will create a boilerplate Clarity and test file, and add the contracts to the configuration file.

Creating file contracts/nft-trait.clar
Creating file tests/nft-trait_test.ts
Enter fullscreen mode Exit fullscreen mode
Creating file contracts/music-bird.clar
Creating file tests/music-bird_test.ts
Enter fullscreen mode Exit fullscreen mode

As we are not going to write any test cases in this post, so we can delete the tests folder.

โœ… Step 2: Configure dependencies and define traits

Now that we have the project setup out of the way, lets configure the dependencies and define the traits.

For creating the NFT on stacks we need to follow the Sip 009 NFT standard and its pre-defined traits. We will be writing our NFT contract based on this traits.

To assert that the music-bird NFT contract implements the trait of SIP-009, we can use the impl-trait function, as the NFT trait is already deployed on Stacks Mainnet, we can use that deployed contract address when deploying our NFT on mainnet but in this post we are deploying on testnet, so we will configure a NFT traits contract on our local.

To do so, open up the nft-trait.clar file inside the contracts directory and add the following traits in it.

(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

Now we have define the traits, lets configure the dependencies for our NFT smart contract. Open Clarinet.toml and edit the [contracts.music-bird] section, we need to add the dependency of nft-trait contract.

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

This will ensure that the nft-traits contract should deployed before our NFT smart contract as it depends on it.

โœ… Step 3: Create the NFT smart contract

Now everything is setup, let us jump into writing our NFT contract. Open the music-bird.clar contracts file and remove all the boilerplate code.

;; Use this when deploying on mainnet
;; (impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
Enter fullscreen mode Exit fullscreen mode
;; Use this when deploying on testnet
;; Implementing traits from the `nft-trait.clar` contract
(impl-trait .nft-trait.nft-trait)
Enter fullscreen mode Exit fullscreen mode

By adding this expression, the analyzer will check if the contract implements the trait specified when the contract is deployed. It will reject the transaction if the contract is not a full implementation. It is therefore recommended to always use impl-trait because it prevent accidental non-conformity.

Now let's define a non fungible token which will take the name of the NFT and its identifier type, Since the SIP requires the asset identifier type to be an unsigned integer, we add our NFT definition next.

(define-non-fungible-token music-bird uint)
Enter fullscreen mode Exit fullscreen mode

The asset identifier for the NFT should be an incrementing unsigned integer. The easiest way to implement it is to increment a counter variable each time a new NFT is minted. Let us define a data variable for it.

(define-data-var last-token-id uint u0)
Enter fullscreen mode Exit fullscreen mode

Now, we will add a constant for the contract deployer and two error codes. Here is everything put together:

;; Implementing traits from the `nft-trait.clar` contract
(impl-trait .nft-trait.nft-trait)

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-token-owner (err u101))

(define-non-fungible-token music-bird uint)

(define-data-var last-token-id uint u0)
Enter fullscreen mode Exit fullscreen mode

To get the token id of last minted NFT, we will define a read only function which will read the chain state and return us the value of last-token-id variable.

(define-read-only (get-last-token-id)
    (ok (var-get last-token-id))
)
Enter fullscreen mode Exit fullscreen mode

After that, we will define one more read only function get-token-uri which is used to get metadata for the NFT. But in this post we are not going to look that, so we will keep it as none.

(define-read-only (get-token-uri (token-id uint))
    (ok none)
)
Enter fullscreen mode Exit fullscreen mode

Now its time to define a public function to mint our NFTs which will take the recipient address and mint the NFT to that address.

(define-public (mint (recipient principal))
  (let
    (
      (token-id (+ (var-get last-token-id) u1))
    )
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (try! (nft-mint? music-bird token-id recipient))
    (var-set last-token-id token-id)
    (ok token-id)
  )
)
Enter fullscreen mode Exit fullscreen mode

In the above mint function,

  • First, we have defined the local variable token-id and set its value by incrementing the last-token-id value with 1.
  • After that, we have an asserts! function which will validate, is the transaction sender who is calling the mint function tx-sender is equal to the contract-owner who has deployed the contract or not.
  • With this check, the mint function can only be called by the contract owner, and not by anyone else.
  • Then we are minting our music-bird NFT to the recipient.
  • Once the NFT is successfully minted, we are updating the last-token-id variable with the updated value of token-id and returning the same.

Now let's define a read only function get-owner which will take the NFT's token-id and return the owner of the NFT.

(define-read-only (get-owner (token-id uint))
    (ok (nft-get-owner? music-bird token-id))
)
Enter fullscreen mode Exit fullscreen mode

And, the final function in our contract will be a public function using which the NFT owner can transfer their NFT to some other address.

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
    (begin
    (asserts! (is-eq tx-sender sender) err-not-token-owner)
    (nft-transfer? music-bird token-id sender recipient)
    )
)
Enter fullscreen mode Exit fullscreen mode

In the above transfer function,

  • It will take the NFT's token id, and the sender and recipient addresses.
  • Then first we will check transaction sender address and the sender address received in parameter is same or not.
  • If it is not same then we will throw an error that the transaction sender is not the owner of the NFT.
  • And If the condition satisfies, we will begin to transfer the NFT of token id from the sender address to the recipient address.

โœ… Step 4: Check the contracts for errors

Now we have written our NFT contract and it's time to check for errors. Run the command on the terminal:

clarinet check
Enter fullscreen mode Exit fullscreen mode

โœ… Step 5: Testing the contract on local chain

Once our code have been checked for errors we can now test our function by running them on local chain, which can easily be possible with clarinet. To do so we just need to run the following command on terminal

clarinet console
Enter fullscreen mode Exit fullscreen mode

Image description

Now our console session is running, lets call our mint function and try to mint a token for ourselves.

The syntax to call a contract from console in Clarinet is:

>> (contract-call? contract-identifier function-name param-1 param-2 ...)
Enter fullscreen mode Exit fullscreen mode

So calling our mint function:

>> (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.music-bird mint 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM)
Enter fullscreen mode Exit fullscreen mode

You can see the NFT mint event and the resulting ok response. We can transfer the newly minted token with ID u1 to a different principal.

>> (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.music-bird transfer u1 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG)
Enter fullscreen mode Exit fullscreen mode

Here as well you can see the NFT transfer event and the resulting ok response. We can confirms that the token is now owned by the specified principal by calling the get-owner function.

>> (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.music-bird get-owner u1)
Enter fullscreen mode Exit fullscreen mode

โœ… Step 5: Deploy the NFT smart contracts

To deploy our NFT smart contract on testnet, open up the Sandbox - Stacks Explorer

  • Connect your wallet and Switch to testnet.
  • First we need to deploy our NFT trait smart contract
  • Paste your NFT trait smart contract code in IDE and Update the contract name.

Image description

  • Click on Deploy and Confirm the transaction.

Image description

  • You can view the transaction details in your wallet activity. Click on the transaction in you wallet activity and you will be redirected to Stacks block explorer page.

Image description

Now we will repeat the same steps for deploying our NFT smart contract, so first

  • Copy and Paste your NFT smart contract code in IDE.
  • Copy the contract name of nft-trait smart contract from the block explorer page and replace the path in impl-trait function in our NFT smart contract.
(impl-trait 'ST2K0T4HFQKJMMCHBYFPRNVBG0B1NZFZTV94RD9WE.nft-trait.nft-trait)
Enter fullscreen mode Exit fullscreen mode

Image description

  • Update the contract name, click on Deploy and Confirm the transaction.

  • Similarly, you can view the transaction details in your wallet activity

  • To view more details about the deployment, click on the transaction in you wallet activity.

That is all there is to it! NFTs in Clarity are really quite easy to do. The full source code of the project can be found here: https://github.com/0xShoaib/music-bird-nft

Top comments (0)