Let's see how an NFT minting smart contract works. | Vishal Chandra

Post

editor-img
Vishal Chandra
Jun 19, 2022

Let's see how an NFT minting smart contract works. Line by line... here is the link to the smart contract: https://gist.github.com/mwmwmw/fcb910bd1fc52f6bd33b44417130ec97, open it up to follow along Feel free to comment and ask a question or share your feedback

// SPDX-License-Identifier: UNLICENSED The SPDX License Identifier tells the compiler, developer, and anyone reading the smart contract what they're allowed to do with it legally.

pragma solidity ^0.8.0; This line tells the Solidity which version of the code compiler to use. In this case it means "any version above and including 0.8.0

We can use a lot of code libraries from OpenZepplin to reduce time and avoid security errors import "@openzeppelin/contracts/utils/Strings.sol"; Strings.sol is a library for working with text import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ERC721 is the spec for a NFT and our code will inherit this code and extend it

import "@openzeppelin/contracts/access/Ownable.sol"; *Ownable* adds code that can help check if the user performing specific actions is the owner. import "@openzeppelin/contracts/utils/Counters.sol"; To keep track of objects, we'll be using counters. These counters allow us to create an ID for each NFT that we can look up later.

import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; This code allows us to protect functions against "re-entrance attacks." We have two contracts, A and B. When Contract A calls contract B. and a reentrancy exploit allows B to call back into A before A finishes execution. This code will allow us to protect our code from that exploit.

import "./IContent.sol"; IContent.sol is the interface to our content renderer. This library also adds another interface IItemRenderer, which we will use to render the NFT contents.

import "hardhat/console.sol"; When you're developing, occasionally, you want to see what is happening inside your contract. This code imports a function called console.log which I can use two write a message out onto the Ethereum terminal screen. It's helpful in finding problems.

contract MyNFT is ERC721, Ownable, ReentrancyGuard { In this line, we finally start to define the contract. It's called MyNFT and extends (or uses as a template), hence is an ERC721, Ownable, and ReentrancyGuard, all of which we imported earlier.

using Counters for Counters.Counter; Counters.Counter private _tokenIds; _tokenIds is where we'll store our token ids for our Non-Fungible Token. As this number can only ever increase in our contract, we know that whatever the current count is, we have minted that many NFTs.

mapping(uint256 => IContent.Item) Content; We'll store the data using the token id as a "mapping." This function says, take a large number and map it to an item. We'll be using token ids to locate the NFT data in the Contents map.

uint256 price = 5000000000000000; The price of the NFT in Wei. So this is 0.005 ETH.

address public renderingContractAddress; When this NFT's data is requested, we'll send all of the data to this contract address for rendering.

event NewItem(address sender, uint256 tokenId, string name); Here we define an event. "New Item." When creating a new NFT, the contract will shout out this info. Application developers can watch for this event and respond with UI changes or let the user know the minting process is complete.

constructor() ERC721("MYNFT", "MYCOLLECTION") { Here, we pass in the name and collection name of our NFT to the 'constructor.' From now on, whenever we create a new NFT using this contact, it will use this information to show that they are related.

function GenerateNFT(string calldata ItemName, uint256[6] calldata Magic) public payable virtual { Above is the definition of the NFT generator function. It takes two inputs, ItemName and Magic. ItemName is a string which means it is text. Magic is a list of six big numbers. This is unique to our NFT and used to assign attributes and render the NFT.

'calldata' is the type of memory we want to use for these inputs. It's pretty technical, but it's the cheapest memory available to store our input data.

'public payable virtual' means that you are allowed to pay the contract to execute this function.

require(msg.value >= price, "Not enough ETH sent; check price!"); A user is generating a new NFT; they need to supply the right amount of currency. If they didn't, halt the transaction immediately.

uint256 newItemId = _tokenIds.current(); Get the current token id. We'll increment this number at the end. if (newItemId >= 10000) { revert("This NFT is sold out."); } If the token id exceeds this number, cancel the transaction. Limiting the tokens, creates scarcity and drives price long term.

IContent.Item memory Item; Create a new item instance. `Item` is our data structure for our NFT. Item.name = ItemName; Set the name of our NFT to the ItemName we passed in. Item.magic = Magic; Set the Magic property of our NFT to the Magic data we passed in by the user.

Item.seed = uint256(keccak256(abi.encodePacked(newItemId,msg.sender,block.difficulty,block.timestamp))); This creates our randomization seed to be used as part of a random number generator. abi.encodePacked combines all the pieces of information into one big string. `keccak256` is a hashing function and converts the string into a pseudo-random hash.

_safeMint(msg.sender, newItemId); It creates and gives the NFT to the user (sender's address) Safe implies that it checks that the transfer will succeed.

Content[newItemId] = Item; Store our item in the content "map" using the token id. Later, when calling tokenURI, we'll get our data from `Content` and use it to draw the NFT.

emit NewItem(msg.sender, newItemId, ItemName); Send out our notification event.

_tokenIds.increment(); Increase the token counter for the next NFT. That concludes our generator function!

function setRenderingContractAddress(address _renderingContractAddress) public onlyOwner { renderingContractAddress = _renderingContractAddress; } This function accepts an `address` as an input. It then stores it in `renderingContractAddress.` Notice how it says "onlyOwner," Only the contract owner can use this function.

function setPrice(uint256 _price) public onlyOwner price = _price; } Like before, only the owner of the contract can set the price to mint the NFT.

function totalContent() public view virtual returns (uint256) return _tokenIds.current(); } This function returns the current count for the NFTs or the total number of NFTs minted using this contract.

tokenURI is the most important function in the entire contract because it allows you to 'see' your NFT. function tokenURI(uint256 _tokenId) public view virtual override returns (string memory { public - means anyone can call this function. view - means that this function can only read from the contract but cannot write any new data

virtual override - This means we are changing the functionality of the template contracts we inherited. require( _exists(_tokenId), "ERC721Metadata: URI query for nonexistent token" ); Check if the token id passed in has an associated NFT. If one doesn't exist, we can return an error message.

if (renderingContractAddress == address(0)) { return ""; } If there is no rendering contract, return nothing. The rendering contract will have a value of address(0) on deployment. The contract owner would need to call the setRenderContract function described earlier.

IItemRenderer renderer = IItemRenderer(renderingContractAddress); IItemRenderer is an interface to the item renderer contract. You obtain a reference by passing in the renderContractAddress. return renderer.tokenURI(_tokenId, Content[_tokenId]); This renderer function returns a string with the name, description, image and metadata associated with the NFT.

The crypto paid by the minter goes to the contract and not the owner. The owner of the contract can then make a withdrawal at any time. Cryptocurrency is transferred from the contract to the owner's wallet when withdraw is called. function withdraw() public onlyOwner nonReentrant { Function is public, but only the owner can run it.

(bool success, ) = msg.sender.call{value: address(this).balance}(""); It calls an anonymous function on the message sender (in this case, the owner) using the contract balance (all the Cryptocurrency collected in sales). address(this).balance is the entire balance of the contract. So we'll transfer everything to the wallet.

require(success, "Withdrawal failed"); Require that the transfer be successful or send a message saying the withdrawal has failed.

The render function for NFT is lot more complicated and will probably explain in another post One nice idea to remember is that to make an editable or upgradable NFT, one way is that we tell the main NFT contract to use a new renderer


Checkout related posts on: