Building an NFT Gating dApp Using thirdweb SDK

14 Apr, 202328 Min ReadBy Mantle
DevelopersNFTTutorials
Building an NFT Gating dApp Using thirdweb SDK

This guide will show you how to create a website that restricts content based on owning an NFT with React. A huge thanks to all the existing content by the thirdweb team and adding support to Mantle Testnet!

You can use any ERC-721 or ERC-1155 contract to enable token gating using the website we are creating.

Overview

Before we begin, feel free to check out the working demo at mantlefordevs.xyz.

A basic NFT-gated website where unless an individual mints the NFT, won’t be able to access the info/ webpage that’s been gated. This is a sample website that we created for developers to access Mantle’s ecosystem fund details during ETHDenver!

What happens if an individual does not own the NFT?

0 X Icy Cj0o3di Q Ia W

As you can check above while claiming the NFT, I intentionally canceled the transaction, hence our wallet doesn’t have the NFT required to access the webpage. The dApp redirects the user to the main screen again. Now, let’s check what happens if we successfully mint the NFT.

0 6 Wi X Xw Qygw5p6 Kh

As seen above, post minting the NFT we are able to click on the “Are You Ready” button which checks if the individual connected to the dApp has the NFT in the wallet. If they own the NFT, then the button takes them to another webpage that we gated using the NFT. Sounds cool!? Follow along and build one for yourself!

Deploying NFT Contract on Mantle Testnet

To get started, head over to thirdweb’s NFT Drop contract.

To begin, navigate to thirdweb NFT Drop contract, which enables you to establish a series of requirements known as “claim phases” that regulate when and how your audience can obtain an NFT from your drop. These conditions include items like release dates, allowlists, claim restrictions, and staggered reveals. Please check the gif below for more steps.

0 Vdttc8 Th Dew7r J Lv

Creating an App

For now, also clone the GitHub repository associated with this guide. (This is a personal GitHub repository, soon we will be migrating every project to the official GitHub)

Set up dApp Details

Create a new folder named const under the project. Under this new folder, create a new file called yourDetails.js. This file will contain all the information such as your smart contract address and minimum numbers of NFTs required to access protected content:

// Replace this with your contract address
export const contractAddress = "0x9c1361fFA29445297711ce5cE5275EFF3D2a79c0";

// Replace this with your domain name.
export const domainName = "mintonmantle.xyz";

Change the erc1155TokenId if you are using an ERC-1155 token. domainName is used for thirdweb authentication. Make sure to replace it with your domain.

Important
Please make sure to copy your NFT contract address in the following files:

  1. ./const/yourDetails.js

0 T J6 Sm Vj C2 a ID O

2. ./util/checkBalance.js

0 4 E Nj2 Zks V62d Ocs (1)Setup the .env

Once you have cloned the repo, there should be a file named .evn.local. Export your private keys and add them to the file.

Click on this link to learn how to export your private key. (A guide by MetaMask)

0 C5 Sd JS Oukvt4 6p

Setup the ThirdwebProvider

Inside the pages/_app.js page, we wrap our application in the ThirdwebProvider component to access all of the React SDK's hooks anywhere in our application.

import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
import Head from "next/head";
import ThirdwebGuideFooter from "../components/ThirdwebGuideFooter";
import { domainName } from "../const/yourDetails";
import "../styles/globals.css";
//import { MantleTestnet } from "@thirdweb-dev/chains";

// This is the chainId your dApp will work on.
//const activeChainId = ChainId.MantleTestnet;

function MyApp({ Component, pageProps }) {
return (
<Head>
<title>Mantle Ecosystem Grants</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="This website is being shared exclusively for ethDenver and events during ethDenver. If you're reading this, you're getting access to some really exciting new/info!"
/>
</Head>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
export default MyApp;

In this file, we need to configure an activeChain that our project will work on. For this step, there are two ways to do it. One would be custom chain config and another would be using the active chain, as Mantle testnet is supported officially with thirdweb.

Custom Chain Config

Note: This configuration is not necessary, but it’s okay to have both just in case SDK has issues in the future or trying out other templates from thirdweb!

<ThirdwebProvider
//activeChain={MantleTestnet}

activeChain={{
// === Required information for connecting to the network === \\
chainId: 5001, // Chain ID of the network
// Array of RPC URLs to use
rpc: ["https://rpc.testnet.mantle.xyz"],

// === Information for adding the network to your wallet (how it will appear for first time users) === \\
// Information about the chains native currency (i.e. the currency that is used to pay for gas)
nativeCurrency: {
decimals: 18,
name: "Testnet BitDAO",
symbol: "BIT",
},
shortName: "mantle-testnet", // Display value shown in the wallet UI
slug: "Mantle-Testnet", // Display value shown in the wallet UI
testnet: true, // Boolean indicating whether the chain is a testnet or mainnet
chain: "ETH", // Name of the network
name: "Mantle Testnet", // Name of the network
}}

authConfig={{
domain: domainName,
authUrl: "/api/auth",
}}
>

Supported Config

import { MantleTestnet } from "@thirdweb-dev/chains";

function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider
activeChain={MantleTestnet}
authConfig={{
domain: domainName,
authUrl: "/api/auth",
}}
>

Creating the Auth Config File

Create a new file called auth.config.js at the root of your project.

Here is where we configure the ThirdwebAuth object for us to use in our authentication endpoints.

We need to pass two configuration options to the ThirdwebAuth object:

  1. You can store our wallet’s private key in the environment variable during development. However, it’s not recommended to store private keys in environment variables during production.
  2. Our domain name.
import { ThirdwebAuth } from "@thirdweb-dev/auth/next";
import { PrivateKeyWallet } from "@thirdweb-dev/auth/evm";
import { domainName } from "./const/yourDetails";

export const { ThirdwebAuthHandler, getUser } = ThirdwebAuth({
domain: domainName,
wallet: new PrivateKeyWallet(process.env.THIRDWEB_AUTH_PRIVATE_KEY || ""),
});

Auth API Endpoint

Finally, we have a catch-all API route called pages/api/auth/[...thirdweb].js, which exports the ThirdwebAuthHandler to manage all of the required auth endpoints like login and logout.

Create a new page inside the pages/api/auth folder (you'll need to create this) and name it [...thirdweb].js, and export the ThirdwebAuthHandler from this file that we set up in our auth.config.js file.

import { ThirdwebAuthHandler } from "../../../auth.config";
export default ThirdwebAuthHandler();

Restricting Home Page Access

On the homepage, we run server-side code within getServersideProps to check:

  1. The user is authenticated
  2. The user has an NFT from our collection

If the user isn't authenticated or doesn't have an NFT, they're redirected to the /login page before they can access the content of the home page.

Let's set up this home page logic now.

First, we need to import the functionality we're going to be using:

import React, { useEffect } from "react";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { useLogout, useUser } from "@thirdweb-dev/react";
import { getUser } from "../auth.config";
import checkBalance from "../util/checkBalance";
import styles from "../styles/Home.module.css";
import { useRouter } from "next/router";

Here, we’ve made quite a few changes to the frontend of the website and even played around with preset CSS templates. Don’t worry if your site doesn't look the same while coding along!

export default function Login() {
const address = useAddress(); // Get the user's address

return (
<div className={styles.container}>
<h1 className={styles.h1}>
Exclusive and Early Access to Some Exciting Information
</h1>

<p className={styles.explain}>
brought to you by{" "}
<b>
<a
href="https://www.mantle.xyz/developers"
target="_blank"
rel="noopener noreferrer"
className={styles.mantleGreen}
>
Team Mantle
</a>
</b>
</p>

<hr className={styles.divider} />

<p></p>
<p></p>

<h1 className={styles.h2}>Step 1</h1>
<p>Connect your wallet with Mantle Testnet.</p>
<>
{address ? (
<p>
Connected to {address?.slice(0, 6)}...{address?.slice(-4)}
</p>
) : (
<p></p>
)}

<ConnectWallet accentColor="#65b3ae"/>
</>

<p></p>
<p></p>

<h1 className={styles.h2}>Step 2</h1>
<p>Please click on the &quot;Claim NFT&quot; that gives you the access.</p>

<Web3Button
contractAddress={contractAddress}
action={(contract) => contract.erc1155.claim(0, 1)}
accentColor="#65b3ae"
>
Claim NFT
</Web3Button>

<p></p>
<p></p>

<h1 className={styles.h2}>Step 3</h1>
<p>Once the transaction is confirmed, click on &quot;Are you ready&quot; below.</p>

<p className={styles.explain}>
{" "}
<Link className={styles.mantleGreen} href="/">
Are You Ready?
</Link>{" "}
</p>

<hr className={styles.divider} />
</div>
);
}

Above, we’re showing a simple thank you message and a button to logout, which will sign them out and redirect the user to the /login page.

Before we reach this point, we’ll run logic inside out getServersideProps function, which checks the user’s authentication status and their NFT balance.

If they are not authenticated or don’t own an NFT, they are redirected back to the /login page.

// This gets called on every request
export async function getServerSideProps(context) {
const user = await getUser(context.req);

if (!user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Ensure we are able to generate an auth token using our private key instantiated SDK
const PRIVATE_KEY = process.env.THIRDWEB_AUTH_PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error("You need to add an PRIVATE_KEY environment variable.");
}

// Instantiate our SDK
const sdk = ThirdwebSDK.fromPrivateKey(
process.env.THIRDWEB_AUTH_PRIVATE_KEY,
MantleTestnet,
);

// Check to see if the user has an NFT
const hasNft = await checkBalance(sdk, user.address);

// If they don't have an NFT, redirect them to the login page
if (!hasNft) {
console.log("User", user.address, "doesn't have an NFT! Redirecting...");
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Finally, return the props
return {
props: {},
};
}

Checking NFT Balance

In the snippet above, we call a utility function called checkBalance, which checks to see if the user has an NFT from our collection.

import { isFeatureEnabled } from "@thirdweb-dev/sdk";import {
contractAddress,
erc1155TokenId,
minimumBalance,} from "../const/yourDetails";export default async function checkBalance(sdk, address) {const contract = await sdk.getContract(
contractAddress // replace this with your contract address);let balance;if (isFeatureEnabled(contract.abi, "ERC1155")) {
balance = await contract.erc1155.balanceOf(address, erc1155TokenId);} else if (isFeatureEnabled(contract.abi, "ERC721")) {
balance = await contract.erc721.balanceOf(address);} else if (isFeatureEnabled(contract.abi, "ERC20")) {
balance = (await contract.erc20.balanceOf(address)).value;return balance.gte((minimumBalance * 1e18).toString());}// gte = greater than or equal toreturn balance.gte(minimumBalance);}

The above snippet detects what type of contract contractAddress is and checks the balance accordingly.

Great! Now each time a user attempts to reach the / page, we'll check to see if they have an NFT before allowing them to enter.

Let's create the /login page now where users can sign into our application with their wallet.

Login Page

Create a new page in the pages folder called login.jsx.

This page needs to contain a button that calls the login function

First, let's import the logic we need for this file:

import { ConnectWallet, useAddress } from "@thirdweb-dev/react";
import Link from "next/link";
import styles from "../styles/Home.module.css";

Beneath the imports, we render some conditional logic based on the address variable (which detects the connected wallet address to our site).

We render the ConnectWallet button that is responsible for connecting the user's wallet to the app and also signing in using auth.

This asks the user to sign a message with their wallet to prove their identity, and redirects them to the URL we configured for the loginRedirect field in the ThirdwebProvider; which we set as /.

This means that after the user approves the message, they will be redirected to the / page; where they will be checked to see if they have an NFT again!

export default function Login() {
const address = useAddress(); // Get the user's address

return (
<div className={styles.container}>
<h1 className={styles.h1}>
Exclusive and Early Access to Some Exciting Information
</h1>

<p className={styles.explain}>
brought to you by{" "}
<b>
<a
href="https://www.mantle.xyz/developers"
target="_blank"
rel="noopener noreferrer"
className={styles.mantleGreen}
>
Team Mantle
</a>
</b>
</p>

<hr className={styles.divider} />

<p></p>
<p></p>

<h1 className={styles.h2}>Step 1</h1>
<p>Connect your wallet with Mantle Testnet.</p>
<>
{address ? (
<p>
Connected to {address?.slice(0, 6)}...{address?.slice(-4)}
</p>
) : (
<p></p>
)}

<ConnectWallet accentColor="#65b3ae"/>
</>

<p></p>
<p></p>

<h1 className={styles.h2}>Step 2</h1>
<p>Please click on the &quot;Claim NFT&quot; that gives you the access.</p>

<Web3Button
contractAddress={contractAddress}
action={(contract) => contract.erc1155.claim(0, 1)}
accentColor="#65b3ae"
>
Claim NFT
</Web3Button>

<p></p>
<p></p>

<h1 className={styles.h2}>Step 3</h1>
<p>Once the transaction is confirmed, click on &quot;Are you ready&quot; below.</p>

<p className={styles.explain}>
{" "}
<Link className={styles.mantleGreen} href="/">
Are You Ready?
</Link>{" "}
</p>

<hr className={styles.divider} />
</div>
);
}

Complete Files

_app.tsx

import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
import Head from "next/head";
import ThirdwebGuideFooter from "../components/ThirdwebGuideFooter";
import { domainName } from "../const/yourDetails";
import "../styles/globals.css";
import { MantleTestnet } from "@thirdweb-dev/chains";

function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider
activeChain={MantleTestnet}
authConfig={{
domain: domainName,
authUrl: "/api/auth",
}}
>
<Head>
<title>Mantle Ecosystem Grants</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="This website is being shared exclusively for ethDenver and events during ethDenver. If you're reading this, you're getting access to some really exciting new/info!"
/>
</Head>
<Component {...pageProps} />
</ThirdwebProvider>
);
}

export default MyApp;

index.jsx

import React, { useEffect } from "react";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { useLogout, useUser } from "@thirdweb-dev/react";
import { getUser } from "../auth.config";
import checkBalance from "../util/checkBalance";
import styles from "../styles/Home.module.css";
import { useRouter } from "next/router";
import { MantleTestnet } from "@thirdweb-dev/chains";

export default function Home() {
const { logout } = useLogout();
const { isLoggedIn, isLoading } = useUser();
const router = useRouter();

useEffect(() => {
if (!isLoading && !isLoggedIn) {
router.push("/login");
}
}, [isLoading, isLoggedIn, router]);

return (
<div className={styles.container}>
<h1 className={styles.h1}>Mantle Ecosystem Fund</h1>
<p></p>
<p className={styles.explain}>
You now have access to info about an exciting program brought to you by Team Mantel !
</p>

<p></p>

<button className={styles.mainButton} onClick={logout}>
Return
</button>
</div>
);
}

// This gets called on every request
export async function getServerSideProps(context) {
const user = await getUser(context.req);

if (!user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Ensure we are able to generate an auth token using our private key instantiated SDK
const PRIVATE_KEY = process.env.THIRDWEB_AUTH_PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error("You need to add an PRIVATE_KEY environment variable.");
}

// Instantiate our SDK
const sdk = ThirdwebSDK.fromPrivateKey(
process.env.THIRDWEB_AUTH_PRIVATE_KEY,
MantleTestnet,
);

// Check to see if the user has an NFT
const hasNft = await checkBalance(sdk, user.address);

// If they don't have an NFT, redirect them to the login page
if (!hasNft) {
console.log("User", user.address, "doesn't have an NFT! Redirecting...");
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Finally, return the props
return {
props: {},
};
}

login.tsx

import React, { useEffect } from "react";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { useLogout, useUser } from "@thirdweb-dev/react";
import { getUser } from "../auth.config";
import checkBalance from "../util/checkBalance";
import styles from "../styles/Home.module.css";
import { useRouter } from "next/router";
import { MantleTestnet } from "@thirdweb-dev/chains";

export default function Home() {
const { logout } = useLogout();
const { isLoggedIn, isLoading } = useUser();
const router = useRouter();

useEffect(() => {
if (!isLoading && !isLoggedIn) {
router.push("/login");
}
}, [isLoading, isLoggedIn, router]);

return (
<div className={styles.container}>
<h1 className={styles.h1}>Mantle Ecosystem Fund</h1>
<p></p>
<p className={styles.explain}>
You now have access to info about an exciting program brought to you by Team Mantel !
</p>

<p></p>

<button className={styles.mainButton} onClick={logout}>
Return
</button>
</div>
);
}

// This gets called on every request
export async function getServerSideProps(context) {
const user = await getUser(context.req);

if (!user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Ensure we are able to generate an auth token using our private key instantiated SDK
const PRIVATE_KEY = process.env.THIRDWEB_AUTH_PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error("You need to add an PRIVATE_KEY environment variable.");
}

// Instantiate our SDK
const sdk = ThirdwebSDK.fromPrivateKey(
process.env.THIRDWEB_AUTH_PRIVATE_KEY,
MantleTestnet,
);

// Check to see if the user has an NFT
const hasNft = await checkBalance(sdk, user.address);

// If they don't have an NFT, redirect them to the login page
if (!hasNft) {
console.log("User", user.address, "doesn't have an NFT! Redirecting...");
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

// Finally, return the props
return {
props: {},
};
}

CLI Commands

Post cloning the repository, makes sure to run the below CLI commands to avoid any importing issues with the SDK.

npm i @thirdweb-dev/sdk
npm i @thirdweb-dev/chains

Congrats on deploying and shipping your NFT gating dApp using thirdweb SDK on Mantle testnet!

What’s Next?

Based on the various requests during hackathons, we experimented with thirdweb’s SDK. thirdweb’s stack is one of the best ways to quickly ship cool dApps and based on feedback, we might have some really cool dApps being shipped in the form of a series!

In conclusion, Mantle is a powerful solution for developers looking to build high-performance dApps on Ethereum’s layer-2 network. Its modular architecture, low fees, and high-security features make it a great option for builders who want to provide an exceptional user experience for their users.

Currently, building on Mantle’s testnet offers numerous benefits, such as the ability to test and experiment with dApps in a safe environment without incurring high gas costs. As Mantle continues to develop and expand, it promises to be a key player in the Ethereum ecosystem, providing developers with the tools they need to build innovative and scalable dApps.

How to get tech support?

Feel free to reach out to our DevRel team for tech support or any queries while building the dApp!

1 7p J P2cki Ngd8smj J Sw Q Rnw

Discord Tech Support: https://discord.com/invite/0xMantle

Join the Community

X/Twitter