ERC721 Contract and IPFS

ERC-721

ERC-721 is the standard for Non-Fungible Tokens (NFTs) on Ethereum, which means that each token is unique and cannot be replaced by another token, unlike ERC-20 tokens that are fungible (interchangeable, like ETH or other tokens). Think of ERC-721 tokens as unique digital items—artwork, collectibles, or property deeds—that you can own, transfer, or sell.

Let’s go through the process of creating an NFT (named ABC) on Ethereum using the Sepolia test network, storing metadata on IPFS, and setting up a basic frontend with Tailwind CSS for minting.

Creating the ERC-721 Contract (ABC NFT)

  1. Set up the Hardhat project:

    mkdir ABCNFT
    cd ABCNFT
    npm init -y
    npm install --save-dev hardhat
  2. Initialize Hardhat:

    npx hardhat

    Follow the prompts to create a basic Hardhat project.

  3. Install OpenZeppelin Contracts:

    npm install @openzeppelin/contracts
  4. Create the Smart Contract: Inside the contracts folder, create a file named ABC.sol:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    
    contract ABC is ERC721, Ownable {
        uint256 public nextTokenId;
        string public baseURI;
    
        constructor(string memory _baseURI) ERC721("ABC", "ABC") {
            baseURI = _baseURI;
        }
    
        function _baseURI() internal view override returns (string memory) {
            return baseURI;
        }
    
        function mint(address to) public onlyOwner {
            _safeMint(to, nextTokenId);
            nextTokenId++;
        }
    
        function setBaseURI(string memory _baseURI) public onlyOwner {
            baseURI = _baseURI;
        }
    }

This Contract:

  1. Base URI: This is the URI where metadata for each NFT will be located. When you call tokenURI(tokenId), it will return baseURI + tokenId, which points to each token’s metadata on IPFS.

  2. Minting Function: Only the contract owner can mint new NFTs. This function assigns a unique tokenId to each new token.

  3. Set Base URI: Allows the owner to set the base URI, which we’ll point to an IPFS folder where all metadata files are stored.

Setting Up IPFS and Metadata

IPFS (InterPlanetary File System) is a decentralized storage solution that allows you to store files (like images, metadata, videos) across a distributed network. Since NFTs often represent assets like images, it’s common to use IPFS to store these files. IPFS provides content-addressable storage, which means files are accessed by their unique hash, ensuring immutability.

For each NFT, we need a metadata file in JSON format. This metadata file will reference the image URL, which will also be stored on IPFS. Each JSON file should follow a standard format to ensure compatibility with NFT platforms.

IPFS Structure:

  1. Upload the image: First, upload your image file to IPFS. You can do this on IPFS's official desktop application or use services like Pinata or NFT.Storage to pin your files on IPFS. We will use Pinate for this tut.

    Let’s say your image file’s IPFS CID is Qm...ImageHash.

  2. Generate the Metadata JSON: Each token needs a metadata JSON file, following the ERC-721 standard format. Here’s an example 0.json file for token 0:

    {
      "name": "ABC #0",
      "description": "An example NFT in the ABC collection",
      "image": "ipfs://<ImageHash>",
      "attributes": [
        {
          "trait_type": "Rarity",
          "value": "Unique"
        },
        {
          "trait_type": "Color",
          "value": "Red"
        }
      ]
    }

    Replace <ImageHash> with the actual IPFS hash of the image.

  3. Upload Metadata File to IPFS: Upload the JSON file to IPFS, then use the CID to set the baseURI in the contract.

Deploying the Contract to Sepolia

  1. Set Up Hardhat Configuration for Sepolia:

    In hardhat.config.js, add the following:

    require("@nomiclabs/hardhat-ethers");
    
    module.exports = {
      solidity: "0.8.0",
      networks: {
        sepolia: {
          url: "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
          accounts: ["YOUR_PRIVATE_KEY"]
        }
      }
    };

    Replace YOUR_INFURA_PROJECT_ID with your Infura project ID and YOUR_PRIVATE_KEY with the private key of the wallet you’ll use for deployment.

  2. Deploy Script:

    Inside scripts/, create a file deploy.js:

    async function main() {
        const [deployer] = await ethers.getSigners();
        console.log("Deploying contract with account:", deployer.address);
    
        const ABC = await ethers.getContractFactory("ABC");
        const abc = await ABC.deploy("ipfs://<MetadataCID>/");
    
        console.log("ABC contract deployed to:", abc.address);
    }
    
    main()
      .then(() => process.exit(0))
      .catch(error => {
        console.error(error);
        process.exit(1);
      });

    Run the deployment script:

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

    Note down the contract address.

Creating a Frontend with Tailwind CSS for Minting

  1. Set up the Frontend: In your root project folder, set up a basic React app:

    npx create-react-app frontend
    cd frontend
  2. Install Dependencies:

    npm install ethers
    npm install -D tailwindcss
    npx tailwindcss init
  3. Configure Tailwind: In tailwind.config.js, add this:

    module.exports = {
      content: [
        "./src/**/*.{js,jsx,ts,tsx}",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
    };

    Update index.css:

    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  4. Connect to the Contract:

    Replace App.js with this code:

    import React, { useState } from "react";
    import { ethers } from "ethers";
    import "./App.css";
    
    const CONTRACT_ADDRESS = "YOUR_CONTRACT_ADDRESS";
    const ABI = [
      "function mint(address to) public",
      "function owner() view returns (address)"
    ];
    
    function App() {
      const [status, setStatus] = useState("");
    
      async function mintNFT() {
        if (!window.ethereum) {
          alert("Please install MetaMask!");
          return;
        }
    
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        await provider.send("eth_requestAccounts", []);
        const signer = provider.getSigner();
    
        const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
    
        try {
          const tx = await contract.mint(await signer.getAddress());
          setStatus("Minting... Check your wallet.");
          await tx.wait();
          setStatus("Minting complete!");
        } catch (error) {
          setStatus("Failed to mint NFT.");
          console.error(error);
        }
      }
    
      return (
        <div className="App min-h-screen flex flex-col items-center justify-center bg-gray-100">
          <h1 className="text-4xl font-bold mb-4">Mint Your ABC NFT</h1>
          <button
            onClick={mintNFT}
            className="bg-blue-500 text-white px-6 py-3 rounded-md hover:bg-blue-600"
          >
            Mint NFT
          </button>
          <p className="mt-4 text-gray-700">{status}</p>
        </div>
      );
    }
    
    export default App;
  5. Styling: Add additional Tailwind classes as desired to enhance the UI.

  6. Run the App:

    npm start

Testing the Frontend and Minting

  1. Make sure you’re connected to the Sepolia network in MetaMask.

  2. Open your frontend in the browser and click the Mint NFT button.

  3. Approve the transaction in MetaMask.

Once the transaction confirms, you should see a message indicating that the minting is complete. If everything is set up correctly, you’ll have successfully minted an NFT through your frontend!

Last updated