I am building an NFT minting DAPP that allows a user to connect their crypto wallet and mint one of 10,000 NFT's stored on IPFS through Pinata. I am having several problems but I believe they are all related to one issue that I cannot figure out. To start, I will show the multiple console logs with a hardcoded numerical value for "tokenId" and then I will change the code to something I added to try and get random data from Pinata (which allows me to upload the images and associated JSON metadata to IPFS and get a CID for each). Here is the main code first:
import { Fragment, useEffect, useState } from "react";
import { useSigner, useContract } from "wagmi";
import { ethers } from "ethers";
import GrapeGrannyNFTs from "../artifacts/contracts/GrapeGrannyNFTs.sol/GrapeGrannyNFTs.json";
import Image from "next/image";
const Hero = ({ heading, message }) => {
const contractAddress = "0xBeFC844FdbD06134591F8370ed060004F798e1e2";
const { data: signer } = useSigner();
const contract = useContract({
address: contractAddress,
abi: GrapeGrannyNFTs.abi,
signerOrProvider: signer,
});
let tokenId = 1;
console.log(tokenId);
const url = "https://gateway.pinata.cloud/ipfs/";
const JsonContentId = "QmcaeeYpPebDwBcRvs5efWJbEXTf8m4aRhaa3WiGz6vBFk";
const ImagesContentId = "QmNowgK88MhMFt6c9mDtWdZzYQoFVK8b4GpWBc61HnRg39";
const metadataURI = `${JsonContentId}/${tokenId}.json`;
console.log("Metadata URI: ", metadataURI);
const imageURI = `${url}/${ImagesContentId}/${tokenId}.png`;
console.log("imageURI: ", imageURI);
const [totalMinted, setTotalMinted] = useState(0);
const [isMinted, setIsMinted] = useState(false);
const [isMinting, setIsMinting] = useState(false);
useEffect(() => {
getCount();
getMintedStatus();
}, [isMinted]);
const getCount = async () => {
const count = await contract.count();
console.log(parseInt(count));
setTotalMinted(parseInt(count));
};
const getMintedStatus = async () => {
const result = await contract.isContentOwned(metadataURI);
console.log(result);
setIsMinted(result);
};
const mintToken = async () => {
const connection = contract.connect(signer);
const addr = connection.address;
console.log("Address: ", addr);
const recipient = signer._address;
console.log("Recipient: ", recipient);
const result = await contract.payToMint(recipient, metadataURI, {
value: ethers.utils.parseEther("0.05"),
});
console.log("Result: ", result);
setIsMinting(true);
await result.wait();
getMintedStatus();
getCount();
setIsMinting(false)
};
function refreshPage() {
// fix to grab new id from array and bring mint button back
window.location.reload(false);
}
return (
<Fragment>
<div className="flex items-center justify-center h-screen custom-img">
{/* Overlay */}
<div className="absolute top-0 left-0 right-0 bottom-0 bg-black/50 z-[2]" />
<div className="p-5 text-white z-[2]">
<h2 className="text-5xl text-purple-500 text-center font-bold">
{!isMinted ? heading : ""}
</h2>
<p className="py-5 text-xl font-bold text-center">
{!isMinted ? message : ""}
</p>
<div className="flex flex-col items-center justify-center">
<p className="text-2xl pt-3 pb-2 font-bold">
Total Minted: {totalMinted}
</p>
{/* display tokenId */}
{!isMinted ? (
<button
onClick={mintToken}
className="bg-purple-500 hover:bg-purple-700 text-black font-bold py-3 px-8 rounded-full inline-flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-6 h-6 mr-2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z"
/>
</svg>
<span className="text-black text-2xl">
{!isMinting ? "Mint" : "Minting..."}
</span>
</button>
) : (
// show card if minted with imageURI and link to opensea
<div className="max-w-md rounded-lg border border-purple-500 shadow-md bg-purple-500/80">
<Image
src={imageURI}
className="rounded-lg"
alt="Grape Granny NFT"
width={500}
height={500}
/>
<div className="p-5">
<h1 className=" flex text-3xl py-2 items-center justify-center text-black font-bold">
Congratulations!
</h1>
<p className="flex items-center justify-center text-lg text-black font-bold">
You have minted a Grape Granny NFT! {tokenId}
</p>
<p className="flex items-center justify-center pt-1">
<span className="text-lg font-bold text-black">
It's rarity is
</span>
<span className="text-lg px-2 font-bold text-green-300">
LENGENDARY!
</span>
</p>
<a
href="https://testnets.opensea.io/account"
target="_blank"
rel="noreferrer"
>
<h2 className="flex text-lg text-purple-200/90 py-1 items-center justify-center font-bold">
View on OpenSea
</h2>
<div className="flex items-center justify-center py-1">
<button
onClick={refreshPage}
className="bg-gray-900 hover:bg-purple-900 text-white font-bold py-3 px-8 rounded-full inline-flex items-center justify-center"
>
Mint Again
</button>
</div>
</a>
</div>
</div>
)}
</div>
</div>
</div>
</Fragment>
);
};
export default Hero;
The important part to look at is where I am declaring the variable tokenId to equal 1. When I start the server or refresh the page, the tokenId, metadataURI, and ImageURI console logs show 3 times as shown in the image.
If I click home, it logs once, so I think it has to do with a full refresh. If I click mint, confirm the transaction, and wait, it will mint the NFT and show on Opensea, as long as the particular image hasn't been minted already. (functionality to check is in smart contract). (the data in pinata is named by number, such as 1.png or 1.jpg, since I generated them using hashlips engine). However, once I click mint, it logs several more times as shown in image below.
So, I decided to look past that and try to get random images to be loaded for minting in the background. The way I did this is as follows:
in my scripts folder, randomTokenId.js
export const tokenIds = [];
export function getTokenIds() {
// for loop to generate 10000 tokenIds
for (let i = 0; i < 21; i++) {
tokenIds.push(i);
}
// delete the first tokenId from the array
tokenIds.shift();
// console.log(tokenIds);
}
// function to get random tokenId from the array
export function getRandomTokenId() {
const tokenId = tokenIds[Math.floor(Math.random() * tokenIds.length)];
// console.log(tokenId);
return tokenId;
}
// function to remove the tokenId from the array
export function removeTokenId(tokenId) {
const index = tokenIds.indexOf(tokenId);
if (index > -1) {
tokenIds.splice(index, 1);
}
console.log(tokenIds.length);
}
getTokenIds();
I then imported the functions into the hero component, set tokenId to equal getRandomTokenId(), called removeTokenId(tokenId); in my mintToken Function within the Hero Component, and then displayed the tokenId and array length of TokenId's on screen to try and see which value it was actually getting. Here's a picture of before I click the mint button.
So it is running the function 3 times, grabbing 3 different values from the array, and then choosing the last one. Lastly, when I click the mint button, it is again, executing the functions multiple times, getting different values, and then settling on the last one before finishing the mint process. The value displayed after the minting process is complete is not the actual token id of the particular image minted. Image below:
As you can see, there are multiple numbers being grabbed and set before minting. The array now has 19 numbers, and it displays the accurate number of minted NFT's from the collection. However, on refresh, the array goes back to 20, the total minted state is lost and resets to zero, and we're back to square one with the 3 logs and numbers pulled from the array.
I do not know of a better way to grab a random image from Pinata. I have been banging my head on the wall trying to find another way. I also like this idea, if it would actually only run once. I am fairly new to nextJS. I have followed along with udemy courses and youtube videos, but the problem with that is I dont actually retain a lot since I am just trying to copy and debug. So this is my first real project alone.
Any help is appreciated, I know this is alot, but I am almost certain it has to do with the way the page is rendered with Next JS.
Also, in my index.js file, I have this:
import dynamic from 'next/dynamic'
const Hero = dynamic(() => import('../Components/Hero'), {
ssr: false,
});
Otherwise, It gives me an error saying this:
Not sure if that is the right solution to implement, but wow is this a mess. I am trying to offer people the opportunity to mint an NFT at a fair set price instead of uploading 10,000 images to opensea and trying to set prices based on rarity. This is something I have dedicated close to a month of work on and I am somewhat reliant on its success, financially speaking. Thank you for any advice or help!