The Graph : résoudre les problèmes de requêtes de données Web3
Cette fois, nous allons nous intéresser de plus près à The Graph, qui est devenu au cours de l'année dernière un élément essentiel du stack standard pour le développement de dapps. Voyons d'abord comment nous ferions les choses de façon traditionnelle...
Sans The Graph...
Prenons donc un exemple simple à titre d'illustration. Nous aimons tous les jeux, alors imaginez un jeu simple avec des utilisateurs qui placent des paris :
1pragma solidity 0.7.1;23contract Game {4 uint256 totalGamesPlayerWon = 0;5 uint256 totalGamesPlayerLost = 0;6 event BetPlaced(address player, uint256 value, bool hasWon);78 function placeBet() external payable {9 bool hasWon = evaluateBetForPlayer(msg.sender);1011 if (hasWon) {12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');13 require(success, "Échec du transfert");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }1819 emit BetPlaced(msg.sender, msg.value, hasWon);20 }21}Afficher toutMaintenant, disons que dans notre dapp, nous voulons afficher le total des paris, le total des parties perdues/gagnées et également mettre à jour ces informations chaque fois que quelqu'un joue à nouveau. L'approche serait :
- Récupérer
totalGamesPlayerWon. - Récupérer
totalGamesPlayerLost. - S'abonner aux événements
BetPlaced.
Nous pouvons écouter l'événement dans Web3 (opens in a new tab) comme illustré à droite, mais cela nécessite de gérer un certain nombre de cas.
1GameContract.events.BetPlaced({2 fromBlock: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // événement déclenché6})7.on('changed', function(event) {8 // l'événement a été retiré9})10.on('error', function(error, receipt) {11 // transaction rejetée12});Afficher toutCela reste acceptable pour notre exemple simple. Mais disons que nous voulons maintenant afficher les montants des paris perdus/gagnés uniquement pour le joueur actuel. Eh bien, pas de chance, vous feriez mieux de déployer un nouveau contrat qui stocke ces valeurs et les récupère. Et maintenant, imaginez un contrat intelligent et une dapp beaucoup plus complexes, les choses peuvent vite se compliquer.
Vous pouvez voir en quoi cette approche n'est pas optimale :
- Ne fonctionne pas pour les contrats déjà déployés.
- Coûts de gaz supplémentaires pour stocker ces valeurs.
- Nécessite un autre appel pour récupérer les données d'un nœud Ethereum.
Voyons maintenant une meilleure solution.
Laissez-moi vous présenter GraphQL
Commençons par parler de GraphQL, initialement conçu et implémenté par Facebook. Vous connaissez peut-être le modèle d'API REST traditionnel. Imaginez maintenant que vous puissiez écrire une requête pour obtenir exactement les données que vous voulez :
Ces deux images illustrent bien l'essence de GraphQL. Avec la requête de droite, nous pouvons définir exactement les données que nous voulons. Ainsi, nous récupérons tout en une seule requête et rien de plus que ce dont nous avons exactement besoin. Un serveur GraphQL gère la récupération de toutes les données requises, ce qui le rend incroyablement facile à utiliser côté frontend. Voici une bonne explication (opens in a new tab) de la manière exacte dont le serveur gère une requête, si le sujet vous intéresse.
Maintenant, avec ces connaissances, parlons enfin de la blockchain et de The Graph.
Qu'est-ce que The Graph ?
Une blockchain est une base de données décentralisée, mais contrairement à ce qui est généralement le cas, nous n'avons pas de langage de requête pour cette base de données. Les solutions pour récupérer les données sont pénibles ou totalement impossibles. The Graph est un protocole décentralisé pour l'indexation et l'interrogation des données de la blockchain. Et vous l'aurez peut-être deviné, il utilise GraphQL comme langage de requête.
Rien de tel que quelques exemples pour comprendre une chose, alors utilisons The Graph pour notre exemple de GameContract.
Comment créer un sous-graphe
La définition de la manière d'indexer les données s'appelle un sous-graphe. Il nécessite trois composants :
- Manifeste (
subgraph.yaml) - Schéma (
schema.graphql) - Mappage (
mapping.ts)
Manifeste (subgraph.yaml)
Le manifeste est notre fichier de configuration et définit :
- quels contrats intelligents indexer (adresse, réseau, ABI...)
- quels événements écouter
- d'autres éléments à écouter, comme les appels de fonction ou les blocs
- les fonctions de mappage qui sont appelées (voir
mapping.tsci-dessous)
Ici, vous pouvez définir plusieurs contrats et handlers. Une configuration typique aurait un dossier de sous-graphe à l'intérieur du projet Hardhat avec son propre dépôt. Ensuite, vous pouvez facilement référencer l'ABI.
Pour des raisons de commodité, vous pouvez également utiliser un outil de template comme Mustache. Ensuite, vous créez un fichier subgraph.template.yaml et y insérez les adresses en fonction des derniers déploiements. Pour un exemple de configuration plus avancé, voir par exemple le dépôt du sous-graphe Aave (opens in a new tab).
Et la documentation complète peut être consultée ici (opens in a new tab).
1specVersion: 0.0.12description: Placer des paris sur Ethereum3repository: - Lien GitHub -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.tsAfficher toutSchéma (schema.graphql)
Le schéma est la définition des données GraphQL. Il vous permettra de définir quelles entités existent et leurs types. Les types pris en charge par The Graph sont :
- Octets
- ID
- String
- Boolean
- Int
- BigInt
- BigDecimal
Vous pouvez également utiliser des entités comme type pour définir des relations. Dans notre exemple, nous définissons une relation « un à plusieurs » entre un joueur et ses paris. Le ! signifie que la valeur ne peut pas être vide. La documentation complète est disponible ici (opens in a new tab).
1type Bet @entity {2 id: ID!3 player: Player!4 playerHasWon: Boolean!5 time: Int!6}78type Player @entity {9 id: ID!10 totalPlayedCount: Int11 hasWonCount: Int12 hasLostCount: Int13 bets: [Bet]!14}Afficher toutMappage (mapping.ts)
Le fichier de mappage dans The Graph définit nos fonctions qui transforment les événements entrants en entités. Il est écrit en AssemblyScript, un sous-ensemble de Typescript. Cela signifie qu'il peut être compilé en WASM (WebAssembly) pour une exécution plus efficace et portable du mappage.
Vous devrez définir chaque fonction nommée dans le fichier subgraph.yaml, donc dans notre cas, nous n'en avons besoin que d'une seule : handleNewBet. Nous essayons d'abord de charger l'entité Player depuis l'adresse de l'expéditeur en tant qu'identifiant. Si elle n'existe pas, nous créons une nouvelle entité et la remplissons avec des valeurs de départ.
Puis nous créons une nouvelle entité Bet. L'identifiant pour cela sera event.transaction.hash.toHex() + "-" + event.logIndex.toString() ce qui garantit une valeur toujours unique. Utiliser uniquement le hachage n'est pas suffisant, car quelqu'un peut appeler la fonction placeBet plusieurs fois dans une transaction via un contrat intelligent.
Enfin, nous pouvons mettre à jour l'entité Player avec toutes les données. Les tableaux ne peuvent pas être poussés directement, mais doivent être mis à jour comme indiqué ici. Nous utilisons l'ID pour référencer le pari. Et .save() est requis à la fin pour stocker une entité.
La documentation complète est disponible ici : https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). Vous pouvez également ajouter une sortie de journalisation au fichier de mappage, voir ici (opens in a new tab).
1import { Bet, Player } from "../generated/schema"2import { PlacedBet } from "../generated/GameContract/GameContract"34export function handleNewBet(event: PlacedBet): void {5 let player = Player.load(event.transaction.from.toHex())67 if (player == null) {8 // créer si elle n'existe pas encore9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }1516 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()2324 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }3031 // mettre à jour le tableau comme ceci32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 player.save()37}Afficher toutUtilisation dans le frontend
En utilisant quelque chose comme Apollo Boost, vous pouvez facilement intégrer The Graph dans votre dapp React (ou Apollo-Vue). Surtout lorsque vous utilisez des hooks React et Apollo, récupérer des données est aussi simple que d'écrire une requête GraphQL dans votre composant. Une configuration type pourrait ressembler à ceci :
1// Voir tous les sous-graphes : https://thegraph.com/explorer/2const client = new ApolloClient({3 uri: "{{ subgraphUrl }}",4})56ReactDOM.render(7 <ApolloProvider client={client}>8 <App />9 </ApolloProvider>,10 document.getElementById("root")11)Afficher toutEt maintenant, nous pouvons écrire par exemple une requête comme celle-ci. Elle nous récupérera :
- combien de fois l'utilisateur actuel a gagné
- combien de fois l'utilisateur actuel a perdu
- une liste d'horodatages de tous ses paris précédents
Le tout en une seule requête au serveur GraphQL.
1const myGraphQlQuery = gql`2 players(where: { id: $currentUser }) {3 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }9 }10`1112const { loading, error, data } = useQuery(myGraphQlQuery)1314React.useEffect(() => {15 if (!loading && !error && data) {16 console.log({ data })17 }18}, [loading, error, data])Afficher toutMais il nous manque une dernière pièce du puzzle et c'est le serveur. Vous pouvez soit l'exécuter vous-même, soit utiliser le service hébergé.
Le serveur The Graph
Graph Explorer : le service hébergé
Le moyen le plus simple est d'utiliser le service hébergé. Suivez les instructions ici (opens in a new tab) pour déployer un sous-graphe. Pour de nombreux projets, vous pouvez trouver des sous-graphes existants dans l'explorateur (opens in a new tab).
Exécuter votre propre nœud
Sinon, vous pouvez faire tourner votre propre nœud. La documentation se trouve ici (opens in a new tab). Une raison d'agir de la sorte peut être d'utiliser un réseau qui n'est pas pris en charge par le service hébergé. Les réseaux actuellement pris en charge se trouvent ici (opens in a new tab).
L'avenir décentralisé
GraphQL prend également en charge les flux pour les nouveaux événements entrants. Ces derniers sont pris en charge sur The Graph par le biais de Substreams (opens in a new tab), qui sont actuellement en bêta ouverte.
En 2021 (opens in a new tab), The Graph a entamé sa transition vers un réseau d'indexation décentralisé. Vous pouvez en savoir plus sur l'architecture de ce réseau d'indexation décentralisé ici (opens in a new tab).
Les deux aspects clés sont :
- Les utilisateurs paient les indexeurs pour les requêtes.
- Les indexeurs mettent en jeu des jetons Graph (GRT).
Dernière mise à jour de la page : 26 février 2026






