NFT Explorer Template
Dive deep into the NFT explorer template that helps you quickly bootstrap a full-stack NFT explorer dapp!
Introduction
create-web3-dapp
comes with pre-built templates that help you boostrap full-stack web3 dapps in a matter of minutes. In this guide, we are going to take a look at one such template: The NFT Explorer Dapp. We'll understand how to use it and how the code works!
How to use the NFT Explorer Template
To use the NFT Explorer Template, follow the steps listed below or check out this video for reference:
1. Install the CW3D NPX NFT Explorer package
In your terminal, navigate to the folder you want to create your project in and run:
npx create-web3-dapp@latest nft-explorer
If you've previously installed
create-web3-dapp
globally vianpx create-web3-dapp
:The CLI builder will notify you if a new version has been released. In any case, we always suggest you to run using the latest available version by running
npx create-web3-dapp@latest
.
2. Insert a project name
4. Choose your chain
Once you have selected your starting template, you'll need to choose the chain you want to configure your project for. Note that you can always modify or add more chains in the future. Current choices include:
- Ethereum
- Polygon
- Arbitrum
- Optimism
5. Select testnet or mainnet
Select if you want to configure with mainnet or testnet. We generally recommend starting with a testnet if you're not ready to deploy to the live chain yet. Here are the corresponding test networks for each chain:
- Ethereum -> Goerli
- Polygon -> Mumbai
- Arbitrum -> Arb-Goerli
- Optimism -> Opt-Goerli
6. Create an Alchemy Account or Enter your API key
Then you will be asked if you already have an Alchemy account, selecting yes or no based on if you have the Alchemy account or not.
If you already have an Alchemy account you will be redirected to your Alchemy dashboard from where you can copy and paste your API key into the terminal.
If not, you will be redirected to create a new Alchemy account and after creating the Alchemy account you can copy and paste your API key in the terminal.
We'll use an Alchemy API key to connect to the blockchain and read data.
That's it! Now you're ready to rock! 🚀 To launch CW3D, simply cd
into your new project folder and run:
npm run dev
. This should open up the app on http://localhost:3000
And you have an NFT explorer dapp ready to go! Just by using the template 🚀
Code Walkthrough
Let's explore the project to understand how the code is working. Open up your project in an IDE or a text editor like VSCode.
At first glance, you can see that it's a NextJS project and has the standard NextJS project structure.
Here are some things to be noted:
- The components reside in the
components
folder, and it has two components:navbar.jsx
(defines the connect wallet button and logo) andnftGallery.jsx
(the main section of the project, displays NFTs). - The APIs are defined under the
pages/api
folder which contains the apis for fetching NFTs by collection and owner. These are named:getNftsForCollection
andgetNftsForOwner
. - The
index.js
is the main file in which we are just rendering theNFTGallery
component. - The
public
folder includes the assets for the project and thestyles
folder includes the styles for the project.
Here are the explanations for the main parts of the project
1. The NFTGallery
component
NFTGallery
component
import { useEffect, useState } from "react";
import styles from "../styles/NftGallery.module.css";
import { useAccount } from "wagmi";
import etherscan from "../public/etherscan.svg";
import verified from "../public/verified.svg";
export default function NFTGallery({}) {
const [nfts, setNfts] = useState();
const [walletOrCollectionAddress, setWalletOrCollectionAddress] =
useState("vitalik.eth");
const [fetchMethod, setFetchMethod] = useState("wallet");
const [pageKey, setPageKey] = useState(false);
const [spamFilter, setSpamFilter] = useState(true);
const [isLoading, setIsloading] = useState(false);
const { address, isConnected } = useAccount();
const [chain, setChain] = useState("eth-mainnet");
const changeFetchMethod = (e) => {
switch (e.target.value) {
case "wallet":
setWalletOrCollectionAddress("vitalik.eth");
break;
case "collection":
setWalletOrCollectionAddress(
"0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e"
);
break;
case "connectedWallet":
setWalletOrCollectionAddress(address);
break;
}
setFetchMethod(e.target.value);
};
const fetchNFTs = async (pagekey) => {
setIsloading(true);
setNfts();
const endpoint =
fetchMethod == "wallet" || fetchMethod == "connectedWallet"
? "/api/getNftsForOwner"
: "/api/getNftsForCollection";
try {
const res = await fetch(endpoint, {
method: "POST",
body: JSON.stringify({
address:
fetchMethod == "connectedWallet"
? address
: walletOrCollectionAddress,
pageKey: pagekey ? pagekey : null,
chain: chain,
excludeFilter: spamFilter,
}),
}).then((res) => res.json());
setNfts(res.nfts);
if (res.pageKey) {
setPageKey(res.pageKey);
} else {
setPageKey();
}
} catch (e) {}
setIsloading(false);
};
useEffect(() => {
fetchNFTs();
}, [fetchMethod]);
useEffect(() => {
fetchNFTs();
}, [spamFilter]);
return (
<div className={styles.nft_gallery_page}>
<div>
<div className={styles.fetch_selector_container}>
<h2 style={{ fontSize: "20px" }}>Explore NFTs by</h2>
<div className={styles.select_container}>
<select
defaultValue={"wallet"}
onChange={(e) => {
changeFetchMethod(e);
}}
>
<option value={"wallet"}>wallet</option>
<option value={"collection"}>collection</option>
<option value={"connectedWallet"}>
connected wallet
</option>
</select>
</div>
</div>
<div className={styles.inputs_container}>
<div className={styles.input_button_container}>
<input
value={walletOrCollectionAddress}
onChange={(e) => {
setWalletOrCollectionAddress(e.target.value);
}}
placeholder="Insert NFTs contract or wallet address"
></input>
<div className={styles.select_container_alt}>
<select
onChange={(e) => {
setChain(e.target.value);
}}
defaultValue={"ETH_MAINNET"}
>
<option value={"ETH_MAINNET"}>Mainnet</option>
<option value={"MATIC_MAINNET"}>Polygon</option>
<option value={"ETH_GOERLI"}>Goerli</option>
<option value={"MATIC_MUMBAI"}>Mumbai</option>
</select>
</div>
<div
onClick={() => fetchNFTs()}
className={styles.button_black}
>
<a>Search</a>
</div>
</div>
</div>
</div>
{isLoading ? (
<div className={styles.loading_box}>
<p>Loading...</p>
</div>
) : (
<div className={styles.nft_gallery}>
{(nfts?.length &&
fetchMethod !=
"collection")&&(
<div
style={{
display: "flex",
gap: ".5rem",
width: "100%",
justifyContent: "end",
}}
>
<p>Hide spam</p>
<label className={styles.switch}>
<input
onChange={(e) =>
setSpamFilter(e.target.checked)
}
checked={spamFilter}
type="checkbox"
/>
<span
className={`${styles.slider} ${styles.round}`}
></span>
</label>
</div>
)}
<div className={styles.nfts_display}>
{nfts?.length ? (
nfts.map((nft) => {
return <NftCard key={nft.tokenId} nft={nft} />;
})
) : (
<div className={styles.loading_box}>
<p>No NFTs found for the selected address</p>
</div>
)}
</div>
</div>
)}
{pageKey && nfts?.length && (
<div>
<a
className={styles.button_black}
onClick={() => {
fetchNFTs(pageKey);
}}
>
Load more
</a>
</div>
)}
</div>
);
}
function NftCard({ nft }) {
return (
<div className={styles.card_container}>
<div className={styles.image_container}>
<img src={nft.media}></img>
</div>
<div className={styles.info_container}>
<div className={styles.title_container}>
<h3>{nft.title}</h3>
</div>
<hr className={styles.separator} />
<div className={styles.symbol_contract_container}>
<div className={styles.symbol_container}>
<p>{nft.symbol}</p>
{nft.verified == "verified" ? (
<img
src={verified.src}
width="20px"
height="20px"
/>
) : null}
</div>
<div className={styles.contract_container}>
<p className={styles.contract_container}>
{nft.contract.slice(0, 6)}...
{nft.contract.slice(38)}
</p>
<img src={etherscan.src} width="15px" height="15px" />
</div>
</div>
<div className={styles.description_container}>
<p>{nft.description}</p>
</div>
</div>
</div>
);
}
-
The code imports various libraries such as React, a CSS file, and some images. It defines a function called "NFTGallery" which renders a page that displays NFTs, a type of digital asset.
-
The code sets up several pieces of state that the function uses to track the data it needs to display the NFTs. These include
nfts
, which stores the NFTs that are displayed,walletOrCollectionAddress
, which is the address of the wallet or collection the NFTs are fetched from, andisLoading
, which is a boolean that indicates whether the NFTs are being loaded or not. -
The function uses two
useEffect
hooks to update the NFTs whenever thefetchMethod
orspamFilter
state changes. ThefetchNFTs
function is called whenever the user clicks the "search" button or changes one of the input fields. -
The return statement displays a form where the user can enter an address to search for NFTs. Once the user enters an address and clicks the "search" button, the function fetches the NFTs associated with the address and displays them. If the user toggles the "hide spam" switch, the function will filter out any NFTs that the user considers spam.
2. The getNftsForCollection
API
getNftsForCollection
API
import { Network, Alchemy } from "alchemy-sdk";
export default async function handler(req, res) {
const { address, pageKey, pageSize, chain } = JSON.parse(
req.body
);
if (req.method !== "POST") {
res.status(405).send({ message: "Only POST requests allowed" });
return;
}
console.log(chain);
const settings = {
apiKey: process.env.ALCHEMY_API_KEY,
network: Network[chain],
};
const alchemy = new Alchemy(settings);
try {
const nfts = await alchemy.nft.getNftsForContract(address, {
pageKey: pageKey ? pageKey : null,
pageSize: pageSize ? pageSize : null,
});
const formattedNfts = nfts.nfts.map((nft) => {
const { contract, title, tokenType, tokenId, description, media } =
nft;
return {
contract: contract.address,
symbol: contract.symbol,
media: media[0]?.gateway
? media[0]?.gateway
: "https://via.placeholder.com/500",
collectionName: contract.openSea?.collectionName,
verified: contract.openSea?.safelistRequestStatus,
tokenType,
tokenId,
title,
description,
};
});
const filteredNFTs = formattedNFTs.filter(
(nft) => nft.title.length && nft.description.length && nft.media
);
res.status(200).json({
nfts: filteredNFTs,
pageKey: nfts.pageKey,
});
// the rest of your code
} catch (e) {
console.warn(e);
res.status(500).send({
message: "something went wrong, check the log in your terminal",
});
}
}
-
The code defines a function that handles HTTP requests sent to a server. The function receives a request (
req
) and a response (res
) object as input parameters. -
The function first extracts the
address
,pageKey
,pageSize
, andchain
properties from the request body, which is expected to be in JSON format. -
Then, it checks whether the HTTP method of the request is POST, and returns an error response with a status code of
405
if it is not. -
Next, it creates an instance of the
Alchemy
class from the"alchemy-sdk"
module, using an API key and a network specified in thesettings
object, which is based on thechain
property extracted from the request body. -
After that, the function calls the
getNftsForContract
method of thealchemy.nft
object, passing theaddress
,pageKey
, andpageSize
values as arguments. The method returns a list of NFTs, which are then formatted into a new list of objects containing selected properties. -
The formatted list is then filtered to remove any NFTs with empty
title
ordescription
properties or missingmedia
property. -
Finally, the function sends a success response with a status code of
200
, containing the filtered list of NFTs and thepageKey
value returned by thegetNftsForContract
method. If an error occurs during the execution of the function, the error is caught and a500
status code error response is sent back to the client with a message.
3. The getNftsForOwner
API
getNftsForOwner
API
import { Network, Alchemy, NftFilters } from "alchemy-sdk";
export default async function handler(req, res) {
const { address, pageSize, chain, excludeFilter, pageKey } = JSON.parse(
req.body
);
console.log(chain);
if (req.method !== "POST") {
res.status(405).send({ message: "Only POST requests allowed" });
return;
}
const settings = {
apiKey: process.env.ALCHEMY_API_KEY,
network: Network[chain],
};
const alchemy = new Alchemy(settings);
try {
const nfts = await alchemy.nft.getNftsForOwner(address, {
pageSize: pageSize ? pageSize : 100,
excludeFilters: excludeFilter && [NftFilters.SPAM],
pageKey: pageKey ? pageKey : "",
});
const formattedNfts = nfts.ownedNfts.map((nft) => {
const { contract, title, tokenType, tokenId, description, media } =
nft;
return {
contract: contract.address,
symbol: contract.symbol,
collectionName: contract.openSea?.collectionName,
media: media[0]?.gateway
? media[0]?.gateway
: "https://via.placeholder.com/500",
verified: contract.openSea?.safelistRequestStatus,
tokenType,
tokenId,
title,
description,
};
});
res.status(200).json({ nfts: formattedNfts, pageKey: nfts.pageKey });
} catch (e) {
console.warn(e);
res.status(500).send({
message: "something went wrong, check the log in your terminal",
});
}
}
-
The code defines a function that handles HTTP requests sent to a server. The function receives a request (req) and a response (res) object as input parameters.
-
The function first extracts the address, pageSize, chain, excludeFilter, and pageKey properties from the request body, which is expected to be in JSON format.
-
Then, it checks whether the HTTP method of the request is POST, and returns an error response with a status code of 405 if it is not.
-
Next, it creates an instance of the Alchemy class from the "alchemy-sdk" module, using an API key and a network specified in the settings object, which is based on the chain property extracted from the request body.
-
After that, the function calls the getNftsForOwner method of the alchemy.nft object, passing the address, pageSize, excludeFilter, and pageKey values as arguments. The method returns a list of NFTs owned by the address, which are then formatted into a new list of objects containing selected properties.
-
The formatted list is then sent back to the client as a success response with a status code of 200, along with the pageKey value returned by the getNftsForOwner method.
-
If an error occurs during the execution of the function, the error is caught and a 500 status code error response is sent back to the client with a message.
Conclusion
In conclusion, the NFT Explorer Template is a beautiful template that comes with create-web3-dapp
, you can use it to create an NFT explorer dapp in minutes and even customize it further to make your own project with extra features!
Updated over 1 year ago