Introduction
The Graph est le protocole d'indexation décentralisé incontournable pour les dApps Web3 en 2026. Il permet de query des données blockchain via GraphQL, surpassant les RPC lents pour des requêtes complexes comme les swaps Uniswap ou transfers ERC20. Pourquoi l'utiliser ? Imaginez interroger 1M+ événements en millisecondes au lieu d'heures de scanning.
Ce tutoriel avancé vous guide pas à pas pour indexer les événements Transfer d'un token ERC20 sur Sepolia (adresse : 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238). On couvre l'installation CLI, schema GraphQL, mappings en AssemblyScript, build local avec Docker, queries playground, et déploiement sur Subgraph Studio. Résultat : un endpoint GraphQL queryable en production. Idéal pour dashboards DeFi ou NFT analytics. Durée estimée : 2h pour un dev expérimenté.
Prérequis
- Node.js 20+ et Yarn 1.22+
- Docker et Docker Compose (pour graph-node local)
- Clé API Alchemy ou Infura (RPC Sepolia)
- Compte Subgraph Studio (gratuit sur thegraph.com/studio)
- Connaissances avancées en Solidity, GraphQL et TypeScript
- Git installé
Installation de la CLI The Graph
npm install -g @graphprotocol/graph-cli
yarn global add @graphprotocol/graph-cli
graph --versionInstallez la CLI officielle via npm ou Yarn pour gérer init, codegen et deploy. Vérifiez avec graph --version (doit afficher 0.38+ en 2026). Évitez les versions globales obsolètes ; utilisez npx si conflits.
Initialisation du projet subgraph
Créez un nouveau répertoire et initialisez le boilerplate. Cela génère subgraph.yaml, schema.graphql et src/mapping.ts. On cible Sepolia (chainId 11155111) et notre ERC20 à 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.
Initialiser le subgraph
mkdir erc20-subgraph && cd erc20-subgraph
graph init --studio erc20-subgraph
# Suivez les prompts :
# - Subgraph name: erc20-sepolia
# - Directory: .
# - Ethereum network: sepolia
# - Contract address: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
# - Contract ABI: (copiez depuis Etherscan ou Alchemy)L'init configure le manifest de base et télécharge l'ABI. Fournissez l'adresse exacte du contrat ERC20 déployé sur Sepolia. Si ABI manquant, exportez-le depuis Sepolia Etherscan.
Définition du schema GraphQL
Analogie : Le schema est le blueprint de votre base de données décentralisée, comme une table SQL mais queryable en GraphQL. On définit Transfer avec entités pour from/to/value/block.
Schema GraphQL complet
type Transfer @entity {
id: ID!
from: Bytes! # adresse hex
to: Bytes!
value: BigInt!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type Account @entity {
id: ID!
totalTransfers: BigInt!
totalSent: BigInt!
totalReceived: BigInt!
}
type Token @entity {
id: ID!
totalSupply: BigInt!
totalTransfers: BigInt!
}Définit trois entités : Transfer (événements primaires), Account (agrégats par adresse), Token (métadonnées globales). Utilisez BigInt pour les uint256 Solidity. @entity active l'indexation automatique.
Manifest subgraph.yaml
specVersion: 0.0.5
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: ERC20
network: sepolia
source:
address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
abi: ERC20
startBlock: 5000000 # bloc de déploiement approx.
mapping:
kind: ethereum/events
apiVersion: 0.0.8
language: wasm/assemblyscript
entities:
- Transfer
- Account
- Token
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.tsLe manifest lie schema, ABI et mappings. startBlock optimise l'indexation (trouvez-le sur Etherscan). apiVersion 0.0.8 supporte les dernières features WASM. Téléchargez ABI dans ./abis/.
Écriture des mappings AssemblyScript
Les mappings traitent les événements blockchain en entités. handleTransfer crée/updates Transfer, incrémente compteurs Account/Token. Utilisez ctx.Token.createOrUpdate pour idempotence.
Mappings complets (mapping.ts)
import { Transfer as TransferEvent } from "../generated/ERC20/ERC20";
import { Transfer, Account, Token } from "../generated/schema";
import { Address, BigInt, log } from "@graphprotocol/graph-ts";
export function handleTransfer(event: TransferEvent): void {
let transferId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
let transfer = new Transfer(transferId);
transfer.from = event.params.from.toHex();
transfer.to = event.params.to.toHex();
transfer.value = event.params.value;
transfer.blockNumber = event.block.number;
transfer.blockTimestamp = event.block.timestamp;
transfer.transactionHash = event.transaction.hash;
transfer.save();
let tokenId = "current";
let token = Token.load(tokenId);
if (token == null) {
token = new Token(tokenId);
token.totalSupply = BigInt.fromI32(0);
}
token.totalTransfers = token.totalTransfers.plus(BigInt.fromI32(1));
token.save();
let fromId = event.params.from.toHex();
let fromAccount = Account.load(fromId);
if (fromAccount == null) {
fromAccount = new Account(fromId);
fromAccount.totalSent = BigInt.fromI32(0);
fromAccount.totalReceived = BigInt.fromI32(0);
fromAccount.totalTransfers = BigInt.fromI32(0);
}
fromAccount.totalSent = fromAccount.totalSent.plus(event.params.value);
fromAccount.totalTransfers = fromAccount.totalTransfers.plus(BigInt.fromI32(1));
fromAccount.save();
let toId = event.params.to.toHex();
let toAccount = Account.load(toId);
if (toAccount == null) {
toAccount = new Account(toId);
toAccount.totalSent = BigInt.fromI32(0);
toAccount.totalReceived = BigInt.fromI32(0);
toAccount.totalTransfers = BigInt.fromI32(0);
}
toAccount.totalReceived = toAccount.totalReceived.plus(event.params.value);
toAccount.totalTransfers = toAccount.totalTransfers.plus(BigInt.fromI32(1));
toAccount.save();
}Génère un ID unique par log. load/create assure upserts atomiques. Incrémente agrégats pour analytics (ex: totalSent). Loggez avec log.info pour debug. Testez avec événements réels post-5000000.
Génération des types et build
graph codegen
graph build
ls generated/ # vérifiez erc20/ERC20.tsCodegen génère les types TS depuis ABI/schema (ex: TransferEvent). Build compile en WASM. Erreur courante : ABI malformé ; validez JSON.
Déploiement local avec Docker
Setup local : Lancez graph-node pour tester sans frais. Fournissez votre RPC Alchemy Sepolia dans docker-compose.yml.
Docker Compose pour graph-node
version: '3.6'
services:
postgres:
image: postgres
environment:
POSTGRES_DB: subgraph
POSTGRES_USER: subgraph
POSTGRES_PASSWORD: subgraph
ports:
- "5432:5432"
graph-node:
image: semaphoreui/graph-node:v0.38.0
depends_on: [postgres]
environment:
postgres_host: postgres
postgres_db: subgraph
postgres_user: subgraph
postgres_pass: subgraph
ethereum: sepolia https://eth-sepolia.g.alchemy.com/v2/VOTRE_CLE_API
GRAPH_LOG: info
ports:
- "8020:8020"
- "8000:8000"
- "8001:8001"
indexer-agent:
image: semaphoreui/indexer-agent:v0.6.0
depends_on: [graph-node]
environment:
GRAPH_NODE: http://graph-node:8020
INDEXER: http://indexer:80Remplacez VOTRE_CLE_API par votre Alchemy key. Ports : 8000/GraphQL, 8020/admin, 8001/query. up -d pour background.
Déployer et query local
docker-compose up -d
graph create-local --node http://localhost:8020/ erc20-sepolia
graph deploy --version-label v0.1.0 --node http://localhost:8020/ --ipfs http://localhost:5001 erc20-sepolia
# Query playground
curl -X POST -H 'Content-Type: application/json' --data '{"query": "{ transfers(first:5, orderBy: blockTimestamp, orderDirection: desc) { id from to value } }"}' http://localhost:8000/subgraphs/name/erc20-sepoliaCrée le subgraph local, déploie le build WASM. Query via curl ou localhost:8001. Attendez sync (vérifiez logs docker logs graph-node).
Déploiement sur Subgraph Studio
Authentifiez-vous et déployez vers le hosted service, puis migrez vers décentralisé.
Déployer sur Studio
graph auth --studio VOTRE_ACCESS_TOKEN
# Obtenez-le sur thegraph.com/studio
graph subgraph create --studio erc20-sepolia
graph deploy --studio erc20-sepolia
# Endpoint: https://api.studio.thegraph.com/query/XXXXXX/erc20-sepolia/v0.1.0Token depuis dashboard Studio. Premier deploy indexe ; suivants updatent. Endpoint queryable publiquement. Pour décentralisé : graph deploy --network mainnet post-indexing.
Bonnes pratiques
- StartBlock précis : Utilisez Etherscan pour minimiser sync time (ex: 5000000 pour notre contrat).
- Indexation paginée : Ajoutez
skip/firstdans queries ; limitez à 1000 entités/page. - Agrégats dénormalisés : Stockez totalsSent dans Account pour éviter joins coûteux.
- Tests unitaires : Utilisez
matchstick-aspour mocker événements. - Monitoring : Surveillez fatal errors via Studio dashboard ; retry avec versioning.
Erreurs courantes à éviter
- ABI incomplet : Oublier
Transferevent → mappings crash. Copiez toujours full ABI. - ID non-unique : tx.hash + logIndex empêche doublons lors de reorgs.
- BigInt overflow : AssemblyScript gère natif ; évitez JS Number.
- Sync bloqué : RPC rate-limit ; upgradez Alchemy Pro ou utilisez snapshots.
Pour aller plus loin
- Docs officielles : The Graph Academy
- Exemple avancé : Indexer Uniswap V3 ici.
- Migrez vers L2 (Optimism/Base) en changeant
network. - Découvrez nos formations Learni sur Web3 pour masterclass The Graph + IPFS.