Muitas vezes em aplicações web nós enviamos uma requisição para um servidor e ela levará um tempo considerável para ser processada. Quando não sabemos quanto é esse tempo e não podemos manter a conexão ativa até receber uma resposta sob risco de timeout temos de pensar em alternativas. A alternativa mais simples e ineficiente é fazer “pulling”, uma técnica que consiste em criar um timer e ficar fazendo requisições ao servidor pedindo por atualizações. A melhor alternativa é buscar ser avisado pelo servidor de algum forma quando o processamento acabar, geralmente envolvendo websockets ou webhooks.
Na programação para blockchain temos este mesmo problema elevado à enésima potência uma vez que o processamento em nós da rede é caro e geralmente lento, ao menos nas redes mais famosas como a Ethereum. Mesmo que esteja usando uma rede mais barata como a BNB Smart Chain (BSC) ou Polygon, ainda enfrentará esse dilema de como saber que um processamento terminou sem ficar consultando várias vezes a blockchain?
Neste universo em específico a solução é monitorar/escutar por eventos específicos nos smart contracts aguardando por novidades e é isso que vamos explorar no tutorial de hoje, usando a biblioteca EthersJS e a tecnologia Node.js. Caso queira ver o mesmo tutorial, mas com a lib web3.js, use este link.
#1 – Eventos em Solidity
O primeiro ponto é entender como os eventos em Solidity funcionam, assumindo aqui que você estará trabalhando com blockchains baseadas em Ethereum/EVM. Quando estamos programando nossos smart contracts nós podemos usar um keyword event para declarar um evento, como abaixo retirado de um contrato de token ERC-20.
1 2 3 |
event Transfer(address indexed from, address indexed to, uint256 value); |
Este evento Transfer é declarado no contrato e indica que ele pode ser monitorado/escutado por alguém conectado à blockchain. Agora para que o evento aconteça de fato, seja disparado, o programador do smart contract deve usar a keyword emit em alguma função do contrato, como abaixo, retirado do mesmo contrato ERC-20.
1 2 3 |
emit Transfer(msg.sender, to, value); |
Assim, quando chamamos emit Transfer, nós enviamos um registro de evento para a blockchain que será incluído junto aos logs do bloco minerado e qualquer sistema que esteja monitorando este evento em específico será avisado, como uma carteira de criptomoedas por exemplo.
Um último ponto ainda sobre eventos no Solidity é que você pode querer escutar os eventos de uma carteira específica, ao invés de ficar escutando qualquer transferência que acontecer. Isso é possível através do uso da keyword indexed como mostrado acima, onde tanto o from quanto o to podem ser usados como filtro no monitoramento para receber apenas eventos de um from ou de um to específicos. Importante frisar que o uso da keyword indexed aumenta o custo de gás das transações que emitirem este evento, além do que existe um limite de até 3 variáveis indexed por evento apenas, então use com parcimônia.
Apesar do exemplo acima ser bem comum, envolvendo tokens e transferências, você pode criar o evento que quiser para qualquer finalidade. Por exemplo, se tiver um smart contract de uma dex (exchange descentralizada), você pode querer ter um evento para quando um trade é realizado entre dois traders, como abaixo.
1 2 3 |
event NewTrade(uint timestamp, address indexed from, address indexed to, uint amount); |
Repare que este evento não tem nada a ver com o anterior, seus campos são completamente diferentes. E aí, na função que faz o trade acontecer de fato, você emite este evento para avisar quem quer que esteja monitorando este evento neste contrato.
1 2 3 |
emit NewTrade(block.timestamp, msg.sender, to, amount); |
Dito isso, agora que você entendeu como os eventos são declarados e emitidos nos contratos é hora de entender como monitoramos eles em nossas aplicações web.
#2 – Monitorando eventos no backend
Existem duas bibliotecas para criar aplicações Web3 no mercado que realmente valem a pena ser citadas: a Web3.js e a EthersJS. Neste tutorial usarei a segunda por ser mais moderna.
Para nosso exemplo de aplicação web frontend eu usarei Node.js, que é uma tecnologia muito usada em backend para Web3. Mesmo sem conhecimento algum de Node.js você pode criar um projeto simples usando o comando abaixo na sua máquina, dentro de uma pasta criada para guardar o projeto (a minha chamei de nodejs-event-ethers).
1 2 3 |
npm init -y |
Depois, entre na pasta do projeto e vamos instalar as bibliotecas EthersJS e dotenv que vamos usar.
1 2 3 |
npm i ethers dotenv |
Agora com tudo preparado, vamos criar uma funcionalidade no backend (crie um arquivo index.js) que espera pelo evento de transferência de um token. Para que você consiga simular esse teste é importante que você já tenha publicado um contrato seu na blockchain, a fim de poder interagir com ele de forma a causar o disparo do evento que estará monitorando. Eu estarei usando este contrato de token ERC-20 que publiquei na BSC Testnet.
Importe a biblioteca Ethers no topo do arquivo index.js junto do carregamento do dotenv (que vamos configurar logo mais).
1 2 3 4 |
const {ethers} = require("ethers"); require("dotenv").config(); |
Para receber eventos da blockchain é importante que você tenha uma conexão de websockets com um full node. Felizmente hoje é dia é muito fácil conseguir acesso a um usando provedores de blockchain as a service como Infura, Moralis, Ankr, Quicknode e outros que fornecem um acesso limitado gratuito aos mesmos. No meu caso que tenho o contrato publicado na BSC Testnet vou usar a Quicknode neste exemplo que é um dos provedores que fornecem nós para BSC. Se vai publicar na Ethereum ou Sepolia, pode usar a Infura por exemplo. Apenas crie uma conta no provedor da sua preferência e use o nó gratuito para obter a URL do servidor de websockets que será necessário mais abaixo.
Essa URL, bem como o endereço do seu contrato na blockchain devem ser colocados por segurança em um arquivo .env na raiz do seu projeto, que não deve ser versionado, como abaixo.
1 2 3 4 |
WEBSOCKET_URL=<sua url> CONTRACT_ADDRESS=<seu contrato> |
Com as importações realizadas e variáveis de ambiente posicionadas, vamos escrever a função dentro do index.js que fará o monitoramento e tratamento do evento depois de recebido.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function doListen() { const provider = new ethers.WebSocketProvider(process.env.WEBSOCKET_URL); const filter = { address: process.env.CONTRACT_ADDRESS, topics: [ ethers.id("Transfer(address,address,uint256)") ] } provider.on(filter, () => { console.log('fire transfer') }); } doListen(); |
Começamos a função carregando um objeto WebSocketProvider com a URL do servidor de websockets. Depois declaramos um objeto de filtro que é a parte mais importante do listening de eventos. Nele definimos o endereço do contrato a ser monitorado (address) e os tópicos que queremos escutar, sendo que devem ser passados no formato Evento(params) sem qualquer espaço pois a função ethers.utils.id vai converter a string para o identificador único do evento.
Uma vez configurado esse filtro, usamos o mesmo para configurar o gatilho com a função on, que irá disparar a função de callback passada como segundo parâmetro toda vez que as condições do filtro forem atingidas.
Agora rode a sua aplicação com ‘node index.js’ e conseguirá testar facilmente desde que consiga simular a situação que emitirá o evento. Com isso finalizamos nosso exemplo de backend.
E com isso finalizamos o nosso tutorial de como monitorar eventos da blockchain com Web3.js, espero que tenha lhe ajudado!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.