WEB3DEV

Cover image for Como construir seu DApp usando a moderna Ethereum Tech Stack: Hardhat e EthersJs
Fatima Lima
Fatima Lima

Posted on

Como construir seu DApp usando a moderna Ethereum Tech Stack: Hardhat e EthersJs

Visão Geral

Ao construir um contrato inteligente na blockchain Ethereum, os novos desenvolvedores tendem a recorrer a ferramentas como truffle e web3.js na construção de seus contratos inteligentes. Este tutorial vai analisar como usar o Hardhat e Ether.js, que agora estão se tornando o padrão na construção de contratos inteligentes.

Metas

Neste tutorial, criaremos, executaremos, compilaremos e implantaremos os contratos inteligentes usando o ambiente de desenvolvimento do Hardhat Ethereum.

Pré-requisitos:

  • Node.js instalado.
  • Um pouco de conhecimento sobre Solidity
  • Conhecimento prático sobre o React

Ferramentas que usaremos:

  • Hardhat - Hardhat é a EVM na qual executaremos nossos contratos de Solidity localmente. É semelhante ao truffle, portanto você não tem que se preocupar em ter profundo conhecimento de truffle.
  • React - Nosso contrato inteligente precisa de uma UI e a estrutura React servirá como a estrutura UI quando construirmos o contrato inteligente.
  • Ethers.js - A biblioteca Ether.js nos permite interagir com os dados que temos na blockchain Ethereum e conectar nosso frontend ao contrato inteligente, o que é muito importante quando se conecta aos dApps.

Iniciando com o projeto

Aqui veremos como montar nosso projeto e alguns pacotes essenciais que precisamos instalar. Para inicializar e configurar nosso projeto, precisamos criar um diretório para abrigar nosso dApp. Como estaremos usando o React como nossa biblioteca UI, podemos usar o create-react-app para inicializar um modelo padrão (template) do projeto.


npx create-react-app moDappTut

Enter fullscreen mode Exit fullscreen mode

Então, adicionaremos cada vez mais pacotes e construiremos nosso dApp. Em seguida, instalaremos o Hardhat, Ethers.js e outras bibliotecas importantes para criar nosso dApp. Abaixo está a lista de pacotes que iremos instalar.


npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle @nomiclabs/hardhat-ethers chai

Enter fullscreen mode Exit fullscreen mode

Olhando para a seção acima, já sabemos para que será utilizado o pacote Ethers e Hardhat. Também temos pacotes de suporte d0 @nomiclabs que permitem um fluxo de desenvolvimento suave ao construir nosso dApp; em seguida, há a biblioteca chai para testar nossos contratos inteligentes. Na próxima seção, estaremos criando nossa amostra do projeto Hardhat.

Configurar o Hardhat

Para criar uma configuração básica com todas as configurações de Hardhat que precisamos, executamos o comando npx hardhat em nosso terminal.


npx hardhat

Enter fullscreen mode Exit fullscreen mode

Selecione as seguintes configurações quando solicitado

  • What do you want to do? (O que você quer fazer?) Selecione Create a basic sample project (Criar uma amostra de projeto)
  • Hardhat Project Root (Raiz do Projeto Hardhat) Pressione Enter para definir como raiz o diretório atual
  • Do you want to add a .gitignore? (Você quer acrescentar o .gitignore?) (Y/n) n

Image description

Agora podemos criar um projeto básico de exemplo, definindo a configuração padrão do Hardhat em nosso diretório atual moDappTut. Em nosso editor, vemos que a configuração do Hardhat, teste, arquivo de script e pasta de contrato foram adicionados ao nosso projeto.

NOTA: Você pode realizar diferentes configurações em seu projeto através do arquivo de configuração do hardhat, como mudar a rede padrão para implantar, ou personalizar os caminhos para seus testes ou artefatos. Mais informações sobre configurações de hardhat podem ser encontradas aqui. Vamos mergulhar na criação do nosso contrato inteligente na próxima seção.

Construindo Nosso Contrato

Nesta seção, analisaremos o contrato padrão fornecido pelo Hardhat e também criaremos o token personalizado que usaremos para as transações no tutorial mais tarde.

Podemos ver um contrato Hardhat já escrito no arquivo Greeter.sol, em nossa pasta de contratos.

No contrato Greeter, temos duas funções. Uma das funções retorna a saudação enquanto a outra define uma nova saudação que mudará o valor da saudação atual. Adicionaremos um novo contrato à nossa pasta de contratos. O contrato será um contrato de token chamado MDToken.


//MDToken.sol

//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.7.0;

contract Token {

    string public name = "Madfinger Token";

    string public symbol = "MHT";

    // A quantidade fixa de tokens armazenada em uma variável não assinada do tipo inteiro.

    uint256 public totalSupply = 1000000;

    // Uma variável de tipo de endereço é usada para armazenar contas da ethereum.

    address public owner;

    // Um mapeamento é um mapa chave/valor. Aqui armazenamos o saldo de cada conta.

    mapping(address => uint256) balances;

    /**

     * Contract initialization.

     *

     * O `constructor` é executado apenas uma vez quando o contrato é criado.

     * O modificador `public` faz com que uma função possa ser chamada de fora do contrato.

     */

    constructor() {

        // O totalSupply é atribuído ao remetente da transação, que é a conta

        // que está implantando o contrato.

        balances[msg.sender] = totalSupply;

        owner = msg.sender;

    }

    /**

     * Uma função para transferir tokens.

     *

     * O modificador `external` faz com que a função *only* possa ser chamada de fora 

     * do contrato.

     */

    function transfer(address to, uint256 amount) external {

        // Verificar se o remetente da transação tem tokens suficientes.

        // Se `require`é o primeiro argumento considerado falso, então a 

        // transação será revertida.

        require(balances[msg.sender] >= amount, "Not enough tokens");

        // Transfere o monstante.

        balances[msg.sender] -= amount;

        balances[to] += amount;

    }

    /**

     * Ler somente a função para recuperar o saldo de token de uma determinada conta.

     *

     * O modificador `view` indica que não modifica o estado do contrato 

     * o que nos permite chamá-lo sem executar uma transação.

     */

    function balanceOf(address account) external view returns (uint256) {

        return balances[account];

    }

}

Enter fullscreen mode Exit fullscreen mode

Em nosso contrato MDToken acima, criamos um token que podemos usar para as transações, bem como enviar e receber o token assim que ele for implantado. Se você ainda é novo no Solidity, há comentários explicando o que cada código faz em nosso contrato acima. A próxima coisa que precisamos fazer é implantar nosso contrato.

Implantando Nosso Contrato

Nesta seção, vamos implantar nosso token em um nó local fornecido pelo Hardhat. Para que o nó local funcione, tudo o que temos a fazer é executar o comando:


npx hardhat node

Enter fullscreen mode Exit fullscreen mode

Depois de executar o nó, o Hardhat nos fornece 20 contas com fundos falsos que podemos usar para as transações. Eis como fica nosso terminal:

Image description

Pela imagem acima você pode ver nossa rede de teste local funcionando em http://127.0.0.1:8545/.

E cada conta tem uma ID de carteira e uma chave privada. Nós as utilizaremos para nos conectarmos à MetaMask mais tarde.

Em nossa pasta de scripts, crie um arquivo deploy.js onde escrevemos nosso script para implantar o contrato. O Hardhat nos fornece um exemplo de script-script.js para a implantação do contrato Greeter. Podemos adaptar o contrato ao nosso gosto e implantar o contrato MDToken. Veja como é nosso roteiro de implantação:


//deploy.js

const hre = require("hardhat");

async function main() {

    // ethers estão disponíveis no escopo global

    const [deployer] = await hre.ethers.getSigners();

    console.log(

      "Implantando os contratos com a conta:",

      await deployer.getAddress()

    );

    console.log("Account balance:", (await deployer.getBalance()).toString());

    const Token = await hre.ethers.getContractFactory("Token");

    const token = await Token.deploy();

    await token.deployed();

    console.log("Token address:", token.address);

  }

  main()

    .then(() => process.exit(0))

    .catch((error) => {

      console.error(error);

      process.exit(1);

    });

Enter fullscreen mode Exit fullscreen mode

Quando implantamos nosso contrato, o Hardhat cria automaticamente uma pasta de artefatos e a armazena na pasta raiz do nosso diretório de projetos. Esta pasta de artefatos contém todas as informações para nosso contrato inteligente que são necessárias para conectar o nosso frontend. Queremos que a pasta de artefatos esteja em nossa pasta src para que possamos configurá-la a partir de nosso arquivo de configuração de hardhat.


//hardhat.config.js

module.exports = {

  paths: {

    artifacts: './src/artifacts',

  },

  solidity: "0.8.4",

};

Enter fullscreen mode Exit fullscreen mode

Agora, para finalmente implantar nosso contrato, executamos o comando


npx hardhat run --network localhost scripts/deploy.js

Enter fullscreen mode Exit fullscreen mode

Nosso contrato está agora implantado na rede de teste, e podemos ver em nosso terminal informações sobre a implantação. A primeira conta fornecida pelo Hardhat é a conta com a qual implantamos o contrato e uma taxa de gas é cobrada pela implantação deste contrato.

Image description

Agora que nosso contrato foi implantado, vamos conectar nossa conta MetaMask a ele.

Conectando à MetaMask

Esta seção percorrerá as etapas para a criação de uma conta MetaMask e a adição da nossa rede local Hardhat.

Instale a extensão MetaMask e configure sua conta. Para configurar nossa conta MetaMask, temos que criar uma nova carteira e fazer backup de nossa frase. Após a criação de nossa carteira MetaMask, se clicarmos no ícone da MetaMask, vemos que estamos na Mainnet Ethereum. O que queremos fazer é conectar nossa rede local de Hardhat à MetaMask. Podemos fazer isso assim:

  1. Certifique-se de configurar suas redes de teste para serem visíveis. Navegue até suas configurações da MetaMask, vá até configurações avançadas e defina a rede de teste a ser conectada.
  2. Vá até suas configurações e selecione configurações de rede, depois clique em adicionar nova rede.
  3. Preencha as informações necessárias e salve e agora você está conectado à rede local Hardhat.

Abaixo está uma captura de tela das informações que você precisa fornecer para configurar sua rede

Image description

Agora vamos importar a conta que usamos para implantar o contrato do nosso Token. Clique em Import Account (Importar Conta) e cole sua chave privada da primeira conta fornecida pelo Hardhat utilizada para implantar seu contrato. Você notará que os 10000 ethers que nos foram fornecidos estão um pouco reduzidos devido à taxa de gas que pagamos para implantar nosso contrato. Agora é hora de finalmente conectar o contrato inteligente que criamos ao nosso frontend React.

Conectando o Contrato Inteligente ao nosso frontend React

Nesta seção, executaremos nosso aplicativo React localmente, conectaremos nosso contrato com a biblioteca ethers e escreveremos o código para testar que tudo está funcionando perfeitamente.

Primeiro, iniciar o aplicativo React executando:


npm start

Enter fullscreen mode Exit fullscreen mode

Para completar nosso dApp, precisamos conectar nosso contrato inteligente ao nosso frontend React. Primeiro, importamos a biblioteca ethers para nos comunicarmos com nossos dados de contrato inteligente, depois usamos o ReactState do React para gerenciar o estado do nosso aplicativo. Precisamos importar a ABI TokenArtifacts, que contém os dados do contrato que precisaremos e depois definir nosso endereço token para o endereço para o qual nosso contrato é implantado, que é o endereço do contrato.

Usamos o UseState react para armazenar três variáveis no estado:

  • tokenData: Este é um objeto que armazena informações sobre o contrato do token, que é o nome e o símbolo
  • amount: usaremos amount para armazenar a quantidade de MDToken que enviaremos para outro endereço
  • userAccountId: esta variável armazena o endereço da conta do usuário para o qual estaremos enviando nosso MDToken. O valor e a conta do usuário serão definidos a partir da entrada do formulário que fornecemos ao usuário na interface do usuário.

// src/app.js

import './App.css';

import {ethers} from 'ethers'

import { useState } from 'react';

import TokenArtifact from "./artifacts/contracts/Token.sol/Token.json"

const tokenAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3"

function App() {

 const [tokenData, setTokenData] = useState({})

 const [amount, setAmount] = useState()

 const [userAccountId, setUserAccountId] = useState()

async function requestAccount() {

  await window.ethereum.request({ method: 'eth_requestAccounts' });

}

 const provider = new ethers.providers.Web3Provider(window.ethereum);

 const signer = provider.getSigner();

 async function _intializeContract(init) {

  // Primeiro inicializamos os ethers criando um fornecedor usando window.ethereum

  // Em seguida, inicializamos o contrato usando esse fornecedor e artefato dos tokens

  // Você pode fazer a mesma coisa com seus contratos.

  const contract = new ethers.Contract(

    tokenAddress,

    TokenArtifact.abi,

    init

  );

  return contract

}

 async function _getTokenData() {

  const contract = await _intializeContract(signer)

  const name = await contract.name();

  const symbol = await contract.symbol();

  const tokenData = {name, symbol}

  setTokenData(tokenData);

}

async function sendMDToken() {

  if (typeof window.ethereum !== 'undefined') {

    await requestAccount()

    const contract = await _intializeContract(signer)

    const transaction = await contract.transfer(userAccountId, amount);

    await transaction.wait();

    console.log(`${amount} MDToken has been sent to ${userAccountId}`);

  }

}

async function getBalance() {

  if (typeof window.ethereum !== 'undefined') {

    const contract = await _intializeContract(signer)

    const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })

    const balance = await contract.balanceOf(account);

    console.log("Account Balance: ", balance.toString());

  }

}

  return (

    <div className="App">

      <header className="App-header">

      <button onClick={_getTokenData}>get token data</button>

      <h1>{tokenData.name}</h1>

      <h1>{tokenData.symbol}</h1>

      <button onClick={getBalance}>Get Balance</button>

      <button onClick={sendMDToken}>Send MDToken</button>

      <input onChange={e => setUserAccountId(e.target.value)} placeholder="Account ID" />

        <input onChange={e => setAmount(e.target.value)} placeholder="Amount" />

      </header>

    </div>

  );

}

export default App;

Enter fullscreen mode Exit fullscreen mode

As duas primeiras funções que iremos escrever serão a função RequestAccount, que nos permitirá conectar à carteira MetaMask do usuário e a função _intializeContract, que inicializará e disponibilizará nosso contato para que possamos usar.


async function requestAccount() {

  await window.ethereum.request({ method: 'eth_requestAccounts' });

}

 const provider = new ethers.providers.Web3Provider(window.ethereum);

 const signer = provider.getSigner();

 async function _intializeContract(init) {

  // Primeiro inicializamos os ethers criando um fornecedor usando window.ethereum

  // Em seguida, inicializamos o contrato usando esse fornecedor e 

 // artefato dos tokens

  // Você pode fazer o mesmo com seus contratos.

  const contract = new ethers.Contract(

    tokenAddress,

    TokenArtifact.abi,

    init

  );

  return contract

}

Enter fullscreen mode Exit fullscreen mode

A função requestAccount é uma função assíncrona que chama o método window.ethereum.request e permite que o usuário se conecte à sua carteira MetaMask. Para que possamos fazer uso da função _intializeContract, precisamos usar o provedor e o signatário. O provedor é obtido de nossa biblioteca ethers que importamos e que utiliza o Web3Provider.

Na função _intializeContract, criaremos uma variável de contrato que usa o método Contract da biblioteca Ethers, que aceita três parâmetros: o endereço do token, a ABI do token, e o signatário ou provedor. Em seguida, devolvemos o contrato. Veremos como a função _intializeContract seria utilizada mais tarde.

Estas duas funções são o que nos permitirá realizar a tarefa principal que queremos em nosso dApp, que são:

  1. Exibir informações do nosso MDToken.
  2. Obter o saldo do nosso MDtoken.
  3. Enviar o MDtoken para o usuário.

As três funções em nosso dApp para a tarefa acima são _getTokenData, getBalance e sendMDToken.

Em nossa UI, temos três botões que chamam as funções _getTokenData, getBalance, e sendMDToken. Depois, duas entradas, onde o usuário pode fornecer o endereço da conta para enviar os tokens e o input da quantidade para a quantidade a ser enviada.


return (

    <div className="App">

      <header className="App-header">

      <button onClick={_getTokenData}>get token data</button>

      <h1>{tokenData.name}</h1>

      <h1>{tokenData.symbol}</h1>

      <button onClick={getBalance}>Get Balance</button>

      <button onClick={sendMDToken}>Send MDToken</button>

      <input onChange={e => setUserAccountId(e.target.value)} placeholder="Account ID" />

        <input onChange={e => setAmount(e.target.value)} placeholder="Amount" />

      </header>

    </div>

  );

Enter fullscreen mode Exit fullscreen mode

Image description

Agora que temos uma visão geral do que nosso dApp estará fazendo, vamos entrar em detalhes sobre como as coisas estão funcionando.

Quando o usuário clica no botão get token data, a função _getTokenData é chamada.

Em nossa função _getTokenData, executamos nossa função _intializeContract e passamos para nosso signatário. A função retorna um contrato e nós atribuímos nosso contrato à variável contract que criamos. Agora podemos usar a contract para obter nosso nome MDToken e o símbolo que são públicos em nosso contrato de token. Então chamamos nossa setTokenData, que aceita o objeto de dados token com nosso nome e símbolo e atualiza o estado tokenData.


async function _getTokenData() {

  const contract = await _intializeContract(signer)

  const name = await contract.name();

  const symbol = await contract.symbol();

  const tokenData = {name, symbol}

  setTokenData(tokenData);

}

Enter fullscreen mode Exit fullscreen mode

A função getBalance verifica antes de tudo se o objeto window.ethereum está presente. Se ele estiver presente, significa que temos a MetaMask instalada e temos uma carteira. Em seguida, inicializamos nosso contrato. Após inicializar o contrato, obtemos a conta conectada à nossa MetaMask e usamos nosso contrato para obter o saldo da conta usando o método balanceOf do nosso contrato, então registramos o saldo da conta.


async function getBalance() {

  if (typeof window.ethereum !== 'undefined') {

    const contract = await _intializeContract(signer)

    const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })

    const balance = await contract.balanceOf(account);

    console.log("Account Balance: ", balance.toString());

  }

}

Enter fullscreen mode Exit fullscreen mode

No último método que é o sendMDToken, primeiro verificamos se o objeto window.ethereum está presente, então executamos a função requestAccount e inicializamos nosso contrato. Usamos o método de transferência de contrato que aceita userAccountId e amount como argumentos, depois esperamos que a transação ocorra e registramos uma mensagem de sucesso.


async function sendMDToken() {

  if (typeof window.ethereum !== 'undefined') {

    await requestAccount()

    const contract = await _intializeContract(signer)

    const transaction = await contract.transfer(userAccountId, amount);

    await transaction.wait();

    console.log(`${amount} MDToken has been sent to ${userAccountId}`);

  }

}

Enter fullscreen mode Exit fullscreen mode

Nota: Para que possamos enviar MDTokens, devemos primeiro importar o token do Metamask. Para fazer isso, clicamos em metamask, clicamos em import token, selecionamos o token personalizado, depois colamos o endereço do token ou o endereço do contrato e você verá que nosso MDToken personalizado foi importado com as moedas disponíveis.

Agora podemos obter nossas informações sobre os tokens, verificar nosso saldo MDToken que está logado no console e transferir MDToken quando fornecemos o ID da conta e a quantia que queremos transferir, em nosso input.

Você deve certificar-se de que não está enviando fundos reais para nenhuma das contas Hardhat fornecidas. Se você transferir quaisquer fundos reais para qualquer conta usada neste tutorial, seus fundos serão perdidos para sempre.

Conclusão

Finalmente chegamos ao final deste tutorial. No tutorial, analisamos como criar, executar, compilar e implantar contratos inteligentes criados usando o ambiente de desenvolvimento do hardhat ethereum. Esperamos que isto o leve a começar sua jornada de construção de coisas incríveis!

Fonte:

Inscreva-se em nossa newsletter para mais artigos e guias sobre a Ethereum. Se você tiver algum feedback, sinta-se à vontade para nos contatar através do Twitter. Você sempre pode conversar conosco em nossa comunidade do Discord, com alguns dos desenvolvedores mais legais que você já conheceu :)

09 Setembro, 2022

Esse artigo foi escrito por Uma Victor e traduzido por Fátima Lima. O original pode ser lido aqui.

Top comments (0)