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.
Prerequisites:
1. Start by navigating to the thirdweb dashboard and connecting your crypto wallet.
2. Select ‘Deploy New Contract'.
3. Under 'NFTs', select ‘NFT Collection’:
Then select 'Deploy Now'.
4. Give your NFT collection a name, icon, and description, then confirm that the recipient address is your crypto wallet address.
For this tutorial, we’ll be using the Mumbai testnet, so we are using a Mumbai wallet.
5. Select ‘Deploy Now’ and confirm the transaction through your crypto wallet.
6. Next, we need to create an IPFS bucket on Filebase.
7. Select ‘Buckets’ from the left sidebar menu, or navigate to.
Select ‘Create Bucket’ in the top right corner to create a new bucket for your NFTs.
8. Enter a bucket name and choose the IPFS storage network to create the bucket.
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.
9. Now, upload your NFTs to Filebase using the web console and selecting ‘Folder’, then select the folder that contains your NFT files.
These files need to be named in sequential order, such as 0.png, 1.png, 2.png, etc.
10. You will see your folder uploaded as a single object:
11. Copy the CID of your folder:
12. Navigate to your IPFS Folder using the Filebase IPFS gateway to see your folder’s files:
https://ipfs.filebase.io/ipfs/IPFS_CID
Take note of this URL.
13. Head back to the thirdweb dashboard, where you will see the page for your NFT collection.
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.
14. Open a command line window and install the thirdweb SDK:
npm install @thirdweb-dev/sdk
15. Open an IDE such as VSCode and insert the following code, replacing the following values:
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";constsdk=newThirdwebSDK("mumbai");constcontract=sdk.getContract("CONTRACT","nft-collection");// Address of the wallet you want to mint the NFT toconstwalletAddress="WALLET_ADDRESS";// Custom metadata of the NFTs you want to mint.constmetadatas= [{ 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"),}];consttx=awaitcontract.mintBatchTo(walletAddress, metadatas);constreceipt= tx[0].receipt; // same transaction receipt for all minted NFTsconstfirstTokenId= tx[0].id; // token id of the first minted NFTconstfirstNFT=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.
16. Save this script as mint.js, then run this script with the command:
node mint.js
17. Refresh your thirdweb dashboard. Your NFTs will be listed.
18. Then, in a new terminal window, create a thirdweb app using the thirdweb CLI:
21. Once created, select the ‘Bot’ option on the left side-bar menu.
Then select ‘Add Bot’, and confirm the action:
22. Give your bot a name to be displayed when it joins the server, and then toggle ‘public bot’ to be off.
This bot should not be allowed to join servers other than the ones that you add it to yourself.
23. Scroll down to bot permissions, then select ‘Manage Roles’ as the only permission given to this bot.
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.
24. Save your changes.
25. Select the ‘OAuth2’ option from the left side bar menu, then ‘URL Generator’.
26. Select the check boxes for ‘bot’ under ‘Scope’, and ‘Manage Roles’ under ‘Bot Permissions’.
Then select ‘Copy’ next to the generated URL:
27. Open the copied URL in your browser.
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.
28. Navigate back to your terminal window, and navigate inside the thirdweb app we created earlier and install the required dependencies:
cd discord-bot
npm install
npm install next-auth
29. Then, create a new folder within the directory pages called api, with another folder inside called auth:
mkdir pages/api/
mkdir pages/api/auth
30. Navigate into the auth folder, then create a new file called [...nextauth].ts
cd pages/api/auth
yarn add next-auth
touch [...nextauth].ts
31. Back in the Discord Developer Portal, copy the Client ID and Client Secret values.
32. Navigate back to the root of your project, then create a new file called .env.local.
Paste these values into this .env.local file in the following format:
CLIENT_ID=xxxxx
CLIENT_SECRET=xxxxx
33. Then, back in the Discord Developer Portal, add the following redirect link for your bot:
http://localhost:3000/api/auth/callback/discord
34. Save your changes.
35. Open the […nextauth].ts file and insert the following code:
import NextAuth from"next-auth";import DiscordProvider from"next-auth/providers/discord";exportdefaultNextAuth({// Configure one or more authentication providers providers: [DiscordProvider({ clientId:process.env.CLIENT_IDasstring, clientSecret:process.env.CLIENT_SECRETasstring, }), ],// When the user signs in, get their token callbacks: {asyncjwt({ token, account }) {// Persist the OAuth access_token to the token right after signinif (account) {token.userId =account.providerAccountId; }return token; },asyncsession({ session, token, user }) {// Send properties to the client, like an access_token from a provider.session.userId =token.userId;return session; }, },});
36. Save this file.
Then, open the _app.txs file in the api directory, and replace the existing content with the following:
importtype { 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.constactiveChainId=ChainId.Mumbai;functionMyApp({ Component, pageProps }:AppProps) {return ( <ThirdwebProviderdesiredChainId={activeChainId}> <SessionProvidersession={pageProps.session}> <Component {...pageProps} /> </SessionProvider> </ThirdwebProvider> );}exportdefault MyApp;
37. Next, we need to create the code for the following workflows:
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.
38. First, create a new folder at the root of the project called components and create a SignIn.tsx file inside it.
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";exportdefaultfunctionSignIn() {constaddress=useAddress();constconnectWithMetamask=useMetamask();constdisconnectWallet=useDisconnect();const { data: session } =useSession();// 1. The user is signed into discord and connected to wallet.if (session && address) {return ( <divclassName={styles.bigSpacerTop}> <aonClick={() =>signOut()} className={styles.secondaryButton}> Sign out of Discord </a> |<aonClick={() =>disconnectWallet()} className={styles.secondaryButton}> Disconnect wallet </a> </div> ); }// 2. Connect Walletif (!address) {return ( <divclassName={styles.main}> <h2className={styles.noGapBottom}>Connect Your Wallet</h2> <p>Connect your wallet to check eligibility.</p> <buttononClick={connectWithMetamask}className={`${styles.mainButton}${styles.spacerTop}`} > Connect Wallet </button> </div> ); }// 3. Connect with Discord (OAuth)if (!session) {return ( <divclassName={`${styles.main}`}> <h2className={styles.noGapBottom}>Sign In with Discord</h2> <p>Sign In with Discord to check your eligibility for the NFT!</p> <buttononClick={() =>signIn("discord")}className={`${styles.mainButton}${styles.spacerTop}`} > Connect Discord </button> </div> ); }// default return nothingreturnnull;}
39. Open the index.tsx file and replace the existing content with the following:
41. Your app will be running at localhost:3000 and will look like this:
42. Select ‘Connect Wallet’ to connect your crypto wallet to your app.
43. Then, select ‘Connect Discord’ to connect your Discord account.
44. Select ‘Authorize’ to authenticate your Discord bot with your Discord account.
45. You will see a ‘Give me the role!’ button. Let’s add functionality for this before using it.
46. Head to the Discord Developer Portal and select ‘Bot’ from the left side-bar menu.
Then select ‘Reset Token’ to generate your bot’s token. Copy this value.
47. Open your .env.local file and insert the following line for your bot’s token:
BOT_TOKEN=xxxxxx
48. Create a new file in the api directory called grant-role.ts.
Open the file, then insert the following content:
import { ThirdwebSDK } from"@thirdweb-dev/sdk";importtype { NextApiRequest, NextApiResponse } from"next";import { getSession } from"next-auth/react";exportdefaultasyncfunctiongrantRole( req:NextApiRequest, res:NextApiResponse,) {// Get the login payload out of the requestconst { loginPayload } =JSON.parse(req.body);// Get the NextAuth session so we can use the user ID as part of the discord API requestconstsession=awaitgetSession({ req });if (!session) {res.status(401).json({ error:"Not logged in" });return; }// Authenticate login payloadconstsdk=newThirdwebSDK("mumbai");constdomain="thirdweb.com"; // This should be the domain name of your own website// Verify the login payload is real and validconstverifiedWalletAddress=sdk.auth.verify(domain, loginPayload);// If the login payload is not valid, return an errorif (!verifiedWalletAddress) {res.status(401).json({ error:"Invalid login payload" });return;}// Check if this user owns an NFTconsteditionDrop=sdk.getEditionDrop("CONTRACT_ADDRESS",);// Get addresses' balance of token ID 0constbalance=awaiteditionDrop.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 ofconstdiscordServerId="1007299321064079380";const { userId } = session;constroleId="1007333470688776323";constresponse=awaitfetch(// 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 contentif (response.ok) {res.status(200).json({ message:"Role granted" }); }// Something went wrong granting the role, but they do have an NFTelse { 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 errorelse {res.status(401).json({ error:"User does not have an NFT" });}}
You will need to replace the server and role ID variables. To do this, follow this guide.
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.
49. Open the index.tsx file and add the following code to create a new function after the const sdk = useSDK(): line:
asyncfunctionrequestGrantRole() {// First, login and sign a messageconstdomain="thirdweb.com"; // This should be the domain name of your own websiteconstloginPayload=awaitsdk?.auth.login(domain);// Then make a request to our API endpoint.try {constresponse=awaitfetch("/api/grant-role", { method:"POST", body:JSON.stringify({ loginPayload, }), });constdata=awaitresponse.json();console.log(data); } catch (e) {console.error(e); }}
50. Then, add the replace the existing button code with the lines:
<buttonclassName={styles.mainButton} onClick={requestGrantRole}>Give me the role!</button>
51. Restart your app with yarn run dev, and connect your wallet and Discord to the app again.
This time, when you get to ‘Give me the role!’, click the button. You’ll be prompted to sign the request.
52. Once signed, the bot will give the authenticated user the configured role:
The completed code can be found here if you need to reference it.