Pular para o conteúdo principal

The Graph: consertando a consulta de dados da Web3

Solidity
smart contracts
consultando
the graph
react
Intermediário
Markus Waas
6 de setembro de 2020
8 minutos de leitura

Desta vez, daremos uma olhada mais de perto no The Graph que, essencialmente, tornou-se parte do stack padrão para o desenvolvimento de dapps no último ano. Primeiro, vamos ver como faríamos as coisas da maneira tradicional...

Sem The Graph...

Então, vamos a um exemplo simples para fins de ilustração. Todos nós gostamos de jogos, então imagine um jogo simples com usuários fazendo apostas:

1pragma solidity 0.7.1;
2
3contract Game {
4 uint256 totalGamesPlayerWon = 0;
5 uint256 totalGamesPlayerLost = 0;
6 event BetPlaced(address player, uint256 value, bool hasWon);
7
8 function placeBet() external payable {
9 bool hasWon = evaluateBetForPlayer(msg.sender);
10
11 if (hasWon) {
12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
13 require(success, "Falha na transferência");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Exibir tudo

Agora, digamos que em nosso dapp, queremos exibir o total de apostas, o total de jogos perdidos/ganhos e também atualizar isso sempre que alguém jogar novamente. A abordagem seria:

  1. Buscar totalGamesPlayerWon.
  2. Buscar totalGamesPlayerLost.
  3. Inscrever-se nos eventos BetPlaced.

Podemos escutar o evento no Web3 (opens in a new tab) como mostrado à direita, mas isso requer lidar com alguns casos.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // evento disparado
6})
7.on('changed', function(event) {
8 // evento foi removido novamente
9})
10.on('error', function(error, receipt) {
11 // tx rejeitada
12});
Exibir tudo

Isso ainda é aceitável para o nosso exemplo simples. Mas digamos que agora queiramos exibir os valores das apostas perdidas/ganhas apenas para o jogador atual. Bem, estamos sem sorte. É melhor implantar um novo contrato que armazene esses valores e os busque. E agora imagine um contrato inteligente e um dapp muito mais complicados. As coisas podem ficar confusas rapidamente.

One Does Not Simply Query

Você pode ver como isso não é o ideal:

  • Não funciona para contratos já implantados.
  • Custos de gás extras para armazenar esses valores.
  • Requer outra chamada para buscar os dados de um nó Ethereum.

Isso não é bom o suficiente

Agora, vamos analisar uma solução melhor.

Deixe-me apresentar o GraphQL

Primeiro, vamos falar sobre o GraphQL, originalmente projetado e implementado pelo Facebook. Você deve estar familiarizado com o modelo tradicional de API REST. Agora imagine que, em vez disso, você pudesse escrever uma consulta para obter exatamente os dados que desejava:

API GraphQL vs. API REST

As duas imagens capturam bem a essência do GraphQL. Com a consulta à direita, podemos definir exatamente quais dados queremos. Assim, obtemos tudo em uma única requisição e nada mais do que exatamente o que precisamos. Um servidor GraphQL cuida da busca de todos os dados necessários, então é incrivelmente fácil de usar para o frontend que o consome. Esta é uma boa explicação (opens in a new tab) de como exatamente o servidor lida com uma consulta, caso você tenha interesse.

Agora com esse conhecimento, vamos finalmente entrar no espaço da blockchain e no The Graph.

O que é The Graph?

Uma blockchain é um banco de dados descentralizado, mas, em contraste com o que geralmente acontece, não temos uma linguagem de consulta para esse banco de dados. As soluções para recuperar dados são penosas ou completamente impossíveis. The Graph é um protocolo descentralizado para indexar e consultar dados da blockchain. E, como você deve ter adivinhado, ele usa GraphQL como linguagem de consulta.

The Graph

Exemplos são sempre a melhor forma de entender algo, então vamos usar o The Graph para nosso exemplo GameContract.

Como criar um Subgraph

A definição de como indexar dados é chamada de subgráfico. Requer três componentes:

  1. Manifesto (subgraph.yaml)
  2. Esquema (schema.graphql)
  3. Mapeamento (mapping.ts)

Manifesto (subgraph.yaml)

O manifesto é nosso arquivo de configuração e define:

  • quais contratos inteligentes indexar (endereço, rede, ABI...)
  • a quais eventos escutar
  • outras coisas para escutar, como chamadas de função ou blocos
  • as funções de mapeamento que são chamadas (veja mapping.ts abaixo)

Você pode definir vários contratos e manipuladores aqui. Uma configuração típica teria uma pasta de subgráfico dentro do projeto Hardhat com seu próprio repositório. Então você pode referenciar facilmente a ABI.

Por conveniência, você também pode querer usar uma ferramenta de template como o mustache. Então, você cria um subgraph.template.yaml e insere os endereços com base nas implantações mais recentes. Para um exemplo de configuração mais avançada, veja, por exemplo, o repositório do subgráfico da Aave (opens in a new tab).

E a documentação completa pode ser vista aqui (opens in a new tab).

1specVersion: 0.0.1
2description: Placing Bets on Ethereum
3repository: - GitHub link -
4schema:
5 file: ./schema.graphql
6dataSources:
7 - kind: ethereum/contract
8 name: GameContract
9 network: mainnet
10 source:
11 address: '0x2E6454...cf77eC'
12 abi: GameContract
13 startBlock: 6175244
14 mapping:
15 kind: ethereum/events
16 apiVersion: 0.0.1
17 language: wasm/assemblyscript
18 entities:
19 - GameContract
20 abis:
21 - name: GameContract
22 file: ../build/contracts/GameContract.json
23 eventHandlers:
24 - event: PlacedBet(address,uint256,bool)
25 handler: handleNewBet
26 file: ./src/mapping.ts
Exibir tudo

Esquema (schema.graphql)

O esquema é a definição de dados do GraphQL. Ele permitirá que você defina quais entidades existem e seus tipos. Os tipos suportados pelo The Graph são

  • Bytes
  • ID
  • String
  • Booleano
  • Int
  • BigInt
  • BigDecimal

Você também pode usar entidades como tipo para definir relacionamentos. Em nosso exemplo, definimos um relacionamento de 1 para muitos de jogador para apostas. O ! significa que o valor não pode estar vazio. A documentação completa pode ser vista aqui (opens in a new tab).

1type Bet @entity {
2 id: ID!
3 player: Player!
4 playerHasWon: Boolean!
5 time: Int!
6}
7
8type Player @entity {
9 id: ID!
10 totalPlayedCount: Int
11 hasWonCount: Int
12 hasLostCount: Int
13 bets: [Bet]!
14}
Exibir tudo

Mapeamento (mapping.ts)

O arquivo de mapeamento no The Graph define nossas funções que transformam eventos recebidos em entidades. Ele é escrito em AssemblyScript, um subconjunto do Typescript. Isso significa que ele pode ser compilado em WASM (WebAssembly) para uma execução mais eficiente e portátil do mapeamento.

Você precisará definir cada função nomeada no arquivo subgraph.yaml, então, no nosso caso, precisamos de apenas uma: handleNewBet. Primeiro, tentamos carregar a entidade Player a partir do endereço do remetente como id. Se ela não existir, criamos uma nova entidade e a preenchemos com os valores iniciais.

Em seguida, criamos uma nova entidade Bet. O id para isso será event.transaction.hash.toHex() + "-" + event.logIndex.toString(), garantindo sempre um valor único. Usar apenas o hash não é suficiente, pois alguém pode chamar a função placeBet várias vezes em uma transação através de um contrato inteligente.

Por fim, podemos atualizar a entidade Player com todos os dados. Não é possível adicionar elementos a arrays diretamente, eles precisam ser atualizados como mostrado aqui. Usamos o id para referenciar a aposta. E .save() é necessário no final para armazenar uma entidade.

A documentação completa pode ser vista aqui: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). Você também pode adicionar uma saída de registro ao arquivo de mapeamento, veja aqui (opens in a new tab).

1import { Bet, Player } from "../generated/schema"
2import { PlacedBet } from "../generated/GameContract/GameContract"
3
4export function handleNewBet(event: PlacedBet): void {
5 let player = Player.load(event.transaction.from.toHex())
6
7 if (player == null) {
8 // cria se ainda não existir
9 player = new Player(event.transaction.from.toHex())
10 player.bets = new Array<string>(0)
11 player.totalPlayedCount = 0
12 player.hasWonCount = 0
13 player.hasLostCount = 0
14 }
15
16 let bet = new Bet(
17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()
18 )
19 bet.player = player.id
20 bet.playerHasWon = event.params.hasWon
21 bet.time = event.block.timestamp
22 bet.save()
23
24 player.totalPlayedCount++
25 if (event.params.hasWon) {
26 player.hasWonCount++
27 } else {
28 player.hasLostCount++
29 }
30
31 // atualize o array assim
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Exibir tudo

Usando no Frontend

Usando algo como o Apollo Boost, você pode integrar facilmente o The Graph ao seu dapp em React (ou Apollo-Vue). Especialmente ao usar hooks do React e Apollo, buscar dados é tão simples quanto escrever uma única consulta GraphQL em seu componente. Uma configuração típica pode ser assim:

1// Veja todos os subgráficos: https://thegraph.com/explorer/
2const client = new ApolloClient({
3 uri: "{{ subgraphUrl }}",
4})
5
6ReactDOM.render(
7 <ApolloProvider client={client}>
8 <App />
9 </ApolloProvider>,
10 document.getElementById("root")
11)
Exibir tudo

E agora podemos escrever, por exemplo, uma consulta como esta. Isso buscará para nós

  • quantas vezes o usuário atual ganhou
  • quantas vezes o usuário atual perdeu
  • uma lista de data e hora de todas as suas apostas anteriores

Tudo em uma única requisição para o servidor GraphQL.

1const myGraphQlQuery = gql`
2 players(where: { id: $currentUser }) {
3 totalPlayedCount
4 hasWonCount
5 hasLostCount
6 bets {
7 time
8 }
9 }
10`
11
12const { loading, error, data } = useQuery(myGraphQlQuery)
13
14React.useEffect(() => {
15 if (!loading && !error && data) {
16 console.log({ data })
17 }
18}, [loading, error, data])
Exibir tudo

Mágica

Mas está faltando uma última peça do quebra-cabeça, que é o servidor. Você pode executá-lo ou usar o serviço hospedado.

O servidor do The Graph

Graph Explorer: o serviço hospedado

A maneira mais fácil é usar o serviço hospedado. Siga as instruções aqui (opens in a new tab) para implantar um subgráfico. Para muitos projetos, você pode encontrar subgráficos existentes no explorador (opens in a new tab).

The Graph-Explorer

Executar seu próprio nó

Como alternativa, você pode executar seu próprio nó. Documentação aqui (opens in a new tab). Uma razão para fazer isso pode ser usar uma rede que não é suportada pelo serviço hospedado. As redes atualmente suportadas podem ser encontradas aqui (opens in a new tab).

O futuro descentralizado

O GraphQL também suporta streams para eventos recém-chegados. Eles são suportados no The Graph através de Substreams (opens in a new tab), que estão atualmente em beta aberto.

Em 2021 (opens in a new tab), o The Graph iniciou sua transição para uma rede de indexação descentralizada. Você pode ler mais sobre a arquitetura desta rede de indexação descentralizada aqui (opens in a new tab).

Dois aspectos principais são:

  1. Os usuários pagam os indexadores por consultas.
  2. Os indexadores fazem stake de Graph Tokens (GRT).

Última atualização da página: 26 de fevereiro de 2026

Este tutorial foi útil?