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 {}
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") {}
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 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"
}
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.
We'll wait a few minutes after it finishes minting. Now, let's take a look at our newly minted NFT on OpenSea/Rarible:
- OpenSea: https://testnets.opensea.io/assets/0x20a65d15fff5315a4c9e79ded87273b1bfc3fd65/1
- Rarible: https://rinkeby.rarible.com/token/0x20a65d15fff5315a4c9e79ded87273b1bfc3fd65:1
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.
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)
Could you also explain the motherf*cking point of doing this? And of motherf*cking NFTs in general?
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.
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.