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.jsonfile
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.solfile. Keep thecontractsfolder. - Delete
scripts/sample-script.jsfile. Keep thescriptsfolder. - Delete
test/sample-test.jsfile. Keep thetestfolder. - Create
contracts/MintingContract.solfile and add the content from this gist. - Create
scripts/run.jsfile 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.jsscript. 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
websitefolder and start dev server.
cd website && yarn dev
The next app should be started on localhost:3000.
- Replace the content of
website/pages/index.tsxwith 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.tsfile. Keep the folders. - While in
websitedirectory, 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].tsfile.
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.solcontent with content in this gist. - Replace
scripts/run.jscontent 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.jsonfile.
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.jsand addnetworkskey to default export using oursecrets.jsonfile.
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:3000inscripts/deploy.jswith 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.jsonfromartifacts/contractsto 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.tsxwith the content this gist.
That’s all, we have a working DAPP that mints NFTs for us.







