Saul  Alaniz

Saul Alaniz

1648607160

Cómo Crear Un DAO Con Next.js Y Thirdweb

¿Qué es un DAO?

DAO significa Organización Autónoma Descentralizada. Como dice el nombre, una DAO es una organización sin un solo líder; en cambio, las reglas están codificadas en la cadena de bloques. Por eso, una DAO es completamente transparente y todos los que participan tienen una participación. Las decisiones importantes se toman mediante la votación entre aquellos que poseen tokens no fungibles (NFT) de la DAO, que otorgan membresía.

Hoy vamos a construir nuestro propio DAO usando Next.js, ThirdWeb, MetaMask y Alchemy. Permitirá a los usuarios acuñar el NFT de su DAO, recibir criptomonedas a través de airdrops y participar en las encuestas de DAO. Este tutorial se escribirá solo con JavaScript, por lo que no necesita saber nada de Solidity.

requisitos previos

Para comprender y seguir este tutorial, debe tener lo siguiente:

  • Conocimiento práctico de JavaScript, Next.js y blockchain .
  • Una billetera MetaMask
  • Una cuenta con Alchemy

Configuración

Comenzaremos configurando una aplicación Next.js con el siguiente comando:

npx create-next-app my-dao

Ahora, crearemos un nuevo proyecto en Thirdweb . Vaya a Thirdweb y conecte su billetera MetaMask. Después de conectar su billetera, haga clic en Crear proyecto y elija la red Rinkeby.

Asigne un nombre y una descripción a su proyecto y presione Crear . Si no tiene suficiente ETH para pagar la gasolina, obtenga algo de este faucet .

Crear una aplicación de Alchemy

A continuación, diríjase a Alchemy, inicie sesión, haga clic en Crear aplicación y proporcione los detalles requeridos. Asegúrese de usar la misma cadena que usó en Thirdweb; en nuestro caso, es la cadena Ethereum y la red Rinkeby.

Después de crear la aplicación, copie la clave API HTTP.

Obtener la clave privada de la billetera

Para acuñar NFT y ejecutar ciertos scripts, necesitaremos la clave privada de la billetera.

Para acceder a él, abra la extensión del navegador MetaMask y haga clic en Detalles de la cuenta . Debería ver su clave privada aquí; expórtelo y cópielo en un lugar seguro.

Agregar .envvariables

Agreguemos estas variables en un .envarchivo para que podamos acceder a ellas más tarde:

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

Debido a que no queremos enviarlos a GitHub, asegúrese de agregarlos en .gitignore

Agregar funcionalidad de inicio de sesión usando MetaMask

En DApps, MetaMask es la billetera más popular, por lo que agregaremos el inicio de sesión de MetaMask con ThirdWeb.

Vamos a necesitar dos paquetes de install:

npm i @3rdweb/sdk @3rdweb/hooks # npm

yarn add @3rdweb/sdk @3rdweb/hooks # yarn

Agregar el tercer proveedor web

Necesitamos envolver toda nuestra aplicación en un tercer proveedor web para acceder a los detalles de inicio de sesión y otra información requerida para los componentes:

import { thirdwebWeb3Provider } from "@3rdweb/hooks";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return (
    <thirdwebWeb3Provider>
      <Component {...pageProps} />
    </thirdwebWeb3Provider>
  );
}

export default MyApp; 

Para fines de autenticación, también debemos especificar el tipo de autenticación y los ID de cadena admitidos. Estamos usando MetaMask y la cadena Rinkeby, así que agregue lo siguiente también:

const supportedChainIds = [4];

const connectors = {
  injected: {},
};

Finalmente, pásalos como accesorios en el proveedor así:

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

Agregar el componente de inicio de sesión

Cree una nueva carpeta llamada componentsen la raíz del proyecto y agréguele un Login.jsarchivo:

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;

¡Afortunadamente, Thirdweb proporciona una connectWalletfunción que podemos usar para agregar autenticación!

Representación del componente de inicio de sesión

En el interior index.js, muestra la pantalla de inicio de sesión si no hay una dirección (si el usuario no ha iniciado sesión):

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

Esto permitirá que nuestros usuarios inicien sesión, pero luego solo muestra una pantalla en blanco. Entonces, en el otro bloque de retorno, mostremos al usuario su dirección:

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>
  );
}

El inicio de sesión funciona, pero no se ve bien en este momento. Por lo tanto, cree un nuevo archivo Login.module.cssen la stylescarpeta y agregue lo siguiente:

.container { altura mínima: 100vh; pantalla: flexible; dirección de flexión: columna; justificar-contenido: centro; alinear elementos: centro; color de fondo: #7449bb; } .botón {color: #7449bb; color de fondo: blanco; borde: ninguno; borde-radio: 5px; relleno: 10px; tamaño de fuente: 16px; margen: 4px 2px; cursor: puntero; peso de fuente: 500; }

A continuación, agregue las siguientes clases a Login.js:

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

Y finalmente, importa los estilos:

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

Esto nos dará una pantalla de inicio de sesión simple pero atractiva.

Inicializando el SDK de Thirdweb

Ahora necesitamos inicializar el SDK de Thirdweb para los diversos scripts que vamos a ejecutar. Comience creando una nueva carpeta llamada scriptscon un initialize-sdk.jsarchivo dentro.

Agregue el siguiente código al archivo:

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;

Esto inicializará el SDK de Thirdweb y, como puede ver, necesitamos instalar algunos paquetes:

npm i ethers dotenv # npm

yarn add ethers dotenv # yarn

Estamos utilizando importaciones modulares aquí, así que cree un nuevo package.jsonarchivo dentro de la scriptscarpeta y simplemente agregue lo siguiente:

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

Finalmente, ejecute el script:

node scripts/initialize-sdk.js

La secuencia de comandos puede tardar un tiempo en ejecutarse, pero después de un tiempo obtendrá la dirección de su aplicación.

Vamos a necesitar esto en los próximos pasos, así que guárdelo en un lugar seguro.

Agregar características para acuñar un NFT

Para este paso, vamos a necesitar algunos ETH de prueba, así que ve a un faucet como este y obtén algunos.

Crear y configurar un NFT

Cree un nuevo archivo llamado deploy-drop.jsdentro de la scriptscarpeta. Aquí, agregue el siguiente 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);
  }
})()

Deberá actualizar algunas cosas aquí:

  • Actualice la dirección de la aplicación con la nueva dirección de la aplicación que obtuvo al ejecutar el script anterior
  • Actualice el nombre de la caída de NFT para el DAO y su descripción
  • Agregue una imagen para la caída de NFT creando una nueva carpeta llamada assetsy agregando la imagen para su NFT allí

Una vez que haya actualizado los detalles, ejecute el siguiente script:

scripts de nodo/deploy-drop.js

Espere a que se ejecute el script y debería obtener una dirección y los metadatos.

Incluso puede consultar la transacción en Rinkeby Etherscan

¡Configuremos nuestro NFT ahora! Cree un nuevo config-nft.jsarchivo dentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Debe actualizar la dirección de entrega del paquete y los detalles en el objeto que se encuentra dentro createBatch. ¡Estos detalles se utilizarán para el NFT!

Una vez que los haya actualizado todos, ejecute el siguiente script:

node scripts/config-nft.js

Debería darte una salida como esta.

Si ve el módulo en el panel de Thirdweb, verá que se ha creado un NFT. 🥳

Finalmente, agreguemos una condición de reclamo a nuestro NFT

Establecer una condición de reclamo nos permitirá establecer un límite para los NFT y permitir un límite máximo específico por transacción. Estableceremos una condición de reclamo desde el panel mismo, así que haga clic en el botón Configuración y podrá actualizar los datos según sus necesidades.

Una vez que haya terminado de actualizar, haga clic en Guardar y confirme la pequeña transacción.

Comprobando si el usuario tiene un NFT

Antes de crear un botón de menta que permita a los usuarios acuñar NFT, verifiquemos si el usuario ya tiene una NFT. ¡No queremos que los usuarios acumulen múltiples NFT!

Comience agregando dos nuevas variables sdky bundleDropModule, así antes de nuestro componente funcional:

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

También necesitarás importar thirdwebSDK:

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

Ahora, vamos a crear un estado para hasClaimedNFT:

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

También necesitamos crear un useEffectHook para verificar si el usuario tiene el 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]);

En primer lugar, comprobará si el usuario ha iniciado sesión. Si el usuario no ha iniciado sesión, no devolverá nada. Luego, esto verifica si el usuario tiene el NFT con la identificación del token 0en el contrato de entrega que importamos en la parte superior.

Si abre la consola en el sitio web, debería mostrar que no tiene un NFT.

Creación de un botón para acuñar NFT

¡Creemos el botón para acuñar NFT! Crea una nueva función llamada mintNftasí:

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);
  }
};

Llamaremos a esta función cuando se haga clic en un botón para acuñar el NFT en la billetera del usuario. Pero primero, agreguemos los dos estados requeridos:

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

También necesitamos llamar a la providerSignerfunción, así que primero obtenga el proveedor de useWeb3:

const { address, provider } = useWeb3();

Y luego llamar a la función:

const signer = provider ? provider.getSigner() : undefined;

useEffect(() => {
  sdk.setProviderOrSigner(signer);
}, [signer]);

¡Vamos a crear el botón ahora! Dentro del bloque de retorno final, agregue lo siguiente:

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

Ahora, después de iniciar sesión, debería mostrarnos una pantalla como esta.

Incluso puede ver su NFT en Opensea Testnets ; simplemente vaya a https://testnets.opensea.io/assets//0

Finalmente, justo encima del bloque de retorno final, agregue esta verificación para ver si el usuario ya ha reclamado el NFT:

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

Agregar estilos

Hemos completado la construcción de la función NFT de acuñación, pero se ve fea, así que agreguemos algunos estilos básicos. Dentro Home.module.cssagrega lo siguiente:

.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;
}

También tenemos que añadir el 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>
  );
};

Esto nos da una mejor pantalla de menta.

Creación e implementación de un token de gobernanza

Cree un nuevo archivo llamado deploy-token.jsen la scriptscarpeta. Añádele lo siguiente:

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);
  }
})();

Este script creará un nuevo módulo de token con un nombre y un símbolo. Deberá actualizar manualmente la dirección de la aplicación, el nombre del token y el símbolo usted mismo.

Después de actualizar, ejecute el script.

Puede verificar este token por la dirección en Rinkeby Etherscan y también agregarlo a su billetera MetaMask haciendo clic en Importar tokens .

Después de importar, debería ver el token debajo de sus activos.

Actualmente es cero, ¡así que vamos a acuñar algunas fichas!

Fichas de acuñación

Cree un nuevo archivo llamado mint-token.jsdentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Actualice la dirección del módulo de token con la dirección que obtuvo en el último script y podrá actualizar la cantidad que desea acuñar.

Una vez que esté listo para acuñar, ejecute el script:

node scripts/mint-token.js

¡Ahora debería ver la cantidad de tokens que acuñó en su billetera MetaMask! 🎉

fichas de lanzamiento aéreo

Es posible que queramos lanzar los tokens desde el aire a nuestros titulares de NFT, así que construyamos un script para eso. Cree un nuevo airdrop.jsarchivo dentro scriptsy agregue lo siguiente:

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);
  }
})();

Después de ejecutar el script, debería obtener algo como esto.

Actualmente, solo usted ha acuñado un NFT, por lo que no enviará el token a otra persona. Pero esto se puede usar para enviarlo a otros titulares de NFT más adelante.

Permitir que los usuarios voten

Cree un nuevo deploy-vote.jsarchivo en la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Actualice la dirección de la aplicación, el nombre y la dirección del token de votación, luego ejecute el script:

node scripts/deploy-vote.js

También necesitamos configurar un módulo de votación, así que cree un nuevo script llamado setup-vote.jsy agregue lo siguiente:

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);
  }
})();

Deberá ejecutar este script para terminarlo:

node scripts/setup-vote.js

Ahora que tenemos listo nuestro módulo de votación, ¡creemos algunas propuestas!

Cree un nuevo archivo llamado vote-proposals.jsdentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Debe actualizar las direcciones del módulo y, si desea actualizar el mensaje de la propuesta, también puede actualizarlo.

Finalmente, ejecute el script. Debería darte algo como esto.

Si ahora revisa el panel de Thirdweb, la propuesta ha sido creada. 🎉

Mostrando propuestas en el sitio web

Primero, importe el módulo de token y voto:

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

Vamos a necesitar tres useStates, así:

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

Recibiendo las propuestas

Necesitamos obtener las propuestas para mostrarlas en la pantalla, así que cree esto useEffect:

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

Luego, crea una nueva handleFormSubmitfunción:

  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);
    }
  };

Esta función va a recoger el voto.

Presentación de las propuestas

Reemplace el if (hasClaimedNFT)bloque con esto:

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>
    );
  }

Estamos creando un componente separado para la propuesta de mantener las cosas limpias. Entonces, cree un nuevo archivo llamado Proposal.jsen la componentscarpeta y agregue lo siguiente:

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;

También agregué un estilo básico, así que cree un nuevo Proposal.module.cssarchivo en la stylescarpeta:

.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;
}

Para centrar los elementos, también he agregado los siguientes estilos Home.module.css:

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

Accederá a esta pantalla donde puede enviar sus votos. 🎉

Finalmente, hagamos una función para verificar si la persona ya votó.

Primero, crea un nuevo 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]);

Y reemplaza el botón con esto:

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

Después de haber votado, debería mostrar el mensaje Ya votó :

Conclusión

¡Esto fue todo para este tutorial, espero que les haya gustado y puedan usarlo para hacer su propio DAO! Siempre puede actualizar el DAO y agregar más funciones si lo desea.✌️ 

Fuente: https://blog.logrocket.com/create-dao-next-js-thirdweb/

#nextjs #thirdweb 

What is GEEK

Buddha Community

Cómo Crear Un DAO Con Next.js Y Thirdweb

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Saul  Alaniz

Saul Alaniz

1648607160

Cómo Crear Un DAO Con Next.js Y Thirdweb

¿Qué es un DAO?

DAO significa Organización Autónoma Descentralizada. Como dice el nombre, una DAO es una organización sin un solo líder; en cambio, las reglas están codificadas en la cadena de bloques. Por eso, una DAO es completamente transparente y todos los que participan tienen una participación. Las decisiones importantes se toman mediante la votación entre aquellos que poseen tokens no fungibles (NFT) de la DAO, que otorgan membresía.

Hoy vamos a construir nuestro propio DAO usando Next.js, ThirdWeb, MetaMask y Alchemy. Permitirá a los usuarios acuñar el NFT de su DAO, recibir criptomonedas a través de airdrops y participar en las encuestas de DAO. Este tutorial se escribirá solo con JavaScript, por lo que no necesita saber nada de Solidity.

requisitos previos

Para comprender y seguir este tutorial, debe tener lo siguiente:

  • Conocimiento práctico de JavaScript, Next.js y blockchain .
  • Una billetera MetaMask
  • Una cuenta con Alchemy

Configuración

Comenzaremos configurando una aplicación Next.js con el siguiente comando:

npx create-next-app my-dao

Ahora, crearemos un nuevo proyecto en Thirdweb . Vaya a Thirdweb y conecte su billetera MetaMask. Después de conectar su billetera, haga clic en Crear proyecto y elija la red Rinkeby.

Asigne un nombre y una descripción a su proyecto y presione Crear . Si no tiene suficiente ETH para pagar la gasolina, obtenga algo de este faucet .

Crear una aplicación de Alchemy

A continuación, diríjase a Alchemy, inicie sesión, haga clic en Crear aplicación y proporcione los detalles requeridos. Asegúrese de usar la misma cadena que usó en Thirdweb; en nuestro caso, es la cadena Ethereum y la red Rinkeby.

Después de crear la aplicación, copie la clave API HTTP.

Obtener la clave privada de la billetera

Para acuñar NFT y ejecutar ciertos scripts, necesitaremos la clave privada de la billetera.

Para acceder a él, abra la extensión del navegador MetaMask y haga clic en Detalles de la cuenta . Debería ver su clave privada aquí; expórtelo y cópielo en un lugar seguro.

Agregar .envvariables

Agreguemos estas variables en un .envarchivo para que podamos acceder a ellas más tarde:

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

Debido a que no queremos enviarlos a GitHub, asegúrese de agregarlos en .gitignore

Agregar funcionalidad de inicio de sesión usando MetaMask

En DApps, MetaMask es la billetera más popular, por lo que agregaremos el inicio de sesión de MetaMask con ThirdWeb.

Vamos a necesitar dos paquetes de install:

npm i @3rdweb/sdk @3rdweb/hooks # npm

yarn add @3rdweb/sdk @3rdweb/hooks # yarn

Agregar el tercer proveedor web

Necesitamos envolver toda nuestra aplicación en un tercer proveedor web para acceder a los detalles de inicio de sesión y otra información requerida para los componentes:

import { thirdwebWeb3Provider } from "@3rdweb/hooks";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return (
    <thirdwebWeb3Provider>
      <Component {...pageProps} />
    </thirdwebWeb3Provider>
  );
}

export default MyApp; 

Para fines de autenticación, también debemos especificar el tipo de autenticación y los ID de cadena admitidos. Estamos usando MetaMask y la cadena Rinkeby, así que agregue lo siguiente también:

const supportedChainIds = [4];

const connectors = {
  injected: {},
};

Finalmente, pásalos como accesorios en el proveedor así:

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

Agregar el componente de inicio de sesión

Cree una nueva carpeta llamada componentsen la raíz del proyecto y agréguele un Login.jsarchivo:

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;

¡Afortunadamente, Thirdweb proporciona una connectWalletfunción que podemos usar para agregar autenticación!

Representación del componente de inicio de sesión

En el interior index.js, muestra la pantalla de inicio de sesión si no hay una dirección (si el usuario no ha iniciado sesión):

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

Esto permitirá que nuestros usuarios inicien sesión, pero luego solo muestra una pantalla en blanco. Entonces, en el otro bloque de retorno, mostremos al usuario su dirección:

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>
  );
}

El inicio de sesión funciona, pero no se ve bien en este momento. Por lo tanto, cree un nuevo archivo Login.module.cssen la stylescarpeta y agregue lo siguiente:

.container { altura mínima: 100vh; pantalla: flexible; dirección de flexión: columna; justificar-contenido: centro; alinear elementos: centro; color de fondo: #7449bb; } .botón {color: #7449bb; color de fondo: blanco; borde: ninguno; borde-radio: 5px; relleno: 10px; tamaño de fuente: 16px; margen: 4px 2px; cursor: puntero; peso de fuente: 500; }

A continuación, agregue las siguientes clases a Login.js:

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

Y finalmente, importa los estilos:

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

Esto nos dará una pantalla de inicio de sesión simple pero atractiva.

Inicializando el SDK de Thirdweb

Ahora necesitamos inicializar el SDK de Thirdweb para los diversos scripts que vamos a ejecutar. Comience creando una nueva carpeta llamada scriptscon un initialize-sdk.jsarchivo dentro.

Agregue el siguiente código al archivo:

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;

Esto inicializará el SDK de Thirdweb y, como puede ver, necesitamos instalar algunos paquetes:

npm i ethers dotenv # npm

yarn add ethers dotenv # yarn

Estamos utilizando importaciones modulares aquí, así que cree un nuevo package.jsonarchivo dentro de la scriptscarpeta y simplemente agregue lo siguiente:

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

Finalmente, ejecute el script:

node scripts/initialize-sdk.js

La secuencia de comandos puede tardar un tiempo en ejecutarse, pero después de un tiempo obtendrá la dirección de su aplicación.

Vamos a necesitar esto en los próximos pasos, así que guárdelo en un lugar seguro.

Agregar características para acuñar un NFT

Para este paso, vamos a necesitar algunos ETH de prueba, así que ve a un faucet como este y obtén algunos.

Crear y configurar un NFT

Cree un nuevo archivo llamado deploy-drop.jsdentro de la scriptscarpeta. Aquí, agregue el siguiente 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);
  }
})()

Deberá actualizar algunas cosas aquí:

  • Actualice la dirección de la aplicación con la nueva dirección de la aplicación que obtuvo al ejecutar el script anterior
  • Actualice el nombre de la caída de NFT para el DAO y su descripción
  • Agregue una imagen para la caída de NFT creando una nueva carpeta llamada assetsy agregando la imagen para su NFT allí

Una vez que haya actualizado los detalles, ejecute el siguiente script:

scripts de nodo/deploy-drop.js

Espere a que se ejecute el script y debería obtener una dirección y los metadatos.

Incluso puede consultar la transacción en Rinkeby Etherscan

¡Configuremos nuestro NFT ahora! Cree un nuevo config-nft.jsarchivo dentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Debe actualizar la dirección de entrega del paquete y los detalles en el objeto que se encuentra dentro createBatch. ¡Estos detalles se utilizarán para el NFT!

Una vez que los haya actualizado todos, ejecute el siguiente script:

node scripts/config-nft.js

Debería darte una salida como esta.

Si ve el módulo en el panel de Thirdweb, verá que se ha creado un NFT. 🥳

Finalmente, agreguemos una condición de reclamo a nuestro NFT

Establecer una condición de reclamo nos permitirá establecer un límite para los NFT y permitir un límite máximo específico por transacción. Estableceremos una condición de reclamo desde el panel mismo, así que haga clic en el botón Configuración y podrá actualizar los datos según sus necesidades.

Una vez que haya terminado de actualizar, haga clic en Guardar y confirme la pequeña transacción.

Comprobando si el usuario tiene un NFT

Antes de crear un botón de menta que permita a los usuarios acuñar NFT, verifiquemos si el usuario ya tiene una NFT. ¡No queremos que los usuarios acumulen múltiples NFT!

Comience agregando dos nuevas variables sdky bundleDropModule, así antes de nuestro componente funcional:

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

También necesitarás importar thirdwebSDK:

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

Ahora, vamos a crear un estado para hasClaimedNFT:

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

También necesitamos crear un useEffectHook para verificar si el usuario tiene el 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]);

En primer lugar, comprobará si el usuario ha iniciado sesión. Si el usuario no ha iniciado sesión, no devolverá nada. Luego, esto verifica si el usuario tiene el NFT con la identificación del token 0en el contrato de entrega que importamos en la parte superior.

Si abre la consola en el sitio web, debería mostrar que no tiene un NFT.

Creación de un botón para acuñar NFT

¡Creemos el botón para acuñar NFT! Crea una nueva función llamada mintNftasí:

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);
  }
};

Llamaremos a esta función cuando se haga clic en un botón para acuñar el NFT en la billetera del usuario. Pero primero, agreguemos los dos estados requeridos:

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

También necesitamos llamar a la providerSignerfunción, así que primero obtenga el proveedor de useWeb3:

const { address, provider } = useWeb3();

Y luego llamar a la función:

const signer = provider ? provider.getSigner() : undefined;

useEffect(() => {
  sdk.setProviderOrSigner(signer);
}, [signer]);

¡Vamos a crear el botón ahora! Dentro del bloque de retorno final, agregue lo siguiente:

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

Ahora, después de iniciar sesión, debería mostrarnos una pantalla como esta.

Incluso puede ver su NFT en Opensea Testnets ; simplemente vaya a https://testnets.opensea.io/assets//0

Finalmente, justo encima del bloque de retorno final, agregue esta verificación para ver si el usuario ya ha reclamado el NFT:

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

Agregar estilos

Hemos completado la construcción de la función NFT de acuñación, pero se ve fea, así que agreguemos algunos estilos básicos. Dentro Home.module.cssagrega lo siguiente:

.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;
}

También tenemos que añadir el 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>
  );
};

Esto nos da una mejor pantalla de menta.

Creación e implementación de un token de gobernanza

Cree un nuevo archivo llamado deploy-token.jsen la scriptscarpeta. Añádele lo siguiente:

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);
  }
})();

Este script creará un nuevo módulo de token con un nombre y un símbolo. Deberá actualizar manualmente la dirección de la aplicación, el nombre del token y el símbolo usted mismo.

Después de actualizar, ejecute el script.

Puede verificar este token por la dirección en Rinkeby Etherscan y también agregarlo a su billetera MetaMask haciendo clic en Importar tokens .

Después de importar, debería ver el token debajo de sus activos.

Actualmente es cero, ¡así que vamos a acuñar algunas fichas!

Fichas de acuñación

Cree un nuevo archivo llamado mint-token.jsdentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Actualice la dirección del módulo de token con la dirección que obtuvo en el último script y podrá actualizar la cantidad que desea acuñar.

Una vez que esté listo para acuñar, ejecute el script:

node scripts/mint-token.js

¡Ahora debería ver la cantidad de tokens que acuñó en su billetera MetaMask! 🎉

fichas de lanzamiento aéreo

Es posible que queramos lanzar los tokens desde el aire a nuestros titulares de NFT, así que construyamos un script para eso. Cree un nuevo airdrop.jsarchivo dentro scriptsy agregue lo siguiente:

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);
  }
})();

Después de ejecutar el script, debería obtener algo como esto.

Actualmente, solo usted ha acuñado un NFT, por lo que no enviará el token a otra persona. Pero esto se puede usar para enviarlo a otros titulares de NFT más adelante.

Permitir que los usuarios voten

Cree un nuevo deploy-vote.jsarchivo en la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Actualice la dirección de la aplicación, el nombre y la dirección del token de votación, luego ejecute el script:

node scripts/deploy-vote.js

También necesitamos configurar un módulo de votación, así que cree un nuevo script llamado setup-vote.jsy agregue lo siguiente:

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);
  }
})();

Deberá ejecutar este script para terminarlo:

node scripts/setup-vote.js

Ahora que tenemos listo nuestro módulo de votación, ¡creemos algunas propuestas!

Cree un nuevo archivo llamado vote-proposals.jsdentro de la scriptscarpeta y agregue lo siguiente:

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);
  }
})();

Debe actualizar las direcciones del módulo y, si desea actualizar el mensaje de la propuesta, también puede actualizarlo.

Finalmente, ejecute el script. Debería darte algo como esto.

Si ahora revisa el panel de Thirdweb, la propuesta ha sido creada. 🎉

Mostrando propuestas en el sitio web

Primero, importe el módulo de token y voto:

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

Vamos a necesitar tres useStates, así:

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

Recibiendo las propuestas

Necesitamos obtener las propuestas para mostrarlas en la pantalla, así que cree esto useEffect:

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

Luego, crea una nueva handleFormSubmitfunción:

  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);
    }
  };

Esta función va a recoger el voto.

Presentación de las propuestas

Reemplace el if (hasClaimedNFT)bloque con esto:

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>
    );
  }

Estamos creando un componente separado para la propuesta de mantener las cosas limpias. Entonces, cree un nuevo archivo llamado Proposal.jsen la componentscarpeta y agregue lo siguiente:

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;

También agregué un estilo básico, así que cree un nuevo Proposal.module.cssarchivo en la stylescarpeta:

.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;
}

Para centrar los elementos, también he agregado los siguientes estilos Home.module.css:

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

Accederá a esta pantalla donde puede enviar sus votos. 🎉

Finalmente, hagamos una función para verificar si la persona ya votó.

Primero, crea un nuevo 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]);

Y reemplaza el botón con esto:

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

Después de haber votado, debería mostrar el mensaje Ya votó :

Conclusión

¡Esto fue todo para este tutorial, espero que les haya gustado y puedan usarlo para hacer su propio DAO! Siempre puede actualizar el DAO y agregar más funciones si lo desea.✌️ 

Fuente: https://blog.logrocket.com/create-dao-next-js-thirdweb/

#nextjs #thirdweb 

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics

Enoch Barcenas

Enoch Barcenas

1648523737

How to Build Your Own DAO using Next.js, Thirdweb, MetaMask & Alchemy

Learn how to build a DAO, mint NFTs, airdrop tokens, and add voting functionality using only JavaScript in this complete tutorial.

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 DAO’s NFT, receive cryptocurrency via airdrops, and participate in the DAO’s polls. This tutorial will be written with just JavaScript, so you don’t 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
    • Creating an Alchemy app
    • Getting the wallet’s private key
    • Adding .env variables
  • 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

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 wallet’s 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

Let’s 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 don’t 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 @thirdweb-dev/react @thirdweb-dev/sdk ethers # npm

yarn add @thirdweb-dev/react @thirdweb-dev/sdk ethers # 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 "../styles/globals.css";
import {ThirdwebProvider } from "@thirdweb-dev/react";

function MyApp({ Component, pageProps }) {
  return (
    <ThirdwebProvider desiredChainId={activeChainId}>
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}
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:

import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
const activeChainId = ChainId.Rinkeby;

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

 <ThirdwebProvider desiredChainId={activeChainId}>
      <Component {...pageProps} />
  </ThirdwebProvider>

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 { useMetamask } from "@thirdweb-dev/react";
const Login = () => {
  const connectWithMetamask = useMetamask();
  return (
    <div>
      <button onClick={connectWithMetamask}>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 = useAddress();
if (!address) {
  return ;
}

This will allow our users to sign in, but afterwards it just shows a blank screen. So, in the other return block, let’s 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 doesn’t 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 "@thirdweb-dev/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 address = await sdk.getSigner().getAddress();
    console.log("SDK initialized by address:", address);
  } catch (err) {
    console.error("Failed to get the address", 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 dotenv # npm

yarn add 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.

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";

(async () => {
  try {
    const editionDropAddress = await sdk.deployer.deployEditionDrop({
      name: "LogRocket DAO", // Name of NFT Collection for DAO
      description: "A DAO for all the LogRocket readers.", // Description
      image: "image_Address", // PFP for NFT collection
      primary_sale_recipient: ethers.constants.AddressZero,
    });
    const editionDrop = sdk.getEditionDrop(editionDropAddress);
    const metadata = await editionDrop.metadata.get();
    console.log(
      "✅ Successfully deployed editionDrop contract, address:",
      editionDropAddress
    );
    console.log("✅ editionDrop metadata:", metadata);
  } catch (error) {
    console.log("failed to deploy editionDrop contract", 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

This will create a new edition drop contract for us! You can even check out the transaction on Rinkeby Etherscan.

Let’s 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";

const editionDrop = sdk.getEditionDrop("EDITION_DROP_ADDDRESS");

(async () => {
  try {
    await editionDrop.createBatch([
      {
        name: "LogRocket DAO", // Name of NFT Collection for DAO
        description: "A DAO for all the LogRocket readers.", // Description
        image: "image_address", // Image for NFT
      },
    ]);
    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, let’s 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 create a new claim phase.

claim condition settings

After you are done updating, click Update claim phase and confirm the small transaction.

Checking if the user has an NFT

Before creating a mint button that allows the users to mint NFTs, let’s check if the user has an NFT already. We don’t want the users to mint multiple NFTs!

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

  const editionDrop = useEditionDrop(
    "0x2f66A5A2BCB272FFC9EB873E3482A539BEB6f02a"
  );

You will also need to import useEditionDrop:

import { useAddress, useEditionDrop } from "@thirdweb-dev/react";

Now, let’s 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(() => {
    if (!address) {
      return;
    }
    const checkBalance = async () => {
      try {
        const balance = await editionDrop.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 get nft balance", error);
      }
    };
    checkBalance();
  }, [address, editionDrop]);

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 don’t have an NFT.

You dont have an NFT message

Creating a button to mint NFTs

Let’s 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 user’s wallet. But first, let’s add the isClaiming state:

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

Let’s 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

If you try the Mint your nft (FREE) button, it should pop up your MetaMask screen to complete the transaction. In the console, should should see the following.

successfully minted nft

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 let’s 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 { AddressZero } from "@ethersproject/constants";
import sdk from "./initialize-sdk.js";
(async () => {
  try {
    const tokenAddress = await sdk.deployer.deployToken({
      name: "LogRocket Token", // name of the token
      symbol: "LR", // symbol
      primary_sale_recipient: AddressZero, // 0x0000000000000000000000000000000000000000
    });
    console.log(
      "✅ Successfully deployed token module, address:",
      tokenAddress
    );
  } 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 let’s 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 let’s build a script for that. Create a new airdrop.js file inside scripts and add the following:

import sdk from "./initialize-sdk.js";
const editionDrop = sdk.getEditionDrop(
  "EDITION_ADDRESS"
);
const token = sdk.getToken("TOKEN_ADDRESS");
(async () => {
  try {
    const walletAddresses = await editionDrop.history.getAllClaimerAddresses(0);
    if (walletAddresses.length === 0) {
      console.log(
        "No NFTs have been claimed yet, ask yourfriends to claim some free NFTs!"
      );
      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);
      const airdropTarget = {
        toAddress: address,
        amount: randomAmount,
      };
      return airdropTarget;
    });
    console.log("🌈 Starting airdrop...");
    await token.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 won’t 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";
(async () => {
  try {
    const voteContractAddress = await sdk.deployer.deployVote({
      name: "LR Dao's Proposals",
      voting_token_address: "TOKEN_ADDRESS",
      voting_delay_in_blocks: 0,
      voting_period_in_blocks: 6570,
      voting_quorum_fraction: 0,
      proposal_token_threshold: 0,
    });
    console.log(
      "✅ Successfully deployed vote contract, address:",
      voteContractAddress
    );
  } catch (err) {
    console.error("Failed to deploy vote contract", 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 sdk from "./initialize-sdk.js";
const vote = sdk.getVote("VOTE_ADDRESS");
const token = sdk.getToken("TOKEN_ADDRESS");

(async () => {
  try {
    await token.roles.grant("minter", vote.getAddress());
    console.log(
      "Successfully gave vote contract permissions to act on token contract"
    );
  } catch (error) {
    console.error(
      "failed to grant vote contract permissions on token contract",
      error
    );
    process.exit(1);
  }
  try {
    const ownedTokenBalance = await token.balanceOf(process.env.WALLET_ADDRESS);
    const ownedAmount = ownedTokenBalance.displayValue;
    const percent90 = (Number(ownedAmount) / 100) * 90;
    await token.transfer(vote.getAddress(), percent90);
    console.log(
      "✅ Successfully transferred " + percent90 + " tokens to vote contract"
    );
  } catch (err) {
    console.error("failed to transfer tokens to vote contract", 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, let’s create some proposals!

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

import sdk from "./initialize-sdk.js";
import { ethers } from "ethers";
const vote = sdk.getVote("0x31c5840b31A1F97745bDCbB1E46954b686828E0F");
const token = sdk.getToken("0x6eefd78C9C73505AA71A13FeE31D9718775c9086");
(async () => {
  try {
    const amount = 420_000;
    const description =
      "Should the DAO mint an additional " +
      amount +
      " tokens into the treasury?";
    const executions = [
      {
        toAddress: token.getAddress(),
        nativeTokenValue: 0,
        transactionData: token.encoder.encode("mintTo", [
          vote.getAddress(),
          ethers.utils.parseUnits(amount.toString(), 18),
        ]),
      },
    ];
    await vote.propose(description, executions);
    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 token = useToken("TOKEN_ADDRESS");
const vote = useVote("VOTE_ADDRESS");

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;
    }
    const getAllProposals = async () => {
      try {
        const proposals = await vote.getAll();
        setProposals(proposals);
        console.log("📋 Proposals:", proposals);
      } catch (error) {
        console.log("failed to get proposals", error);
      }
    };
    getAllProposals();
  }, [hasClaimedNFT, vote]);

Then, create a new handleFormSubmit function:

const handleFormSubmit = async (e) => {
    e.preventDefault();
    e.stopPropagation();
    setIsVoting(true);
    const votes = proposals.map((proposal) => {
      const 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 token.getDelegationOf(address);
      if (delegation === AddressZero) {
        await token.delegateTo(address);
      }
      try {
        await Promise.all(
          votes.map(async ({ proposalId, vote: _vote }) => {
            const proposal = await vote.get(proposalId);
            if (proposal.state === 1) {
              return vote.vote(proposalId, _vote);
            }
            return;
          })
        );
        try {
          await Promise.all(
            votes.map(async ({ proposalId }) => {
              const proposal = await vote.get(proposalId);
              if (proposal.state === 4) {
                return vote.execute(proposalId);
              }
            })
          );
          setHasVoted(true);
          console.log("successfully voted");
        } 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");
    } 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, let’s make a function to check if the person has already voted.

First, create a new useEffect:

useEffect(() => {
    if (!hasClaimedNFT) {
      return;
    }
    if (!proposals.length) {
      return;
    }
    const checkIfUserHasVoted = async () => {
      try {
        const hasVoted = await vote.hasVoted(proposals[0].proposalId, address);
        setHasVoted(hasVoted);
      } catch (error) {
        console.error("Failed to check if wallet has voted", error);
      }
    };
    checkIfUserHasVoted();
  }, [hasClaimedNFT, proposals, address, vote]);

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.

Original article source at https://blog.logrocket.com

#dao #next #nextjs #javascript #thirdweb #nft #metamask #alchemy #blockchain 

Eva  Murphy

Eva Murphy

1625751960

Laravel API and React Next JS frontend development - 28

In this video, I wanted to touch upon the functionality of adding Chapters inside a Course. The idea was to not think much and start the development and pick up things as they come.

There are places where I get stuck and trying to find answers to it up doing what every developer does - Google and get help. I hope this will help you understand the flow and also how developers debug while doing development.

App url: https://video-reviews.vercel.app
Github code links below:
Next JS App: https://github.com/amitavroy/video-reviews
Laravel API: https://github.com/amitavdevzone/video-review-api

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #api #react next js #next #frontend #development