Mint your own basic off chain NFT

Description

This project teaches you how to mint your own NFT token. Here are some important packages that we will be using:

hardhat

We will be using hardhat to set up our local blockchain environment to run tests on and mint some NFTs locally.

@openzeppelin/contracts

This package is a collection of Solidity contracts that help us mint NFTs.

ethers

ethers is a typescript friendly package that we will be using to connect to a user’s metamask wallet and perform transactions to mint and NFT.

Note: Make sure to have Metamask installed and working.

Stack

We will be using Solidity to write our smart contract, nextjs for some backend needs along with SSR and reactjs on the front end.

Versions

  • npm: 7.10.0
  • node: 16.0.0

Create a basic NFT minting contract

We will start by create a basic NFT minting contract which will console log a message.

  1. Create a directory mkdir minting-an-nft
  2. Create package.json file

npm init -y
  1. Install hardhat

npx hardhat install

Select Create a basic sample project and say Yes to everything. Your console output would look something like this:

npx-hardhat-install

  1. Test hardhat installation by running npx hardhat run ./scripts/sample-script.js

You should see Hello Hardhat printed in your console along with other things:

npx-hardhat-test

  1. Delete contracts/Greeter.sol file. Keep the contracts folder.
  2. Delete scripts/sample-script.js file. Keep the scripts folder.
  3. Delete test/sample-test.js file. Keep the test folder.
  4. Create contracts/MintingContract.sol file and add the content from this gist.
  5. Create scripts/run.js file and add the following content.

Note: see this gist for detailed notes on this script.

const hre = require("hardhat");

async function main() {
  const contract = await hre.ethers.getContractFactory("MintingContract");
  const token = await contract.deploy();

  await token.deployed();

  console.log("Greeter deployed to:", token.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
  1. Run the run.js script. The output should contain the console log string and a hash that is the address of the contract. This address will be useful for our frontend.

npx hardhat run ./scripts/run.js

npx-hardhat-run-script-first-time

Start the next app

We need start out next app to create an endpoint which out NFT minting contract can use to general NFT metadata.

  1. Create nextjs app. I called it website.

npx create-next-app@latest --ts
  1. Navigate to website folder and start dev server.

cd website && yarn dev

The next app should be started on localhost:3000.

  1. Replace the content of website/pages/index.tsx with the following content.

Note: see this gist for detailed notes on this component.

import type { NextPage } from "next";
import Head from "next/head";
import { useState, useCallback, useEffect } from "react";

import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  const [comments, setComments] = useState<string[]>(["Initialized"]);
  const handleAddComment = useCallback((comment: string, ...args: any[]) => {
    console.log(comment, args);
    setComments((prevState) => [...prevState, comment]);
  }, []);

  useEffect(() => {
    handleAddComment('Ready to code.');
  }, []);

  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">NFT Minting Example</a>
        </h1>

        <div className={styles.grid}>
          <a href="#" className={styles.card} style={{ width: '100%' }}>
            <h2>Mint Basic NFT</h2>
            <ul>
              {comments.map((comment, index) => (
                <li key={index}>{comment}</li>
              ))}
            </ul>
          </a>
        </div>
      </main>
    </div>
  );
};

export default Home;
  1. Delete website/api/hello.ts file. Keep the folders.
  2. While in website directory, create pages/api/tokens/[id].ts.

mkdir -p "pages/api/tokens" && touch "pages/api/tokens/[id].ts"
  1. Write the following code in the new website/pages/api/tokens[id].ts file.

Note: see this gist for detailed notes on this handler.

import type { NextApiRequest, NextApiResponse } from "next";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const tokenId = req.query.id as string;

  res.status(200).json({
    image: `https://media.giphy.com/media/X7IoVUJXtO3wk/giphy.gif`,
    name: `Giphy #${tokenId}`,
  });
}

export default handler;
  1. Test the new api/tokens/[id] handler.

curl --location --request GET 'localhost:3000/api/tokens/1'

handler-test

Finish smart contract logic

  1. Replace contracts/MintingContract.sol content with content in this gist.
  2. Replace scripts/run.js content with the following.

Note: see this gist for detailed notes on this script.

const hre = require("hardhat");

async function main() {
  const contract = await hre.ethers.getContractFactory("MintingContract");
  const token = await contract.deploy('http://localhost:3000/api/tokens/');

  await token.deployed();

  console.log("Greeter deployed to:", token.address);

  let txn = await token.mintBasicNFT();
  await txn.wait();

  txn = await token.mintBasicNFT();
  await txn.wait();
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
  1. While in root folder. Run the contract.

npx hardhat run ./scripts/run.js

Note: make sure the next app is running, otherwise this script will fail

The output should look something like this.

npx-hardhat-test-2

Deploy Next App to Vercel

Feel free to deploy your next app to hosting service of your choice, I am going to deploy Vercel. Follow this guide to deploy your next app.

My deployment link is https://website-kzspirq6d-ximxim.vercel.app/.

Running curl --location --request GET 'https://website-kzspirq6d-ximxim.vercel.app/api/tokens/1' successfully returns our NFT metadata.

vercel-test

Deploy Smart Contract

We will use Rinkeby test net. This way we don’t use real money when deploying.

  1. Create secrets.json file.

echo '{"ALCHEMY_KEY": "", "PRIVATE_KEY": ""}' >> secrets.json
  1. Create an Alchemy Key and place it in secrets.json.
  2. Get Metamask Private Key and place it in secrets.json.

secrets-json

  1. Modify hardhat.config.js and add networks key to default export using our secrets.json file.

Note: see this gist for detailed notes on this script.

const secret = require('./secrets.json');
require("@nomiclabs/hardhat-waffle");

task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

module.exports = {
  solidity: "0.8.4",
  networks: {
    rinkeby: {
      url: secret.ALCHEMY_KEY,
      accounts: [secret.PRIVATE_KEY],
    },
  }
};
  1. Create a deploy script.

cp scripts/run.js scripts/deploy.js
  1. Replace http://localhost:3000 in scripts/deploy.js with your vercel url https://website-kzspirq6d-ximxim.vercel.app/

Note: see this gist for detailed notes on this script.

const hre = require("hardhat");

async function main() {
  const contract = await hre.ethers.getContractFactory("MintingContract");
  const token = await contract.deploy('https://website-kzspirq6d-ximxim.vercel.app/api/tokens/');

  await token.deployed();

  console.log("Greeter deployed to:", token.address);

  let txn = await token.mintBasicNFT();
  await txn.wait();

  txn = await token.mintBasicNFT();
  await txn.wait();
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
  1. Deploy to rinkeby network.

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

Note: Keep track of the address hash, we will need this to make requests from the frontend.

npx-hardhat-rinkeby

The contract is deployed and an NFT should be minted at this point. Try navigating to this link https://testnets.opensea.io/assets/<CONTRACT_ADDRESS>/1.

Also, try https://rinkeby.etherscan.io/address/<CONTRACT_ADDRESS>.

Finish front end

  1. While in root directory. Copy MintingContract.json from artifacts/contracts to your frontend project.

cp artifacts/contracts/MintingContract.sol/MintingContract.json website/MintingContract.json
  1. Install ethers package.

cd website && npm install ethers
  1. Replace the content of website/pages/index.tsx with the content this gist.

That’s all, we have a working DAPP that mints NFTs for us.

GitHub

View Github