Build a Token-Gated NFT Minting dApp with Thirdweb and RootstockCollective
In this guide, we’ll build a token-gated platform using Thirdweb and RootstockCollective, this dApp will enable members of the platform to mint unique collectibles based on the amount of stRIF tokens they hold. We will learn how to integrate Web3 wallets authentication, use Thirdweb to mint NFT drops, verify token balances, and enable NFT minting.
This guide will also demonstrate how token ownership can be used to control access. We’ll use stRIF, the governance token of the RootstockCollective, to determine which NFTs users can mint. Holding stRIF will enable members to mint unique collectibles based on the amount of stRIF tokens they hold, they will be granted access to mint two ERC-721 collections: Rooties (Level 1) and Legends (Level 2). This ensures that only engaged community members can participate, creating an exclusive and verifiable experience.
What we’ll cover
- Understanding Tokenization and Token-gating
- Set up the development environment
- Implement Web3 authentication with Sign-In With Ethereum (SIWE) using Thirdweb Auth.
- Create a token-gating mechanism to verify ERC-20 (stRIF) balances using Thirdweb.
- Create and Mint NFT drops using Thirdweb platform
- Deploy smart contracts for stRIF, Rooties (ERC-721), and Legends (ERC-721).
- Integrate NFT minting functionality using Thirdweb’s prebuilt React components.
- Implement conditional media rendering based on token ownership and levels.
Prerequisites
- Node v18+
- Bun package manager
bun install
- MetaMask or a compatible Web3 wallet
- Thirdweb account. Create an account.
Bun is used in this article but you could use your preferred package manager.
Understanding Tokenization & Token-Gating
Tokenization refers to the process of converting real-world assets into digital tokens on a blockchain. These tokens can represent anything of value—real estate, art, commodities, or even financial instruments—and make them tradable, divisible, and accessible globally.
Token-Gating leverages this by granting exclusive access based on token ownership, it provides a way to restrict access based on a user’s token holdings, ensuring that only eligible users can interact with certain features or content. This method, common in dApps, governance (DAOs) and NFT platforms, enables communities to reward loyalty and manage privileges transparently. Token-gating moves access control from central authorities to on-chain proof of ownership, linking digital asset value to real-world utility.
RootstockCollective, or The Collective, is a DAO (Decentralized Autonomous Organization) designed to develop the Rootstock ecosystem by empowering and rewarding builders and users of Rootstock, and RIF token holders. Members of the RootstockCollective gain access exclusive voting rights and participation in the DAO’s governance and decision-making process on Rootstock.
Thirdweb’s pre-built contracts to avoid writing the smart contracts from scratch, this makes the deployment fast and hassle-free. These contracts are fully compatible with Rootstock and allows us focus on configuring the logic of the platform rather than low-level implementation details. This guide will show how to use the Thirdweb SDK to handle key actions like connecting wallets, check token holdings, and mint NFTs. We’ll also use Thirdweb’s UI components to manage media rendering and user interactions with transactions on Rootstock.
Getting Started
Clone the RootstockCollective rewards sample repository and open in code editor.
git clone https://github.com/rsksmart/rootstock-collective-rewards.git
Create a Project on Thirdweb
Visit the Thirdweb dashboard to sign up and create your project, set up authentication and generate the required Client ID and Secret Key.
Once signed in, navigate to "Create Project" and add a project name and allow domains.
For this mock project, you will need to allow all domains since requests will be made through localhost. In the future, you can restrict access to only your app’s domain.
Now you will be able to able to copy the necessary credentials for this project, note the admin wallet could be any wallet, including metamask or others.
Add Environment Variables
# Required: Client ID from thirdweb dashboard
# Get it from: https://thirdweb.com/dashboard/
NEXT_PUBLIC_TEMPLATE_CLIENT_ID=your_client_id_here
# Required: Secret key for server-side operations
# Get it from: https://thirdweb.com/dashboard/
# WARNING: Never expose this in client-side code or commit to version control
THIRDWEB_SECRET_KEY=your_secret_key_here
# Required: Domain for authentication
# Format: hostname:port
# Use localhost:3000 for local development
# Use your actual domain in production (e.g., myapp.com)
NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN=localhost:3000
# Required: Private key of the admin wallet
# Get it from: Your wallet's export private key option
# WARNING: Keep this secure and never share or commit this
# Used for: Contract deployments, admin operations
THIRDWEB_ADMIN_PRIVATE_KEY=your_private_key_here
Run the Project
To run the project, rename .env.example
to .env.local
and configure the required credentials. This includes getting a Client ID from the Thirdweb dashboard for authentication, a Secret Key for secure server-side operations, an Authentication Domain to define where logins are processed, and an Web3 Wallet Private Key for deploying contracts and managing admin tasks. Use process.env
in Next.js to safely access these variables within your application.
To run the project:
bun run dev
Implement Web3 authentication
Web3 authentication allows users to verify their identity by signing a unique message with their wallet. This replaces traditional passwords with cryptographic signatures, ensuring secure and decentralized authentication. We’ll set this up using Thirdweb’s authentication tools in Next.js.
I. Set up Auth
Locate the file in src/app/utils/thirdwebAuth.ts
, this file initializes createAuth
, defining the authentication domain and linking the admin wallet using a private key stored in environment variables. This setup ensures secure handling of authentication requests.
// /src/app/utils/thirdwebAuth.ts
import { createAuth } from "thirdweb/auth";
import { privateKeyToAccount } from "thirdweb/wallets";
import { client } from "./client";
const privateKey = process.env.THIRDWEB_ADMIN_PRIVATE_KEY || "";
if (!privateKey) {
throw new Error("Missing THIRDWEB_ADMIN_PRIVATE_KEY in .env file.");
}
export const thirdwebAuth = createAuth({
domain: process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN || "",
adminAccount: privateKeyToAccount({ client, privateKey }),
});
II. Define Auth Logic
Define the authentication logic in src/app/actions/auth.ts
.
generatePayload
: Creates a signable message for authentication.login
: Verifies the signed message and stores a session token (JWT) in cookies.isLoggedIn
: Checks if the user has a valid session.logout
: Clears the session, this logs the user out.
By keeping these operations on the server, we ensure that sensitive data like authentication tokens remain secure.
// src/app/actions/auth.ts
"use server";
import { VerifyLoginPayloadParams } from "thirdweb/auth";
import { cookies } from "next/headers";
import { thirdwebAuth } from "../utils/thirdwebAuth";
export const generatePayload = thirdwebAuth.generatePayload;
export async function login(payload: VerifyLoginPayloadParams) {
const verifiedPayload = await thirdwebAuth.verifyPayload(payload);
if (verifiedPayload.valid) {
const jwt = await thirdwebAuth.generateJWT({
payload: verifiedPayload.payload,
});
cookies().set("jwt", jwt);
}
}
export async function isLoggedIn() {
const jwt = cookies().get("jwt");
if (!jwt?.value) {
return false;
}
const authResult = await thirdwebAuth.verifyJWT({ jwt: jwt.value });
if (!authResult.valid) {
return false;
}
return true;
}
export async function logout() {
cookies().delete("jwt");
}
III. Connect Auth to Frontend
Connect the authentication to the frontend in src/app/components/LoginButton.tsx
.
This component extends Thirdweb’s ConnectButton
to handle authentication. It automatically connects wallets and checks session status, ensuring users stay logged in without needing to sign in repeatedly. The button manages login, logout, and verifies if a user is authenticated based on their wallet signature.
// /src/app/components/LoginButton.tsx
"use client";
import { ConnectButton } from "thirdweb/react";
import { client } from "../utils/client";
import { generatePayload, isLoggedIn, login, logout } from "../actions/auth";
import { rootstockTestnet } from "../utils/consts";
export const LoginButton = () => {
return (
<ConnectButton
autoConnect={true}
client={client}
chain ={rootstockTestnet}
auth={{
isLoggedIn: async (address) => {
return await isLoggedIn();
},
doLogin: async (params) => {
await login(params);
},
getLoginPayload: async ({ address }) => generatePayload({ address }),
doLogout: async () => {
await logout();
},
}}
/>
);
};