WEB3DEV

Cover image for Minha Primeira Experiência em Construir um Aplicativo Web3 com Solidity, React e TypeScript
Panegali
Panegali

Posted on

Minha Primeira Experiência em Construir um Aplicativo Web3 com Solidity, React e TypeScript

Ultimamente, tenho tido muito tempo livre em mãos. As exchanges centralizadas estão enfrentando problemas por toda parte, não indo muito bem durante esses tempos difíceis no espaço das criptomoedas, e infelizmente fui afetado por isso. Se 2022 nos ensinou algo, é que a centralização realmente não é o caminho a seguir.

Este é o começo de uma nova era para a Internet. Transparência, controle sobre seus dados e descentralização são alguns dos aspectos-chave da Web 3.0: a reinvenção da Internet. Não vou explicar aqui as diferenças com a Web2, nem a evolução a partir da Web1. Deixo isso para o leitor pesquisar. Há muitas informações disponíveis explicando o que realmente é a Web3, sua história, evolução e por que isso importa.

Com esse tempo livre, decidi aprofundar-me mais no assunto do ponto de vista do desenvolvimento, aprender e compreender as diferentes peças, linguagens, ferramentas e estruturas, bem como a arquitetura necessária para construir e implantar um aplicativo descentralizado ou dApp. Basicamente, fazer a transição do desenvolvimento tradicional da Web2 para a Web3.

Originalmente, sou um desenvolvedor Frontend, embora também tenha realizado muito trabalho de Backend, bem como tarefas de DevOps, o que me deu as habilidades para construir e implantar aplicativos do início ao fim. Pessoalmente, acho essa "generalização" mais gratificante do que construir apenas a parte Frontend. Não me entenda mal, adoro o Frontend, o navegador é uma plataforma incrível com uma rica API, e todo o ecossistema em torno do Frontend é tão vasto que é difícil ficar entediado. Sempre há tanto para aprender... mas sinto que é hora de seguir em frente e evoluir junto com a Internet. A Web3 veio para ficar, e estou aqui para absorver tudo sobre ela.

O Projeto: Contrato de Loteria

Como parte da minha rotina ao aprender algo novo, seja uma tecnologia, linguagem, arquitetura, padrão, biblioteca ou estrutura, decidi escolher uma ideia e executá-la.

Inspirado por um dos projetos propostos no curso Ethereum and Solidity: The Complete Developer’s Guide (Ethereum e Solidity: O Guia Completo para Desenvolvedores), decidi construí-lo utilizando as mais recentes tecnologias Web3 e melhorá-lo com uma interface de usuário (UI) mais envolvente. A ideia por trás deste jogo é ter um prêmio acumulado e uma lista de participantes. Cada participante entra no jogo enviando uma quantidade de ether para o contrato. O gerente, ou proprietário do contrato, decide quando escolher um vencedor. O total de ether bloqueado no contrato é então transferido para o sortudo.

Rede Ethereum

Existem algumas blockchains nas quais é possível construir dapps. Como sou novo nesse mundo, decidi escolher a mais fácil e optei pela que sei que possui uma grande comunidade e muitos recursos: Ethereum e cadeias compatíveis com EVM (Ethereum Virtual Machine). Isso também significou que tive que aprender uma nova linguagem, a linguagem nativa: Solidity. Adoro aprender novas linguagens de programação, então essa foi a parte mais divertida.

Se você conhece Python e JavaScript, Solidity é fichinha, já que ela tem muita influência dessas duas.

Solidity é uma linguagem de alto nível orientada a objetos para implementar contratos inteligentes. Contratos inteligentes são programas que regem o comportamento das contas dentro do estado Ethereum. É uma linguagem com tipagem estática, suporta herança, bibliotecas e tipos complexos definidos pelo usuário, entre outras características.

Ferramentas

Essa foi a parte mais difícil. O ecossistema é vasto aqui. Desde frameworks até bibliotecas de Frontend, outras bibliotecas e SDKs, blockchains de teste e provedores de nós, carteiras, armazenamento de dados descentralizado e uma longa lista de ferramentas que você precisa para construir, testar e implantar aplicativos Web3. Isso me lembra das ferramentas de Frontend, que podem ser avassaladoras para os iniciantes.

Frontend

Decidi usar minha pilha de tecnologia habitual:

Gosto de usar a interface de usuário do Antd e o Tailwind css ao trabalhar em projetos pessoais, porque os conheço bem e eles me ajudam a construir a interface rapidamente.

Backend/Blockchain

Para esta parte, optei pelas seguintes ferramentas:

O Truffle é um ótimo framework que ajuda a construir, testar e implantar contratos inteligentes. Há outro framework popular que está ganhando muito impulso e parece ser o preferido hoje em dia, e que usarei no meu próximo projeto: o Hardhat.

O Web3.js é um conjunto de bibliotecas que permite interagir com um nó Ethereum local ou remoto usando JavaScript. Essa biblioteca é praticamente a única opção quando se usa o Truffle. Acredito que seja difícil migrar para outra biblioteca. Outra opção popular é o ethers.js. Ao observar sua página no npm, parece que ele está superando o Web3.js em downloads semanais, então, mesmo sendo mais recente, sua popularidade ultrapassou a do Web3.js. Da mesma forma que com o Hardhat, usarei o ethers.js no meu próximo projeto.

Testes

Temos algumas opções aqui:

Ganache não passa de uma blockchain Ethereum rodando em sua máquina local, permitindo que você implante, execute e, portanto, teste contratos inteligentes de forma fácil e local. Você pode instalar tanto a versão de interface de usuário quanto a versão da interface de linha de comando (CLI). Embora eu seja do tipo que prefere a CLI, por conveniência, utilizei a versão de interface de usuário neste primeiro projeto.

Com o Truffle, você pode testar seus contratos inteligentes, escritos em Solidity, usando JavaScript, e é por isso que você pode usar o Mocha, que é um framework de testes para JavaScript. Junto com ele, para escrever esses testes em TypeScript, incluí o ts-node no fluxo de testes.

Carteira

Precisamos de uma última peça importante ao construir e usar qualquer aplicativo descentralizado: uma carteira. Na Web3, uma carteira é como sua identidade, e a usamos para interagir com dapps. Não temos mais acesso concedido ao aplicativo por meio de um login em um banco de dados centralizado; em vez disso, conectamos nossa carteira ao aplicativo e pronto, estamos conectados. Isso funciona para qualquer dapp, então não é necessário passar por um processo de registro, configurar credenciais, confirmação de e-mail, etc. Há muito mais a ser explorado sobre carteiras, mas não é o propósito deste artigo explicar detalhadamente. Existem muitos artigos disponíveis se você quiser saber mais sobre o assunto.

Existem várias carteiras e extensões de navegador, para que você possa conectá-las facilmente aos dapps. Provavelmente a mais famosa delas é a MetaMask.

Executando a ideia

Após muita pesquisa, cursos, experimentação e construção de contratos no Remix IDE (altamente recomendado se você está aprendendo Solidity, já que não requer configuração, tudo é executado no navegador, incluindo a blockchain), decidi avançar para o mundo real do Github e do VSCode para fazer isso acontecer. Vamos construir um aplicativo Web3, com Frontend e Backend, rodando na rede Ethereum.

A primeira coisa que você precisa é de uma blockchain rodando em sua máquina local. Como mencionei antes, o Ganache é seu amigo. Ao executar a interface de usuário, você terá 10 contas, cada uma com 100 ETH.

A segunda coisa é uma extensão de carteira em seu navegador para interagir com o aplicativo. Para isso, a MetaMask é uma das melhores opções. O próximo passo é importar as contas fornecidas pelo Ganache para a sua carteira e conectar-se à rede local. Você pode seguir este tutorial para fazer isso.

Agora, vamos direto para o código.

Projeto Truffle

Conforme mencionado, estou usando o Truffle como meu framework para o desenvolvimento de contratos inteligentes. A configuração é bastante simples. Após instalar o Truffle usando npm install truffle, você pode criar um projeto básico executando: npx truffle init. Isso criará a seguinte estrutura de projeto:

Aqui está o meu arquivo truffle-config.js para este projeto específico:

// Isso nos ajudará a executar o teste escrito em Typescript
require('ts-node').register({
  files: true,
  project: './tsconfig.test.json',
})

// Obtém variáveis de ambiente do arquivo .env
require('dotenv').config()

const HDWalletProvider = require('@truffle/hdwallet-provider')

const { MNEMONIC, NEXT_PUBLIC_PROJECT_URL } = process.env

module.exports = {
  /**
   * As redes definem como você se conecta ao seu cliente Ethereum e permitem que você defina as configurações
   * padrões que a Web3 usa para enviar transações. Se você não especificar uma, o Truffle
   * iniciará uma instância gerenciada do Ganache para você na porta 9545 quando você
   * executar `develop` ou `test`. Você pode solicitar a um comando Truffle que use uma rede
   * específica na linha de comando, por exemplo
   *
   * $ truffle test --network <network-name>
   */
  networks: {
    // Útil para testes. O nome `development` é especial - o Truffle o utiliza por padrão
    // se ele for definido aqui e nenhuma outra rede for especificada na linha de comando.
    // Você deve executar um cliente (como Ganache, Geth ou Parity) em uma aba separada do terminal
    // se estiver usando esta rede e também deve definir as opções `host`, `port` e `network_id`
    // abaixo com algum valor.
    //
    development: {
      host: '127.0.0.1', // Localhost (padrão: nenhum)
      port: 7545, // Porta padrão da Ethereum (padrão: nenhuma)
      network_id: '*', // Qualquer rede (padrão: nenhuma)
    },

    // Útil para implantar em uma rede pública.
    // Observação: É importante encapsular o provedor como uma função para garantir que o Truffle utilize um novo provedor toda vez.
    goerli: {
      provider: () => new HDWalletProvider(MNEMONIC, NEXT_PUBLIC_PROJECT_URL),
      network_id: 5, // ID do Goerli
      confirmations: 2, // número de confirmações a serem aguardadas entre as implantações. (padrão: 0)
      timeoutBlocks: 200, // número de blocos antes do tempo limite de uma implantação (mínimo/padrão: 50)
      skipDryRun: true, // Pular a execução seca antes das migrações? (padrão: falso para redes públicas)
    },
  },

  // Defina as opções padrão do Mocha aqui, use relatórios especiais, etc.
  mocha: {
    // timeout: 100000
  },

  // Configure seus compiladores
  compilers: {
    solc: {
      version: '0.8.17', // Obtenha a versão exata do solc-bin (padrão: versão do Truffle)
      // docker: true,        // Use "0.5.1" se você o instalou localmente com o Docker (padrão: falso)
      // settings: {          // Consulte a documentação do Solidity para obter orientações sobre otimização e evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

O Contrato

O segundo passo foi implementar o contrato: Lottery.sol. É aqui que você se aprofunda na linguagem de programação Solidity. Ao olhar o código, a funcionalidade é bem clara, e se você vem do mundo de C++, Javascript ou Python, isso parecerá muito familiar para você:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/Strings.sol";

/**
 * @título Jogo de loteria
 * @autor Francisco Ramos
 * @aviso Jogo Web3 simples baseado no curso da Udemy 
 * https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/
 */
contract Lottery {
  // Armazena o proprietário do contrato
  address public owner;

  // mantém o registro das pessoas que entram no jogo
  address payable[] public players;

  // Nós o usamos para emitir alguns detalhes sobre a transação pickWinner
  event WinnerPicked(
    uint index,
    uint prize,
    address winner
  );

  /**
   * @dev Armazena o endereço da pessoa que está implantando o contrato
   */
  constructor() {
    owner = msg.sender;
  }

  /**
   * @dev Aplica uma quantidade mínima de ether a ser enviada para uma função
   * @param value O valor mínimo a ser enviado
   */
  modifier minimum(uint value) {
    string memory requiredMsg = string.concat("O valor minimo necessário e", Strings.toString(value));
    require(msg.value >= value, requiredMsg);
    _;
  }

  /**
   * @dev Garante que o proprietário seja o único que pode chamar uma função
   */
  modifier restricted() {
    require(msg.sender == owner, "Somente o proprietario desse contrato pode chamar a funcao");
    _;
  }

  /**
   * @dev Será chamado pelo jogador que entrar no jogo enviando ether
   * e garante que ele/ela está enviando um mínimo de 0,01 ether
   */
  function enter() public payable minimum(.01 ether) {
    players.push(payable(msg.sender));
  }

  /**
   * @dev Gera um número pseudoaleatório
   * https://medium.com/0xcode/hashing-functions-in-solidity-using-keccak256-70779ea55bb0
   * https://docs.soliditylang.org/en/v0.8.17/abi-spec.html
   * @return o índice do jogador em nossa lista 
   */
  function random() private view returns (uint) {
    return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players)));
  }

  /**
   * @dev Obtém a lista de jogadores atualmente no jogo
   * @return jogadores
   */
  function getPlayers() public view returns (address payable[] memory) {
    return players;
  }

  /**
   * @dev Chamado pelo gerente, ele escolhe um vencedor
   * emitindo o evento WinnerPicked
   */
  function pickWinner() public restricted {
    // Calcule o índice (pseudo)aleatório do vencedor
    uint index = random() % players.length;

    uint prize = address(this).balance;
    address payable winner = players[index];

    // Transfere o valor total para o vencedor
    winner.transfer(prize);

    // "Esvazia a lista de jogadores."
    players = new address payable[](0);

    // Emite um evento com detalhes do resultado
    emit WinnerPicked(
        index,
        prize,
        winner
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Testando o contrato

Essa é a parte divertida, testar a lógica de negócios do contrato (pessoalmente, adoro testes unitários). O legal aqui é que você pode fazer isso usando Javascript, e com a ajuda do TypeChain, é possível gerar os tipos para ter suporte do IDE e tudo com tipagem estática. Se você quiser aprender mais sobre como usar o TypeScript para escrever testes no Truffle, este artigo explica toda a configuração, ou você também pode conferir este exemplo. Aqui estou tentando abranger todos os cenários que poderíamos ter ao interagir com este contrato:

import type {
  LotteryInstance,
  LotteryContract,
} from '../types/truffle-contracts/Lottery'

const Lottery: LotteryContract = artifacts.require('Lottery')

let lotteryInstance: LotteryInstance

beforeEach(async () => {
  lotteryInstance = await Lottery.new()
})

contract('Lottery', (accounts) => {
  it('permite que uma conta entre', async () => {
    await lotteryInstance.enter({
      from: accounts[0],
      value: web3.utils.toWei('0.01', 'ether'),
    })

    const players = await lotteryInstance.getPlayers({
      from: accounts[0],
    })

    assert.equal(accounts[0], players[0])
    assert.equal(players.length, 1)
  })

  it('requer um valor mínimo', async () => {
    try {
      await lotteryInstance.enter({
        from: accounts[0],
        value: web3.utils.toWei('0.0099', 'ether'),
      })

      assert.fail('Deveria ter levantado a excecao "valor minimo necessario"')
    } catch (e) {
      assert.ok(e, 'A excecao "valor minimo exigido" foi levantada)
    }
  })

  it('permite a entrada de várias contas', async () => {
    await lotteryInstance.enter({
      from: accounts[0],
      value: web3.utils.toWei('0.02', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[1],
      value: web3.utils.toWei('0.03', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[2],
      value: web3.utils.toWei('0.04', 'ether'),
    })

    const players = await lotteryInstance.getPlayers({
      from: accounts[0],
    })

    assert.equal(accounts[0], players[0])
    assert.equal(accounts[1], players[1])
    assert.equal(accounts[2], players[2])

    assert.equal(3, players.length)
  })

  it('somente o proprietário pode chamar o método pickWinner', async () => {
    await lotteryInstance.enter({
      from: accounts[0],
      value: web3.utils.toWei('0.02', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[1],
      value: web3.utils.toWei('0.02', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[2],
      value: web3.utils.toWei('0.02', 'ether'),
    })

    try {
      await lotteryInstance.pickWinner({
        from: accounts[1], // accounts[1] não é o proprietário
      })

      assert.fail('Deveria ter levantado a excecao "Nao e o proprietario"')
    } catch (err) {
      assert.ok(err, 'A excecao "Nao e o proprietario" foi levantada')
    }
  })

  it('envia dinheiro para o vencedor e redefine a matriz de jogadores', async () => {
    await lotteryInstance.enter({
      from: accounts[0],
      value: web3.utils.toWei('0.02', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[1],
      value: web3.utils.toWei('0.03', 'ether'),
    })

    await lotteryInstance.enter({
      from: accounts[2],
      value: web3.utils.toWei('0.04', 'ether'),
    })

    const tx = await lotteryInstance.pickWinner({
      from: accounts[0],
    })

    // Vamos capturar o evento 'WinnerPicked' emitido aqui, onde
    // temos os detalhes dessa transação
    const { logs } = tx
    assert.ok(Array.isArray(logs))
    assert.equal(logs.length, 1)

    const log = logs[0]
    assert.equal(log.event, 'WinnerPicked')

    const index = parseInt(log.args.index.toString(10), 10)
    const prize = parseFloat(web3.utils.fromWei(log.args.prize.toString(10))) // in ether

    assert.equal(prize, 0.09) // 0.02 + 0.03 + 0.04
    assert.equal(accounts[index], log.args.winner)

    // Certifique-se de que a lista de jogadores esteja vazia
    const players = await lotteryInstance.getPlayers()
    assert.equal(0, players.length)
  })
})
Enter fullscreen mode Exit fullscreen mode

Frontend

Embora a parte do Frontend seja a maior do aplicativo, não é necessário gastar muito tempo aqui. É um aplicativo simples do Next.js, uma página, sem rotas de API, tudo no lado do cliente:

A parte interessante aqui é a conexão entre o Frontend e a blockchain, e como podemos interagir com ela por meio da nossa interface de usuário. Isso pode ser feito facilmente graças ao Web3.js. Eu implementei um módulo com uma API "simpática" em torno dessa biblioteca para simplificar os métodos que precisamos, uma espécie de fachada:

import Web3 from 'web3'
import { EventData } from 'web3-eth-contract'
import lotteryJson from './contract.json'
import emitter from '../emitter'

type Address = string
type Amount = string // valores sempre são apresentados como strings

type ContractDetails = {
  owner: Address
  players: Address[]
  balance: Amount
  isManager: boolean
  hasEntered: boolean
  participants: number
  contractBalance: Amount
}

// As configurações de rede têm mais propriedades,
// mas estamos interessados apenas no `address`
type NetworkSettings = Record<string, { address: Address }>

const projectUrl = process.env['NEXT_PUBLIC_PROJECT_URL']
const networkId = process.env['NEXT_PUBLIC_NETWORK_ID']

const networkSettings = lotteryJson.networks as NetworkSettings

const CONTRACT_ABI = lotteryJson.abi as unknown as AbiItem
const CONTRACT_ADDRESS = networkSettings[networkId ?? 5777].address as Address

export const web3 = new Web3(Web3.givenProvider ?? projectUrl)

// Cria uma interface semelhante ao contrato inteligente
export const lotteryContract = new web3.eth.Contract(
  CONTRACT_ABI,
  CONTRACT_ADDRESS,
)

// Queremos informar ao front-end quando o contrato emitir o 
// evento WinnerPicked, passando o objeto do evento, que contém 
// todos os detalhes enviados no evento.
lotteryContract.events.WinnerPicked((error: Error, event: EventData) => {
  if (error) {
    emitter.emit('error-picking-winner', error)
  } else {
    emitter.emit('winner-picked', event)
  }
})

/**
 * @returns endereço de implantação do contrato
 */
export function getContractAddress(): Address {
  return CONTRACT_ADDRESS
}

/**
 * Esta função solicitará permissão ao usuário para conectar sua carteira
 * @returns lista de contas de carteira conectadas.
 */
export async function requestAccounts(): Promise<Address[]> {
  return web3.eth.requestAccounts()
}

/**
 * @param endereço do qual queremos obter o saldo
 * @returns o valor nesse endereço
 */
export async function getBalance(address: Address): Promise<Amount> {
  const balanceWei = await web3.eth.getBalance(address)
  return web3.utils.fromWei(balanceWei)
}

/**
 * @returns o valor bloqueado no contrato
 */
export async function getContractBalance(): Promise<Amount> {
  return getBalance(CONTRACT_ADDRESS)
}

/**
 * @returns endereço do proprietário do contrato
 */
export async function getContractOwner(): Promise<Address> {
  return lotteryContract.methods.owner().call()
}

/**
 * @param do endereço de entrada no jogo
 * @param quantidade de ether enviado ao pool pelo participante
 */
export async function enterLottery(
  from: Address,
  ether: Amount,
): Promise<string> {
  return lotteryContract.methods.enter().send({
    from,
    value: web3.utils.toWei(ether),
  })
}

/**
 * @param do endereço que consulta a lista de participantes
 * @returns a lista de participantes
 */
export async function getPlayers(from: Address): Promise<Address[]> {
  if (from) {
    return lotteryContract.methods.getPlayers().call({ from })
  }

  return []
}

/**
 * @param do endereço que consulta o número de participantes
 * @returns o número de participantes
 */
export async function numPlayers(from: Address): Promise<number> {
  const players = await getPlayers(from)
  return players.length
}

/**
 * Essa função só pode ser chamada pelo gerente (proprietário do contrato).
 * Ele escolherá um vencedor aleatório e enviará todo o ether que havia no pool
 * para esse jogador sortudo.
 * @param do endereço que chama essa função
 */
export async function pickWinner(from: Address): Promise<void> {
  if (from) {
    return lotteryContract.methods.pickWinner().send({ from })
  }
}

/**
 * @param endereço para consulta dos detalhes do contrato
 * @returns uma promessa com detalhes sobre o contrato
 */
export async function getContractDetails(
  address: Address,
): Promise<ContractDetails> {
  const [balance, players, owner, contractBalance] = await Promise.all([
    getBalance(address),
    getPlayers(address),
    getContractOwner(),
    getContractBalance(),
  ])

  const participants = players.length
  const hasEntered = players.includes(address)
  const isManager = owner === address

  return {
    owner,
    players,
    balance,
    isManager,
    hasEntered,
    participants,
    contractBalance,
  }
}
Enter fullscreen mode Exit fullscreen mode

Repare que estamos importando um arquivo json, contract.json. O que é isso? Este é o resultado da compilação do nosso contrato inteligente, o artefato do contrato. A parte importante deste json é a ABI, ou Interface Binária de Aplicação. Isso é usado para criar uma abstração de contrato, que converterá automaticamente todas as chamadas em chamadas ABI de baixo nível através de RPC para você. A maneira como gosto de pensar nessa ABI é como a API na Web2.

Tornando público

Até agora, estivemos trabalhando e executando tudo localmente, incluindo a blockchain, mas nossos stakeholders (partes interessadas) estão ficando nervosos, eles querem ver algo, ou simplesmente queremos mostrar nosso aplicativo legal. Em algum momento, precisaremos/queremos tornar isso público, certo? Implantar o Frontend não é um problema, já fizemos isso milhões de vezes, mas como podemos implantar nosso contrato inteligente em uma blockchain pública? E a outra pergunta importante é: se quisermos apenas mostrar o aplicativo internamente, como podemos fazer isso sem precisar gastar ether real toda vez que implantamos e/ou interagimos com o contrato? A resposta: redes de teste.

As redes de testes, ou testnets, são redes blockchain de teste que agem e funcionam de maneira semelhante às redes principais (mainnets) com as quais estão associadas. Como operam em registros separados da rede principal, as moedas em uma rede de teste não têm conexão com transações e valor na rede principal. Isso permite que os desenvolvedores implantem, testem e executem seus projetos em uma blockchain funcional de forma gratuita.

Para este projeto, estou implantando o contrato na rede de testes Goerli. Para que eu possa fazer isso, também preciso de um provedor de nós. Executar seu próprio nó de rede é uma tarefa realmente complicada. Não é uma tarefa fácil por várias razões. Levaria outro artigo para explicar por quê, mas acredite em mim, é complexo. Em vez disso, você pode usar algumas das plataformas disponíveis, como Infura, Alchemy ou QuickNode. As três delas oferecem suporte à rede de teste Goerli. Criei uma conta com a Alchemy, mas não tenho preferência, escolhi a primeira que aprendi.

O próximo passo é adicionar a rede Goerli à sua carteira MetaMask, para que você possa ter contas e enviar a si mesmo ETH falso nessas contas, que você pode usar para implantar seu contrato na rede de teste e testar seu aplicativo o quanto quiser. Você pode seguir este tutorial para adicionar a rede, lembrando que o URL RPC pode ser diferente dependendo da plataforma que você está usando. Por exemplo, na Alchemy, o URL se parece com isso: https://eth-goerli.g.alchemy.com/v2/YOUR_API_KEY.

Então você precisa enviar ETH falso (chamo de GoerliETH) para suas contas. Use a Torneira Goerli para fazer isso. É simples, basta copiar e colar sua conta naquela caixa de texto e clicar Send Me ETH (me envie ETH). Você receberá 0,5 ETH, o que é o suficiente para você se divertir um pouco.

O último passo é implantar seu contrato e conectar seu Frontend. O framework Truffle, com a ajuda de outro pacote chamado @truffle/hdwallet-provider, torna isso muito fácil. O provedor HDWallet é usado pelo Truffle ao implantar o contrato. Dê outra olhada nesta parte de configuração do arquivo truffle-config.js:

// ...
require('dotenv').config()
const HDWalletProvider = require('@truffle/hdwallet-provider')
const { MNEMONIC, NEXT_PUBLIC_PROJECT_URL } = process.env

module.exports = {
  // ...
  networks: {
    // ...
    goerli: {
      provider: () => new HDWalletProvider(MNEMONIC, NEXT_PUBLIC_PROJECT_URL),
      network_id: 5, // ID do Goerli
      confirmations: 2, // número de confirmações a serem aguardadas entre as implantações. (padrão: 0)
      timeoutBlocks: 200, // número de blocos antes do tempo limite de uma implantação (mínimo/padrão: 50)
      skipDryRun: true, // Pular a execução de teste antes das migrações? (padrão: falso para redes públicas)
    },
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Quando executamos uma migração, truffle migrate --network goerli (veja o package.json, scripts npm), o Truffle usará esse provedor para obter o gás necessário da primeira conta, com base no MNEMONIC, para implantar o contrato na rede fornecida, NEXT_PUBLIC_PROJECT_URL.

Quanto à forma como o frontend se comunicará com o contrato, ao executar a migração, o contrato é compilado gerando o artefato do contrato, como vimos anteriormente. A ABI, juntamente com algumas variáveis de ambiente definidas, como o URL do projeto e o ID da rede, serão usadas para realizar essa comunicação nos bastidores:

//...
import lotteryJson from './contract.json'

//...
const projectUrl = process.env['NEXT_PUBLIC_PROJECT_URL']
const networkId = process.env['NEXT_PUBLIC_NETWORK_ID']

const networkSettings = lotteryJson.networks as NetworkSettings

const CONTRACT_ABI = lotteryJson.abi as unknown as AbiItem
const CONTRACT_ADDRESS = networkSettings[networkId ?? 5777].address as Address

export const web3 = new Web3(Web3.givenProvider ?? projectUrl)

// Cria uma interface semelhante ao contrato inteligente
export const lotteryContract = new web3.eth.Contract(
  CONTRACT_ABI,
  CONTRACT_ADDRESS,
)
//...
Enter fullscreen mode Exit fullscreen mode

E é praticamente isso.

Acredito que abordei as partes mais importantes do processo de construção e implantação (pelo menos em uma rede de teste) de um aplicativo Web 3.0 simples, mas completo. Estou ciente de que deixei alguns detalhes de lado e posso ter sido um pouco vago em algumas das explicações. Há muito o que absorver aqui e estou apenas arranhando a superfície.

Reflexões

A Web2 está meio quebrada e, isso se deve ao fato de que a Internet é governada por algumas grandes empresas. Essa centralização está causando alguns problemas:

  • Censura
  • Ponto único de falha
  • Seus dados são de propriedade de terceiros

Deixe-me fazer uma pergunta: em que você confia mais?

  • Instituições como bancos e governos
  • Plataformas da Internet como a Meta e a Amazon
  • Matemática

A Web3 está tentando resolver esses problemas, solucionando questões de confiança por meio da matemática, com base nessas ideias principais: descentralização, sem necessidade de permissão e sem necessidade mínima de confiança. A Web3 parece uma evolução natural da Web que hospeda aplicativos descentralizados executados na tecnologia blockchain e com um sistema de pagamento nativo. Mas essa nova versão está longe de ser perfeita. Ela tem limitações, embora ainda estejamos em um estágio muito inicial e estou confiante de que resolveremos a maioria delas.

Espero que tenha gostado do artigo. Clique no botão 👏 se gostou 😊 e, muito obrigado por lê-lo.

Repositório: https://github.com/jscriptcoder/lottery-contract


Artigo escrito por Francisco Ramos. Traduzido por Marcelo Panegali.

Top comments (0)