Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 4, 2022 03:23 pm GMT

How to create a DAO with Next.js and thirdweb

Written by Avneesh Agarwal

What is a DAO?

DAO stands for Decentralized Autonomous Organization. As it says in the name, a DAO is an organization without a single leader; instead, rules are encoded in the blockchain. Because of this, a DAO is completely transparent and everyone who participates has a stake. Large decisions are made via voting amongst those who own non-fungible tokens (NFTs) from the DAO, which grant membership.

Today, we are going to build our very own DAO using Next.js, thirdweb, MetaMask, and Alchemy. It will allow users to mint your DAOs NFT, receive cryptocurrency via airdrops, and participate in the DAOs polls. This tutorial will be written with just JavaScript, so you dont need to know any Solidity.

Prerequisites

To understand and follow along with this tutorial, you should have the following:

  • Working knowledge of JavaScript, Next.js, and the blockchain
  • A MetaMask wallet
  • An account with Alchemy

Contents

  • Setup
  • Adding sign in functionality using MetaMask
    • Adding the thirdweb provider
    • Adding the sign in component
    • Initializing the thirdweb SDK
  • Adding features to mint an NFT
    • Creating and configuring an NFT
    • Checking if the user already has an NFT
    • Creating a button to mint NFTs
    • Adding styles
  • Creating and deploying a governance token
    • Minting tokens
    • Airdropping tokens
    • Allowing users to vote
    • Showing proposals on the website
    • Getting the proposals
    • Rendering the proposals
  • Conclusion

Setup

We will begin by setting up a Next.js app with the following command:

npx create-next-app my-dao

Now, we will create a new project in thirdweb. Go to thirdweb and connect your MetaMask wallet. After you connect your wallet, click on Create Project and choose the Rinkeby network.

Give your project a name and a description, and hit Create. If you dont have enough ETH to pay for gas, get some from this faucet.

Creating an Alchemy app

Next, head to Alchemy, sign in, click on Create App, and provide the required details. Make sure to use the same chain as the one you used in thirdweb - in our case, it is the Ethereum chain and the Rinkeby network.

Creating alchemy app

After the app is created, copy the HTTP API key.

Getting the wallet's private key

In order to mint NFTs and perform certain scripts, we are going to need the wallets private key.

To access it, open the MetaMask browser extension and click on Account Details. You should see your private key here; export it and copy it somewhere safe.

Adding .env variables

Lets add these variables in a .env file so we can access them later:

PRIVATE_KEY=<wallet_private_key>ALCHEMY_API_URL=<alchemy_http_key>WALLET_ADDRESS=<public_wallet_address>

Because we dont want to push these to GitHub, be sure to add them in .gitignore

Adding sign in functionality using MetaMask

In DApps, MetaMask is the most popular wallet used, so we will add MetaMask sign in with thirdweb.

We are going to need two packages from install:

npm i @3rdweb/sdk @3rdweb/hooks # npmyarn add @3rdweb/sdk @3rdweb/hooks # yarn

Adding the thirdweb provider

We need to wrap our whole app in a thirdweb provider in order to access the login details and other information required for the components:

import { thirdwebWeb3Provider } from "@3rdweb/hooks";import "../styles/globals.css";function MyApp({ Component, pageProps }) {  return (    <thirdwebWeb3Provider>      <Component {...pageProps} />    </thirdwebWeb3Provider>  );}export default MyApp; 

For authentication purposes, we also have to specify the type of authentication and the supported chain IDs. We are using MetaMask and the Rinkeby chain, so add the following as well:

const supportedChainIds = [4];const connectors = {  injected: {},};

Finally, pass these as props in the provider like so:

  <thirdwebWeb3Provider      connectors={connectors}      supportedChainIds={supportedChainIds}    >      <Component {...pageProps} />  </thirdwebWeb3Provider>

Adding the sign in component

Create a new folder called components in the root of the project and add a Login.js file to it:

import { useWeb3 } from "@3rdweb/hooks";const Login = () => {  const { connectWallet } = useWeb3();  return (    <div>      <button onClick={() => connectWallet("injected")}>        Sign in using MetaMask      </button>    </div>  );};export default Login;

Thankfully, thirdweb provides a connectWallet function which we can use to add authentication!

Rendering the sign in component

Inside index.js, render the login screen if there is no address (if the user is not signed in):

const { address } = useWeb3();if (!address) {  return <Login />;}

This will allow our users to sign in, but afterwards it just shows a blank screen. So, in the other return block, lets show the user her address:

export default function Home() {  const { address } = useWeb3();  if (!address) {    return <Login />;  }  return (    <div className={styles.container}>      <h2>You are signed in as {address}</h2>    </div>  );}

The login works but it doesnt look good right now. So, create a new file Login.module.css in the styles folder and add the following:

.container {  min-height: 100vh;  display: flex;  flex-direction: column;  justify-content: center;  align-items: center;  background-color: #7449bb;}.button {  color: #7449bb;  background-color: white;  border: none;  border-radius: 5px;  padding: 10px;  font-size: 16px;  margin: 4px 2px;  cursor: pointer;  font-weight: 500;}

Next, add the following classes to Login.js:

<div className={styles.container}>  <button className={styles.button} onClick={() => connectWallet("injected")}>    Sign in using MetaMask  </button></div>

And finally, import the styles:

import styles from "../styles/Login.module.css";

This will give us a simple, but good-looking login screen.

metamask sign in

Initializing the thirdweb SDK

Now we need to initialize the thirdweb SDK for the various scripts we are going to run. Start by creating a new folder called scripts with an initialize-sdk.js file inside of it.

Add the following code to the file:

import { thirdwebSDK } from "@3rdweb/sdk";import ethers from "ethers";import dotenv from "dotenv";dotenv.config();if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY == "") {  console.log(" Private key not found.");}if (!process.env.ALCHEMY_API_URL || process.env.ALCHEMY_API_URL == "") {  console.log(" Alchemy API URL not found.");}if (!process.env.WALLET_ADDRESS || process.env.WALLET_ADDRESS == "") {  console.log(" Wallet Address not found.");}const sdk = new thirdwebSDK(  new ethers.Wallet(    process.env.PRIVATE_KEY,    ethers.getDefaultProvider(process.env.ALCHEMY_API_URL)  ));(async () => {  try {    const apps = await sdk.getApps();    console.log("app address:", apps[0].address);  } catch (err) {    console.error("Failed to get apps from the sdk", err);    process.exit(1);  }})();export default sdk;

This will initialize the thirdweb SDK, and as you can see, we need to install some packages:

npm i ethers dotenv # npmyarn add ethers dotenv # yarn

We are using modular imports here, so create a new package.json file inside the scripts folder and simply add the following:

{  "name": "scripts",  "type": "module"}

Finally, run the script:

node scripts/initialize-sdk.js

The script may take some time to run, but after some time you will get your app address.

Thirdweb app address

We are going to need this in the next steps, so store it somewhere safe.

Adding features to mint an NFT

For this step, we are going to need some test ETH, so go to a faucet like this and get some.

Creating and configuring an NFT

Create a new file called deploy-drop.js inside the scripts folder. In here, add the following script:

import { ethers } from "ethers";import sdk from "./initialize-sdk.js";import { readFileSync } from "fs";const app = sdk.getAppModule("APP_ADDRESS");(async () => {  try {    const bundleDropModule = await app.deployBundleDropModule({      name: "LogRocket DAO", // Name of NFT Collection for DAO      description: "A DAO for all the LogRocket readers.", // Description      image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection      primarySaleRecipientAddress: ethers.constants.AddressZero,    });    console.log(      " Successfully deployed bundleDrop module, address:",      bundleDropModule.address,    );    console.log(      " bundleDrop metadata:",      await bundleDropModule.getMetadata(),    );  } catch (error) {    console.log("failed to deploy bundleDrop module", error);  }})()

You will need to update a few things here:

  • Update the app address with the new app address that you got by running the previous script
  • Update the name of the NFT drop for the DAO, and its description
  • Add an image for the NFT drop by creating a new folder called assets, and adding the image for your NFT there

After you have updated the details, run the following script:

node scripts/deploy-drop.js

Wait for the script to run, and you should get an address and the metadata.

NFT address and metadata

You can even check out the transaction on Rinkeby Etherscan

Lets configure our NFT now! Create a new config-nft.js file inside the scripts folder and add the following:

import sdk from "./initialize-sdk.js";import { readFileSync } from "fs";const bundleDrop = sdk.getBundleDropModule(  "BUNDLE_DROP_ADDRESS");(async () => {  try {    await bundleDrop.createBatch([      {        name: "LogRocket DAO", // Name of NFT Collection for DAO        description: "A DAO for all the LogRocket readers.", // Description        image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection      },    ]);    console.log(" Successfully created a new NFT in the drop!");  } catch (error) {    console.error("failed to create the new NFT", error);  }})();

You need to update the bundle drop address and the details in the object inside createBatch. These details are going to be used for the NFT!

Once, you have updated all of them, run the following script:

node scripts/config-nft.js

It should give you an output like this.

NFT-configs-output

If you see the module in the thirdweb dashboard, you will see that an NFT has been created!

Thirdweb dashboard

Finally, lets add a claim condition to our NFT

Setting a claim condition will allow us to set a limit for the NFTs and allow a specific max limit per transaction. We will set a claim condition from the dashboard itself, so click on the Settings button and you can update the data based on your needs.

claim condition settings

After you are done updating, click Save and confirm the small transaction.

Checking if the user has an NFT

Before creating a mint button that allows the users to mint NFTs, lets check if the user has an NFT already. We dont want the users to mint multiple NFTs!

Start by adding two new variables, sdk and bundleDropModule, like this before our functional component:

const sdk = new thirdwebSDK("rinkeby");const bundleDropModule = sdk.getBundleDropModule(  "BUNDLE_DROP_ADDRESS");

You will also need to import thirdwebSDK:

import { thirdwebSDK } from "@3rdweb/sdk";

Now, lets create a state for hasClaimedNFT:

const [hasClaimedNFT, setHasClaimedNFT] = useState(false);

We also need to create a useEffect Hook to check if the user has the NFT:

  useEffect(() => {    const handler = async () => {      if (!address) {        return;      }      try {        const balance = await bundleDropModule.balanceOf(address, "0");        if (balance.gt(0)) {          setHasClaimedNFT(true);          console.log(" You have an NFT!");        } else {          setHasClaimedNFT(false);          console.log(" You don't have an NFT.");        }      } catch (error) {        setHasClaimedNFT(false);        console.error("failed to nft balance", error);      }    };    handler();  }, [address]);

Firstly, it will check if the user is signed in. If the user is not signed in, it will return nothing. Then, this checks if the user has the NFT with the token ID 0 in the drop contract that we imported at the top.

If you, open the console in the website, it should show that you dont have an NFT.

You dont have an NFT message

Creating a button to mint NFTs

Lets create the button to mint NFTs! Create a new function called mintNft like so:

const mintNft = async () => {  setIsClaiming(true);  try {    await bundleDropModule.claim("0", 1);    setHasClaimedNFT(true);    console.log(" Successfully Minted the NFT!");  } catch (error) {    console.error("failed to claim", error);  } finally {    setIsClaiming(false);  }};

We will call this function when a button is clicked to mint the NFT to the users wallet. But first, lets add the two required states:

const [hasClaimedNFT, setHasClaimedNFT] = useState(false);const [isClaiming, setIsClaiming] = useState(false);

We also need to call the providerSigner function, so first get the provider from useWeb3:

const { address, provider } = useWeb3();

And then call the function:

const signer = provider ? provider.getSigner() : undefined;useEffect(() => {  sdk.setProviderOrSigner(signer);}, [signer]);

Lets create the button now! Inside the final return block add the following:

<div>  <h1>Mint your free LogRocket DAO Membership NFT </h1>  <button disabled={isClaiming} onClick={() => mintNft()}>    {isClaiming ? "Minting..." : "Mint your nft (FREE)"}  </button></div>

Now, after we sign in, it should show us a screen like this.

Mint NFT screen button

You can even view your NFT on Opensea Testnets; just go to https://testnets.opensea.io/assets//0

Finally, just above the final return block, add this check to see if the user has claimed the NFT already:

if (hasClaimedNFT) {  return (    <div>      <h1>You have the DAO Membership NFT!</h1>    </div>  );}

Adding styles

We have completed building the minting NFT functionality, but it looks ugly, so lets add some basic stylings. Inside Home.module.css add the following:

.container {  min-height: 100vh;  display: flex;  flex-direction: column;  justify-content: center;  align-items: center;  background-color: #7449bb;}.container > h1 {  font-size: 3rem;  color: #fff;  font-weight: bold;}.button {  color: #7449bb;  background-color: white;  border: none;  border-radius: 5px;  padding: 10px;  font-size: 16px;  margin: 4px 2px;  cursor: pointer;  font-weight: 500;}

We also need to add the classNames:

  if (hasClaimedNFT) {    return (      <div className={styles.container}>        <h1>You have the DAO Membership NFT!</h1>      </div>    );  }  return (    <div className={styles.container}>      <h1>Mint your free LogRocket DAO Membership NFT </h1>      <button        className={styles.button}        disabled={isClaiming}        onClick={() => mintNft()}      >        {isClaiming ? "Minting..." : "Mint your NFT (FREE)"}      </button>    </div>  );};

This gives us a better mint screen.

Styled NFT minting screen

Creating and deploying a governance token

Create a new file called deploy-token.js in the scripts folder. Add the following to it:

import sdk from "./initialize-sdk.js";const app = sdk.getAppModule("YOUR_APP_ADDRESS");(async () => {  try {    const tokenModule = await app.deployTokenModule({      name: "LogRocket Token", // name of the token      symbol: "LR", // symbol    });    console.log(      " Successfully deployed token module, address:",      tokenModule.address    );  } catch (error) {    console.error("failed to deploy token module", error);  }})();

This script will create a new token module with a name and symbol. You will need to manually update the app address, token name, and symbol yourself.

After updating, run the script.

You can check this token by the address on Rinkeby Etherscan, and also add it on your MetaMask wallet by clicking Import tokens.

metamask import tokens

After importing, you should see the token under your assets.

Token count 0 LR

It is currently zero, so lets mint some tokens!

Minting tokens

Create a new file called mint-token.js inside the scripts folder and add the following:

import { ethers } from "ethers";import sdk from "./initialize-sdk.js";const tokenModule = sdk.getTokenModule(  "TOKEN_MODULE_ADDRESS");(async () => {  try {    const amount = 1_000_000;    const amountWith18Decimals = ethers.utils.parseUnits(amount.toString(), 18);    await tokenModule.mint(amountWith18Decimals);    const totalSupply = await tokenModule.totalSupply();    console.log(      " There now is",      ethers.utils.formatUnits(totalSupply, 18),      "$LR in circulation"    );  } catch (error) {    console.error("Failed to mint tokens", error);  }})();

Update the token module address with the address you got in the last script, and you can update the amount you want to mint.

After you are ready to mint, run the script:

node scripts/mint-token.js

You should now see the amount of tokens you minted on your MetaMask wallet!

token count 1 million

Airdropping tokens

We might want to airdrop the tokens to our NFT holders, so lets build a script for that. Create a new airdrop.js file inside scripts and add the following:

import { ethers } from "ethers";import sdk from "./initialize-sdk.js";const bundleDropModule = sdk.getBundleDropModule(  "BUNDLE_DROP_ADDRESS");const tokenModule = sdk.getTokenModule(  "TOKEN_MODULE_ADDRESS");(async () => {  try {    const walletAddresses = await bundleDropModule.getAllClaimerAddresses("0");    if (walletAddresses.length === 0) {      console.log(        "No NFTs have been claimed yet, ask your friends to mint some free ones :P!"      );      process.exit(0);    }    const airdropTargets = walletAddresses.map((address) => {      const randomAmount = Math.floor(        Math.random() * (10000 - 1000 + 1) + 1000      );      console.log(" Going to airdrop", randomAmount, "tokens to", address);      return {        address,        amount: ethers.utils.parseUnits(randomAmount.toString(), 18),      };    });    console.log(" Starting airdrop...");    await tokenModule.transferBatch(airdropTargets);    console.log(      " Successfully airdropped tokens to all the holders of the NFT!"    );  } catch (err) {    console.error("Failed to airdrop tokens", err);  }})();

After you run the script you should get something like this.

Airdrop success message

Currently, only you have minted an NFT, so it wont send the token to someone else. But this can be used to send it to other NFT holders later on.

Allowing users to vote

Create a new deploy-vote.js file in the scripts folder and add the following:

import sdk from "./initialize-sdk.js";const appModule = sdk.getAppModule(  "APP_MODULE_ADDRESS");(async () => {  try {    const voteModule = await appModule.deployVoteModule({      name: "LR Dao's Proposals",      votingTokenAddress: "0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca",      proposalStartWaitTimeInSeconds: 0,      proposalVotingTimeInSeconds: 24 * 60 * 60,      votingQuorumFraction: 0,      minimumNumberOfTokensNeededToPropose: "0",    });    console.log(      " Successfully deployed vote module, address:",      voteModule.address    );  } catch (err) {    console.error("Failed to deploy vote module", err);  }})();

Update the app address, the name, and the voting token address, then run the script:

node scripts/deploy-vote.js

We also need to set up a vote module, so create a new script called setup-vote.js and add the following:

import { ethers } from "ethers";import sdk from "./initialize-sdk.js";const voteModule = sdk.getVoteModule(  "0xA57A03B9e117723b6873100742116A01140C43f4");const tokenModule = sdk.getTokenModule(  "0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca");(async () => {  try {    await tokenModule.grantRole("minter", voteModule.address);    console.log(      "Successfully gave vote module permissions to act on token module"    );  } catch (error) {    console.error(      "failed to grant vote module permissions on token module",      error    );    process.exit(1);  }  try {    const ownedTokenBalance = await tokenModule.balanceOf(      process.env.WALLET_ADDRESS    );    const ownedAmount = ethers.BigNumber.from(ownedTokenBalance.value);    const percent90 = ownedAmount.div(100).mul(90);    await tokenModule.transfer(voteModule.address, percent90);    console.log(" Successfully transferred tokens to vote module");  } catch (err) {    console.error("failed to transfer tokens to vote module", err);  }})();

You will need to run this script to finish it up:

node scripts/setup-vote.js

Now that we have our vote module ready, lets create some proposals!

Create a new file called vote-proposals.js inside the scripts folder and add the following:

import { ethers } from "ethers";import sdk from "./initialize-sdk.js";const voteModule = sdk.getVoteModule(  "VOTE_MODULE");const tokenModule = sdk.getTokenModule(  "TOKEN_MODULE");(async () => {  try {    const amount = 10000;    await voteModule.propose(      "Should the DAO mint an additional " +        amount +        " tokens into the treasury?",      [        {          nativeTokenValue: 0,          transactionData: tokenModule.contract.interface.encodeFunctionData(            "mint",            [voteModule.address, ethers.utils.parseUnits(amount.toString(), 18)]          ),          toAddress: tokenModule.address,        },      ]    );    console.log(" Successfully created proposal to mint tokens");  } catch (error) {    console.error("failed to create first proposal", error);    process.exit(1);  }})();

You need to update the module addresses, and if you want to update the message of the proposal, you can update that as well.

Finally, run the script. It should give you something like this.

successful proposal to mint tokens message

If you now check the thirdweb dashboard, the proposal has been created.

thirdweb cleared proposal

Showing proposals on the website

First, import the token and vote module:

const voteModule = sdk.getVoteModule(  "0xf738973379b8B6444e429D2fd6C8B1f223247390");const tokenModule = sdk.getTokenModule(  "0x8C35288de335070dd1C00d68d71383d81437472A");

We are going to need three useStates, like so:

const [proposals, setProposals] = useState([]);const [isVoting, setIsVoting] = useState(false);const [hasVoted, setHasVoted] = useState(false); 

Getting the proposals

We need to get the proposals to display them on the screen, so create this useEffect:

useEffect(() => {    if (!hasClaimedNFT) {      return;    }    voteModule      .getAll()      .then((proposals) => {        setProposals(proposals);      })      .catch((err) => {        console.error("failed to get proposals", err);      });  }, [hasClaimedNFT]);

Then, create a new handleFormSubmit function:

  const handleFormSubmit = async (e) => {    e.preventDefault();    e.stopPropagation();    setIsVoting(true);    const votes = proposals.map((proposal) => {      let voteResult = {        proposalId: proposal.proposalId,        vote: 2,      };      proposal.votes.forEach((vote) => {        const elem = document.getElementById(          proposal.proposalId + "-" + vote.type        );        if (elem.checked) {          voteResult.vote = vote.type;          return;        }      });      return voteResult;    });    try {      const delegation = await tokenModule.getDelegationOf(address);      if (delegation === ethers.constants.AddressZero) {        await tokenModule.delegateTo(address);      }      try {        await Promise.all(          votes.map(async (vote) => {            const proposal = await voteModule.get(vote.proposalId);            if (proposal.state === 1) {              return voteModule.vote(vote.proposalId, vote.vote);            }            return;          })        );        try {          await Promise.all(            votes.map(async (vote) => {              const proposal = await voteModule.get(vote.proposalId);              if (proposal.state === 4) {                return voteModule.execute(vote.proposalId);              }            })          );          setHasVoted(true);        } catch (err) {          console.error("failed to execute votes", err);        }      } catch (err) {        console.error("failed to vote", err);      }    } catch (err) {      console.error("failed to delegate tokens", err);    } finally {      setIsVoting(false);    }  };

This function is going to collect the vote.

Rendering the proposals

Replace the if (hasClaimedNFT) block with this:

if (hasClaimedNFT) {    return (      <div className={styles.container}>          <h2>Active Proposals</h2>          <form onSubmit={handleFormSubmit}>            {proposals.map((proposal) => (              <Proposal                key={proposal.proposalId}                votes={proposal.votes}                description={proposal.description}                proposalId={proposal.proposalId}              />            ))}            <button              onClick={handleFormSubmit}              type="submit"              className={styles.button}            >              {isVoting                ? "Voting..."                "Submit Votes"}            </button>          </form>        </div>    );  }

We are creating a separate component for the proposal to keep things clean. So, create a new file called Proposal.js in the components folder and add the following:

import styles from "../styles/Proposal.module.css";const Proposal = ({ description, votes, proposalId }) => {  return (    <div className={styles.proposal}>      <h5 className={styles.description}>{description}</h5>      <div className={styles.options}>        {votes.map((vote) => (          <div key={vote.type}>            <input              type="radio"              id={proposalId + "-" + vote.type}              name={proposalId}              value={vote.type}              defaultChecked={vote.type === 2}            />            <label htmlFor={proposalId + "-" + vote.type}>{vote.label}</label>          </div>        ))}      </div>    </div>  );};export default Proposal;

I also added basic styling, so create a new Proposal.module.css file in the styles folder:

.proposal {  display: flex;  flex-direction: column;  align-items: center;  justify-content: center;  background-color: #fafafa;  border-radius: 10px;  box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);  padding: 20px;  margin: 20px;}.options {  display: flex;  flex-direction: column;  justify-content: space-between;  align-items: flex-start;  width: 100%;  margin-top: 1rem;}

To center the items, I have added the following styles in Home.module.css as well:

.container > form {  display: flex;  flex-direction: column;  align-items: center;}

You will get to this screen where you can submit your votes.

Proposal vote screen

Finally, lets make a function to check if the person has already voted.

First, create a new useEffect:

  useEffect(() => {    if (!hasClaimedNFT) {      return;    }    if (!proposals.length) {      return;    }    voteModule      .hasVoted(proposals[0].proposalId, address)      .then((hasVoted) => {        setHasVoted(hasVoted);        if (hasVoted) {        } else {        }      })      .catch((err) => {        console.error("failed to check if wallet has voted", err);      });  }, [hasClaimedNFT, address, proposals]);

And replace the button with this:

 <button  onClick={handleFormSubmit}  type="submit"  disabled={isVoting || hasVoted}  className={styles.button}>  {isVoting ? "Voting..." : hasVoted ? "You Already Voted" : "Submit Votes"}</button>

After you have voted, it should show the message You Already Voted:

You already voted message

Conclusion

This was it for this tutorial, hope you liked it and can use it to make your own DAO! You can always update the DAO and add more features if you like.

You can find the GitHub repo for this project here.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If youre interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps start monitoring for free.


Original Link: https://dev.to/logrocket/how-to-create-a-dao-with-nextjs-and-thirdweb-5069

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To