WEB3DEV

Cover image for DApp baseado em React no Celo
Felipe Gueller
Felipe Gueller

Posted on

DApp baseado em React no Celo

Noções básicas de desenvolvimento de um aplicativo descentralizado (DApp) no Celo.

Imagem de introdução para o tema

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
  },
};
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode
Arquivo: pages/index.js

Certifique-se de adicionar esse novo componente GovernanceAppao seu componente WrappedApp.

function WrappedApp() {
  return (
    <ContractKitProvider
      dapp={{
        name: "My awesome dApp",
        description: "My awesome description",
        url: "https://example.com",
      }}
    >
      <GovernanceApp />
    </ContractKitProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode
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]);
Enter fullscreen mode Exit fullscreen mode
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>
);
Enter fullscreen mode Exit fullscreen mode
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 });
}
Enter fullscreen mode Exit fullscreen mode

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]
);
Enter fullscreen mode Exit fullscreen mode
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>
);
Enter fullscreen mode Exit fullscreen mode
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.

Top comments (0)