Tutorial_4: Minting NFTs

Near Protocol Nigeria (NPK Guild)
11 min readJul 23, 2022

This is the fourth tutorial of NEAR Nigeria zero to hero series. The goal of this series is to transform a programmer with no experience in developing decentralized applications to a skilled NEAR developer. Learn more about this series here.

This article is part 1 of a three part tutorial on building Non-Fungible Token (NFT) on NEAR. At the end of this tutorial, you will cover some of the essential concepts and skills required to build and deploy a NFT contract.

Introduction​

In the four years since ERC-721 was ratified by the Ethereum community, Non-Fungible Tokens have proven themselves as an incredible new opportunity across a wide array of disciplines: collectibles, art, gaming, finance, virtual reality, real estate, and more.

In this NFT series, you will learn how to build and deploy a NFT contract using the de facto NFT standard NEP 171.

Prerequisites​

To complete this tutorial successfully, you will need:

  1. Rust toolchain
  2. A NEAR account
  3. NEAR command-line interface (near-cli)
  4. Working knowledge of Rust

#note: check the previous tutorial to set-up the first three requirements.

Getting started

To get started, you will create a new cargo project. Go to your terminal and run:

cargo new nft_z2H — lib && cd nft_z2H

Ensure a Cargo.toml and src/lib.rs were created for you, and open your project in the code editor of your choice.

Editing Cargo.toml

Open Cargo.toml in the code editor of your choice. Modify your cargo.toml like so:

cargo.toml
cargo.toml

Your NFT project uses NEAR SDK to enable usage of battle tested Rust crates and serde_json for json serialization and deserialization.

Building the NFT contracts

As with most NEAR contracts, you will use some essential utilities from NEAR SDK and NEAR STD crates in your contract. To add the following lines of code to src/lib.rs to use near_sdk and std crates

use external crate

From the previous tutorial, you can recall that the pattern of writing NEAR contracts is to define a struct and implement functions for that struct. In line with this design pattern, the next step is to declare and initialize the struct that will be used in your NFT contract.

contract starter struct
contract struct init

Now you can go ahead and add a corresponding impl like so:

contract starter impl
contract impl init

You will learn how to fill out the code as you progress through the tutorial

Adding NFT functionalities

So far, you have created a simple contract that uses external crates from near_sdk, and contains a struct with a corresponding impl. You have also written a cargo.toml file that allows your smart contract to use near-sdk and serde_json as dependencies.

In this section, you will add the core functionalities of the NFT contract.

Adding Minting Functionality

In order to implement the logic needed for minting, you should break it up into smaller tasks and handle those one-by-one. Step back a bit and think about the best way to do this by asking yourself a simple question: what does it mean to mint an NFT?

To mint a non-fungible token, in the most simple way possible, a contract needs to be able to associate a token with an owner on the blockchain. This means you will need:

  • A way to keep track of tokens and other information on the contract.
  • A way to store information for each token such as metadata (more on that later).
  • A way to link a token with an owner.

That is it! You have now broken down the larger problem into some smaller, less daunting, sub-tasks. Let’s start by tackling the first and work our way through the rest.

Storing information on the contract

Start by navigating to src/lib.rs and filling in some of the code blocks. You need to be able to store important information on the contract such as the list of tokens that an account has.

The first thing to do is to modify the contract struct like so:

struct element

This allows you to get the information stored in these data structures from anywhere in the contract. The code above has created 3 token specific storage:

  • tokens_per_owner: allows you to keep track of the tokens owned by any account
  • tokens_by_id: returns all the information about a specific token
  • token_metadata_by_id: returns just the metadata for a specific token

In addition, you’ll keep track of the owner of the contract as well as the metadata for the contract.

You might be confused as to some of the types that are being used. In order to make the code more readable, custom data types were introduced which we’ll briefly outline below:

  • AccountId: a string that ensures there are no special or unsupported characters.
  • TokenId: simply a string.

The Token, TokenMetadata, and NFTContractMetadata data types, those are structs that we’ll define later in this tutorial.

Next, you will modify the impl functions. Start by modifying what is called an initialization function; new(). This function needs to be invoked when you first deploy the contract. It will initialize all the contract’s fields that you have defined above with default values.

Modify the new() function in src/lib.rs so that it look like so:

contract initializer
contract initializer

This function will default all the collections to be empty and set the owner and metadata equal to what you pass in.

More often than not when doing development, you will need to deploy contracts several times. You can imagine that it might get tedious to have to pass in metadata every single time you want to initialize the contract. For this reason, let’s create a function that can initialize the contract with a set of default metadata. You can call it new_default_meta and it’ll only take the owner_id as a parameter.

Modify the new() function in src/lib.rs so that it look like so:

default contract initialiser

This function is simply calling the previous new function and passing in the owner that you specify and also passes in some default metadata.

Metadata and token information

Now that you have defined what information to store on the contract itself and you have defined some ways to initialize the contract, you need to define what information should go in the TokenMetadata, and NFTContractMetadata data types.

If you look at the standards for metadata, you’ll find all the necessary information that you need to store for both TokenMetadata and NFTContractMetadata. Simply fill in the following code.

Below your contract implementation add the following lines of code

NFTContractMetadata
NFTContractMetadata data type
TokenMetadata data type

This now leaves you with the Token struct and something called a JsonToken. The Token struct will hold all the information directly related to the token excluding the metadata. The metadata, if you remember, is stored in a map on the contract in a data structure called token_metadata_by_id.

This allows you to quickly get the metadata for any token by simply passing in the token’s ID.

For the Token struct, you’ll just keep track of the owner for now. Add the Token struct like so:

Token struct
Token data type

For the Token struct, you’ll just keep track of the owner for now.

The purpose of the JsonToken is to hold all the information for an NFT that you want to send back as JSON whenever someone does a view call (via a frontend). This means you’ll want to store the owner, token ID, and metadata.

Add JsonToken like so:

JsonToken data type
JsonToken data type

Function for querying contract metadata

Now that you have defined some of the types that were used in the previous section, let’s move on and create the first view function nft_metadata. This will allow users to query for the contract’s metadata as per the metadata standard.

Add the block of code below

nft_metadata()
nft_metadata()

This function will get the metadata object from the contract which is of type NFTContractMetadata and will return it.

Just like that, you’ve completed the first two tasks (create a way to keep track of tokens and other information on the contract, and a way to store information for each token such as metadata). You are now ready to move onto the last part of the tutorial.

Minting Logic

Now that all the information and types are defined, let’s start brainstorming how the minting logic will play out. In the end, you need to link a Token and TokenId to a specific owner.

There are a couple data structures that might be useful:

notable contract element

#note: code already exists in the Contract struct.

Looking at these data structures, you could do the following:

  • Add the token ID into the set of tokens that the receiver owns. This will be done on the tokens_per_owner field.
  • Create a token object and map the token ID to that token object in the tokens_by_id field.
  • Map the token ID to its metadata using the token_metadata_by_id.

With those steps outlined, it is important to take into consideration the storage costs of minting NFTs. Since you’re adding bytes to the contract by creating entries in the data structures, the contract needs to cover the storage costs.

If you just made it so any user could go and mint an NFT for free, that system could easily be abused and users could essentially “drain” the contract of all its funds by minting thousands of NFTs.

For this reason, you’ll make it so that users need to attach a deposit to the call to cover the cost of storage. You’ll measure the initial storage usage before anything was added and you’ll measure the final storage usage after all the logic is finished. Then you’ll make sure that the user has attached enough $NEAR to cover that cost and refund them if they’ve attached too much.

Now that you’ve got a good understanding of how everything should play out, let’s fill in the necessary code.

nft_mint()
nft_mint()

You’ll notice that you are using some internal methods such as refund_deposit() and internal_add_token_to_owner(). refund_deposit() has been described in the leading statement. internal_add_token_to_owner(), this will add a token to the set of tokens an account owns for the contract’s tokens_per_owner data structure. Add the functions to your src/lib.rs

internal functions
internal refund_deposit functions
internal function
internal add token to owner function

As well as this helper function, which provides the sha256 of an accountId

sha256 helper

Tidying up the smart contract

You may have observed that src/lib.rs is composed of four divisible components,

  • Initialization
  • Metadata
  • Minting
  • internal functions

To keep the contract code modularized, you will group each component in a different file.

Create three new files like so; src/metadata.rs, src/internal.rs, and src/mint.rs

Creating src/metadata.rs

Add this code to the top of src/metadata.rs:

top of metadata.rs

This allows metadata to “communicate” with other files in your contract. It also defines a data type Token of type String.

The next step is to cut the Token, TokenMetadata, and NFTContractMetadata data types and their implementation to src/metadata.rs.

Creating src/internal.rs

Add this lines of code to the top of src/internal.rs

top of internal.rs

This allows metadata to “communicate” with other files in your contract. It also use CryptoHash utility from near_sdk

The next step is to cut the internal functions refund_deposit, as well as the helper function hash_account_id into src/internal.rs. Next, cut the implementation containing internal_add_token_to_owner into src/internal.rs.

Creating src/mint.rs

By now you should be left with the Contract implementation for nft_mint and the bare struct and implementation for your contract.

Cut the implementation of nft_mint into the src/mint.rs

top of mint.rs

Add the line of code above at the top of the src/mint.rs file to allow its “communication” with src/internal.rs and other files of your contract.

Modify src/lib.rs

Although your newly created files can “communicate” with one another, they can not be accessed from src/lib.rs which initializes the contract.

To facilitate the use of other files in src/lib.rs, you will have to use them.

Add the code below to src/lib.rs after the use statements for near_sdk and std

use internal modules

Finally add the helper Storage helper data type that is used to initialize the metadata and persist the data to the state of the blockchain like so:

helper key storage

Querying for token information

If you were to go ahead and deploy this contract, initialize it, and mint an NFT, you would have no way of knowing or querying for the information about the token you just minted.

Next, you will add a way to query for the information of a specific NFT.

Create a new file like so src/nft_core. Create a function nft_token which takes a token ID as a parameter and return the information for that token. The JsonToken contains the token ID, the owner ID, and the token’s metadata.

Your function should look like so:

query nft metadata by id

Add the lines of code below to make it accessible anywhere in your contract.

Wrapping up

With that finished, You can begin to add features that add utilities to your NFT. In the coming tutorial, you will add a mechanism to allow Royalty payment on owners of your NFT, as well as add a medium of moving your NFT from an external contract.

Versioning for this article

At the time of this writing, this example works with the following versions:

  • cargo 1.62.0 (a748cf5a3 2022–06–08)
  • rustc 1.62.0 (a8314ef7d 2022–06–27)
  • near-cli: 3.4.0

--

--

Near Protocol Nigeria (NPK Guild)

NPK Guild is a Near Protocol Community based in Nigeria, aimed at educating and incentivising young Nigerians onBlockchain technology and the NEAR ECOSYSTEM