How to mint your own NFT token with react
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.
- Create a directory
mkdir minting-an-nft
- Create
package.json
file
npm init -y
- Install
hardhat
npx hardhat install
Select Create a basic sample project
and say Yes
to everything. Your console output would look something like this:
- 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:
- Delete
contracts/Greeter.sol
file. Keep thecontracts
folder. - Delete
scripts/sample-script.js
file. Keep thescripts
folder. - Delete
test/sample-test.js
file. Keep thetest
folder. - Create
contracts/MintingContract.sol
file and add the content from this gist. - 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);
});
- 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
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.
- Create nextjs app. I called it
website
.
npx create-next-app@latest --ts
- Navigate to
website
folder and start dev server.
cd website && yarn dev
The next app should be started on localhost:3000
.
- 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;
- Delete
website/api/hello.ts
file. Keep the folders. - While in
website
directory, createpages/api/tokens/[id].ts
.
mkdir -p "pages/api/tokens" && touch "pages/api/tokens/[id].ts"
- 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;
- Test the new
api/tokens/[id]
handler.
curl --location --request GET 'localhost:3000/api/tokens/1'
Finish smart contract logic
- Replace
contracts/MintingContract.sol
content with content in this gist. - 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);
});
- 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.
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.
Deploy Smart Contract
We will use Rinkeby test net. This way we don’t use real money when deploying.
- Create
secrets.json
file.
echo '{"ALCHEMY_KEY": "", "PRIVATE_KEY": ""}' >> secrets.json
- Create an Alchemy Key and place it in
secrets.json
. - Get Metamask Private Key and place it in
secrets.json
.
- Modify
hardhat.config.js
and addnetworks
key to default export using oursecrets.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],
},
}
};
- Create a deploy script.
cp scripts/run.js scripts/deploy.js
- Replace
http://localhost:3000
inscripts/deploy.js
with your vercel urlhttps://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);
});
- 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.
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
- While in root directory. Copy
MintingContract.json
fromartifacts/contracts
to your frontend project.
cp artifacts/contracts/MintingContract.sol/MintingContract.json website/MintingContract.json
- Install ethers package.
cd website && npm install ethers
- 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.