Nota do editor: este artigo foi atualizado em 11 de abril de 2022 para se alinhar ao lançamento do thirdweb v2.
O que é uma DAO?
DAO significa Organização Autônoma Descentralizada. Como diz o nome, uma DAO é uma organização sem um líder único; em vez disso, as regras são codificadas na blockchain. Por isso, uma DAO é completamente transparente e todos os membros têm uma participação. Grandes decisões são tomadas por meio de votação entre aqueles que possuem tokens não fungíveis (NFTs) da DAO, que concedem a possibilidade de ser membro.
Hoje, vamos construir nossa própria DAO usando Next.js, thirdweb, MetaMask e Alchemy. Isto permitirá que os usuários cunhem o NFT de sua DAO, recebam criptomoedas por meio de airdrops e participem das pesquisas da DAO. Este tutorial será escrito apenas com JavaScript, então você não precisa conhecer nada de Solidity.
Pré-requisitos
Para entender e acompanhar este tutorial, você deve ter o seguinte:
- Conhecimento prático de JavaScript, Next.js e blockchain
- Uma carteira MetaMask
- Uma conta com Alchemy
Configuração
Começaremos configurando um aplicativo Next.js com o seguinte comando:
npx create-next-app my-dao
Criando Um Aplicativo Com Alchemy
Em seguida, vá para Alchemy, faça login, clique em Criar aplicativo e forneça os detalhes necessários. Certifique-se de usar a mesma cadeia que você usou no thirdweb – no nosso caso, é a cadeia Ethereum e a rede Rinkeby.
Depois que o aplicativo for criado, copie a chave de API HTTP.
Obtendo a chave privada da carteira
Para cunhar NFTs e executar certos scripts, precisaremos da chave privada da carteira.
Para acessá-la, abra a extensão do navegador MetaMask e clique em Detalhes da conta. Você deve ver sua chave privada aqui; exporte-a e copie-a em algum lugar seguro.
Adicionando variáveis .env
Vamos adicionar essas variáveis em um arquivo .env
para podermos acessá-las mais tarde:
PRIVATE_KEY=<wallet_private_key>
ALCHEMY_API_URL=<alchemy_http_key>
WALLET_ADDRESS=<public_wallet_address>
Como não queremos enviá-las para o GitHub, certifique-se de adicioná-las no gitignore
Adicionando a funcionalidade de login usando a MetaMask
Nos DApps, a MetaMask é a carteira mais popular usada, então adicionaremos o login da MetaMask com o thirdweb.
Vamos precisar de dois pacotes para instalar:
npm i @thirdweb-dev/react @thirdweb-dev/sdk ethers # npm
yarn add @thirdweb-dev/react @thirdweb-dev/sdk ethers # yarn
Adicionando o provedor thirdweb
Precisamos envolver todo o nosso aplicativo no provedor thirdweb para acessar os detalhes de login e outras informações necessárias para os componentes:
import "../styles/globals.css";
import {ThirdwebProvider } from "@thirdweb-dev/react";
function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider desiredChainId={activeChainId}>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
export default MyApp;
Para fins de autenticação, também precisamos especificar o tipo de autenticação e os chain IDs suportados. Estamos usando MetaMask e a cadeia Rinkeby, então adicione o seguinte também:
import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
const activeChainId = ChainId.Rinkeby;
Por fim, passe-os como adereços no provedor assim:
<ThirdwebProvider desiredChainId={activeChainId}>
<Component {...pageProps} />
</ThirdwebProvider>
Adicionando o componente de login
Crie uma nova pasta chamada components
na raiz do projeto e adicione um arquivo Login.js
a ela:
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;
Felizmente, o thirdweb fornece uma função connectWallet
que podemos usar para adicionar autenticação!
Renderizando o componente de login
Dentro do index.js
, renderize a tela de login se não houver endereço (se o usuário não estiver logado):
const address = useAddress();
if (!address) {
return ;
}
Isso permitirá que nossos usuários façam login, mas depois ele mostra apenas uma tela em branco. Então, no outro bloco de retorno, vamos mostrar ao usuário seu endereço:
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>
);
}
O login funciona, mas não parece bom agora. Então, crie um novo arquivo Login.module.css
na pasta styles
e adicione o seguinte:
.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;
}
Em seguida, adicione as seguintes classes ao Login.js
:
<div className={styles.container}>
<button className={styles.button} onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
E, finalmente, importe os estilos:
import styles from "../styles/Login.module.css";
Isso nos dará uma tela de login simples, mas de boa aparência.
Inicializando o SDK thirdweb
Agora precisamos inicializar o thirdweb SDK para os vários scripts que vamos executar. Comece criando uma nova pasta chamada scripts
com um arquivo initialize-sdk.js
dentro dela.
Adicione o seguinte código ao arquivo:
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;
Isso inicializará o thirdweb SDK e, como você pode ver, precisamos instalar alguns pacotes:
npm i dotenv # npm
yarn add dotenv # yarn
Estamos usando importações modulares aqui, então crie um novo arquivo package.json
dentro da pasta de scripts
e simplesmente adicione o seguinte:
{
"name": "scripts",
"type": "module"
}
Por fim, execute o script:
node scripts/initialize-sdk.js
O script pode levar algum tempo para ser executado, mas depois de algum tempo você obterá o endereço do seu aplicativo.
Vamos precisar disso nas próximas etapas, então guarde-o em algum lugar seguro.
Adicionando recursos para cunhar uma NFT
Para esta etapa, vamos precisar de algum ETH de teste, então vá para uma torneira (faucet) como esta e pegue um pouco.
Criando e configurando um NFT
Crie um novo arquivo chamado deploy-drop.js
dentro da pasta de scripts
. Aqui, adicione o seguinte script:
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
(async () => {
try {
const editionDropAddress = await sdk.deployer.deployEditionDrop({
name: "LogRocket DAO", // Nome da Coleção NFT para a DAO
description: "A DAO for all the LogRocket readers.", // Description
image: "image_Address", // PFP para a Coleção NFT
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);
}
})();
Você precisará atualizar algumas coisas aqui:
- Atualize o endereço do aplicativo com o novo endereço do aplicativo que você obteve executando o script anterior
- Atualize o nome do lançamento NFT para a DAO e sua descrição
- Adicione uma imagem para o lançamento NFT criando uma nova pasta chamada
assets
e adicionando a imagem para seu NFT lá
Depois de atualizar os detalhes, execute o seguinte script:
node scripts/deploy-drop.js
Aguarde a execução do script e você deverá obter um endereço e os metadados.
Isso criará uma nova edição do contrato de lançamento para nós! Você pode até conferir a transação no Rinkeby Etherscan.
Vamos configurar nosso NFT agora! Crie um novo arquivo config-nft.js
dentro da pasta de scripts
e adicione o seguinte:
import sdk from "./initialize-sdk.js";
const editionDrop = sdk.getEditionDrop("EDITION_DROP_ADDDRESS");
(async () => {
try {
await editionDrop.createBatch([
{
name: "LogRocket DAO", // Nome da Coleção NFT para a DAO
description: "A DAO for all the LogRocket readers.", // Description
image: "image_address", // Imagem para o NFT
},
]);
console.log("✅ Successfully created a new NFT in the drop!");
} catch (error) {
console.error("failed to create the new NFT", error);
}
})();
Você precisa atualizar o endereço do pacote de lançamento e os detalhes no objeto dentro de createBatch
. Esses detalhes serão usados para o NFT!
Depois de atualizar todos eles, execute o seguinte script:
node scripts/config-nft.js
Deve fornecer uma saída como esta.
Se você vir o módulo no painel de controle da thirdweb, verá que um NFT foi criado!🥳
Por fim, vamos adicionar uma condição de reivindicação ao nosso NFT.
Definir uma condição de reivindicação nos permitirá definir um limite para os NFTs e permitir um limite máximo específico por transação. Vamos definir uma condição de reivindicação a partir do próprio painel, então clique no botão Configurações e crie uma nova fase de reivindicação.
Após concluir a atualização, clique em Atualizar fase de reivindicação e confirme a pequena transação.
Verificando se o usuário tem um NFT
Antes de criar um botão mint que permita aos usuários cunhar NFTs, vamos verificar se o usuário já possui um NFT. Não queremos que os usuários criem vários NFTs!
Comece adicionando duas novas variáveis, sdk
e bundleDropModule
, dessa forma antes do nosso componente funcional:
const editionDrop = useEditionDrop(
"0x2f66A5A2BCB272FFC9EB873E3482A539BEB6f02a"
);
Você também precisará importar useEditionDrop
:
import { useAddress, useEditionDrop } from "@thirdweb-dev/react";
Agora, vamos criar um estado para hasClamedNFT
:
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
Também precisamos criar um Hook useEffect
para verificar se o usuário possui o 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]);
Primeiramente, ele verificará se o usuário está conectado. Se o usuário não estiver conectado, não retornará nada. Então, isso verifica se o usuário tem o NFT com o token ID 0
no contrato de lançamento que importamos no topo.
Se você abrir o console no site, deve mostrar que você não possui um NFT.
Criando um botão para cunhar NFTs
Vamos criar o botão para cunhar NFTs! Crie uma nova função chamada mintNft
assim:
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);
}
};
Chamaremos esta função quando um botão for clicado para cunhar o NFT na carteira do usuário. Mas primeiro, vamos adicionar o estado isClaiming
:
const [isClaiming, setIsClaiming] = useState(false);
Vamos criar o botão agora! Dentro do bloco de retorno final, adicione o seguinte:
<div>
<h1>Mint your free LogRocket DAO Membership NFT 💳</h1>
<button disabled={isClaiming} onClick={() => mintNft()}>
{isClaiming ? "Minting..." : "Mint your nft (FREE)"}
</button>
</div>
Agora, depois de entrarmos, ele deve nos mostrar uma tela como esta.
Se você tentar o botão Mint your nft (FREE), ele deve aparecer na tela MetaMask para concluir a transação. No console, você deve ver o seguinte.
Por fim, logo acima do bloco de retorno final, adicione esta verificação para ver se o usuário já reivindicou o NFT:
if (hasClaimedNFT) {
return (
<div>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
Adicionando estilos
Concluímos a construção da funcionalidade de cunhagem NFT, mas parece feia, então vamos adicionar alguns estilos básicos. Dentro de Home.module.css
, adicione o seguinte:
.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;
}
Também precisamos adicionar os 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>
);
};
Isso nos dá uma melhor tela de cunhagem.
Criando e implantando um token de governança
Crie um novo arquivo chamado deploy-token.js
na pasta de scripts
. Adicione o seguinte a ele:
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);
}
})();
Este script criará um novo módulo de token com um nome e símbolo. Você precisará atualizar manualmente o endereço do aplicativo, o nome do token e o símbolo.
Após a atualização, execute o script.
Você pode verificar esse token pelo endereço no Rinkeby Etherscan e também adicioná-lo à sua carteira MetaMask clicando em Importar tokens.
Após a importação, você deverá ver o token em seus ativos.
Atualmente é zero, então vamos cunhar alguns tokens!
Cunhando os tokens
Crie um novo arquivo chamado mint-token.js
dentro da pasta de scripts
e adicione o seguinte:
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);
}
})();
Atualize o endereço do módulo de token com o endereço obtido no último script e você pode atualizar a quantidade que deseja cunhar.
Depois de estar pronto para cunhar, execute o script:
node scripts/mint-token.js
Agora você deve ver a quantidade de tokens que cunhou em sua carteira MetaMask!
Tokens de lançamento aéreo (Airdrop)
Podemos querer enviar os tokens para nossos detentores de NFT, então vamos criar um script para isso. Crie um novo arquivo airdrop.js
dentro dos scripts
e adicione o seguinte:
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);
}
})();
Depois de executar o script, você deve obter algo assim.
Atualmente, apenas você cunhou um NFT, portanto, ele não enviará o token para outra pessoa. Mas isso pode ser usado para enviá-lo para outros detentores de NFT posteriormente.
Permitindo que os usuários votem
Crie um novo arquivo deploy-vote.js
na pasta de scripts
e adicione o seguinte:
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);
}
})();
Atualize o endereço do aplicativo, o nome e o endereço do token de votação e execute o script:
node scripts/deploy-vote.js
Também precisamos configurar um módulo de votação, então crie um novo script chamado setup-vote.js
e adicione o seguinte:
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);
}
})();
Você precisará executar este script para concluí-lo:
node scripts/setup-vote.js
Agora que temos nosso módulo de votação pronto, vamos criar algumas propostas!
Crie um novo arquivo chamado vote-proposals.js
dentro da pasta scripts
e adicione o seguinte:
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);
}
})();
Você precisa atualizar os endereços dos módulos e, se quiser atualizar a mensagem da proposta, também pode atualizar.
Por fim, execute o script. Deve mostrar algo assim.
Se você verificar agora o painel de controle do thirdweb, a proposta foi criada. 🎉
Exibição de propostas no site
Primeiro, importe o token e o módulo de voto:
const token = useToken("TOKEN_ADDRESS");
const vote = useVote("VOTE_ADDRESS");
Vamos precisar de três useStates
, assim:
const [proposals, setProposals] = useState([]);
const [isVoting, setIsVoting] = useState(false);
const [hasVoted, setHasVoted] = useState(false);
Obtendo as propostas
Precisamos obter as propostas para exibi-las na tela, então crie este 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]);
Em seguida, crie uma nova função handleFormSubmit
:
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, 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);
}
};
Esta função vai coletar o voto.
Renderizando as propostas
Substitua o bloco if (hasClamedNFT)
por este:
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 criando um componente separado para a proposta para manter as coisas limpas. Então, crie um novo arquivo chamado Proposal.js
na pasta components
e adicione o seguinte:
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;
Eu também adicionei um estilo básico, então crie um novo arquivo Proposal.module.css
na pasta de estilos:
.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 centralizar os itens, também adicionei os seguintes estilos em Home.module.css
:
.container > form {
display: flex;
flex-direction: column;
align-items: center;
}
Você chegará a esta tela onde poderá enviar seus votos. 🎉
Por fim, vamos fazer uma função para verificar se a pessoa já votou.
Primeiro, crie um novo 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]);
E substitua o botão por este:
<button
onClick={handleFormSubmit}
type="submit"
disabled={isVoting || hasVoted}
className={styles.button}
>
{isVoting ? "Voting..." : hasVoted ? "You Already Voted" : "Submit Votes"}
</button>
Depois de votar, ele deve mostrar a mensagem You Already Voted:
Conclusão
Isto foi tudo para este tutorial, espero que tenham gostado e possam usá-lo para fazer sua própria DAO! Você sempre pode atualizar a DAO e adicionar mais recursos, se desejar.✌️
Você pode encontrar o repositório GitHub para este projeto aqui.
Esse artigo é uma tradução de Avneesh Agarwal feita por @bananlabs. Você pode encontrar o artigo original aqui
Top comments (0)