Noções básicas de desenvolvimento de um aplicativo descentralizado (DApp) no Celo.
Este exemplo será desenvolvido usando um dos principais contratos do Celo, Governance.sol, e permitindo que os usuários do nosso DApp votem em propostas ativas do Celo Governance.
Prefácio
Este guia requer uma compreensão de algumas tecnologias populares da web. Nosso aplicativo será escrito em React, utilizando hooks para gerenciamento de estado e construído com Next.js, uma estrutura popular de geração de sites estáticos.
Se você achar que este tutorial de alguma forma não é suficiente ou deseja mergulhar no código mais profundamente, confira o repositório Celo Tools no GitHub, de onde grande parte deste tutorial foi portado.
Começando
A primeira etapa do desenvolvimento de nosso aplicativo é desenvolvê-lo com o create-next-app
e adicionar a compilação TypeScript para que possamos desenvolver com mais confiança.
yarn create next-app voting-dapp
cd voting-dapp
touch tsconfig.json
yarn add --dev typescript @types/react @types/node
Agora, executando o comando yarn dev
, deve-se abrir o nosso novo site Next.js no localhost:3000
.
Em seguida, precisaremos adicionar algumas dependências específicas do Celo para que possamos trabalhar com nossos contratos principais.
yarn add @celo/contractkit @celo-tools/use-contractkit bignumber.js
Aqui está o que usaremos para cada um desses pacotes:
@celo/contractkit é um wrapper (invólucro) leve em torno do objeto Web3 com o qual você já deve estar familiarizado. Ele contém interfaces digitadas para os contratos principais (gerados a partir das Contract ABIs) e funções auxiliares para facilitar as operações comuns no Celo.
@celo-tools/use-contractkit é uma biblioteca fornecida pela comunidade para facilitar o estabelecimento da conexão com a carteira de um usuário, seja ela de hardware, móvel ou web. Ao desenvolver com esta biblioteca, seus usuários podem manter o Celo via Valora, um Ledger, Metamask e outros.
bignumber.js é uma biblioteca para expressar grandes números em JavaScript. Ao interagir com uma blockchain, muitas das vezes precisamos lidar com decimais de precisão arbitrária e aritmética não decimal.
Também precisaremos adicionar algumas configurações do Next.js para trabalhar com esses pacotes. Atualize next.config.js
com o seguinte trecho de código:
module.exports = {
webpack: (config) => {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
child_process: false,
readline: false,
};
return config;
},
};
Arquivo: next.config.js
Precisamos reiniciar o servidor para que as alterações de configuração entrem em vigor.
Desenvolvendo o aplicativo
Depois que todo o nosso padrão estiver configurado, estamos prontos para começar a desenvolver nosso aplicativo.
Conexão com a carteira do usuário
Quando um usuário deseja interagir com seu DApp, precisamos permitir que ele conecte sua carteira de alguma forma. A interação com contratos inteligentes na cadeia é impossível sem esta etapa.
Aproveitando nossa biblioteca @celo-tools/use-contractkit adicionada anteriormente, podemos fornecer um botão que solicita ao usuário que conecte sua carteira.
Atualize o arquivo pages/index.js
com o seguinte código:
import React from "react";
import { useContractKit } from "@celo-tools/use-contractkit";
import { ContractKitProvider } from "@celo-tools/use-contractkit";
import "@celo-tools/use-contractkit/lib/styles.css";
function App() {
const { address, connect } = useContractKit();
return (
<main>
<h1>Celo Voting DApp</h1>
<p>{address}</p>
<button onClick={connect}>Click here to connect your wallet</button>
</main>
);
}
function WrappedApp() {
return (
<ContractKitProvider
dapp={{
name: "My awesome dApp",
description: "My awesome description",
url: "https://example.com",
}}
>
<App />
</ContractKitProvider>
);
}
export default WrappedApp;
Ao clicar neste botão, será exibido o modal use-contractkit
e permitirá que o usuário se conecte com a carteira de sua escolha. Depois que o modal for descartado, a propriedade address
exposta pelo use-contractkit
será preenchida com a conta principal do usuário.
Acessando contratos
INFORMAÇÃO
Na blockchain Celo, apenas as propostas enfileiradas e desenfileiradas são mantidas no estado de Governança. Isso significa que, para acessar propostas antigas, precisaríamos acessar um histórico indexado da blockchain. Isso está fora do escopo do nosso tutorial, no entanto, há muitos recursos on-line que você pode encontrar que o ajudarão a acessar o estado da blockchain indexada.
Para uma visão abrangente de como interpretar isso no estado da cadeia, dê uma olhada na implementação do comando celocli governance:list.
Para os fins deste tutorial, examinaremos apenas as propostas retiradas da fila ou as propostas nas quais podemos votar no momento.
Veja como fica usando uma combinação dos hooks useEffect
e useCallback
para solicitar e exibir todas as propostas desenfileiradas da blockchain.
import React, { useCallback, useEffect, useState } from "react";
import { useContractKit } from "@celo-tools/use-contractkit";
function GovernanceApp() {
const { address, connect, kit, getConnectedKit } = useContractKit();
const [proposals, setProposals] = useState([]);
const fetchProposals = useCallback(async () => {
const governance = await kit.contracts.getGovernance();
const dequeue = await governance.getDequeue();
const fetchedProposals = await Promise.all(
dequeue.map(async (id) => ({
id,
...(await governance.getProposalRecord(id)),
}))
);
setProposals(fetchedProposals);
}, [kit]);
useEffect(() => {
fetchProposals();
}, [fetchProposals]);
return (
<div>
<h1>Celo Voting DApp</h1>
<p>{address}</p>
<button onClick={connect}>Click here to connect your wallet</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Description URL</th>
</tr>
</thead>
<tbody>
{proposals.map((proposal) => (
<tr>
<td>{proposal.id.toString()}</td>
<td>
{proposal.passed
? "Passed"
: proposal.approved
? "Approved"
: "Not approved"}
</td>
<td>
<a
href={proposal.metadata.descriptionURL}
target="_blank"
style={{ color: "blue", textDecoration: "underline" }}
>
Link
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Arquivo: pages/index.js
Certifique-se de adicionar esse novo componente GovernanceApp
ao seu componente WrappedApp
.
function WrappedApp() {
return (
<ContractKitProvider
dapp={{
name: "My awesome dApp",
description: "My awesome description",
url: "https://example.com",
}}
>
<GovernanceApp />
</ContractKitProvider>
);
}
Arquivo: pages/index.js
Isso funciona muito bem, mas faz sentido mostrar adicionalmente se o usuário votou em qualquer proposta de governança desenfileirada. Para mostrar essas informações, podemos alterar nossa função fetchProposals
da seguinte maneira:
const fetchProposals = useCallback(async () => {
if (!address) {
return;
}
const governance = await kit.contracts.getGovernance();
const dequeue = await governance.getDequeue();
const fetchedProposals = await Promise.all(
dequeue.map(async (id) => {
const [record, voteRecord] = await Promise.all([
governance.getProposalRecord(id),
governance.getVoteRecord(address, id),
]);
return {
id,
...record,
vote: voteRecord ? voteRecord.value : undefined,
};
})
);
setProposals(fetchedProposals);
}, [kit, address]);
Arquivo: pages/index.js
Agora que temos acesso a saber se o usuário votou nessa proposta, podemos renderizar essa informação em nossa tabela.
return (
<table>
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Description URL</th>
<th>Voted</th>
</tr>
</thead>
<tbody>
{proposals.map((proposal) => (
<tr>
<td>{proposal.id.toString()}</td>
<td>
{proposal.passed
? "Passed"
: proposal.approved
? "Approved"
: "Not approved"}
</td>
<td>
<a
style={{ color: "blue", textDecoration: "underline" }}
href={proposal.metadata.descriptionURL}
target="_blank"
>
Link
</a>
</td>
<td>{proposal.vote ?? "No vote yet"}</td>
</tr>
))}
</tbody>
</table>
);
Arquivo: pages/index.js
Bloqueando a Celo (opcional)
Um pré-requisito para votar nas propostas de governança da Celo é ter bloqueado a Celo para votar. Não abordaremos as várias maneiras para bloquear, desbloquear e bloquear novamente a Celo neste tutorial, mas você pode verificar a implementação na Celo Tools ou inspirar-se no seguinte script:
const lockValue = new BigNumber(res.flags.value);
const lockedGold = await this.kit.contracts.getLockedGold();
const pendingWithdrawalsValue =
await lockedGold.getPendingWithdrawalsTotalValue(address);
const relockValue = BigNumber.minimum(pendingWithdrawalsValue, value);
const lockValue = value.minus(relockValue);
const txos = await lockedGold.relock(address, relockValue);
for (const txo of txos) {
await kit.sendAndWaitForReceipt({ from: address });
}
Tudo o que você precisa cuidar em seu aplicativo React é manipular a entrada do usuário para selecionar o valor a ser bloqueado e lidar com erros caso o usuário tente bloquear mais CELOs do que possui.
Também é possível que os usuários do seu DApp já tenham bloqueado a CELO, então você não precisa se preocupar com a complexidade de permitir essa operação.
Votando em uma proposta
Para realmente votar em uma proposta, precisamos interagir novamente com o contrato inteligente Governance.sol. Nossa lógica para lidar com uma votação é a seguinte:
const vote = useCallback(
async (id: string, value: VoteValue) => {
const kit = await getConnectedKit();
const governance = await kit.contracts.getGovernance();
await (await governance.vote(id, value)).sendAndWaitForReceipt();
fetchProposals();
},
[kit, fetchProposals]
);
Arquivo: pages/index.js
Como você irá lidar com a chamada dessa função depende de você. Com o Celo Tools optamos por setas simples voltadas para cima e para baixo para lidar com a votação das propostas, porém os dados podem ser renderizados como você preferir.
Aqui está um exemplo simples mostrando botões para votos Yes
(sim) ou No
(não) quando nenhum voto foi lançado.
import { VoteValue } from "@celo/contractkit/lib/wrappers/Governance";
return (
<tr>
<td>{proposal.id.toString()}</td>
<td>
{proposal.passed
? "Passed"
: proposal.approved
? "Approved"
: "Not approved"}
</td>
<td>
<a href={proposal.descriptionURL} target="_blank">
Description link
</a>
</td>
<td>
{proposal.vote ? (
<span>{proposal.vote}</span>
) : (
<div>
<button onClick={() => vote(proposal.id, VoteValue.Yes)}>Yes</button>
<button onClick={() => vote(proposal.id, VoteValue.No)}>No</button>
</div>
)}
</td>
</tr>
);
Arquivo: pages/index.js
Melhores práticas
Compilamos uma pequena lista de práticas recomendadas a serem seguidas ao desenvolver DApps. Segui-las melhorará a experiência do usuário final e os manterá mais engajados com o ecossistema Celo. Se você tiver alguma dúvida sobre isso, sinta-se à vontade para entrar em contato no Discord, estamos sempre lá e felizes em conversar.
Último endereço usado
A @celo-tools/use-contractkit lembrará o endereço com o qual o usuário se conectou pela última vez (através do navegador LocalStorage). Use isso a seu favor e permita que seu DApp exiba os mesmos dados, independentemente de o usuário ter conectado ou não sua carteira. Um bom teste é atualizar seu DApp após a conexão e ver se algo muda. No máximo, os botões de interação podem ser desativados, porém, é preferível avisar para conectar a carteira ao clicar no botão.
Manter a interface do usuário consistente usando o último endereço conectado é uma vitória rápida que podemos ter com os DApps que tornam a experiência de usá-los mais próxima da Web2, uma experiência com a qual mais usuários irão estar familiarizados.
Carregando estados
Os tempos de carregamento geralmente indicam que um aplicativo é um Web3 DApp. Seja liberal com as telas de carregamento e priorize a fluidez das animações.
Nada é pior do que uma tela permanentemente suspensa que leva vários segundos para se tornar interativa. Ao mostrar um spinner, ele comunica ao usuário que as coisas estão acontecendo, por mais lentas que sejam.
Isso geralmente é compensado pela capacidade de indexar uma blockchain e fornecer os dados em um formato mais acessível (talvez um banco de dados SQL ou por trás de uma API GraphQL). Como mencionado anteriormente, não abordamos isso neste tutorial, no entanto, há muito conteúdo na web sobre a otimização de DApp por meio da indexação de estado anterior.
Pré-renderize o que puder
Com geradores de sites estáticos modernos, temos uma vantagem incrível sobre o que é computado no lado do servidor e o que o navegador precisa solicitar e calcular antes de renderizar. Se você não conseguir indexar a blockchain antes que um cliente solicite acesso a uma página, considere carregar o lado do servidor de dados relevante com um cache invalidado a cada hora ou mais.
O Next.js getStaticProps vem à mente aqui como uma ótima maneira de descarregar computação pesada para o servidor.
Mostrando números em wei x celo x moeda local
Aceite este conselho com cautela, pois realmente depende de quão familiarizados com criptomoedas e blockchains seus usuários estão. Em algum momento, a maioria dos usuários do DApp precisará lidar com grandes números. Depende de você exibi-los em wei (1e18) CELO ou convertidos para uma moeda de preferência do usuário (BTC, USD ou EUR, por exemplo).
A generalização abrangente seria permitir a entrada de valores em CELO ou em sua moeda preferida e nunca expor os valores brutos do wei aos usuários finais.
Resumindo
Espero que você tenha uma melhor compreensão do desenvolvimento de DApps contra os contratos principais da Celo agora. Neste tutorial, abordamos:
Conectando-se a carteiras de usuários (use-contractkit);
Buscando dados na cadeia;
Chamando funções simples nos contratos principais;
Uma breve palavra sobre as melhores práticas em relação ao desenvolvimento de DApp.
Este não é um tutorial abrangente sobre os recursos e capacidades da Celo, continue explorando os documentos para saber mais e conecte-se conosco no Discord se precisar de ajuda (ou apenas quiser bater um papo)!
Este artigo é uma tradução de Josh Crites feita por Felipe Gueller. Você pode encontrar o artigo original aqui.
Oldest comments (0)