DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to create a motherf*cking NFT using Solidity
Abdul Rauf
Abdul Rauf

Posted on • Updated on

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

Initialising the constructor

Since we're inheriting from ERC721URIStorage, we'll initialise 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") {}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
 }
Enter fullscreen mode Exit fullscreen mode

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 favourite 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"
}
Enter fullscreen mode Exit fullscreen mode

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 decentralised file storage system which 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.


Don't leave me, take me with you

Like what you read? Follow me on social media to know more about NFTs, Web development, and shit-posting.

Twitter: @lilcoderman

Instagram: @lilcoderman

Top comments (3)

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ

Could you also explain the motherf*cking point of doing this? And of motherf*cking NFTs in general?

Collapse
 
lilcoderman profile image
Abdul Rauf

Personally, I'm interested in the creator economy and digital ownership. The following motherf*cking article by @dabit3 helped me understand the point of all this:
freecodecamp.org/news/what-is-web3/

Hope it's helpful to you as well, my beautiful friend.

Collapse
 
tjkoury profile image
TJKoury • Edited on

How about tracking goods to make sure you’re not getting a counterfeit? Do you know how many knock-offs are β€œdrop shipped” by Amazon using the name under license? Nike, Levi’s, anything you buy based on what you think is quality can be counterfeited. Sometimes it’s just clothing, and sometimes it can kill you.

Companies will use their crypto keys in their production processes just like they do now, the difference is using a public ledger that everyone can audit, as opposed to closed tracking systems.

Fun with console.log()

This post briefly outlines the top 10 neat tricks you can use to level up your logging experience.