The Graph: Risolvere l'interrogazione dei dati Web3
Questa volta daremo un'occhiata più da vicino a The Graph, che nell'ultimo anno è diventato essenzialmente parte dello stack standard per lo sviluppo di dApp. Vediamo prima come faremmo le cose nel modo tradizionale...
Senza The Graph...
Quindi procediamo con un semplice esempio a scopo illustrativo. A tutti piacciono i giochi, quindi immagina un semplice gioco con utenti che piazzano scommesse:
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, "Transfer failed");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }18
19 emit BetPlaced(msg.sender, msg.value, hasWon);20 }21}Ora supponiamo che nella nostra dApp vogliamo mostrare le scommesse totali, il totale delle partite perse/vinte e anche aggiornarlo ogni volta che qualcuno gioca di nuovo. L'approccio sarebbe:
- Recuperare
totalGamesPlayerWon. - Recuperare
totalGamesPlayerLost. - Iscriversi agli eventi
BetPlaced.
Possiamo ascoltare l'evento in Web3 (opens in a new tab) come mostrato a destra, ma richiede la gestione di un bel po' di casi.
1GameContract.events.BetPlaced({2 fromBlock: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // evento emesso6})7.on('changed', function(event) {8 // evento rimosso di nuovo9})10.on('error', function(error, receipt) {11 // tx rifiutata12});Ora, questo va ancora in qualche modo bene per il nostro semplice esempio. Ma supponiamo di voler mostrare ora gli importi delle scommesse perse/vinte solo per il giocatore corrente. Beh, siamo sfortunati, faresti meglio a distribuire un nuovo contratto intelligente che memorizzi quei valori e li recuperi. E ora immagina un contratto intelligente e una dApp molto più complicati, le cose possono diventare rapidamente disordinate.
Puoi vedere come questo non sia ottimale:
- Non funziona per i contratti già distribuiti.
- Costi del gas extra per memorizzare quei valori.
- Richiede un'altra chiamata per recuperare i dati per un nodo di Ethereum.
Ora diamo un'occhiata a una soluzione migliore.
Lascia che ti presenti GraphQL
Prima parliamo di GraphQL, originariamente progettato e implementato da Facebook. Potresti avere familiarità con il modello tradizionale delle API REST. Ora immagina invece di poter scrivere un'interrogazione esattamente per i dati che desideri:
Le due immagini catturano praticamente l'essenza di GraphQL. Con l'interrogazione a destra possiamo definire esattamente quali dati vogliamo, in modo da ottenere tutto in una singola richiesta e niente di più di ciò di cui abbiamo esattamente bisogno. Un server GraphQL gestisce il recupero di tutti i dati richiesti, quindi è incredibilmente facile da usare per il lato consumatore del frontend. Questa è una bella spiegazione (opens in a new tab) di come esattamente il server gestisce un'interrogazione, se sei interessato.
Ora, con questa conoscenza, tuffiamoci finalmente nello spazio della blockchain e in The Graph.
Cos'è The Graph?
Una blockchain è un database decentralizzato, ma contrariamente a quanto accade di solito, non abbiamo un linguaggio di interrogazione per questo database. Le soluzioni per il recupero dei dati sono dolorose o completamente impossibili. The Graph è un protocollo decentralizzato per l'indicizzazione e l'interrogazione dei dati della blockchain. E come potresti aver intuito, utilizza GraphQL come linguaggio di interrogazione.
Gli esempi sono sempre il modo migliore per capire qualcosa, quindi usiamo The Graph per il nostro esempio GameContract.
Come creare un Subgraph
La definizione di come indicizzare i dati è chiamata subgraph (sottografo). Richiede tre componenti:
- Manifesto (
subgraph.yaml) - Schema (
schema.graphql) - Mappatura (
mapping.ts)
Manifesto (subgraph.yaml)
Il manifesto è il nostro file di configurazione e definisce:
- quali contratti intelligenti indicizzare (indirizzo, rete, ABI...)
- quali eventi ascoltare
- altre cose da ascoltare come chiamate di funzione o blocchi
- le funzioni di mappatura che vengono chiamate (vedi
mapping.tsdi seguito)
Puoi definire più contratti e gestori qui. Una configurazione tipica avrebbe una cartella subgraph all'interno del progetto Hardhat con il proprio repository. Quindi puoi facilmente fare riferimento all'ABI.
Per motivi di comodità potresti anche voler usare uno strumento di template come mustache. Quindi crei un subgraph.template.yaml e inserisci gli indirizzi in base alle ultime distribuzioni. Per un esempio di configurazione più avanzato, vedi ad esempio il repository del subgraph di Aave (opens in a new tab).
E la documentazione completa può essere consultata qui (opens in a new tab).
1specVersion: 0.0.12description: Placing Bets on Ethereum3repository: - GitHub link -4schema:5 file: ./schema.graphql6dataSources:7 - kind: ethereum/contract8 name: GameContract9 network: mainnet10 source:11 address: '0x2E6454...cf77eC'12 abi: GameContract13 startBlock: 617524414 mapping:15 kind: ethereum/events16 apiVersion: 0.0.117 language: wasm/assemblyscript18 entities:19 - GameContract20 abis:21 - name: GameContract22 file: ../build/contracts/GameContract.json23 eventHandlers:24 - event: PlacedBet(address,uint256,bool)25 handler: handleNewBet26 file: ./src/mapping.tsSchema (schema.graphql)
Lo schema è la definizione dei dati GraphQL. Ti permetterà di definire quali entità esistono e i loro tipi. I tipi supportati da The Graph sono
- Bytes
- ID
- String
- Boolean
- Int
- BigInt
- BigDecimal
Puoi anche usare le entità come tipo per definire le relazioni. Nel nostro esempio definiamo una relazione 1-a-molti dal giocatore alle scommesse. Il ! significa che il valore non può essere vuoto. La documentazione completa può essere consultata qui (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: Int11 hasWonCount: Int12 hasLostCount: Int13 bets: [Bet]!14}Mappatura (mapping.ts)
Il file di mappatura in The Graph definisce le nostre funzioni che trasformano gli eventi in arrivo in entità. È scritto in AssemblyScript, un sottoinsieme di TypeScript. Questo significa che può essere compilato in WASM (WebAssembly) per un'esecuzione più efficiente e portabile della mappatura.
Dovrai definire ogni funzione nominata nel file subgraph.yaml, quindi nel nostro caso ne serve solo una: handleNewBet. Per prima cosa cerchiamo di caricare l'entità Player dall'indirizzo del mittente come id. Se non esiste, creiamo una nuova entità e la riempiamo con i valori iniziali.
Quindi creiamo una nuova entità Bet. L'id per questa sarà event.transaction.hash.toHex() + "-" + event.logIndex.toString() garantendo sempre un valore univoco. Usare solo l'hash non è sufficiente poiché qualcuno potrebbe chiamare la funzione placeBet più volte in una singola transazione tramite un contratto intelligente.
Infine possiamo aggiornare l'entità Player con tutti i dati. Gli array non possono essere inseriti direttamente, ma devono essere aggiornati come mostrato qui. Usiamo l'id per fare riferimento alla scommessa. E .save() è richiesto alla fine per memorizzare un'entità.
La documentazione completa può essere consultata qui: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). Puoi anche aggiungere l'output di registrazione al file di mappatura, vedi qui (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 // crea se non esiste ancora9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }15
16 let bet = new Bet(17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()18 )19 bet.player = player.id20 bet.playerHasWon = event.params.hasWon21 bet.time = event.block.timestamp22 bet.save()23
24 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }30
31 // aggiorna l'array in questo modo32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets35
36 player.save()37}Usarlo nel Frontend
Usando qualcosa come Apollo Boost, puoi facilmente integrare The Graph nella tua dApp React (o Apollo-Vue). Specialmente quando si usano gli hook di React e Apollo, recuperare i dati è semplice come scrivere una singola interrogazione GraphQL nel tuo componente. Una configurazione tipica potrebbe apparire così:
1// Vedi tutti i sottografi: 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)E ora possiamo scrivere ad esempio un'interrogazione come questa. Questo ci recupererà
- quante volte l'utente corrente ha vinto
- quante volte l'utente corrente ha perso
- un elenco di timestamp con tutte le sue scommesse precedenti
Tutto in una singola richiesta al server GraphQL.
1const myGraphQlQuery = gql`2 players(where: { id: $currentUser }) {3 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }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])Ma ci manca un ultimo pezzo del puzzle e questo è il server. Puoi eseguirlo tu stesso o usare il servizio ospitato.
Il server The Graph
Graph Explorer: Il servizio ospitato
Il modo più semplice è usare il servizio ospitato. Segui le istruzioni qui (opens in a new tab) per distribuire un subgraph. Per molti progetti puoi effettivamente trovare subgraph esistenti nell'esploratore (opens in a new tab).
Eseguire il proprio nodo
In alternativa puoi eseguire il tuo nodo. Documentazione qui (opens in a new tab). Un motivo per farlo potrebbe essere l'utilizzo di una rete che non è supportata dal servizio ospitato. Le reti attualmente supportate possono essere trovate qui (opens in a new tab).
Il futuro decentralizzato
GraphQL supporta anche i flussi (stream) per i nuovi eventi in arrivo. Questi sono supportati su The Graph tramite i Substreams (opens in a new tab) che sono attualmente in open beta.
Nel 2021 (opens in a new tab) The Graph ha iniziato la sua transizione verso una rete di indicizzazione decentralizzata. Puoi leggere di più sull'architettura di questa rete di indicizzazione decentralizzata qui (opens in a new tab).
Due aspetti chiave sono:
- Gli utenti pagano gli indicizzatori per le interrogazioni.
- Gli indicizzatori mettono in stake i Graph Token (GRT).
Ultimo aggiornamento della pagina: 3 marzo 2026






