WEB3DEV

Cover image for Criando um sistema de votação na Ethereum usando Web3r
Bernardo Perelló
Bernardo Perelló

Posted on

Criando um sistema de votação na Ethereum usando Web3r

Este é um artigo traduzido por Bernardo Perelló. O original de Gábor Boros pode ser visto aqui.

Gábor Boros

Isenção de responsabilidade: Este tutorial é para programadores experientes com conhecimento básico sobre blockchains e fundamentos da web.

Antes de mais nada, este post não é sobre sistemas de votação política. Este post é sobre a implementação de um sistema de votação simples baseado em tecnologias blockchain. Como demonstração, vamos votar em filmes. Parece simples, certo?

Pré-requisitos

Para implementar nosso sistema de votação, você precisará do software Node.js instalado e uma conta no Ankr. Suponho que você já saiba o que é Node.js, mas o que é Ankr? Simplificando, o Ankr é uma plataforma para desenvolvedores e investidores de todas as escalas. Com o Ankr, podemos implantar sem esforço um servidor de API se comunicando com nossa blockchain favorita. Melhor ainda, não precisamos cuidar de sua manutenção – o que poderia custar uma fortuna.

Fora isso, você precisará de uma carteira de criptomoedas se ainda não tiver uma. Neste tutorial usaremos a MetaMask, uma das carteiras de criptomoedas mais utilizadas, para interagir com nosso dApp. Você pode instalar a MetaMask no Firefox, Chrome, Brave, iOS, Android e muitos outros sistemas operacionais e navegadores. Já existe muito material disponível sobre como fazer isso, portanto, não vou guiá-lo pelas etapas básicas de instalação da MetaMask agora, mas mostrarei como adicionar contas falsas para teste mais tarde.

Antes de prosseguir, verifique se você instalou a versão LTS mais recente do Node.js, criou uma conta no Ankr e instalou o MetaMask em seu navegador.

Um pouco sobre Web3

Se você for familiarizado com blockchain e tópicos relacionados, provavelmente já encontrou o termo "Web3". É um tema muito em alta hoje em dia. Existem podcasts, webinários e inúmeros artigos dedicados a ensinar as pessoas sobre a Web3. Para aqueles que encontram este termo pela primeira vez, deixe-me citar a Wikipedia:
"Web3 (também conhecido como Web 3.0 e às vezes chamado como web3) é uma ideia para uma nova iteração da World Wide Web baseada na tecnologia blockchain, que incorpora conceitos como descentralização e economia baseada em tokens." -

Há debates em andamento sobre se a Web3 já chegou ou não, embora evitemos essa arapuca por enquanto.

Neste tutorial, quando dizemos "Web3", queremos dizer "web3.js", que é um pacote JavaScript projetado para funcionar melhor com a blockchain Ethereum. Ele tem outras implementações disponíveis para Ruby, Python, Java, etc., mas não as usaremos neste tutorial.

Crie um novo projeto Truffle

Até agora, falamos sobre Web3 e como o Ankr está relacionado ao nosso tutorial, mas precisamos de algo para juntar todas as peças. É aqui que entra a Truffle Suite. Truffle Suite tem três elementos principais: Truffle, Ganache e Drizzle.

Truffle e Ganache são usados juntos para desenvolver aplicativos distribuídos e contratos inteligentes localmente. Esse ambiente de desenvolvimento nos permite testar o aplicativo sem implantá-lo em uma testnet ou mainnet em uma rede blockchain ativa. O Truffle fornece o ecossistema, enquanto o Ganache é uma blockchain pessoal na memória.

Drizzle, por outro lado, é um modelo de projeto pré-criado baseado em React. Em uma situação do mundo real, você usaria React ou Vue.js, embora estejamos usando uma codificação de JavaScript bem leve para simplificar tudo ao longo deste tutorial.

Instale Truffle e Ganache

Antes de começarmos a usar Truffle e Ganache, precisamos instalá-los. Para instalar o Truffle e suas dependências, rode o comando run npm install -g truffle. Para instalar o Ganache, usaremos seu instalador autônomo, que você pode baixar no site da Truffle. Ao contrário do Truffle, o Ganache vem com uma interface de usuário, oferecendo várias opções para personalizar o blockchain na memória. Para validar que o Truffle está instalado corretamente, execute a truffle version em uma nova sessão de terminal. Você deve ver uma saída semelhante a esta.

$ truffle version

Truffle v5.4.31 (core: 5.4.31)
Solidity v0.5.16 (solc-js)
Node v16.13.0
Web3.js v1.5.3
Enter fullscreen mode Exit fullscreen mode

Criar um novo projeto

Agora estamos prontos para criar nosso projeto Truffle. Como discutimos, não abriremos nenhuma caixa, mas usaremos um layout de projeto simples. Implante os seguintes comandos para iniciar um novo projeto Truffle:

$ mkdir movie-vote
$ cd movie-vote
$ truffle init
Enter fullscreen mode Exit fullscreen mode

Depois de executar os comandos acima, você terá iniciado um novo projeto Truffle e criado alguns arquivos e diretórios, vistos aqui:

├── contracts
   └── Migrations.sol
├── migrations
   └── 1_initial_migration.js
├── test
└── truffle-config.js
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, temos uma configuração padrão, contrato e uma migração relacionada. O que são migrações? Pode parecer familiar se você já trabalhou com Django ou Ruby on Rails. No Truffle, as migrações são usadas para implantar contratos inteligentes em uma blockchain.

Antes de prosseguir, precisamos ajustar um pouco a configuração padrão. Inicie o Ganache para coletar as informações necessárias. Na tela de boas-vindas, selecione "Quickstart" para começar.

Ganache

Depois de clicar em “Quickstart”, você verá a configuração padrão do Ganache. Ele mostra algumas contas com Ether em suas carteiras, vistas aqui:

Conta Ganache

É bom ter em mente que, por padrão, o Ganache usa a primeira conta quando executamos as migrações. Anote o ID da rede e o endereço do servidor RPC.

Em seguida, abra truffle-config.js e encontre a chave networks. Certifique-se de que a configuração de rede seja semelhante a esta e salve o arquivo:

networks: {
 development: {
   host: "127.0.0.1",     // Host do servidor Ganache RPC
   port: 7545,            // Porta do servidor Ganache RPC
   network_id: 5777,      // ID da rede Ganache
 },
},
Enter fullscreen mode Exit fullscreen mode

Agora, a única coisa que resta a fazer para esta etapa é compilar o contrato inteligente e executar as migrações que temos. Execute Truffle compile que criará um novo diretório, chamado build. Este diretório contém todos os arquivos relacionados à compilação. Ao verificar o conteúdo desse diretório, você pode encontrar a versão JSON do contrato "Migrations". truffle migrate usará este contrato compilado para implantar na blockchain.

Agora, execute truffle migrate para implantar nosso contrato inteligente inicial no Ganache. A execução do comando resultará em uma saída relativamente longa contendo o resumo da implantação. Se olharmos mais de perto para Ganache, veremos que o saldo da primeira conta foi reduzido em 0,01 ETH. Embora a leitura não custe nada na blockchain, a escrita sempre tem algumas taxas de gas associadas. Essa é uma das muitas razões pelas quais estamos usando o Ganache para desenvolvimento.

Image description

Até agora tudo bem! Estamos prontos para escrever nosso Contrato Inteligente que lidará com a votação.

Escreva o Contrato Inteligente de Votação

Quando executamos a migração, aprendemos que gravar na blockchain tem alguns custos. Com isso em mente, a implantação de um contrato inteligente cheio de bugs pode resultar em gastos que não foram planejados - não apenas o tempo e a energia necessários para corrigir os bugs, mas também as taxas reais. Para reduzir o risco de bugs, gostaríamos de escrever vários testes para o contrato inteligente, embora este tutorial não aborde esse tópico. O teste em si poderia preencher vários tutoriais independentes.

Então vamos direto ao assunto e criar o contrato de votação. A ferramenta Truffle CLI está nos ajudando com alguns comandos utilitários para facilitar o desenvolvimento. Para criar o contrato "Voting", execute truffle create contract Voting dentro do diretório do projeto inicializado.

O comando truffle create criou um novo arquivo no diretório contracts, chamado "Voting.sol":

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Voting {
 constructor() public {
 }
}
Enter fullscreen mode Exit fullscreen mode

Dependendo da sua versão do Truffle, o conteúdo pode ser um pouco diferente.

Neste contrato inteligente, queremos permitir que cada usuário coloque três votos. Esses votos não precisam ser únicos; os usuários podem votar três vezes no mesmo filme se quiserem. Para limitar o número máximo de votos, introduza uma nova constante dentro do bloco de contrato, uint public constant MAX_VOTES_PER_VOTER = 3;. Mais tarde, usaremos este contrato para verificar se o usuário atingiu o máximo de votos disponíveis ou não.

Ainda dentro do bloco contract, agora é hora de definir a estrutura dos filmes. Do ponto de vista da votação, não nos importamos quando o filme foi lançado ou quem o dirigiu. Precisamos apenas de um ID, um título e o número de votos colocados no filme. Para permitir que os usuários identifiquem o filme mais facilmente, usaremos uma imagem de capa também - alguns de nós são mais orientados visualmente:

// [...]

contract Voting {
 uint public constant MAX_VOTES_PER_VOTER = 3;

 struct Movie {
   uint id;
   string title;
   string cover;
   uint votes;
 }
}
Enter fullscreen mode Exit fullscreen mode

Como já sabemos o que queremos fazer, vamos pensar um pouco na experiência do usuário. Não seria ótimo se pudéssemos notificar o usuário quando um novo filme fosse adicionado à blockchain? Claro que sim! Felizmente, o Solidity nos dá blocos de construção, chamados de “eventos”. Os contratos inteligentes podem emitir esses eventos e os dApps podem ouvi-los. Definimos dois eventos: Voted e NewMovie.

// [...]

 struct Movie {
   uint id;
   string title;
   string cover;
   uint votes;
 }

 event Voted ();
 event NewMovie ();

// [...]
Enter fullscreen mode Exit fullscreen mode

Embora os eventos possam receber argumentos, não definiremos nenhum. Em nosso caso de uso, não precisamos diferenciar os votos, nem queremos saber quem adicionou qual filme. Depois de definir os eventos, vá em frente e defina um getter para filmes e votos.

// [...]

 event NewMovie ();
 event Voted ();

 mapping(uint => Movie) public movies;
 uint public moviesCount;

 mapping(address => uint) public votes;

 constructor() {
   moviesCount = 0;
 }

// [...]
Enter fullscreen mode Exit fullscreen mode

Os mapeamentos são estruturas de dados de valor-chave. Para filmes, associamos um número a cada filme, onde o número representa o ID do filme fornecido. No entanto, no caso de votos, vinculamos um endereço de carteira a um número. Este número representa o número de votos oriundos de uma única carteira.

Você pode se perguntar o que é moviesCount então. Esse contador registra quantos filmes foram adicionados. Também é usado para saber qual é o próximo ID de filme. Portanto, começamos com o moviesCount em zero.

Em seguida, devemos definir a função de votação, a função central deste contrato inteligente. Defina a função de votação implantando os seguintes comandos:

// [...]

 function vote(uint _movieID) public {
   require(votes[msg.sender] < MAX_VOTES_PER_VOTER, "O votante não tem mais votos.");
   require(_movieID > 0 && _movieID <= moviesCount, "A ID do filme está fora do intervalo.");

   votes[msg.sender]++;
   movies[_movieID].votes++;

   emit Voted();
 }

// [...]
Enter fullscreen mode Exit fullscreen mode

As responsabilidades desta função são verificar se o usuário pode votar em um filme existente, acrescentar os contadores de votação e emitir o evento Voted. Embora esta função seja simples, gostaria de mencionar uma parte especial: msg.sender. Contratos inteligentes têm algumas variáveis globais integradas e msg é uma delas. Essa variável permite o acesso à mensagem recebida pelo contrato inteligente. msg.sender representa o endereço que é chamado pelo contrato.

Agora, a única coisa que resta a fazer é implementar uma maneira de adicionar novos filmes. Implante os seguintes comandos para definir a função addMovie:

// [...]

 function addMovie(string memory _title, string memory _cover) public {
   moviesCount++;

   Movie memory movie = Movie(moviesCount, _title, _cover, 0);
   movies[moviesCount] = movie;

   emit NewMovie();
   vote(moviesCount);
 }
}  // fechando o bloco de contrato
Enter fullscreen mode Exit fullscreen mode

Embora originalmente tenhamos usado moviesCount na função vote, aqui você pode ver seu valor real. A chave de mapeamento movies representa o ID do filme e rastreamos o ID mais recente globalmente usando o moviesCount.

É isso aí, o contrato inteligente está feito! Mas agora precisamos interagir com ele de alguma forma.

Escreva um dApp usando Web3

Agora que temos o contrato implantado, podemos começar a trabalhar em um dApp para chamá-lo.

Prepare o ambiente

Como mencionado anteriormente, não usaremos React ou Vue.js. Para executar o dApp localmente, usaremos o lite-server, um servidor de desenvolvimento leve para Node.js. Para gerenciar dependências com mais facilidade, crie um arquivo package.json com o conteúdo a seguir e execute yarn install, conforme visto aqui:

{
 "name": "movie-vote",
 "version": "1.0.0",
 "description": "Um tutorial para sistemas de votação simples.",
 "license": "MIT",
 "main": "truffle-config.js",
 "scripts": {
   "dev": "lite-server",
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "devDependencies": {
   "@truffle/hdwallet-provider": "^2.0.3",
   "lite-server": "^2.6.1"
 }
}
Enter fullscreen mode Exit fullscreen mode

O Yarn instalará o lite-server que usa o BrowserSync. Portanto, precisamos configurar o BrowserSync para incluir o contrato que criamos. Estamos fazendo isso criando um arquivo bs-config.json com o seguinte conteúdo:

{
 "server": {
   "baseDir": ["./src", "./build/contracts"]
 }
}
Enter fullscreen mode Exit fullscreen mode

No momento, devemos ter uma estrutura de pastas como esta:

├── bs-config.json
├── contracts
   ├── Migrations.sol
   └── Voting.sol
├── migrations
   ├── 1643907400_voting.js
   └── 1_initial_migration.js
├── package.json
├── truffle-config.js
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

Crie o website

Crie um novo diretório na raiz do projeto, chamado src. Dentro do novo diretório, crie dois arquivos: index.html e app.js. Começando com index.html, monte um arquivo como mostrado abaixo:

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1">

 <title>Votação em Filmes</title>

 <!-- Tailwind CSS -->
 <script src="https://cdn.tailwindcss.com"></script>
</head>

<body class="bg-gray-50">
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Embora não seja necessário, você pode ver acima que adicionamos o Tailwind CSS. Afinal, queremos construir um belo dApp, certo?

Em seguida, é hora de preencher a tag body. Como este tutorial não é sobre como escrever HTML, cole os seguintes comandos:

<!-- ... -->

 <div x-cloak class="w-3/5 mx-auto mt-20 mb-10">
   <div class="mb-12">
     <h1 class="text-4xl mb-6">Vamos votar em filmes juntos!</h1>
     <div>
       <p>
         Vote em qualquer filme da lista. Se você não conseguir encontrar seu filme favorito, pode adicioná-lo facilmente abaixo.
         <br />
         Esteja ciente de que adicionar um novo filme custa um voto! Todo mundo tem três votos.
       </p>
     </div>
   </div>

   <div class="flex flex-col mb-10">
     <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
       <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
         <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
           <table class="min-w-full divide-y divide-gray-200">
             <thead class="bg-gray-50">
               <tr>
                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Capa</th>
                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Título</th>
                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"># Votos</th>
                 <th scope="col" class="relative px-6 py-3"></th>
               </tr>
             </thead>
             <tbody id="movies" class="bg-white divide-y divide-gray-200" />
           </table>
         </div>
       </div>
     </div>
   </div>

   <form id="form-new-movie">
     <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
       <div class="px-4 py-5 bg-white space-y-6 sm:p-6">

         <div class="mb-12">
           <h1 class="text-2xl mb-3">Proponha um novo filme</h1>
           <p>Não se esqueça, propor um novo filme custará um voto!</p>
         </div>

         <div class="grid grid-cols-6 gap-6">
           <div class="col-span-6 sm:col-span-3">
             <label for="title" class="block text-sm font-medium">Titulo do Filme</label>
             <input required type="text" name="title" id="title" class="mt-2 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 border p-2 rounded-md">
           </div>

           <div class="col-span-6 sm:col-span-3">
             <label for="coverUrl" class="block text-sm font-medium">URL da Capa</label>
             <input required type="text" name="coverUrl" id="coverUrl" class="mt-2 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 border p-2 rounded-md">
           </div>
         </div>

       </div>
       <div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
         <button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
           Adicione o filme
         </button>
       </div>
     </div>
   </form>
 </div>

<!-- ... 
Enter fullscreen mode Exit fullscreen mode

Por fim, importe alguns arquivos JavaScript antes de fechar a tag body:

 <!-- ... -->

   <!-- Web3.js -->
   <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>

   <!-- JavaScript relacionado ao Truffle e à DApp -->
   <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/truffle-contract.min.js"></script>
   <script src="app.js"></script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Pare aqui e dê uma olhada no trecho mais recente: as últimas três importações precisam de alguma explicação. Importamos web3.js para interagir com a blockchain local e importamos app.js para utilizá-lo, mas também importamos um terceiro script. O singular truffle-contrato.js é responsável pela abstração do contrato Ethereum.

Se você seguiu pacientemente, deverá ver o seguinte após executar o yarn dev.

teste voto

Adicione alguma interação

Se clicarmos no botão "adicionar filme", ​​nada acontece agora. É hora de adicionar alguma interação e mudar isso. Abra o arquivo app.js e adicione alguns auxiliares:

const addRow = (id, title, cover, votes, canVote) => {
 const element = document.createElement('tr');
 element.innerHTML = `
 <tr>
   <td class="px-6 py-4 whitespace-nowrap">
     <div class="flex items-center">
       <div class="flex-shrink-0">
         <img class="h-24" src="${cover}" alt="Cover of ${title}">
         </div>
     </div>
   </td>
   <td class="px-6 py-4">${title}</td>
   <td class="px-6 py-4">${votes}</td>
   <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
     ${
       canVote
       ? `<a data-id="${id}" href="#" class="btn-vote text-indigo-600 hover:text-indigo-900">Vote!</a>`
       : 'sem votos restantes'
     }
   </td>
 </tr>
 `;

 document.getElementById("movies").appendChild(element);
}
Enter fullscreen mode Exit fullscreen mode

A função addRow criará uma nova linha na tabela vazia que preenchemos com os detalhes do filme. Depois disso, certifique-se de criar o App, o qual iremos preencher juntos:

App = {
 account: null,
 web3Provider: null,
 contracts: {},

 init: async function () {
 },

 initContract: async function () {
 },

 bindEvents: async function () {
 },

 listenForEvents: async function () {
 },

 render: async function () {
 },

 handleVote: function (event) {
 },

 handleAddMovie: function (event) {
 }
};

window.addEventListener('load', function (event) {
 App.init();
});
Enter fullscreen mode Exit fullscreen mode

Como você vê, quando a janela for carregada, inicializaremos o App. Na função de inicialização, inicializamos o web3Provider e a conta que usaremos para transações. Além disso, voltamos ao Ganache se não pudermos inicializar o provedor de outra maneira:

// [...]

 init: async function() {
   if (window.ethereum) {
     // Navegadores dapp modernos
     App.web3Provider = window.ethereum;

     try {
       // Solicitar acesso à conta
       await window.ethereum.request({ method: 'eth_requestAccounts'});
     } catch (error) {
       console.error('User denied account access');
     }
   } else if (window.web3) {
     // Fique atento ao web3.js injetado
     App.web3Provider = window.web3.currentProvider;
   } else {
     // Se nenhuma instância web3 injetada for detectada, volte para o Ganache
     App.web3Provider = new Web3.providers.HttpProvider(ganacheURL);
   }

   web3 = new Web3(App.web3Provider);

   let accounts = await web3.eth.getAccounts();
   App.account = accounts[0];

   await App.initContract();
 },

 // [...]
Enter fullscreen mode Exit fullscreen mode

Na última linha, chamamos initContract. Esta função é responsável por inicializar o contrato usando o script de contrato-truffle mencionado acima.

 // [...]

 initContract: async function() {
   const response = await fetch('Voting.json');
   const data = await response.json();

   App.contracts.Voting = TruffleContract(data);
   App.contracts.Voting.setProvider(App.web3Provider);

   await App.render();
   await App.listenOnEvents();
 },

 // [...]
Enter fullscreen mode Exit fullscreen mode

Após a inicialização, o aplicativo inicia o procedimento de renderização e conecta os listeners de eventos. Agora é hora de definir o comportamento da função bindEvents.

// [...]

 bindEvents: async function() {
   const newMovieForm = document.getElementById('form-new-movie');
   newMovieForm.addEventListener('submit', App.handleAddMovie);

   const voteButtons = document.getElementsByClassName('btn-vote');
   for(var i = 0; i < voteButtons.length; i++){
     voteButtons[i].addEventListener('click', App.handleVote);
   }
 },

 // [...]
Enter fullscreen mode Exit fullscreen mode

bindEvents define os manipuladores de envio de voto e formulário. Para fornecer uma experiência de usuário tranquila, definimos “eventos” no contrato inteligente. Embora os eventos sejam emitidos no contrato, ainda não há ouvintes desses eventos. Para observar esses eventos, preencha a função listenForEvents da seguinte forma:

// [...]

 listenOnEvents: async function() {
   const instance = await App.contracts.Voting.deployed();

   instance.Voted({ fromBlock: 0 }).on('data', function(event){
       App.render();
   }).on('error', console.error);

   instance.NewMovie({ fromBlock: 0 }).on('data', function(event){
       console.log("new movie added");
   }).on('error', console.error);
 },

 // [...]
Enter fullscreen mode Exit fullscreen mode

Isso representa a primeira vez que interagimos com o contrato inteligente. Instanciamos o contrato chamando App.contracts.Voting.deployed(). Essa função retorna um identificador para o contrato implantado. Chamar qualquer método ou acessar qualquer propriedade da constante instance resultará em uma chamada ao contrato.

Ouvir eventos é tratado pela função on. Quando o evento é emitido com sucesso, o manipulador de dados data é chamado. No entanto, se a execução resultou em um erro, o manipulador de erros será chamado.

Agora vamos continuar com a função render, o “coração” do aplicativo.

// [...]

 render: async function() {
   document.getElementById("movies").innerHTML = "";

   const instance = await App.contracts.Voting.deployed();
   const moviesCount = (await instance.moviesCount.call()).toNumber();
   const userVotes = (await instance.votes(App.account)).toNumber();
   const maxVotesPerUser = (await instance.MAX_VOTES_PER_VOTER.call()).toNumber();

   for (let i = 1; i <= moviesCount; i++) {
     const movie = await instance.movies.call(i);
     const movieID = movie[0].toNumber();
     const userCanVote = userVotes < maxVotesPerUser;

     addRow(
       movieID,  // ID
       movie[1].toString(),  // Title
       movie[2].toString(),  // Cover
       movie[3].toNumber(),  // Votes
       userCanVote,
     );

     if (!userCanVote) {
       document.getElementById("form-new-movie").remove()
     }
   }

   await App.bindEvents();
 },

 // [...]
Enter fullscreen mode Exit fullscreen mode

A função render cria um identificador para o contrato inteligente e, em seguida, envia uma solicitação para obter o moviesCount, userVotes e maxVotesPerUser. Lembre-se, o maxVotesPerUser foi criado como um auxiliar no contrato inteligente, embora também possamos usá-lo no dApp.

Depois de coletar as informações necessárias, podemos percorrer os filmes armazenados na blockchain. Até o momento, não teremos nenhum filme, mas iremos adicioná-los posteriormente. Em seguida, preenchemos as linhas da tabela de filmes chamando addRow. Se o usuário não tiver mais votos, removemos o formulário para que o usuário não possa adicionar mais filmes à blockchain.

As duas funções restantes têm praticamente a mesma funcionalidade: capturar um evento e chamar um método no contrato inteligente.

 // [...]

 handleVote: function(event) {
   event.preventDefault();

   const movieID = parseInt(event.target.dataset.id);

   App.contracts.Voting.deployed().then(function(instance) {
     instance.vote(movieID, { from: App.account }).then(function(address) {
       console.log(`Votou com sucesso em ${movieID}`, address);
     }).catch(function(err) {
       console.error(err);
     });
   });

   return false;
 },

 handleAddMovie: function(event) {
   event.preventDefault();

   const inputs = event.target.elements;
   const title = inputs['title'].value;
   const cover = inputs['coverUrl'].value;

   App.contracts.Voting.deployed().then(function(instance) {
     instance.addMovie(title, cover, { from: App.account }).then(function() {
       console.log(`Filme adicionado com sucesso ${title}`);
       event.target.reset();
     }).catch(function(err) {
       console.error(err);
     });
   }).catch(function(err) {
     console.error(err);
   });

   return false;
 }

 // [...]
Enter fullscreen mode Exit fullscreen mode

O primeiro manipulador garantirá que o usuário possa votar em filmes, enquanto o segundo trata de novos envios de filmes.

Vamos votar

Finalmente estamos prontos para testar nosso dApp! Abra uma nova sessão do terminal, altere o diretório atual para a raiz do projeto e rode run truffle migrate --reset. Este comando irá compilar e reimplantar o contrato inteligente na blockchain. A partir de agora, podemos nos comunicar com ele. Agora, execute yarn dev se ainda não estiver em execução e abra localhost:3000.

Você já instalou a MetaMask, mas ainda não nos conectamos ao Ganache. Conecte a MetaMask ao Ganache e atualize o localhost:3000 no seu navegador. Você deve ver o MetaMask conectado e deve ver ~ 100 ETH, dependendo de qual conta você escolher.

Para votar, precisamos adicionar um novo filme:

  1. Preencha o formulário de proposta de filme e envie-o.
  2. A MetaMask aparecerá solicitando a confirmação; revise a caixa de diálogo e confirme as alterações.
  3. Após a confirmação, o novo filme será adicionado à lista e o formulário será limpo.

voto feito

Maravilha! O contrato inteligente foi chamado, confirmamos que a transação foi bem-sucedida e que nosso voto foi computado. Poderíamos adicionar mais filmes ou continuar votando nos existentes. Vamos adicionar outro filme e votar em um existente. Quando votarmos, veremos o formulário de proposta de filme desaparecer e notaremos que um dos filmes teve dois votos.

resultado dos filmes

Parabéns! Criamos com sucesso um dApp básico de votação onde podemos votar em filmes. A última etapa é implantar o contrato inteligente em uma rede de teste Ethereum.

APIs Ankr​

Nota: a partir de agora, você precisará de uma carteira conectada à rede de teste Ropsten Ethereum com algum saldo. Você pode solicitar o Ether na rede de teste Ropsten aqui.

No início deste tutorial, combinamos que usaríamos a Ankr para se conectar à rede de teste Ethereum. Se você seguiu os pré-requisitos, já deve ter uma conta na Ankr. Se não, por favor crie uma.

Entre e crie uma nova API para se conectar a uma rede de teste Ethereum:

  1. Navegue até
  2. Clique em "Create"
  3. Vá com o mouse até "Ethereum" (não "Ethereum 2.0") e clique em "Deploy"
  4. Selecione o plano de API desejado no pop-up
  5. Defina o nome do projeto como "tutorial"
  6. Selecione a rede de teste "Ropsten" e clique em "Create"
  7. Escolha “token” para o tipo de autenticação e clique em “Create” novamente

api criada

Ao clicar no nome da API, podemos acessar suas configurações. Selecione a guia "Settings" e copie o endpoint começando com "https".

Volte para o editor e abra truffle-config.js. No início do arquivo, cole o seguinte e substitua o valor da constante endpoint:

const HDWalletProvider = require('@truffle/hdwallet-provider');
const fs = require('fs');
const mnemonic = fs.readFileSync('.secret').toString().trim();
const endpoint = '<ME SUBSTITUA>'

// [...]
Enter fullscreen mode Exit fullscreen mode

Certifique-se de que o arquivo .secret exista na raiz do projeto e contenha 12 palavras. Encontre a seção de redes e adicione a seguinte configuração de rede:

// [...]

 ankrRopsten: {
   provider: () => new HDWalletProvider(mnemonic, endpoint),
   network_id: 3,
   gas: 5500000,
   confirmations: 2,
   skipDryRun: true,

// [...]
 },
Enter fullscreen mode Exit fullscreen mode

Agora, abra uma nova sessão de terminal e execute run truffle migrate --network ankrRopsten na raiz do projeto. O comando migrate implantará o contrato inteligente na rede de teste para que outros possam experimentá-lo também depois que você publicar o dApp em um serviço de hospedagem, como Netlify ou Vercel.

Sumário

Neste tutorial, aprendemos o que é o Truffle, nos familiarizamos com o Ganache e escrevemos um contrato inteligente e um dApp para ele. Em seguida, implantamos nosso contrato inteligente na rede de teste Ropsten graças à incrível API hospedada na Ankr. No geral, este tutorial ensinou como combinar vários elementos distintos para fazer algo na blockchain Ethereum. Obrigado por ter lido!

Top comments (0)