NFT tutorial for beginners
Pepsi dropped its genesis NFT collection on Dec. 2021 (source), till today the trading volume for this project is 2.3k ETH ($5.6 million). I took a close look into the Pepsi NFT contract, to learn how they implemented this project. In this tutorial, I will explain the smart contract line-by-line.
If you are new to NFT and want to learn how to develop an NFT smart contract by yourself, this is the right article for you. If you are curious about how the technology behind NFT works, this article would also help.
If you understand ERC-721 and have coding experience, please you are good to skip this section.
- Solidity: Till now, most NFT contracts use Solidity. You do not need to know Solidity to understand the concept. But if you want to implement an NFT contract by yourself afterward, some coding experience will be very helpful. To learn Solidity, I would highly recommend reading the official document.
- ERC-721: Pepsi NFT is built on the ERC-721 smart contract implemented by Open Zeppelin. In a nutshell, the ERC-721 standard provides the basic functionality of NFT including transfer, check account balance, mint, etc. This article gives a great overview: ERC-721 Non-Fungible Token Standard.
Like a lot of people, I am new to this industry. I tried to double-check the correctness of the content, but mistakes might occur. Please let me know if you spot any, I would truly appreciate your help and updating this tutorial.
Many projects would open-source their contract, and people can get educated before making any (investment) decision. Transparency is also one of my favorite features in the Blockchain industry.
Pepsi unveiled its NFT contract address on the official website. You can simply copy that address and search it in Etherscan, the go-to place to check information on the Ethereum blockchain. Then you will see the following page:
By clicking the
Contract button, you will see 14 files. However, we only need the first file,
PepsiMicDrop.sol. This file contains the Pepsi NFT contract. The rest are ERC-721 contracts by Open Zeppelin, which we will not cover.
For your convenience, I put the code on GitHub, and you can find it here. I recommend you to open the code side by side with this article.
Before diving into the code, let’s first understand how smart contracts define NFTs. Under the hood, an NFT has two pieces of information: (1) Id (2) URI.
Under the ERC-721 standard, each NFT is unique. How? The contract assigns a unique Id to each NFT, thus enabling the uniqueness. In an ERC-721 based contract, you will not find two different NFTs with the same Id.
Then, where is the image (it can also be video, document, etc)? That is where URI comes into play. URI stands for Universal Resource Identifier. Think of it as an URL, through which you can get all metadata associated with the NFT. The metadata can be image URL, description, attributes, etc. The current most popular NFT marketplace OpenSea has its metadata standard. You can find a comprehensive list of metadata.
Now, you might ask: Would URI and Id ensure the uniqueness of NFT?
Ideally, ERC-721 based NFT will have its unique image or metadata, which is why they are non-fungible. However, you can assign the same URI to a different Id, thus creating two NFTs that look the same from the surface. However, that is not the purpose of ERC-721. There is another standard, ERC-1155, which supports semi-fungible NFT. You can learn more here.
Besides, you might wonder: Why metadata is not stored in the contract? Because it is super expensive to store data on the blockchain, especially images or videos. However, you can still store metadata on-chain and pay the high gas fee. Some projects use SVG format images, which drastically reduce the data size, thus reducing gas fees. However, this is beyond the scope of this tutorial. Let me know if you want to learn more about SVG-based NFT.
The contract might look complicated at first glance, but it is well organized. Fig.2 visualizes the structure of this contract. Let’s go through each component.
- SPDX License Identifier is the first line after comment. It indicates how other people can use this code. (line 48)
- Solidity Version lets the compiler translate the code correctly, and then the EVM can understand. (line 49)
- Import ERC-721 contracts then the Pepsi NFT contract will use the ERC-721 contract as its blueprint.
- PepsiMicDrop is the NFT contract we will further discuss in the next section. It has three subparts: (1)State Variables (2)Constructor (3)Functions.
- State variables are variables whose values are permanently stored in contract storage.
- Builder is a special function that is only executed upon contract creation. You can run the contract initialization code.
- Functions are self-explanatory. Most functions are used to set or get state values.
Let’s look into the
PepsiMicDrop contract. I will explain the contract by its functionality, each functionality is related to some functions and state variables.
The constructor takes 2 inputs:
- “Pepsi Mic Drop” is the NFT token name
- “PEPSIMICDROP” is the token symbol.
The assignment happens in the ERC721 contract code, which the contract imports at line 51. That is why you cannot find anything related to this constructor body. It is the beauty of inheritance, and we do not need to redo it.
Inside the constructor, we can see 2 state variables get new values,
micDropsId. Why they are needed? In this NFT drop, Pepsi kept the first 50 NFTs to themselves, so the NFTs that are publicly available start from id 51.
Wait, isn’t NFT an image? Why the here they are using numbers to represent NFTs? If you have these questions, I was with you. To understand that, we need to look into what is an NFT.
Now let’s talk about the mint.
Part 1 — Merkle Proof
There are two inputs of the mint function, proof, and leaf. They are used for Merkle Proof from lines 3 to 7. Essentially, it checks if a user is qualified to mint the NFT. There is a concept called whitelist. Sometimes you have to get into this whitelist, to be able to mint NFTs later. I will not explain this part, because someone did a great job. Please check this article, which gives a comprehensive explanation of Merkle Proof.
Part 2 — Prerequisites
The next part, lines 8 to 11, defines the 4 prerequisites of mint. Let’s go through each of them.
(1) Before the NFT started to sell, the state variable
saleStarted is set to false. So it will not pass the check-in line 8. When the sale begins, the contract owner can call the function
startSale() to change the value of
require(saleStarted == true, "The sale is paused");
(2) This check makes sure the user address is not 0x0. 0x0 is the Ethereum genesis address, and no user will use it. I am also not sure why this check is necessary. Let me know if you understand.
require(msg.sender != address(0x0), "Public address is not correct");
(3) In Pepsi Drop, each address can only mint one NFT.
alreadyMinted is a state variable in mapping type, like a dictionary, that keeps track of all addresses that minted the NFT.
require(alreadyMinted[msg.sender] == false, "Address already used");
(4) There is a limited supply, up to 1983 NFTs. This checks if all NFTs have been claimed.
micDropsId is the unique token id we discussed above.
require(micDropsId <= maxMint, "Mint limit reached");
Easy, right? And we are almost done, hanging there.
Part 3. The actual mint.
This is where mint actually happens. The good news is we do not need to implement it because ERC-721 already did this.
To mint, we call the function
_safemint() with the user’s wallet address and the unique token id, and that’s it. Under the hood, there is a state variable (like a dictionary) that keeps track of the ownership of each token. By calling
_safemint() function, we update this state variable, assign or change the ownership of the corresponding token.
One note here, the state variable
micDropId represents token id, and it increments by 1 right before each mint.
Part 4: Update Minted Address
As mentioned in Part2, each address can only mint one NFT.
alreadyMinted[msg.sender] = true;
This line ensures whoever successfully minted an NFT gets recorded.
Part 5: Return
After minting, the token id is returned to the frontend.
(3) Update URI
The last part we need to discuss is the function that updates URI.
As you already know, each NFT has its URI. And this is the function used to change the base URI.
The base URI is the mutual part among each NFT’s URI. By default, the URI is
baseURI/tokenId. The reason why we set baseURI is that it saves gas fees. Imagine if you set URI for each NFT that is extremely expensive.
You can change how to combine
tokenIdby overriding the function
tokenURI defined in the ERC-721 contract. For example, you can do something like
baseURI/tokenId + ‘.json’ if that is how the URI is formatted.
You might wonder, what are
onlyOwner. These are function modifiers, which define the condition for a function to run.
onlyOwner means only the contract owner can call this function. Of course, we only want the contract owner to have the ability to change the token URI.
(4) Other Parts
The uncovered parts are simply functions to get or set state variables, I am sure you do not need my help.
So, that is it. Thanks for reading this article. I hope you find it helpful.
Want to Connect?Please feel free to reach out (my LinkedIn) if you have any questions, feedback, or even just a random chat.