thirdweb: Create a Discord Bot That Gives Roles to NFT Holders
Learn how to create a Discord bot that gives roles to NFT holders.
thirdweb is an easy-to-use platform to build Web3 applications with code or no-code. thirdweb makes creating and deploying apps such as NFT collections or NFT marketplaces easy. thirdweb can be used with objects stored on IPFS, so objects stored in a Filebase IPFS bucket can be seamlessly uploaded for use with a Thirdweb app.
Read below to learn how to create a Discord bot that gives roles to NFT holders.



Then select 'Deploy Now'.

For this tutorial, we’ll be using the Mumbai testnet, so we are using a Mumbai wallet.

To do this, navigate to console.filebase.com. If you don’t have an account already, sign up, then log in.
Select ‘Create Bucket’ in the top right corner to create a new bucket for your NFTs.

Bucket names must be unique across all Filebase users, be between 3 and 63 characters long, and can contain only lowercase characters, numbers, and dashes.

These files need to be named in sequential order, such as 0.png, 1.png, 2.png, etc.


https://ipfs.filebase.io/ipfs/IPFS_CID
Take note of this URL.
Select the ‘Code’ tab. This code showcases different code snippets to use to mint your NFTs using scripts, with your contract address and crypto wallet address inputted automatically for easy copy and paste. This tutorial will showcase the JavaScript code examples, but you can use any of the languages showcased in this Code tab. You can follow along with the code examples showcased in this tutorial, or copy and paste the snippets provided in this tab.
npm install @thirdweb-dev/sdk
CONTRACT
with your contract address.WALLET_ADDRESS
with your crypto wallet address.- Each
IPFS_CID
with your IPFS folder CID you took note of earlier. - Replace the name and description of each NFT with your desired information.
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
const sdk = new ThirdwebSDK("mumbai");
const contract = sdk.getContract("CONTRACT", "nft-collection");
// Address of the wallet you want to mint the NFT to
const walletAddress = "WALLET_ADDRESS";
// Custom metadata of the NFTs you want to mint.
const metadatas = [{
name: "Cool NFT #1",
description: "This is a cool NFT",
image: fs.readFileSync("https://ipfs.filebase.io/ipfs/IPFS_CID/0.png"), // This can be an image url or file
}, {
name: "Cool NFT #2",
description: "This is a cool NFT",
image: fs.readFileSync("https://ipfs.filebase.io/ipfs/IPFS_CID/1.png"),
}];
const tx = await contract.mintBatchTo(walletAddress, metadatas);
const receipt = tx[0].receipt; // same transaction receipt for all minted NFTs
const firstTokenId = tx[0].id; // token id of the first minted NFT
const firstNFT = await tx[0].data(); // (optional) fetch details of the first minted NFT
You can edit this script to include as many NFTs as you’d like to mint at one time.
node mint.js

npx thirdweb create
When prompted, select the following options:
- Type of Project: App
- Project Name: discord-bot
- Framework: Next.js
- Language: TypeScript



Then select ‘Add Bot’, and confirm the action:

This bot should not be allowed to join servers other than the ones that you add it to yourself.

This is important since Discord bots can be compromised, and if the permissions allow for destructive actions, malicious attackers can use bots to ruin a Discord server.


Then select ‘Copy’ next to the generated URL:

You will be prompted to add your bot to a server. Select a server that you have Manage Server permissions in, then select ‘Continue’. Confirm that the bot will obtain the Manage Roles permission, then authorize the bot to join the server.

cd discord-bot
npm install
npm install next-auth
mkdir pages/api/
mkdir pages/api/auth
cd pages/api/auth
yarn add next-auth
touch [...nextauth].ts

Paste these values into this
.env.local
file in the following format:CLIENT_ID=xxxxx
CLIENT_SECRET=xxxxx
http://localhost:3000/api/auth/callback/discord

import NextAuth from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
export default NextAuth({
// Configure one or more authentication providers
providers: [
DiscordProvider({
clientId: process.env.CLIENT_ID as string,
clientSecret: process.env.CLIENT_SECRET as string,
}),
],
// When the user signs in, get their token
callbacks: {
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.userId = account.providerAccountId;
}
return token;
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.userId = token.userId;
return session;
},
},
});
Then, open the
_app.txs
file in the api directory, and replace the existing content with the following:import type { AppProps } from "next/app";
import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
import { SessionProvider } from "next-auth/react";
import "../styles/globals.css";
// This is the chainId your dApp will work on.
const activeChainId = ChainId.Mumbai;
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThirdwebProvider desiredChainId={activeChainId}>
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
</ThirdwebProvider>
);
}
export default MyApp;
- A user signs into the app with their Crypto wallet.
- The user must authenticate with their Discord account.
We’ll create code for the three possible outcomes of this workflow:
- A user is connected to both their wallet and Discord.
- A user is not connected to their wallet.
- A user is not connected to their Discord account.
Open this file in your IDE and insert the following code:
import { useAddress, useDisconnect, useMetamask } from "@thirdweb-dev/react";
import { useSession, signIn, signOut } from "next-auth/react";
import React from "react";
import styles from "../styles/Home.module.css";
export default function SignIn() {
const address = useAddress();
const connectWithMetamask = useMetamask();
const disconnectWallet = useDisconnect();
const { data: session } = useSession();
// 1. The user is signed into discord and connected to wallet.
if (session && address) {
return (
<div className={styles.bigSpacerTop}>
<a onClick={() => signOut()} className={styles.secondaryButton}>
Sign out of Discord
</a>
|<a onClick={() => disconnectWallet()} className={styles.secondaryButton}>
Disconnect wallet
</a>
</div>
);
}
// 2. Connect Wallet
if (!address) {
return (
<div className={styles.main}>
<h2 className={styles.noGapBottom}>Connect Your Wallet</h2>
<p>Connect your wallet to check eligibility.</p>
<button
onClick={connectWithMetamask}
className={`${styles.mainButton} ${styles.spacerTop}`}
>
Connect Wallet
</button>
</div>
);
}
// 3. Connect with Discord (OAuth)
if (!session) {
return (
<div className={`${styles.main}`}>
<h2 className={styles.noGapBottom}>Sign In with Discord</h2>
<p>Sign In with Discord to check your eligibility for the NFT!</p>
<button
onClick={() => signIn("discord")}
className={`${styles.mainButton} ${styles.spacerTop}`}
>
Connect Discord
</button>
</div>
);
}
// default return nothing
return null;
}
import { useAddress, useSDK } from "@thirdweb-dev/react";
import { useSession } from "next-auth/react";
import SignIn from "../components/SignIn";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
const Home: NextPage = () => {
const address = useAddress();
const { data: session } = useSession();
const sdk = useSDK();
return (
<div>
<div className={styles.container} style={{ marginTop: 0 }}>
<SignIn />
{address && session && (
<div className={styles.collectionContainer}>
<button className={styles.mainButton}>Give me the role!</button>
</div>
)}
</div>
</div>
);
};
export default Home;
yarn run dev



Then select ‘Reset Token’ to generate your bot’s token. Copy this value.

BOT_TOKEN=xxxxxx
Open the file, then insert the following content:
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
export default async function grantRole(
req: NextApiRequest,
res: NextApiResponse,
) {
// Get the login payload out of the request
const { loginPayload } = JSON.parse(req.body);
// Get the NextAuth session so we can use the user ID as part of the discord API request
const session = await getSession({ req });
if (!session) {
res.status(401).json({ error: "Not logged in" });
return;
}
// Authenticate login payload
const sdk = new ThirdwebSDK("mumbai");
const domain = "thirdweb.com"; // This should be the domain name of your own website
// Verify the login payload is real and valid
const verifiedWalletAddress = sdk.auth.verify(domain, loginPayload);
// If the login payload is not valid, return an error
if (!verifiedWalletAddress) {
res.status(401).json({ error: "Invalid login payload" });
return;
}
// Check if this user owns an NFT
const editionDrop = sdk.getEditionDrop(
"CONTRACT_ADDRESS",
);
// Get addresses' balance of token ID 0
const balance = await editionDrop.balanceOf(verifiedWalletAddress, 0);
if (balance.toNumber() > 0) {
// If the user is verified and has an NFT, return the content
// Make a request to the Discord API to get the servers this user is a part of
const discordServerId = "1007299321064079380";
const { userId } = session;
const roleId = "1007333470688776323";
const response = await fetch(
// Discord Developer Docs for this API Request: <https://discord.com/developers/docs/resources/guild#add-guild-member-role>
`https://discordapp.com/api/guilds/${discordServerId}/members/${userId}/roles/${roleId}`,
{
headers: {
// Use the bot token to grant the role
Authorization: `Bot ${process.env.BOT_TOKEN}`,
},
method: "PUT",
},
);
// If the role was granted, return the content
if (response.ok) {
res.status(200).json({ message: "Role granted" });
}
// Something went wrong granting the role, but they do have an NFT
else {
res
.status(500)
.json({ error: "Error granting role, are you in the server?" });
}
}
// If the user is verified but doesn't have an NFT, return an error
else {
res.status(401).json({ error: "User does not have an NFT" });
}
}
You will also need to replace
CONTRACT_ADDRESS
with the Contract address of your NFT collection. Navigate to the thirdweb dashboard, select your NFT collection from earlier, then copy the contract address as shown here:
This code does the following steps:
- First, it authenticates the user’s login payload to ensure the user owns their wallet.
- Then, it checks the wallet’s NFT balance for the NFT.
- Lastly, it makes a request to the Discord API telling it to give the role to the user.
async function requestGrantRole() {
// First, login and sign a message
const domain = "thirdweb.com"; // This should be the domain name of your own website
const loginPayload = await sdk?.auth.login(domain);
// Then make a request to our API endpoint.
try {
const response = await fetch("/api/grant-role", {
method: "POST",
body: JSON.stringify({
loginPayload,
}),
});
const data = await response.json();
console.log(data);
} catch (e) {
console.error(e);
}
}
<button className={styles.mainButton} onClick={requestGrantRole}>
Give me the role!
</button>
This time, when you get to ‘Give me the role!’, click the button. You’ll be prompted to sign the request.


If you have any questions, please join our Discord server, or send us an email at [email protected]