Build an NFT minting website

Build an NFT minting website

We are going to build a DAPP that lets users connect their wallets and mint NFTs from our contract Since I don't have money at the moment, lets build it on the testnet :)

Overview of the entire process

ERC721 contract will be deployed on the testnet.
For the frontend we'll be using React.
For wallet integration we'll be using RainbowKit (no worries if you don't know wtf it is).
we'll also be using Wagmi (its basically a library that is a collection of react hooks containing everything you need to start working with react).

ERC721 contract.

you by now already know how to write a basic ERC721 contract. if you dont you can either go to Openzeppline Wizard and copy the boilerplate. or you can copy my code if you don't have the patience to set up an IPFS metadata pointing to the image you want to mint.
The following is the metadata that I've published. You can get the URI of it in the code as well.

Next open https://remix.ethereum.org/ and type out the following code.

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

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

contract Blocktrain is ERC721, ERC721URIStorage, Ownable {

    //all the functions of COunters are forwarded to COunters.Counter
    using Counters for Counters.Counter;

    //instead of saying Counters.counter all the time make it a variable for easy access
    Counters.Counter private _tokenIdCounter;

    //how much does the NFT supposed to cost
    uint256 public mintRate = 0.00022 ether;

    constructor() ERC721("OctoZard", "OZ3") {}

    //the _baseURI points to the metadata that I hosted on pinata
    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://Qmf8ZeCdFeiJbrHX8oDejMHXRN6htaA9TmdvAe741tnUg8/";
    }

    //if you've got the required testnet cash, buy it.
    function safeMint() public payable {
        require(msg.value >= mintRate, "Not enough ether");

        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        //token Id will start with one
        _safeMint(msg.sender, tokenId);
    }

    // The following functions are overrides required by Solidity.
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        require(_exists(tokenId), "Token doesn't exists");
        return string(abi.encodePacked(_baseURI(), Strings.toString(tokenId), '.json'));
    }
}

The address of the contract I published is "0x8a38091418d02Fc78EEB7Bb1843C108575DeFcf9"

Creating React App for minting our NFT!!

Now this is where shit gets real!!

  • Create a basic template for a react-app

      npx create-react-app nft-minting-dapp
    
  • Install dependencies

      cd nft-minting-dapp //this moves into the directory
      npm install -D tailwindcss postcss autoprefixer //installs the dependencies (-D is shorthand for --save-dev )
      npx tailwindcss init -p
      /*
      * npx is a tool that installs and runs the tailwind package without installing it globally
      * tailwindcss is a css framework that helps to build CSS faster
      * init creates a default config file.
      * -p is used to install POSTcss which is a library that optimizeses CSS across different/older browser.
      */
    
  • Replace tailwind.config.js with the following code

      /** @type {import('tailwindcss').Config} */
      module.exports = {
        content: [
          "./src/**/*.{js,jsx,ts,tsx}",
        ],
        theme: {
          extend: {},
        },
        plugins: [],
      }
    
  • Add the following in the ./src/index.css

      @tailwind base;
      @tailwind components;
      @tailwind utilities;
      //you are basically importing the above files in the .css files.
    
  • Run the following cmd

      npm start //starts the react application
    
  • You'll get a screen that looks like this

  • delete the following files as you wont be needing it
    - App.css
    - App.test.js
    - Logo.svg

  • Get rid of the unwanted App.js content below the div with the classname "App".

  • replace it with something like this

  • The output should be something like this

Setup wallet adapter

So we are gonna use Rainbow Kit. Now let's install a few packets shown below

  •   npm install @rainbow-me/rainbowkit@latest wagmi ethers@^5
    
  • Wagmi and ethers are other dependencies for rainbowkit.

  • Import rainbowKit, wagmi, and ethers. in src/index.js

      import '@rainbow-me/rainbowkit/styles.css';
    
      import {
        getDefaultWallets,
        RainbowKitProvider,
      } from '@rainbow-me/rainbowkit';
      import { alchemyProvider } from 'wagmi/providers/alchemy';
      import { publicProvider } from 'wagmi/providers/public';
    
  • add the following to configure your given chain, to generate the required connectors.

      const { chains, provider } = configureChains(
      //!VERYYY IMPORTNANT
      //These are the chains that you'll be getting an //option of. if your working with sepolia use //chain.sepolia
        [chain.mainnet, chain.polygon, chain.sepolia, chain.polygonMumbai],
        [
          alchemyProvider({ apiKey: process.env.ALCHEMY_ID }),
          publicProvider()
        ]
      );
    
      const { connectors } = getDefaultWallets({
        appName: 'My NFT Minting DAPP',
        chains
      });
    
      const wagmiClient = createClient({
        autoConnect: true,
        connectors,
        provider
      })
    
  • get your alchemy Id and paste it in your env file. so process.env.ALCHEMY_ID can access it.

  • delete this before publishing this blog : _fiz9yCF6yQly5xHLtX1XANU5Bqw3z0l

  • Then wrap your application with RainbowKitProvider and [WagmiConfig](https://wagmi.sh/docs/provider).

      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
        <React.StrictMode>
          <WagmiConfig client={wagmiClient}>
            <RainbowKitProvider chains={chains}>
              <App />
            </RainbowKitProvider>
          </WagmiConfig>
        </React.StrictMode>
      );
    
  • Once this is done we should be able to use ConnectWallet hook from rainbowkit to let users connect their wallet and use our App.js

      import { ConnectButton } from '@rainbow-me/rainbowkit';
      import { useAccount } from 'wagmi'
    
      function App() {
        const { address } = useAccount()
    
          return (
              <div>
            <div className='flex items-center justify-end p-2'>
              <ConnectButton />
            </div>
                  <div className="flex flex-col items-center justify-center min-h-[90vh] gap-4">
              <h1 className="text-4xl font-extrabold">Mint an NFT</h1>
              {
                address ? (
                  <button className="bg-black text-white py-2 px-4 rounded-xl transform hover:scale-105">
                    Mint
                  </button>
                ) : (
                  <ConnectButton />
                )
              }
            </div>
              </div>
          );
      }
    
      export default App;
    

Connecting the React App to our contract address and Contract ABI.

  • write the following with your contract address on app.js

      const CONTRACT_ADDRESS="0xbA33b8fc28818d114E9DAe4ed8B4d458d2D2bEA6"
    
  • Copy the ABI of the contract

  • Go to ./src/ABI.js (you'll have to create that application) and paste what you copied from the above

  • Now you'll have to give access to this ABI, so our react application can interact with the following, in the app.js file

  •   import { useAccount, useContract, useSigner } from 'wagmi'
    
      function App() {
    
        const { address } = useAccount()
    
        //we need this hook to make transactions like minting the NFT
        const { data: signer } = useSigner()
    
        const contract = useContract({
          address: CONTRACT_ADDRESS,
          abi: CONTRACT_ABI,
          signerOrProvider: signer
        })
    
        console.log(contract)
    
      ...
    
  •   const [mintRate, setMintRate] = useState("0")
      const [error, setError] = useState(false)
      const [tokenId, setTokenId] = useState(null)
      const [loading, setLoading] = useState(false)
    
      const getMint = async() => {
        const price = await contract.mintRate()
        setMintRate(price.toString()) //its in WEI
      }
    
      useEffect(() => {
        if(contract?.signer) { 
      //it means you have permission to write on this contract
      //calls the getMint function to get the price
          getMint()
        }
      }, [contract])
    
      const mintNft = async() => {
        setLoading(true)
        try {
          // value of safemint is sent in wei
          const mint = await contract.safeMint({ value: mintRate })
    
          console.log(mint)
          //listen to this contract on the event called Transfer.
          contract.on("Transfer", (to, from, token) => {
            console.log(to, from, token)
            setTokenId(token.toString())
            setLoading(false)
          })
        } catch(err) {
          console.log(err)
          setError(err)
          setLoading(false)
        }
      }
    
      console.log("MINT ", mintRate)
      console.log("TOKEN ", tokenId)
      console.log("ERROR ", error)
      console.log("LOADING ", loading)
    

Its that simple ")