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 Viem e a tecnologia Node.js. Caso queira ver o mesmo tutorial, mas com a lib EthersJS, use este link.
Vamos lá!
#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 – Setup do Projeto
Existem duas bibliotecas para criar aplicações Web3 no mercado que realmente valem a pena ser citadas: a Viem e a EthersJS. Neste tutorial usarei a primeira por ser a mais popular atualmente.
Para nosso exemplo de aplicação 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-viem).
|
1 2 3 |
npm init -y |
Com o projeto inicializado, vamos instalar e inicializar o TypeScript na pasta e o TS-Node, pois a biblioteca que vamos usar a seguir é muito mais poderosa com ele.
|
1 2 3 4 |
npm i -D typescript ts-node @types/node npx tsc --init |
Para que os types do Node que instalamos sejam interpretados corretamente, é importante ir no tsconfig.json e ajustar a seção types para inclui-los. Incluo abaixo outras configurações importantes que provavelmente você terá de ajustar a fim de obter os mesmos resultados com o tutorial.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "compilerOptions": { "module": "nodenext", "resolveJsonModule": true, "target": "esnext", "types": ["node"], "sourceMap": true, "declaration": true, "declarationMap": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "strict": true, "jsx": "react-jsx", "verbatimModuleSyntax": true, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, } } |
Com o projeto inicializado, vamos instalar a única dependência que usaremos.
|
1 2 3 |
npm i viem |
O Viem é o módulo que usaremos para comunicação com a blockchain. Ele nada mais faz do que abstrai todas as chamadas ao node RPC da blockchain que vamos nos conectar, facilitando toda a comunicação da nossa aplicação com o mesmo, já que usaremos boas e velhas funções JS. Mais que isso, ele infere tipos de uma maneira extremamente poderosa, sendo o seu maior trunfo frente às bibliotecas concorrentes no âmbito de web3.
Agora ajuste no seu package.json para inicializar o projeto usando ts-node, para evitar a etapa de transpilação durante o desenvolvimento e ajuste o type do projeto também para suporte a sintaxe mais moderna do JS/TS.
|
1 2 3 4 5 6 |
"type": "module", "scripts": { "start": "node --loader ts-node/esm src/index.ts" }, |
Crie um index.ts em uma pasta src na raiz do projeto e teste um olá mundo com npm start para garantir que está tudo funcionando.
Agora terminamos o setup e podemos começar a programar.
#3 – Monitorando eventos no backend
Agora com tudo preparado, vamos criar uma funcionalidade no nosso arquivo src/index.ts 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 Viem no topo do arquivo index.ts junto do carregamento do ABI.
|
1 2 3 4 5 6 |
import { createPublicClient, http, parseAbiItem } from "viem"; import { bscTestnet } from "viem/chains"; const transferEvent = parseAbiItem(`event Transfer(address indexed from, address indexed to, uint256 value)`); |
As importações do Viem serão usadas e explicadas logo mais, mas vale uma menção à importação do objeto com as configurações da rede que vou usar bscTestnet, pois é onde o contrato do meu token está. Ajuste de acordo com a blockchain que estiver usando no seu teste.
Após as importações, a instrução parseAbiItem serve para carregar um item do ABI e transformá-lo em um objeto que será usado logo mais. Não há necessidade nesse exemplo de importar um ABI inteiro, mas apenas o evento Transfer pois é o único elemento que vamos usar. Esse parse de ABI é um dos grandes superpoderes do Viem em relação aos seus concorrentes, pois a tipagem forte do objeto resultante vai ajudar enormemente logo mais.
Com a preparação inicial realizada, vamos iniciar um objeto de conexão com o nó RPC da blockchain e definir a variável do endereço do nosso contrato, que tranquilamente poderia estar em um .env mas coloquei aqui para simplificar, ajuste de acordo com seu cenário.
|
1 2 3 4 5 6 7 8 9 10 |
const client = createPublicClient({ chain: bscTestnet, transport: http() }); const address = "0x94a9838528E1b0022c334D3c1c7D5e684c222B07"; console.log(`Monitoring Transfer at contract ${address}...\n`); |
No exemplo acima eu criei um cliente público (somente leitura) com a blockchain BSC Testnet via HTTP. Com esse client eu posso enviar calls para a blockchain, incluindo funções de leitura em smart contracts, desde que eu tenha o ABI e endereço do mesmo. Para monitorar os eventos do contrato, o Viem vai fazer pulling internamente para este nó RPC, uma vez por bloco, para ver se algum evento foi registrado (eles ficam nos logs). Essa solução foi escolhida nesse exemplo por ser a mais simples e HTTP ter amplo suporte em todos nós RPC, mas querendo implementar com websockets, não muda nada no restante do código apenas ajuste o transport do client para:
|
1 2 3 |
transport: webSocket("wss://...") |
Lembrando que, neste caso, você precisa de um nó RPC com suporte a websockets (nem todos tem). Players de mercado como Infura e Alchemy fornecem esse tipo de nó.
E por fim, vamos usar este client para monitorar os eventos Transfer disparados para uma carteira específica (ajuste ou remova o filtro conforme a sua realidade).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const unwatch = client.watchEvent({ address, event: transferEvent, args: { to: "0x576906e0321cd8d57928aef6b3e9e81cd6d0ecef" // FILTRO DIRETO NO RPC }, onLogs: (logs) => { for (const log of logs) { const { from, to, value } = log.args; console.log(`Transfer detected:`); console.log(`From: ${from}`); console.log(`To: ${to}`); console.log(`Value: ${value}`); console.log(`Tx: ${log.transactionHash}\n`); } }, }); |
O objeto client posui uma função watchEvent que retorna uma outra função unwatch, que você pode usar para encerrar o monitoramento apenas chamando-a. Mas focando no watchEvent em si, ele espera o endereço do contrato, o ABI Item do evento a ser monitorado, possíveis filtros (args) e executará a função onLogs toda vez que as condições forem atendidas. Como podem haver vários eventos por transação, fiz um laço nos logs e então pego os parâmetros deles facilmente graças à tipagem do transferEvent (eu não consigo mostrar o quão isso é bom aqui no tutorial, mas o autocomplete do VS Code te entrega exatamente os args corretos). Depois é só imprimir ou usar essa informação como desejar.
Agora rode a sua aplicação com ‘npm start’ 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 Viem, espero que tenha lhe ajudado!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.


