Hey, let’s dive into what makes up a Subgraph in The Graph—it’s this cool decentralized tool that helps pull and sort blockchain data for apps like dApps. Think of a Subgraph as your own custom API that grabs stuff from a blockchain, say Ethereum, and gets it ready for your project. It’s built from three main files: subgraph.yaml, schema.graphql, and mapping.ts. Here’s the rundown on what each one’s all about!
Table of Contents
Subgraph scaffold (subgraph.yaml
, schema.graphql
, mapping.ts
)
1. subgraph.yaml (The Manifest)
This is the configuration file that defines the Subgraph’s metadata, data sources, and how it interacts with the blockchain. It’s written in YAML format and serves as the entry point for the Subgraph.
- Purpose: Tells The Graph what to index, where to get the data, and how to process it.
- Key Sections:
- specVersion: Specifies the version of the Subgraph manifest spec (e.g., 0.0.7).
- description (optional): A human-readable description of the Subgraph.
- repository (optional): Link to the source code repository.
- dataSources:
- kind: The type of data source (e.g., ethereum/contract for Ethereum smart contracts).
- network: The blockchain network (e.g., mainnet, rinkeby).
- source:
- address: The contract address to index.
- abi: The ABI (Application Binary Interface) name, linking to the contract’s interface.
- startBlock: The block number to start indexing from (optional, for performance).
- mapping:
- language: Usually wasm/assemblyscript (AssemblyScript is used for mappings).
- file: Path to the mapping.ts file (e.g., ./src/mapping.ts).
- entities: List of entities defined in schema.graphql (e.g., User, Transaction).
- abis: References to ABI files (e.g., JSON files in ./abis/ folder).
- eventHandlers: List of smart contract events to listen for (e.g., Transfer(address,address,uint256)), mapped to functions in mapping.ts.
- callHandlers (optional): For handling contract function calls.
- blockHandlers (optional): For processing every block or specific block conditions.
- Example
yaml
specVersion: 0.0.7
description: A Subgraph for tracking ERC20 transfers
repository: https://github.com/example/repo
dataSources:
- kind: ethereum/contract
name: ERC20Token
network: mainnet
source:
address: "0x6b175474e89094c44da98b954eedeac495271d0f"
abi: ERC20
startBlock: 10000000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(address,address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
Here, Transfer stores individual transfer events, and User tracks users with a derived list of their transfers.
2. schema.graphql (The Schema)
This file defines the data model for the Subgraph—essentially the structure of the database that will store the indexed data. It’s written in GraphQL Schema Definition Language (SDL).
- Purpose: Specifies the entities (like tables in a database) and their fields that the Subgraph will store and make queryable.
- Key Features:
- Entities: Defined with the @entity directive, representing objects like User, Transfer, etc.
- Fields: Each entity has fields with types (e.g., String, Int, BigInt, Bytes).
- Relationships: Fields can reference other entities (e.g., user: User! for a required relation).
- Directives: Special annotations like @derivedFrom for reverse lookups.
- Scalar Types: Built-in types like ID, Boolean, BigDecimal, etc.
- Example:
graphql
type Transfer @entity {
id: ID! # Unique identifier (e.g., tx hash + log index)
from: Bytes! # Sender address
to: Bytes! # Receiver address
value: BigInt! # Amount transferred
timestamp: Int! # Block timestamp
}
type User @entity {
id: Bytes! # User address
transfers: [Transfer!] @derivedFrom(field: "from") # All transfers sent by this user
}
3. mapping.ts (The Mapping Logic)
This is the code that processes blockchain data (events, calls, or blocks) and populates the entities defined in schema.graphql. It’s written in AssemblyScript (a TypeScript-like language compiled to WebAssembly).
- Purpose: Defines the logic to transform raw blockchain data (e.g., event logs) into structured entity data.
- Key Components:
- Imports: Utilities from @graphprotocol/graph-ts (e.g., BigInt, ethereum.Event).
- Handler Functions: Functions named in subgraph.yaml (e.g., handleTransfer) that process specific events or calls.
- Entity Manipulation: Create, update, or fetch entities and save them to the store.
- Event Data: Access event parameters (e.g., event.params.from) and block info (e.g., event.block.timestamp).
- Example:
typescript
import { BigInt, Bytes } from "@graphprotocol/graph-ts";
import { Transfer as TransferEvent } from "../generated/ERC20Token/ERC20";
import { Transfer, User } from "../generated/schema";
export function handleTransfer(event: TransferEvent): void {
// Create or load Transfer entity
let transferId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
let transfer = new Transfer(transferId);
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.value = event.params.value;
transfer.timestamp = event.block.timestamp.toI32();
transfer.save();
// Update sender User entity
let sender = User.load(event.params.from.toHex());
if (sender == null) {
sender = new User(event.params.from.toHex());
sender.id = event.params.from;
}
sender.save();
// Update receiver User entity
let receiver = User.load(event.params.to.toHex());
if (receiver == null) {
receiver = new User(event.params.to.toHex());
receiver.id = event.params.to;
}
receiver.save();
}
This code processes a Transfer event, creates a Transfer entity, and ensures User entities exist for both sender and receiver.
How They Work Together
- subgraph.yaml tells The Graph what contract to watch and which events to listen for, pointing to mapping.ts for logic.
- schema.graphql defines the structure of the data that will be stored and queried.
- mapping.ts executes the logic to process raw blockchain events and populate the schema-defined entities.
When deployed, The Graph indexer uses these files to sync data from the blockchain, store it in a PostgreSQL database, and expose it via a GraphQL API for querying. For example, you could query all transfers from a specific user or total value transferred after deploying this Subgraph.
Define entities and schema
In the context of The Graph (and specifically for a Subgraph), entities and schema are core concepts that define how data is structured, stored, and made queryable. Let’s break them down clearly:
Entities
- Definition: Entities are the individual objects or records that represent the data you want to index and store from the blockchain. Think of them as rows in a database table or instances of a class. Each entity type corresponds to a specific kind of data you’re tracking (e.g., a user, a transaction, a token transfer).
- Role: Entities are defined in the schema.graphql file and populated by the logic in mapping.ts. They are the building blocks of your Subgraph’s data model.
- Characteristics:
- Each entity has a unique id field (type ID!), which acts as its primary key.
- Entities can have fields of various types (e.g., String, Int, BigInt, Bytes) to store data.
- Entities can relate to each other (e.g., a User entity might link to multiple Transfer entities).
- Example:
- If you’re indexing an ERC20 token’s Transfer events, you might define a Transfer entity to store each transfer’s details (sender, receiver, amount) and a User entity to track participants.
In schema.graphql
graphql
type Transfer @entity {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
}
Here, Transfer is an entity type, and each instance of Transfer (e.g., a specific token transfer event) is an entity.
Schema
- Definition: The schema is the overall data model of your Subgraph, written in GraphQL Schema Definition Language (SDL) in the schema.graphql file. It defines all the entity types, their fields, and their relationships—essentially the structure of the database that The Graph will create and populate.
- Role: The schema serves as the blueprint for how blockchain data will be organized and what can be queried via the Subgraph’s GraphQL API. It’s the contract between the raw blockchain data (processed by mapping.ts) and the end user querying the data.
- Components:
- Entity Definitions: Each entity type is marked with the @entity directive, indicating it will be stored and indexed.
- Fields: Each entity has fields with specific types (e.g., String! for a required string, BigInt for large integers, [Transfer!] for a list of related entities).
- Relationships: Fields can reference other entities, creating links (e.g., user: User! or transfers: [Transfer!]!).
- Directives: Special annotations like @derivedFrom allow reverse lookups (e.g., querying all transfers from a user without explicitly storing that list).
- Scalar Types: Built-in types like ID, Bytes, Boolean, BigDecimal, etc., used to define field types.
- Example:
graphql
type Transfer @entity {
id: ID! # Unique identifier (e.g., tx hash + log index)
from: Bytes! # Sender address
to: Bytes! # Receiver address
value: BigInt! # Amount transferred
timestamp: Int! # Block timestamp
}
type User @entity {
id: Bytes! # User address
transfers: [Transfer!] @derivedFrom(field: "from") # Derived list of transfers sent
}
- This schema defines two entity types: Transfer and User. The transfers field in User is a derived field, automatically populated by The Graph based on the from field in Transfer.
Key Differences and Relationship
- Entities are the instances of data (e.g., a specific transfer event with id: “0x123-0”, from: “0xabc”, etc.), while the schema is the template or structure that defines what those entities look like.
- The schema (in schema.graphql) tells The Graph what entities to create and what fields they should have. The mapping (mapping.ts) then populates those entities with data from the blockchain, based on the schema’s structure.
- Together, they enable a Subgraph to transform raw blockchain events into a structured, queryable dataset.
Practical Context
- In subgraph.yaml: You list the entity types (e.g., Transfer, User) under entities in the mapping section, linking the schema to the data source.
- In mapping.ts: You write logic to create or update entities (e.g., let transfer = new Transfer(id)), matching the schema’s structure.
- When Querying: Users interact with the schema via GraphQL queries like:graphql
{
transfers(where: { from: "0xabc" }) {
id
to
value
}
}
The schema ensures these queries are valid and return data from the stored entities.
Creating and mapping events
Creating and mapping events in a Subgraph involves defining how blockchain events (e.g., smart contract events like Transfer in an ERC20 token) are indexed and transformed into structured data. This process spans all three core files: subgraph.yaml, schema.graphql, and mapping.ts. Below, I’ll walk you through the steps to create and map events, using a practical example of indexing an ERC20 Transfer event.
Step 1: Define the Event in subgraph.yaml
The subgraph.yaml file specifies which events to listen for, where they come from, and how they’ll be handled.
- Key Sections:
- dataSources.source: Defines the contract address and ABI.
- dataSources.mapping.eventHandlers: Lists the events to index and links them to handler functions in mapping.ts.
- Example: Suppose you’re indexing the Transfer(address,address,uint256) event from an ERC20 token contract (e.g., DAI on Ethereum mainnet). Your subgraph.yaml might look like this:
yaml
specVersion: 0.0.7
description: Indexes ERC20 Transfer events
dataSources:
- kind: ethereum/contract
name: ERC20Token
network: mainnet
source:
address: "0x6b175474e89094c44da98b954eedeac495271d0f" # DAI contract
abi: ERC20
startBlock: 10000000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(address,address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
Note: You’ll need the ABI file (ERC20.json) in the abis/ folder, which includes the Transfer event definition. You can get this from Etherscan or the contract’s source code.
Step 2: Define the Event Data Structure in schema.graphql
The schema.graphql file defines the entity that will store the event data, including its fields and relationships.
- Key Considerations:
- Use an @entity directive to mark it as a storable object.
- Include an id field for uniqueness (often a combination of transaction hash and log index).
- Map event parameters (e.g., from, to, value) to fields with appropriate types.
- Example:
graphql
type Transfer @entity {
id: ID! # Unique identifier (e.g., tx hash + log index)
from: Bytes! # Sender address
to: Bytes! # Receiver address
value: BigInt! # Amount transferred
timestamp: Int! # Block timestamp (optional, for context)
}
- id: A unique identifier for each transfer event.
- from, to: Ethereum addresses stored as Bytes.
- value: The transfer amount, stored as BigInt (suitable for large numbers).
- timestamp: Added for querying purposes, sourced from the block.
Step 3: Map the Event in mapping.ts
The mapping.ts file contains the logic to process the event and populate the entity. It’s written in AssemblyScript and uses the event’s parameters to create or update entities.
- Key Steps:
- Import necessary types and the event definition.
- Define a handler function (named in subgraph.yaml) to process the event.
- Create a new entity instance and populate its fields with event data.
- Save the entity to the store.
- Example:
typescript
import { BigInt, Bytes } from "@graphprotocol/graph-ts";
import { Transfer as TransferEvent } from "../generated/ERC20Token/ERC20"; // Auto-generated from ABI
import { Transfer } from "../generated/schema"; // From schema.graphql
export function handleTransfer(event: TransferEvent): void {
// Create a unique ID using transaction hash and log index
let transferId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// Create a new Transfer entity
let transfer = new Transfer(transferId);
// Populate fields from the event parameters
transfer.from = event.params.from; // Address of sender
transfer.to = event.params.to; // Address of receiver
transfer.value = event.params.value; // Amount transferred
transfer.timestamp = event.block.timestamp.toI32(); // Block timestamp as integer
// Save the entity to the store
transfer.save();
}
- TransferEvent: Auto-generated by The Graph CLI based on the ABI and subgraph.yaml. It provides access to event.params (e.g., from, to, value).
- Transfer: The entity type from schema.graphql.
- event.transaction.hash and event.logIndex: Combined to ensure a unique id.
- event.block.timestamp: Adds contextual data not directly in the event.
How It All Ties Together
- Event Trigger: When a Transfer event occurs on the blockchain (e.g., someone sends DAI), the indexer detects it based on subgraph.yaml.
- Handler Execution: The handleTransfer function in mapping.ts runs, receiving the event data.
- Entity Creation: The function creates a Transfer entity, fills it with event data, and saves it.
- Storage: The Graph stores the entity in its database, structured according to schema.graphql.
- Querying: Users can query the data via GraphQL, e.g.:graphql
{
transfers(where: { from: "0xabc..." }) {
id
to
value
timestamp
}
}
Additional Tips
- Generating Code: Run graph codegen after editing subgraph.yaml to generate TypeScript types (e.g., TransferEvent) from the ABI and schema.
- Multiple Events: Add more eventHandlers in subgraph.yaml (e.g., Approval(address,address,uint256) with handler: handleApproval) to index additional events.
- Relationships: Extend the schema and mapping to link entities (e.g., a User entity tracking all transfers): graphql
type User @entity {
id: Bytes!
transfers: [Transfer!] @derivedFrom(field: "from")
}
Then update mapping.ts to create or update User entities.
- Testing: Use a local Graph node or a testnet to verify your Subgraph before deploying to mainnet.
Deploying to the Hosted Service
Hey, just a heads up: The Graph’s Hosted Service got shut down on June 12, 2024, according to the team’s announcement. So, those old hosted endpoints? Gone. They’re nudging everyone to switch over to The Graph’s decentralized network or check out other options like SubQuery’s Managed Service. That said, since you asked about deploying to the Hosted Service, I’ll walk you through how it used to work back when it was still a thing—kind of a throwback guide. If you’re deploying now, though, you’ll want to tweak this for the decentralized setup or whatever service you’re using instead. Cool? Let’s dive in!
Deploying a Subgraph to The Graph Hosted Service (Historical Process)
Deploying a Subgraph to The Graph’s Hosted Service involved preparing your Subgraph files (subgraph.yaml, schema.graphql, mapping.ts), setting up authentication, and using the Graph CLI to deploy to the hosted infrastructure. Here’s how it was done:
Prerequisites
- Graph CLI Installed:
- Install the Graph CLI using npm or yarn:
bash
npm install -g @graphprotocol/graph-cli
# or
yarn global add @graphprotocol/graph-cli
This tool was essential for initializing, building, and deploying Subgraphs.
- Hosted Service Account:
- You needed an account on The Graph Hosted Service (https://thegraph.com/hosted-service/).
- Sign up using a GitHub account, then navigate to the dashboard to create a Subgraph.
- Subgraph Created:
- From the Hosted Service dashboard, click “Add Subgraph” and provide:
- Subgraph Name: e.g., myusername/my-subgraph (used in endpoints and deployments).
- Account: Your GitHub username or organization.
- Optional metadata: Subtitle, description, GitHub URL, preview image.
- Once created, note the Subgraph’s name (e.g., myusername/my-subgraph).
- From the Hosted Service dashboard, click “Add Subgraph” and provide:
- Access Token:
- Copy the access token from your Hosted Service dashboard. This token authenticates your CLI with the service.
Steps to Deploy
- Initialize or Prepare Your Subgraph:
- If starting fresh, initialize a Subgraph: bash
graph init --product hosted-service myusername/my-subgraph
- This creates a directory with subgraph.yaml, schema.graphql, and mapping.ts.
- Alternatively, use an existing Subgraph project with these files already defined (see previous responses for details on their anatomy).
- Example subgraph.yaml for an ERC20 Transfer event: yaml
specVersion: 0.0.7
description: Indexes ERC20 Transfers
dataSources:
- kind: ethereum/contract
name: ERC20Token
network: mainnet
source:
address: "0x6b175474e89094c44da98b954eedeac495271d0f" # DAI
abi: ERC20
startBlock: 10000000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(address,address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
- Authenticate with the Hosted Service:
- Run the authentication command with your access token: bash
graph auth --product hosted-service <ACCESS_TOKEN>
- Replace <ACCESS_TOKEN> with the token from your dashboard. This stores the token locally, and you only need to do this once (or if the token changes).
- Generate Code:
- Generate TypeScript types from your ABI and schema: bash
graph codegen
- This creates files in generated/ (e.g., ERC20Token/ERC20.ts) used by mapping.ts.
- Build the Subgraph:
- Compile the Subgraph into WebAssembly (WASM): bash
graph build
- This prepares the files for deployment, ensuring no syntax errors.
- Compile the Subgraph into WebAssembly (WASM): bash
- Deploy to the Hosted Service:
- Deploy using the Subgraph name from the dashboard: bash
graph deploy --product hosted-service myusername/my-subgraph
- This command:
- Uploads the Subgraph files to IPFS.
- Notifies the Hosted Service to start indexing.
- You might see output like:
Deployed to https://thegraph.com/hosted-service/subgraph/myusername/my-subgraph
- Deploy using the Subgraph name from the dashboard: bash
- Monitor Syncing:
- Check the dashboard for the Subgraph’s status. It starts as “Syncing” and switches to “Synced” once it processes all historical data from the startBlock onward.
- Sync time depends on the chain’s block height and event volume (minutes to hours).
Example Files
- schema.graphql: graphql
type Transfer @entity { id: ID! from: Bytes! to: Bytes! value: BigInt! timestamp: Int! }
- mapping.ts: typescript
import { Transfer as TransferEvent } from "../generated/ERC20Token/ERC20";
import { Transfer } from "../generated/schema";
export function handleTransfer(event: TransferEvent): void {
let id = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
let transfer = new Transfer(id);
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.value = event.params.value;
transfer.timestamp = event.block.timestamp.toI32();
transfer.save();
}
Post-Deployment
- Querying: Once synced, query the Subgraph via its GraphQL endpoint (e.g., https://api.thegraph.com/subgraphs/name/myusername/my-subgraph): graphql
{ transfers(first: 10) { id from to value timestamp } }
- Updates: To update the Subgraph (e.g., fix mappings), modify the files, rebuild (graph build), and redeploy (graph deploy …). The Hosted Service reindexes from the startBlock, replacing the old version once synced.
Key Notes (When It Was Active)
- Supported Networks: The Hosted Service supported Ethereum mainnet, testnets (e.g., Goerli), and other chains like Polygon, BNB Chain, etc. Unsupported networks required a local Graph Node.
- Performance: Inactive Subgraphs (no queries for 45 days) were archived but could be redeployed.
- Cost: It was free, unlike the decentralized network, which requires GRT staking and curation.
Current Context (Post-Sunset)
Since the Hosted Service is no longer available as of June 12, 2024, you’d need to:
- Migrate to The Graph Network:
- Use Subgraph Studio (https://thegraph.com/studio/) to deploy to the decentralized network.
- Requires a deploy key and ETH for gas fees to publish.
- Command: graph deploy –studio <SUBGRAPH_NAME>.
- Alternative Services: Deploy to SubQuery’s Managed Service or run a local Graph Node.
If you’d like steps for deploying to the decentralized network or an alternative, let me know!
Testing with Playground
Testing a Subgraph with a “Playground” typically refers to using The Graph’s GraphQL Playground or query interface to interact with your Subgraph after deployment. This allows you to test queries, verify data indexing, and debug your schema and mappings. Since the Hosted Service is sunset (as of June 12, 2024), I’ll assume you’re either working with a historical context or adapting this to the current decentralized network (e.g., via Subgraph Studio or a local Graph Node). Below, I’ll explain how to test with a Playground, focusing on the process that applied to the Hosted Service and remains largely similar for other deployments.
What is the Playground?
The Playground is an interactive GraphQL interface provided by The Graph (or similar tools) where you can:
- Write and execute GraphQL queries against your Subgraph.
- Explore the schema (auto-generated documentation).
- Test the data being indexed from the blockchain.
For the Hosted Service, it was accessible via the Subgraph’s endpoint (e.g., https://api.thegraph.com/subgraphs/name/myusername/my-subgraph). For the decentralized network, it’s available in Subgraph Studio or via hosted explorers.
Steps to Test with the Playground
1. Deploy Your Subgraph
- Hosted Service (Historical): Deployed via graph deploy –product hosted-service myusername/my-subgraph.
- Decentralized Network (Current): Deploy via Subgraph Studio (graph deploy –studio my-subgraph) or a local Graph Node.
- After deployment, get the query endpoint:
- Hosted Service: From the dashboard (e.g., https://api.thegraph.com/subgraphs/name/myusername/my-subgraph).
- Subgraph Studio: Provided after publishing (e.g., a temporary endpoint or mainnet URL).
2. Access the Playground
- Open the query endpoint in your browser or a GraphQL client (e.g., Postman, Insomnia).
- For Hosted Service: Append the endpoint URL directly (e.g., https://api.thegraph.com/subgraphs/name/myusername/my-subgraph).
- For Subgraph Studio: Use the “Query” tab in the Studio UI or the provided endpoint.
- The GraphQL Playground loads automatically, showing:
- A query editor (left side).
- Schema documentation (right side, via “Docs” or “Schema” tab).
3. Explore the Schema
- In the Playground, check the “Docs” or “Schema” tab to see the available entities and fields.
- Example (based on a Transfer entity): graphql
type Transfer {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
timestamp: Int!
}
- This confirms your schema.graphql is correctly reflected.
4. Write and Test Queries
- Use the query editor to test your Subgraph. Start with simple queries, then add filters, ordering, or pagination.
- Example Queries:
- Basic Query: Fetch the first 5 transfers.graphql
{ transfers(first: 5) { id from to value timestamp } }
- Filtered Query: Get transfers from a specific address.graphql
{ transfers(where: { from: "0xabc123..." }) { id to value } }
- Ordered Query: Sort by timestamp descending.graphql
{ transfers(orderBy: timestamp, orderDirection: desc, first: 10) { id from to value timestamp } }
- Hit the “Play” button () to execute. Results appear on the right in JSON format.
5. Validate Results
- Check if the returned data matches your expectations:
- Are all fields populated (e.g., from, to, value)?
- Does the timestamp align with block data?
- Are filters working (e.g., where: { from: “0x…” })?
- If data is missing or incorrect, revisit your mapping.ts or subgraph.yaml.
6. Debug Issues
- No Data:
- Subgraph still syncing? Check the status (e.g., Hosted Service dashboard or Studio).
- Wrong startBlock in subgraph.yaml? Ensure it’s before the events you’re indexing.
- Field Errors:
- Check schema.graphql for typos or mismatched types (e.g., BigInt vs. Int).
- Verify mapping.ts populates all fields (e.g., transfer.timestamp = event.block.timestamp.toI32()).
- Query Errors: Ensure query syntax matches the schema (e.g., transfers not transfer).
7. Iterate
- Update schema.graphql or mapping.ts as needed, rebuild (graph build), redeploy (graph deploy), and retest in the Playground until satisfied.
Example Scenario
Assume your Subgraph indexes ERC20 Transfer events:
subgraph.yaml:
yaml
dataSources:
- kind: ethereum/contract
name: ERC20Token
network: mainnet
source:
address: "0x6b175474e89094c44da98b954eedeac495271d0f" # DAI
abi: ERC20
mapping:
eventHandlers:
- event: Transfer(address,address,uint256)
handler: handleTransfer
schema.graphql:
graphql
type Transfer @entity {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
timestamp: Int!
}
mapping.ts:
typescript
import { Transfer as TransferEvent } from "../generated/ERC20Token/ERC20";
import { Transfer } from "../generated/schema";
export function handleTransfer(event: TransferEvent): void {
let id = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
let transfer = new Transfer(id);
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.value = event.params.value;
transfer.timestamp = event.block.timestamp.toI32();
transfer.save();
}
Playground Query:
graphql
{
transfers(first: 5, orderBy: timestamp, orderDirection: desc) {
id
from
to
value
timestamp
}
}
Expected Output: json
{
"data": {
"transfers": [
{
"id": "0x123...-0",
"from": "0xabc...",
"to": "0xdef...",
"value": "1000000000000000000",
"timestamp": 1712689200
},
...
]
}
}
Current Context (Post-Hosted Service)
- Subgraph Studio: After deploying via graph deploy –studio, use the Studio’s built-in Playground to test queries during development. Publish to the decentralized network for public access.
- Local Graph Node: Run a local node (docker-compose up), deploy with graph deploy –node http://localhost:8020/, and test at http://localhost:8000/subgraphs/name/my-subgraph/graphql.
- Decentralized Network: Once published, query via a gateway like https://gateway.thegraph.com/api/[api-key]/subgraphs/id/[subgraph-id].
Tips
- Start with small queries to confirm basic functionality.
- Use first: N and skip: M for pagination testing.
- Test edge cases (e.g., empty results, large value fields).
Need help crafting a specific query or setting up a local Playground? Let me know!
Example: Index Transfer events of ERC-20
Example: Index Transfer events of ERC-20
Let’s walk through an example of indexing Transfer events from an ERC-20 token using The Graph. This will cover setting up the Subgraph files (subgraph.yaml, schema.graphql, mapping.ts) and deploying it to the decentralized network (since the Hosted Service is sunset as of June 12, 2024). I’ll use the DAI token on Ethereum mainnet as a concrete example, but you can adapt this to any ERC-20 token.
Overview
- Goal: Index all Transfer(address,address,uint256) events from the DAI contract to track token movements.
- Contract: DAI (address: 0x6b175474e89094c44da98b954eedeac495271d0f).
- Network: Ethereum mainnet.
- Tools: Graph CLI, Subgraph Studio (for decentralized deployment).
Step 1: Set Up the Project
- Install Graph CLI (if not already installed):bash
npm install -g @graphprotocol/graph-cli
- Initialize a Subgraph:bash
graph init --studio my-erc20-transfers
- Select:
- Protocol: ethereum.
- Product: subgraph-studio (for decentralized network).
- Contract address: 0x6b175474e89094c44da98b954yuteac495271d0f.
- Network: mainnet.
- ABI: Fetch from Etherscan or upload ERC20.json (standard ERC-20 ABI).
- This creates a folder (my-erc20-transfers/) with starter files.
- Select:
Step 2: Configure subgraph.yaml
Edit subgraph.yaml to specify the DAI contract and the Transfer event.
yaml
specVersion: 0.0.7
description: Indexes ERC-20 Transfer events for DAI
repository: https://github.com/yourusername/my-erc20-transfers
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: ERC20Token
network: mainnet
source:
address: "0x6b175474e89094c44da98b954eedeac495341d0f" # DAI address
abi: ERC20
startBlock: 8928150 # DAI deployment block (optional, speeds up indexing)
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(address,address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
- address: DAI’s contract address.
- startBlock: The block when DAI was deployed (optional, reduces sync time).
- eventHandlers: Specifies the Transfer event and links it to handleTransfer in mapping.ts.
- entities: Lists Transfer as the entity to store event data.
Note: Ensure ERC20.json (the ABI) is in the abis/ folder. A minimal ERC-20 ABI with the Transfer event looks like:
json
[
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "from", "type": "address" },
{ "indexed": true, "name": "to", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
],
"name": "Transfer",
"type": "event"
}
]
Step 3: Define the Schema in schema.graphql
Create a simple schema to store Transfer event data.
graphql
type Transfer @entity {
id: ID! # Unique ID (tx hash + log index)
from: Bytes! # Sender address
to: Bytes! # Receiver address
value: BigInt! # Amount transferred
timestamp: Int! # Block timestamp
}
- id: Combines transaction hash and log index for uniqueness.
- from, to: Addresses as Bytes (Ethereum address type).
- value: BigInt for large token amounts (ERC-20 tokens often use 18 decimals).
- timestamp: Adds block context, useful for sorting.
Step 4: Write the Mapping in mapping.ts
Edit mapping.ts to process Transfer events and populate the Transfer entity.
typescript
import { BigInt } from "@graphprotocol/graph-ts";
import { Transfer as TransferEvent } from "../generated/ERC20Token/ERC20";
import { Transfer } from "../generated/schema";
export function handleTransfer(event: TransferEvent): void {
// Create a unique ID from tx hash and log index
let id = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// Create a new Transfer entity
let transfer = new Transfer(id);
// Populate fields from event data
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.value = event.params.value;
transfer.timestamp = event.block.timestamp.toI32();
// Save to the store
transfer.save();
}
- TransferEvent: Auto-generated from the ABI, gives access to event.params (from, to, value).
- id: Ensures each transfer is uniquely stored.
- toI32(): Converts timestamp from BigInt to Int to match the schema.
Step 5: Build and Deploy to Subgraph Studio
Since the Hosted Service is gone, we’ll deploy to The Graph’s decentralized network via Subgraph Studio.
- Generate Types:bash
graph codegen
- Creates generated/ folder with types like ERC20Token/ERC20.ts.
- Build the Subgraph:bash
graph build
- Compiles to WebAssembly (WASM).
- Authenticate with Studio:
- Go to Subgraph Studio (https://thegraph.com/studio/), sign in, and create a Subgraph if not already done.
- Get your deploy key from the Studio dashboard.
- Authenticate:bash
graph auth --studio <DEPLOY_KEY>
- Deploy to Studio:bash
graph deploy --studio my-erc20-transfers
- Prompts for a version (e.g., 0.0.1). Use semver (e.g., 0.0.1, 0.0.2 for updates).
- Uploads to IPFS and registers with Studio.
- Test in Studio Playground:
- In Subgraph Studio, go to your Subgraph’s page.
- Use the “Query” tab to test:graphql
{ transfers(first: 5, orderBy: timestamp, orderDirection: desc) { id from to value timestamp } }
- Check results as it syncs (syncing may take time due to mainnet’s block volume).
- Publish to Decentralized Network (Optional)**:
- Once tested, click “Publish” in Studio.
- Requires ETH for gas to register on-chain.
- Query via a gateway like https://gateway.thegraph.com/api/[api-key]/subgraphs/id/[subgraph-id].
Sample Output
After syncing, a query might return:
json
{
"data": {
"transfers": [
{
"id": "0x123...-0",
"from": "0xabc...",
"to": "0xdef...",
"value": "1000000000000000000", // 1 DAI (18 decimals)
"timestamp": 1712689200
},
...
]
}
}
Enhancements
- Add User Entity:graphql
type User @entity { id: Bytes! transfersSent: [Transfer!] @derivedFrom(field: "from") transfersReceived: [Transfer!] @derivedFrom(field: "to") }
Update mapping.ts to track users:typescriptlet sender = User.load(event.params.from.toHex()); if (!sender) { sender = new User(event.params.from.toHex()); sender.id = event.params.from; sender.save(); }
- Local Testing: Use a local Graph Node (docker-compose up) and deploy to http://localhost:8020/ for faster iteration.
This example gets you indexing ERC-20 Transfer events for DAI. Want to tweak it for another token or add features? Let me know!