lilcoderman

Posted on Dec 08, 2021Read on Mirror.xyz

How to create a motherf*cking NFT using Solidity

This is a simple no-bullshit guide to creating an NFT using Solidity.

Before you go any further, you need to know a little about NFTs, Crypto wallets, Solidity, and the ERC721 token standard to understand this article.

I am going to start by showing you the entire code.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";


contract BasicNFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("BasicNFT", "BNFT") {}

    function mint(string memory tokenURI) public {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
    }
}

Wait…that’s it?

Yes, what else did you expect you beautiful b*stard?

Now let's try to understand the not-so-obvious lines of code.

OpenZeppelin to the rescue

The OpenZeppelin library is there to save you from writing any shitty code. It does all the hard work for you. You import the necessary contracts from the library, and they just work. I am talking about the following imports made in the code.

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

We'll discuss what exactly I am using from these contracts in the coming sections.

Inheriting from 'ERC721URIStorage'

Next, we inherit our contract from the ERC721URIStorage contract - which was given to us by the OpenZeppelin library. In short, contracts that inherit from other contracts can access their (non-private) functions and variables.

contract BasicNFT is ERC721URIStorage {}

Initializing the constructor

Since we're inheriting from ERC721URIStorage, we'll initialize its constructor in the BasicNFT constructor. It requires two parameters i.e. a name and a symbol.

// name: 'BasicNFT', symbol: 'BNFT'
constructor() ERC721("BasicNFT", "BNFT") {}

The name and symbol are not f*cking important, so let's just move on.

Assign token ids with 'Counters'

Let's see another utility provided to us by the brilliant OpenZeppelin library i.e. Counters. This gets you a simple counter that can be incremented or decremented. It will allow us to issue ids to our ERC721 tokens.

// include counters
using Counters for Counters.Counter;
// declare token ids as counters
Counters.Counter private _tokenIds;

Coding the mint function

Now comes the most important function in this contract i.e the motherf*cking mint function. I've declared it as a public method so that everyone is allowed to call it.

Let's go through all the steps we take inside this function. Here's the code with some comments:

 function mint(string memory tokenURI) public {
     // 1. Increment the the token Ids by one
     _tokenIds.increment();
     // 2. Get the current Id (the first id will be 1)
     uint256 newItemId = _tokenIds.current();
     // 3. Call the `_safeMint` function provided by OpenZeppelin
     _safeMint(msg.sender, newItemId);
     // 4. Call the `_setTokenURI` function provided by OpenZeppelin
     _setTokenURI(newItemId, tokenURI);
 }

Yeah, I should probably explain the last two lines of the code.

_safeMint explained

This function literally mints an ERC721 token for you. It takes in an address and a token id as arguments. I am passing in msg.sender and newItemId as the address and token id respectively.

  • msg.sender is the wallet calling the mint function
  • newItemId is the current token id i.e. _tokenIds.current()

Once called, this function will safely mint a token and transfer it to the wallet calling the mint function.

_setTokenURI explained

This is one of my favorite methods provided by OpenZeppelin. You can set the metadata for your token through this function. You know the kind of metadata you see on OpenSea - a name, description, and even an image. It takes in a token id and URI as arguments.

We'll discuss how to get a tokenURI soon. Basically, it's just a URL that sends back a JSON object.

This is the tokenURI I'll be using as an example. It returns the following object:

{
  "name": "Basic NFT",
  "description": "Basic NFT tutorial by @lilcoderman",
  "image": "https://ipfs.io/ipfs/bafkreid5vd3sw2wwj2uagm22nomnktbhgl2qqcgksgxvu7xfwwbxtxecly"
}

After the contract is deployed, you will be able to call the mint function with a tokenURI and get a unique ERC721 token in return.

WHEW! It looks like most of the work was done by the OpenZeppelin library. You ask why? Well, because you deserve to be taken care of you beautiful son of a b*tch.

Deploying to testnet

I am going to use Remix to compile, deploy and interact with the contract. The contract will be deployed to the Rinkeby testnet. If you need to learn how to do that, please go through this link.

My contract got deployed to this address: 0x20A65d15fFf5315A4c9E79dED87273b1BfC3Fd65

After deploying, let's call the mint function with the tokenURI I mentioned above.

Contract interface provided by Remix

We'll wait a few minutes after it finishes minting. Now, let's take a look at our newly minted NFT on OpenSea/Rarible:

So freaking cool, right?

Image and Metadata

Throughout this article, we've assumed that we already have the tokenURI i.e. the URL pointing towards our metadata. But, how did I get that? Where did I upload my NFT's image and metadata?

I uploaded them to an IPFS. IPFS is a decentralized file storage system that isn't controlled by one entity and is instead maintained by a large number of peers in the network.

nft.storage

I used a free service called nft.storage to upload my assets to the IPFS network. You need to upload the image first and then use its link in your metadata. After doing that, you need to upload your metadata file as well.

Image and metadata files as shown on nft.storage

Copy the link to your metadata and pass it as an argument to the mint function.

Off-chain vs. On-chain

In this article, we've only discussed how we can create an off-chain NFT. You might have heard about an on-chain token as well. The difference between them is simple:

  • off-chain: Only the token ids are stored on the blockchain. Image and metadata are stored somewhere else e.g. IPFS.
  • on-chain: Both the metadata and image are stored directly on the blockchain.

We stored our image and metadata on IPFS, which is a pretty good second option. Many top projects have used this same approach.

Get in touch if you want to know more about NFTs and Web dev. You can find me on Twitter @lilcoderman