{"id":20974220,"url":"https://github.com/thirdweb-example/discord-role-granter","last_synced_at":"2025-07-30T04:03:25.593Z","repository":{"id":48148599,"uuid":"516601868","full_name":"thirdweb-example/discord-role-granter","owner":"thirdweb-example","description":"Grant users who own NFTs a role in your Discord server!","archived":false,"fork":false,"pushed_at":"2024-04-12T00:34:42.000Z","size":335,"stargazers_count":15,"open_issues_count":0,"forks_count":14,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-30T23:06:05.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://discord-role-granter.thirdweb-example.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thirdweb-example.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-22T03:52:48.000Z","updated_at":"2025-04-09T05:09:06.000Z","dependencies_parsed_at":"2024-04-12T01:50:55.529Z","dependency_job_id":null,"html_url":"https://github.com/thirdweb-example/discord-role-granter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thirdweb-example/discord-role-granter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fdiscord-role-granter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fdiscord-role-granter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fdiscord-role-granter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fdiscord-role-granter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thirdweb-example","download_url":"https://codeload.github.com/thirdweb-example/discord-role-granter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fdiscord-role-granter/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267807665,"owners_count":24147352,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T04:27:16.955Z","updated_at":"2025-07-30T04:03:25.516Z","avatar_url":"https://github.com/thirdweb-example.png","language":"TypeScript","readme":"\u003e [!Important]  \n\u003e This repository is referencing the `mumbai` chain.\n\u003e \n\u003e `Mumbai` [is deprecated since 08/04/2024](https://blog.thirdweb.com/deprecation-of-mumbai-testnet/), meaning the code in this repository will no longer work out of the box.\n\u003e\n\u003e You can still use this repository, however you will have to switch any references to `mumbai` to another chain.\n\n# Discord Role Granter\n\nThis template uses the [authentication SDK](https://thirdweb.com/authentication)\nalong with Discord OAuth to grant users who own an NFT from a specific collection a special role\nin a Discord server.\n\n## Tools:\n\n- [**React SDK**](https://docs.thirdweb.com/react): To connect to the user's MetaMask wallet.\n\n- [**TypeScript SDK**](https://docs.thirdweb.com/typescript): To view the balance of the connected wallet in an NFT collection.\n\n- [**Authentication SDK**](https://thirdweb.com/authentication) to allow the user to sign in with Ethereum and verify they own their wallet.\n\n- [**Next Auth**](https://next-auth.js.org/): To authenticate with Discord and access the user's Discord data such as their user ID.\n\n- [**Discord API**](https://discord.com/developers/docs) to grant users a role in our Discord server.\n\n## Using This Repo\n\nTo create a clone of this repository, you can use the [thirdweb CLI](https://portal.thirdweb.com/thirdweb-cli)\n\n```bash\nnpx thirdweb create --template discord-role-granter\n```\n\n# Guide\n\nYou can follow along with the guide below to set this up for your Discord server and role.\n\n### Setting Up the Discord Bot\n\nTo create a Discord bot, head to the [Discord Developer Portal](https://discord.com/developers/applications) and click on `New Application`, give it a name and click `create`!\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378066493/uTAi0N7I-.png)\n\nOnce it's created, head to the `Bot` tab, and click `Add Bot`.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378135064/eGNkkLpf3.png)\n\nGive your bot a username, and I'm unchecking the `Public Bot` field so that only we can invite our bot.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378382494/i5srin_Du.png)\n\nScroll down to `Bot Permissions` and give our bot the `Manage Roles` permission:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378279649/0SCOzdku8.png)\n\nOnce you're ready, click `Save Changes`!\n\nNow we're ready to invite our bot to our server!\n\nClick `OAuth2` \u003e `URL Generator` on the sidebar:\n\nSelect `bot` and `Manage Roles` scopes.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378764505/Q6hmuH8Ml.png)\n\nCopy the Generated URL and open it in your browser.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378817253/bzEMllDkE.png)\n\nSelect the server you want to add your bot to and click `Continue`. It will ask you to approve this bot's permissions, you should see a prompt to authorise the bot for `Manage Roles` permissions:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378873543/clVZw5YdI.png)\n\nClick `Authorise`, once successful, you'll see an `Authorised` window. Your bot will be added to your server - say hi!\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658378948085/jchIC5F1o.png)\n\nCopy across your `Client ID` and `Client Secret` into environment variables in your project, by creating a `.env.local` file at the root of the directory.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658380648801/1rp4zc2-a.png)\n\n```text\nCLIENT_ID=xxxxx\nCLIENT_SECRET=xxxxx\n```\n\nWe also need to add a Redirect URL into our Application while we're here:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658443956419/84fGElQk0.png)\n\nTo grant a role to the connected user, we are going to use the Discord API on behalf of the bot that we created. Specifically, we'll be hitting the `Add Guild Member Role` API endpoint:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658455468735/Pe6SJyvls.png)\n\nTo make requests from our bot, we'll need a token to act on its behalf. To generate a token, head to the `Bot` tab from your Discord Developer portal, and click `Reset Token` on your bot:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658455558002/Y1ZUud12k.png)\n\nWe then need to store this inside our environment variables as well securely:\n\n```text\nBOT_TOKEN=xxxx\n```\n\nFinally, inside the [constants/index.ts](./constants/index.ts) file, you need to configure your:\n\n- Discord Server ID\n- Role ID\n- NFT Collection ID\n\nYou can learn how to do that from [this guide](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-).\n\n### thirdweb and NextAuth Wrappers\n\nTo authenticate users with both their **wallet** and their Discord account, we wrap our application in two Provider components:\n\n```tsx\n// This is the chain your dApp will work on.\nconst activeChain = \"mumbai\";\n\nfunction MyApp({ Component, pageProps }: AppProps) {\n  return (\n    \u003cThirdwebProvider\n      desiredChain={activeChain}\n      authConfig={{\n        domain: process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN!,\n        authUrl: \"/api/thirdweb-auth\",\n      }}\n    \u003e\n      \u003cSessionProvider session={pageProps.session}\u003e\n        \u003cComponent {...pageProps} /\u003e\n        \u003cThirdwebGuideFooter /\u003e\n      \u003c/SessionProvider\u003e\n    \u003c/ThirdwebProvider\u003e\n  );\n}\n```\n\nThis allows us to access the helpful hooks of the React SDK and NextAuth to read information about the current user and their wallet. We also configure authentication with the `authConfig` prop on the `ThirdwebProvider` component.\n\n### Discord Oauth\n\nNextAuth handles the Oauth flow of signing in with Discord for us in\nthe [[...nextauth.ts]](./pages/api/auth/[...nextauth].ts) file.\nWe add some additional logic to append the user ID to the\ninformation that is available to us so that we can read that inside our API route.\n\n```tsx\nexport default NextAuth({\n  // Configure one or more authentication providers\n  secret: process.env.AUTH_SECRET,\n\n  providers: [\n    DiscordProvider({\n      clientId: process.env.CLIENT_ID as string,\n      clientSecret: process.env.CLIENT_SECRET as string,\n    }),\n  ],\n\n  // When the user signs in, get their token\n  callbacks: {\n    async jwt({ token, account }) {\n      // Persist the user ID to the token right after signin\n      if (account) {\n        token.userId = account.providerAccountId;\n      }\n      return token;\n    },\n\n    async session({ session, token, user }) {\n      session.userId = token.userId;\n      return session;\n    },\n  },\n});\n```\n\n### Connect Wallet \u0026 Sign In With Discord\n\nWe have a component called [SignIn](./components/SignIn.tsx) that shows the user different buttons\ndepending on whether they have their wallet connected and Discord account connected.\n\nIn this component, three states can occur:\n\n1. The user is connected to both `wallet` and `Discord` =\u003e We show them the `Give me the role` button.\n2. The user is not connected to `wallet` =\u003e We ask them to connect their wallet.\n3. The user is not connected to `Discord`=\u003e We ask them to authenticate with Discord.\n\n### Authenticating Wallet\n\nOnce users have signed in with their wallet and Discord account, they are shown a button that makes an API\nrequest to grant them a role in the Discord server.\n\nFirst, we need to prove that the user owns the wallet by using the authentication SDK.\n\n**Sign in with ethereum**:\n\n```tsx\nconst { isLoggedIn } = useUser();\nconst login = useLogin();\n\nif (!isLoggedIn) {\n  return (\n    \u003cdiv className={`${styles.main}`}\u003e\n      \u003ch2 className={styles.noGapBottom}\u003eSign using your wallet\u003c/h2\u003e\n      \u003cp\u003e\n        This proves that you really own the wallet that you've claimed to be\n        connected.\n      \u003c/p\u003e\n\n      \u003cbutton\n        onClick={async () =\u003e {\n          await login.login();\n        }}\n        className={`${styles.mainButton} ${styles.spacerTop}`}\n      \u003e\n        Sign message!\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n**Make the request to the API route**\n\n```tsx\nasync function requestGrantRole() {\n  // Then make a request to our API endpoint.\n  try {\n    const response = await fetch(\"/api/grant-role\", {\n      method: \"POST\",\n    });\n    const data = await response.json();\n    console.log(data);\n    alert(\"Check the console for the response!\");\n  } catch (e) {\n    console.error(e);\n  }\n}\n```\n\n### Checking NFT Balance\n\nUsing the TypeScript SDK, we can check the balance of the wallet for a specific ERC-1155 token in a specific collection.\n\n```tsx\n// Check if this user owns an NFT\nconst editionDrop = sdk.getContract(\n  \"0x1fCbA150F05Bbe1C9D21d3ab08E35D682a4c41bF\",\n  \"edition-drop\"\n);\n\n// Get addresses' balance of token ID 0\nconst balance = await editionDrop.balanceOf(verifiedWalletAddress, 0);\n```\n\n### Granting Role\n\nUsing our Discord bot's token as the authorization header, we can grant the user a role in the Discord server\n_if_ they own an NFT in the collection.\n\n```tsx\nif (balance.toNumber() \u003e 0) {\n  // If the user is verified and has an NFT, return the content\n\n  // Make a request to the Discord API to get the servers this user is a part of\n  const discordServerId = \"999533680663998485\";\n  const { userId } = session;\n  const roleId = \"999851736028172298\";\n  const response = await fetch(\n    // Discord Developer Docs for this API Request: https://discord.com/developers/docs/resources/guild#add-guild-member-role\n    `https://discordapp.com/api/guilds/${discordServerId}/members/${userId}/roles/${roleId}`,\n    {\n      headers: {\n        // Use the bot token to grant the role\n        Authorization: `Bot ${process.env.BOT_TOKEN}`,\n      },\n      method: \"PUT\",\n    }\n  );\n\n  // If the role was granted, return the content\n  if (response.ok) {\n    res.status(200).json({ message: \"Role granted\" });\n  }\n\n  // Something went wrong granting the role, but they do have an NFT\n  else {\n    const resp = await response.json();\n    console.error(resp);\n    res\n      .status(500)\n      .json({ error: \"Error granting role, are you in the server?\" });\n  }\n}\n```\n\n## Join our Discord!\n\nFor any questions, suggestions, join our discord at [https://discord.gg/thirdweb](https://discord.gg/thirdweb).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthirdweb-example%2Fdiscord-role-granter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthirdweb-example%2Fdiscord-role-granter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthirdweb-example%2Fdiscord-role-granter/lists"}