Quickstart Guide To Building NFT Minting DAPPs On The Ethereum Chain

Quickstart Guide To Building NFT Minting DAPPs On The Ethereum Chain

Probably Not So Quick But It Will Be Comprehensive ๐Ÿ“ƒ

ยท

21 min read

Hey folks ๐Ÿ‘‹๐Ÿป, it's been a while since I wrote my last article and I thought why not write another article. In this tutorial, we'll discuss how to generate NFTs, create a minting DAPP, set up a delayed reveal and many more. So, let's just talk about the background story for a little bit and hop into the actual smart contract.

What On The Earth Are NFTs?

If you haven't been living under a rock for the past year or so you should know what are NFTs at this point but if you don't know, NFTs are just digital assets that are most importantly stored in the blockchain. (Well, not exactly stored in the blockchain but kind of like that) NFTs are not just sitting in the blockchain doing nothing. They are bought and sold online, not for 5$ but for millions of dollars ๐Ÿค‘. You can go to opensea and scroll through a bunch of awesome NFTs that costs millions of dollars.

NFTs are unique, as the name suggests. (Non-Fungible Tokens) So they can't be replaced with something else. For example crypto is fungible, you can trade 1ETH for 1ETH which are exactly the same but in NFTs, you can't do that. If you traded an NFT for a different NFT you will have a completely different NFT.

Also, I must say that they are a TON of hate going on for NFTs. Read more about that. But I don't care because,

NFT Meme.png

So without any further ado, let's jump into the REAL STUFF.

How We Can Create A NFT Collection?

Well, there are many methods for you to create NFT collections, you can create them with code, low code or even no code. But in this article, we focus on the method with code to mint our NFTs. But we'll just go over the other method briefly.

  • No Code

So the easiest method of all of them is Open Sea Lazy Mint. You can just go to opensea and click that create button and enter the details manually and save the NFT. I'll actually be minted when someone bought your NFT. That's why they call it Lazy Minting.

image.png

  • Low Code

You can use services like Thirdweb or NFT Port to mint your NFTs and you don't actually have to write a single line of solidity. It might be a good way as well if you think about it.

These are definitely not No Code. You will have to write some code in order to work with these. But you are free from writing blockchain specific languages like Solidity. So it might be a great fit for your choice. Especially Thirdweb, I find Thirdweb really interesting so make sure to give Thirdweb a go as well.

Let's start Building Our NFT Collection

Getting The Base Character And Layers Ready

Before writing any smart contract, DAPPs or anything like that, we need some digital thing to mint as NFTs, right? So far the most popular thing is little artworks. So let's build our project with some artwork as well.

image.png

But most of them are not just a piece of art. If you take a look at popular NFT collections like Bored Ape Yacht Club. You can see a similarity between all of those. It is basically like there is a base figure, in this case, an ape and on that ape, there are different things that are called traits. You have to design those base characters and traits separately, to learn how to properly do that, you can check out this amazing video by codestackr. I am not gonna explain it too much because I feel like it is out of the scope of this article.

You can generate all the layers you need and come back to this article to proceed further.

Generating The Actual NFT Arts

So this is where the interesting things start happening. Now you will need an Art Generator to merge all of your layers and create a single PNG file. There are many ways you could achieve that but in this case, I am gonna use something called HashLips. I have made a customized fork of the official HashLips art generator on my GitHub profile.

You will need to clone the premade repository by me to proceed with this tutorial. In that repository, you will be able to find starter codes for all of the things that we are gonna be doing. To clone the repository, use this command.

git clone -b starter https://github.com/osadavc/build-a-minting-dapp.git

once you have it cloned, cd into the art-generator directory. This is where all the magic happens. If you have generated your layers according to the video I suggested I have before, you should have a folder structure like this or something similar to this structure

images/
โ”œโ”€โ”€ Background/
โ”‚   โ”œโ”€โ”€ GreenBackground.png
โ”‚   โ””โ”€โ”€ BlueBackground.png
โ”œโ”€โ”€ Base/
โ”‚   โ””โ”€โ”€ MainBase.png
โ”œโ”€โ”€ Eyes/
โ”‚   โ”œโ”€โ”€ SunGlasses.png
โ”‚   โ”œโ”€โ”€ Eyes.png
โ”‚   โ””โ”€โ”€ EyebrowRaised.png
โ””โ”€โ”€ Hair/
    โ”œโ”€โ”€ StraightHair.png
    โ””โ”€โ”€ CurlyHair.png

What you have to do is remove everything inside the art-generator/layers that you cloned earlier and copy all of the folders that you have generated and put them inside that art-generator/layers directory.

Then open the file called art-generator/src/config.js and look from lines 6 to 23. Which looks something like this.

const namePrefix = "Your Collection";
const description = "Remember to replace this description";
const baseUri = "ipfs://NewUriToReplace";

const layerConfigurations = [
  {
    growEditionSizeTo: 5,
    layersOrder: [
      { name: "Background" },
      { name: "Eyeball" },
      { name: "Eye color" },
      { name: "Iris" },
      { name: "Shine" },
      { name: "Bottom lid" },
      { name: "Top lid" },
    ],
  },
];

Here you have a number of things to change here. First, change the namePrefix variable to the name of your NFT Collection and make sure to add a little description to the description variable as well. We'll have to do some work before changing the base URL, so we'll look into that really soon. Then you have the most important part.

const layerConfigurations = [
  {
    growEditionSizeTo: 5,
    layersOrder: [
      { name: "Background" },
      { name: "Eyeball" },
      { name: "Eye color" },
      { name: "Iris" },
      { name: "Shine" },
      { name: "Bottom lid" },
      { name: "Top lid" },
    ],
  },
];

First, change the property called growEditionSizeTo to the maximum number of arts that you need and then you have the layer order, so you have to put all of your folder names in the order of the real layer order, so the topmost object in the array layersOrder should be the layer that sits behind all of the other layers in the actual artwork. Most probably this will be the background and you have to put all of your traits in this array for the art generator to work properly.

Then you've come to the satisfying part. Make sure you're in the art-generator directory in your terminal and run

yarn generate

or

npm run generate

Once you've done that you'll see a directory called build with all of your images and metadata, if you take a look at one of these images, you will see that the image is composed of all of the layers that you defined previously.

image.png

Uploading Images To IPFS

Now that you have all the images that you need, you have to upload them somewhere in order for the other people to see them. You can store them in many places but the most recommended way is to store them in IPFS (InterPlanetary File System). We can't store them in the blockchain itself because the images are too big to be stored in the blockchain. This is the reason why I said NFTs are not really stored in the blockchain. We only have a record of them in the blockchain.

To upload the images to IPFS, you'll need a service that'll allow you to store files in IPFS. We are gonna use Pinata. Go ahead and create an account in pinata.

Now, you can select your images folder and upload them to pinata like this, Animation1.gif

image.png

Now, if you take a closer look at the newly created row, you will see something called CID, make sure to copy that and replace it like this in the config file in the art generator.

const namePrefix = "Beautiful Eyes";
const description = "A NFT Collection That Contains 50 Beautiful Eyes";
// Update The CID
const baseUri = "ipfs://QmPAgyCjcMQLag5HM2rJN71Zkt1TFtgLdyqcG8qzum73vh";

Now that you have updated the config file, you need to update the metadata files you previously generated as well. Obviously doing it manually will be a tedious task, so make sure you are at the root level of the art-generator and run the below command which will update the metadata.

yarn update_info

or

npm run update_info

Once it is successfully completed, you can check a metadata file and you'll see that the information is correctly updated. Now you have to follow the same steps that we did previously for images and upload these metadata files to Pinata as well.

image.png

Congratulations ๐ŸŽŠ. Now you have your artwork generated and hosted in a decentralized manner.

Writing Solidity Smart Contract

Now that the artworks are hosted on IPFS, it is time to write the smart contract to allow users to mint NFTs. We are going to use the Hardhat toolchain to be able to compile and deploy Solidity code. You usually have to set up hardhat with their CLI tool but to save time I have created a starter for you. If you would like to learn how to set up Hardhat by yourself, read Hardhat documentation, they have explained how to set up a new project very clearly.

I am gonna show you how to build the smart contract but if you need to learn solidity from zero to hero, I highly suggest taking a look at Hashlips Solidity playlist.

The starter code for the smart contract is available in the smart-contract directory in the previously cloned repository.

There are a few folders in the smart-contracts folder. All of your smart contracts will go to contracts and all of the scripts that you define will go inside the scripts directory. We will later see to what we use these scripts.

Creating A Basic NFT Minting Contract

We are going to use the ERC721 industry standard which is suitable to build Non Fungible tokens to build our smart contract.

To make our lives a lot easier and writing smart contracts so easier and more secure, we are going to use a tool called OpenZeppelin. Before doing anything you need to install OpenZeppelin, in-order to do that run one of the following commands inside the smart-contract directory.

yarn add @openzeppelin/contracts

or

npm i @openzeppelin/contracts

and to get the boilerplate for our contract, we are going to use the OpenZeppelin tool, they have an awesome tool called OpenZeppelin Wizard. Open their wizard and select the following options.

image.png

  1. First we are gonna choose ERC721 because that is the standard that we are planning to use.

  2. Then, you need to choose a Name and Symbol for your NFT Contract.

  3. From features, choose Mintable, Auto Increment Ids and URI Storage

  4. Choose Ownable because the contract is owned by only one user.

Once the code is generated, click on the Copy To Clipboard button and paste the copied code to the contract you have created.

Next, we need to add a couple of functions to customize the behaviour of our smart contract.

  • First, we are gonna add a new mint function to allow users to mint
function mint(address to) public payable {
   require(msg.value >= 0.005 ether, "Not Enough ETH");

   _tokenIdCounter.increment();
   uint256 newItemId = _tokenIdCounter.current();

   _mint(to, newItemId);
}

With this mint function, we are allowing users to mint an NFT by paying 0.005ETH, if they haven't paid the correct amount we are aborting the function.

  • Base URI Method
function _baseURI() internal pure override returns (string memory) {
    return "ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/";
}

This is the URL where our METADATA is hosted, make sure to change it to your own IPFS content id and keep a slash after the id.

  • Token URI Function
function tokenURI(uint256 _tokenId)
    public
    view
    override(ERC721, ERC721URIStorage)
    returns (string memory)
{
    string memory currentBaseURI = _baseURI();
    return
        bytes(currentBaseURI).length > 0
            ? string(
                abi.encodePacked(
                    currentBaseURI,
                    uintToString(_tokenId),
                    ".json"
                )
            )
            : "";
}

What we are basically doing here is when a marketplace requests for the metadata of the NFT, we get the token id and return the actual metadata URL. Notice here we are using a utility function called uintToString to convert a uint256 to string. That function is given below.

function uintToString(uint256 _i)
    internal
    pure
    returns (string memory str)
{
    if (_i == 0) {
        return "0";
    }
    uint256 j = _i;
    uint256 length;
    while (j != 0) {
        length++;
        j /= 10;
    }
    bytes memory bstr = new bytes(length);
    uint256 k = length;
    j = _i;
    while (j != 0) {
        bstr[--k] = bytes1(uint8(48 + (j % 10)));
        j /= 10;
    }
    str = string(bstr);
}

Once you have made all these changes your contract should look something like this,

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract BeautifulEyesNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("BeautifulEyesNFT", "BEN") {}

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/";
    }

    function mint(address to) public payable {
        require(msg.value >= 0.005 ether, "Not Enough ETH");

        _tokenIdCounter.increment();
        uint256 newItemId = _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    function _burn(uint256 tokenId)
        internal
        override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        uintToString(_tokenId),
                        ".json"
                    )
                )
                : "";
    }

    // utils

    function uintToString(uint256 _i)
        internal
        pure
        returns (string memory str)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 length;
        while (j != 0) {
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint256 k = length;
        j = _i;
        while (j != 0) {
            bstr[--k] = bytes1(uint8(48 + (j % 10)));
            j /= 10;
        }
        str = string(bstr);
    }
}

With that in place, the most basic NFT minting contract is done ๐Ÿ™Œ๐Ÿป. If you really want to test this Contract as it is now, go to the very bottom and read the Deployment section but for now, I am not gonna do it.

Adding A Sale Time

Right now a user can mint tokens at any time that they need, but you might need to restrict this to a specific time, let's just say the time that you launch your project. So let's stop users from minting at any time that they want.

First, go to your contract and create a new variable of the type uint256 to store the sale starting time like the following,

uint256 public saleTimeStamp;

Then in the constructor, get a new argument to change the saleTimeStamp when the contract is deployed like the following,

constructor(uint256 _saleTimeStamp) ERC721("BeautifulEyesNFT", "BEN") {
    saleTimeStamp = _saleTimeStamp;
}

You might need to change the sale time later, so you will also need a function to change the value stored in the save timestamp variable.

function setSaleTimeStamp(uint256 _saleTimeStamp) public onlyOwner {
    saleTimeStamp = _saleTimeStamp;
}

Now, when a user is trying to mint we need to check if the time stored in saleTimeStamp is lower than the time now meaning the sale is already going on. To do that add another require statement to the mint function.

function mint(address to) public payable {
    // Compare the time now and the saved timestamp
    require(saleTimeStamp < block.timestamp, "Sale has not started yet");
    require(msg.value >= 0.005 ether, "Not Enough ETH");

    _tokenIdCounter.increment();
    uint256 newItemId = _tokenIdCounter.current();

    _mint(to, newItemId);
}

after necessary changes are made, the contract should look like this

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract BeautifulEyesNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    uint256 public saleTimeStamp;

    Counters.Counter private _tokenIdCounter;

    constructor(uint256 _saleTimeStamp) ERC721("BeautifulEyesNFT", "BEN") {
        saleTimeStamp = _saleTimeStamp;
    }

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/";
    }

    function mint(address to) public payable {
        require(saleTimeStamp < block.timestamp, "Sale has not started yet");
        require(msg.value >= 0.005 ether, "Not Enough ETH");

        _tokenIdCounter.increment();
        uint256 newItemId = _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    function _burn(uint256 tokenId)
        internal
        override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        uintToString(_tokenId),
                        ".json"
                    )
                )
                : "";
    }

    // utils

    function uintToString(uint256 _i)
        internal
        pure
        returns (string memory str)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 length;
        while (j != 0) {
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint256 k = length;
        j = _i;
        while (j != 0) {
            bstr[--k] = bytes1(uint8(48 + (j % 10)));
            j /= 10;
        }
        str = string(bstr);
    }

    // setters

    function setSaleTimeStamp(uint256 _saleTimeStamp) public onlyOwner {
        saleTimeStamp = _saleTimeStamp;
    }
}

Now, users should not be able to mint beyond the time limit that we've specified. Again, if you want to deploy this and check, you can do that.

Reveal NFT Metadata After Drop

Normally when someone mints your NFT, they will see the actual metadata at the particular moment. But to create hype around the collection what we can do is just show users a placeholder image and at a fixed date we can reveal the collection to display the real images, metadata etc.

In order to do this, you need to prepare the unrevealed metadata, I am gonna use this as unrevealed metadata.

{
  "name": "Beautiful Eyes Not Revealed",
  "description": "A NFT Collection That Contains 50 Beautiful Eyes",
  "image": "ipfs://QmPAgyCjcMQLag5HM2rJN71Zkt1TFtgLdyqcG8qzum73vh/20.png",
  "dna": "?",
  "edition": 0,
  "date": 1652033960576,
  "attributes": [
    {
      "trait_type": "Background",
      "value": "?"
    },
    {
      "trait_type": "Eyeball",
      "value": "?"
    },
    {
      "trait_type": "Eye color",
      "value": "?"
    },
    {
      "trait_type": "Iris",
      "value": "?"
    },
    {
      "trait_type": "Shine",
      "value": "?"
    },
    {
      "trait_type": "Bottom lid",
      "value": "?"
    },
    {
      "trait_type": "Top lid",
      "value": "?"
    }
  ]
}

As you can see I have used ? to represent the unrevealed metadata and a static image, so before revealing, every NFT will have the same image. You need to upload this to IPFS as well.

First, you need a couple of different variables to get this working, add the following. The notRevealedMetadata should contain the metadata URL for NFTs that are not revealed and the isReveal variable is what we toggle to reveal or unreveal NFTs.

string public notRevealedMetadata =
    "ipfs://QmWX1bxTYxMtgsnZQA1PwWkVZtM9ZYGfJMsLUBgzPsp1MK";
bool public isRevealed = false;

Then you need to add a function to toggle the isReveal variable accordingly,

function setIsRevealed(bool _isRevealed) public onlyOwner {
    isRevealed = _isRevealed;
}

Then what you need to do is modify the tokenURI function so that it'll return the hidden metadata URL if the metadata is not revealed.

function tokenURI(uint256 _tokenId)
    public
    view
    override(ERC721, ERC721URIStorage)
    returns (string memory)
{
   // Add if check here to check if the metadata is not revealed.
    if (isRevealed == false) {
        return notRevealedMetadata;
    }

    string memory currentBaseURI = _baseURI();
    return
        bytes(currentBaseURI).length > 0
            ? string(
                abi.encodePacked(
                    currentBaseURI,
                    uintToString(_tokenId),
                    ".json"
                )
            )
            : "";
}

Once you have made those changes, your contract should look like this,

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract BeautifulEyesNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    uint256 public saleTimeStamp;

    string public notRevealedMetadata =
        "ipfs://QmWX1bxTYxMtgsnZQA1PwWkVZtM9ZYGfJMsLUBgzPsp1MK";
    bool public isRevealed = false;

    Counters.Counter private _tokenIdCounter;

    constructor(uint256 _saleTimeStamp) ERC721("BeautifulEyesNFT", "BEN") {
        saleTimeStamp = _saleTimeStamp;
    }

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/";
    }

    function mint(address to) public payable {
        require(saleTimeStamp < block.timestamp, "Sale has not started yet");
        require(msg.value >= 0.005 ether, "Not Enough ETH");

        _tokenIdCounter.increment();
        uint256 newItemId = _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    function _burn(uint256 tokenId)
        internal
        override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        if (isRevealed == false) {
            return notRevealedMetadata;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        uintToString(_tokenId),
                        ".json"
                    )
                )
                : "";
    }

    // utils

    function uintToString(uint256 _i)
        internal
        pure
        returns (string memory str)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 length;
        while (j != 0) {
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint256 k = length;
        j = _i;
        while (j != 0) {
            bstr[--k] = bytes1(uint8(48 + (j % 10)));
            j /= 10;
        }
        str = string(bstr);
    }

    // setters

    function setSaleTimeStamp(uint256 _saleTimeStamp) public onlyOwner {
        saleTimeStamp = _saleTimeStamp;
    }

    function setIsRevealed(bool _isRevealed) public onlyOwner {
        isRevealed = _isRevealed;
    }
}
Deploying The Smart Contract

Now that the smart contract is done, we need to deploy it to actually use it, when you're moving your app to production you have to deploy your contract on Main Network but in Ethereum, deploying on Main Network costs huge gas fees, so for sake of this tutorial we are gonna deploy on a test net called Rinkeby.

We will need something called Ethereum node to deploy a smart contract, there are multiple node providers like Alchemy and Infura, for this tutorial we are going to use Alchemy. First, go ahead and create an account in Alchemy.

Then you can create an application like this,

Animation1.gif

Then create a new .env file in your smart-contract directory and add the following variables.

RINKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/XXX-XXXXXXXXXXXXXXXXXXXXXXXXX
PRIVATE_KEY=XXXXXXXXXX

You need to set the HTTP URL from Alchemy to the RINKEBY_URL and you need to get your wallet private key and set it to PRIVATE_KEY (You will need some Rinkeby in your wallet, use this faucet to get it if you don't have already).

Read this to learn how to export the private key from your wallet.

Now open the hardhat config file in your smart-contract directory and in the exporting object, add a new key called networks and modify it like this.

module.exports = {
  solidity: "0.8.4",
  networks: {
    rinkeby: {
      url: process.env.RINKEBY_URL || "",
      accounts:
        process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
    },
  },
};

Now let's move on to the scripts directory, you will see a file called deploy.js. Open that file, here is how we deploy the contract. The main thing you should pay attention to is this line.

const beautifulEyesNFT = await BeautifulEyesNFT.deploy(1652017663);

Here, as parameters, we need to pass everything that we expect from the contract's constructor. In this case the sale starting time. Once you have prepared that open your terminal and navigate to the smart-contract directory and run the following command.

npx hardhat run scripts/deploy.js --network rinkeby

If everything went okay you should see the contract address in the terminal like this.

Animation.gif

Congratulations ๐ŸŽŠ. Now you have deployed your smart contract to the Rinkeby test network.

Creating A DAPP To Mint NFTs

Now that the contract is deployed, all we have to do is build a frontend to allow the user to mint the NFTs.

As always the starter for the frontend is available inside the client folder in the Github repo. In the starter, I have implemented the design and installed the dependencies that we are gonna use.

For this tutorial, I am going to use a really interesting library that I found recently called wagmi. It is essentially a library to interact with the blockchain from a react application.

First, go ahead and open index.tsx in the pages directory, here we will have to do a bunch of imports from the wagmi library. You can add these lines to the very top of the file.

import {
  useAccount,
  useConnect,
  useNetwork,
  useContractWrite,
  chain,
} from "wagmi";

import { InjectedConnector } from "wagmi/connectors/injected";

// Make sure to import ethers as well
import { ethers } from "ethers";

You will also need the artifacts from the solidity contract. Once you have deployed your contract a folder called artifacts will be generated. Find the JSON file with the name of the contract and copy that and put that somewhere in the client folder. Then make sure to import it in the index.tsx like this.

import contractInterface from "../lib/BeautifulEyesNFT.json";

Then we'll initialize these hooks

// Used to change the button text dynamically
const [buttonMessage, setButtonMessage] = useState("Mint NFT");

// Used to connect to the wallet
const { connect } = useConnect({
  connector: new InjectedConnector(),
});

// Used to connect to the correct network
const { activeChain, switchNetworkAsync } = useNetwork();

// Used to get wallet information
const { data: account } = useAccount();

// Call the minting function in the contract with 0.005 ETH
const { writeAsync } = useContractWrite(
  {
   // Update your contract address and pass the `abi` object in the artifact to contract interface.
    addressOrName: "0x40ac75b32e02bcb48e24c7a2061b399446157f14",
    contractInterface: contractInterface.abi,
  },
  "mint",
  {
    args: [account?.address],
    overrides: {
      gasLimit: 3000000,
      value: ethers.utils.parseEther("0.005"),
    },
  }
);

Then we'll need some lifecycle hooks to keep the wallet connected to the network we need.

useEffect(() => {
  switchNetworks();
}, [account]);

useEffect(() => {
  switchNetworks();
}, [activeChain?.id]);

const switchNetworks = async () => {
  if (account && activeChain?.id != chain.rinkeby.id && switchNetworkAsync) {
    setButtonMessage("Switching Network...");
    await switchNetworkAsync(chain.rinkeby.id);
    setButtonMessage("Mint NFT");
  }
};

What we are essentially doing here, is checking what is the network and switching according to every time the user manually changes the network or when they connect to metamask.

Then we'll render the button conditionally according to the minting status/wallet connected status

{!account ? (
  <button
    className="w-full bg-[#a04edf] rounded-md mt-5 text-white py-3"
    onClick={() => {
      connect();
    }}
  >
    Connect Wallet
  </button>
) : (
  <button
    className="w-full bg-[#a04edf] rounded-md mt-5 text-white py-3 animate-pulse disabled:bg-[#a04edf]/30 disabled:animate-none disabled:cursor-none"
    disabled={buttonMessage != "Mint NFT"}
    onClick={async () => {
      setButtonMessage("Minting...");
      await writeAsync();
      setButtonMessage("Check Your NFT On Opensea ๐ŸŽ‰");
    }}
  >
    {buttonMessage}
  </button>
)}

Make sure to add your contract address and the ABI in the correct places as mentioned above.

If you put a past time when deploying, you should be able to mint now.

Change Variables In The Smart Contract

If you want to change the information in the smart contract like saleTimeStamp or isRevealed there are a few ways you can do that, for now, we're gonna use Remix IDE to do that.

  1. Open the remix IDE and create a new contract.
  2. Copy and paste all your smart contract code to remix.
  3. Compile the contract.
  4. Selected Injected Web3 as the provider and connect Metamask.
  5. Paste your smart contract address and interact with the contract.

Here is how you do it in detail,

Revealing the NFTs

Once it is time for NFT reveal, you can follow the steps mentioned above and call the function called setIsRevealed with true as an argument. Once you do that opensea won't automatically pick up the new metadata, you either have to update them manually from opensea or you can use a service such as nftrefreshmetadata.com (only work on mainnet) to refresh all the metadata and show the real image.

So, that is pretty much for the article and I hope you enjoyed it. The completed code is available here at the final branch - github.com/osadavc/build-a-minting-dapp

Let me know what you think in the comments ๐Ÿ™Œ๐Ÿป.

ย