---
name: subgraph
description: "Integrate The Graph subgraph into a Scaffold-ETH 2 project for indexing blockchain events. Use when the user wants to: index contract events with The Graph, add a subgraph, query onchain data with GraphQL, set up a local graph node, or deploy a subgraph to Subgraph Studio."
---The Graph Subgraph Integration for Scaffold-ETH 2
Prerequisites
Check if ./packages/nextjs/scaffold.config.ts exists directly in the current working directory (do not search subdirectories). If it doesn't exist, this is not a Scaffold-ETH 2 project. Follow the instructions at https://docs.scaffoldeth.io/SKILL.md to scaffold it first. If it exists, continue directly with building.
Overview
The Graph is a decentralized indexing protocol for querying blockchain data via GraphQL. A subgraph defines which contract events to index, how to transform them, and exposes the indexed data through a GraphQL API. This skill adds a subgraph workspace to SE-2, with a local Graph Node (via Docker) for development and deployment to Subgraph Studio for production.
For The Graph's full API reference, see the official docs. This skill focuses on the SE-2 integration โ the workspace structure, the ABI copy bridge, and local development workflow.
Dependencies & Scripts
Subgraph package (packages/subgraph/)
Create packages/subgraph/package.json:
{
"name": "@se-2/subgraph",
"version": "0.0.1",
"type": "module",
"scripts": {
"abi-copy": "tsx scripts/abi_copy.ts",
"codegen": "graph codegen",
"build": "graph build",
"graph": "graph",
"deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ your-contract",
"create-local": "graph create --node http://localhost:8020/ scaffold-eth/your-contract",
"remove-local": "graph remove --node http://localhost:8020/ scaffold-eth/your-contract",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract",
"local-ship": "yarn abi-copy && yarn codegen && yarn build --network localhost && yarn deploy-local",
"test": "graph test -d",
"run-node": "cd graph-node && docker compose up",
"stop-node": "cd graph-node && docker compose down",
"clean-node": "rm -rf graph-node/data/"
},
"dependencies": {
"@graphprotocol/graph-cli": "^0.98.0",
"@graphprotocol/graph-ts": "^0.38.0",
"tsx": "^4.0.0",
"typescript": "^5.7.0"
},
"devDependencies": {
"@types/chalk": "^2.2.0",
"@types/node": "^20.11.0",
"matchstick-as": "~0.6.0"
}
}NextJS package additions
For querying the subgraph from the frontend via Graph Client:
{
"scripts": {
"client": "graphclient build"
},
"dependencies": {
"graphql": "^16.8.0"
},
"devDependencies": {
"@graphprotocol/client-cli": "^3.0.0"
}
}Root package.json scripts
{
"graph": "yarn workspace @se-2/subgraph graph",
"graphclient:build": "yarn workspace @se-2/nextjs client",
"subgraph:abi-copy": "yarn workspace @se-2/subgraph abi-copy",
"subgraph:build": "yarn workspace @se-2/subgraph build",
"subgraph:clean-node": "yarn workspace @se-2/subgraph clean-node",
"subgraph:codegen": "yarn workspace @se-2/subgraph codegen",
"subgraph:create-local": "yarn workspace @se-2/subgraph create-local",
"subgraph:local-ship": "yarn workspace @se-2/subgraph local-ship",
"subgraph:run-node": "yarn workspace @se-2/subgraph run-node",
"subgraph:stop-node": "yarn workspace @se-2/subgraph stop-node",
"subgraph:test": "yarn workspace @se-2/subgraph test -d"
}Docker Setup (Local Graph Node)
The Graph requires three services: a Graph Node, IPFS, and PostgreSQL. Create packages/subgraph/graph-node/docker-compose.yml with these three services:
- graph-node:
graphprotocol/graph-node:v0.41.1โ ports 8000 (GraphQL), 8001, 8020 (admin), 8030, 8040. Setethereum: "localhost:http://host.docker.internal:8545"to connect to the local chain. Addextra_hosts: ["host.docker.internal:host-gateway"]. - ipfs:
ipfs/kubo:v0.39.0(not the legacyipfs/go-ipfs) โ port 5001, volume./data/ipfs:/data/ipfs - postgres:
postgresโ port 5432, volume./data/postgres:/var/lib/postgresql/data. Credentials: usergraph-node, passwordlet-me-in, dbgraph-node. Must setPOSTGRES_INITDB_ARGS: "--locale=C --encoding=UTF8"โ graph-node requires the C locale and will panic on startup otherwise.
The graph-node environment also needs: postgres_host: postgres, postgres_user/pass/db, ipfs: "ipfs:5001", GRAPH_LOG: info.
Subgraph Configuration
Subgraph manifest (subgraph.yaml)
The manifest defines what to index. Adapt this to the project's actual contracts:
# packages/subgraph/subgraph.yaml
specVersion: 0.0.4
description: Your subgraph description
schema:
file: ./src/schema.graphql
dataSources:
- kind: ethereum/contract
name: YourContract
network: localhost
source:
abi: YourContract
address: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Greeting
- Sender
abis:
- name: YourContract
file: ./abis/localhost_YourContract.json
eventHandlers:
- event: GreetingChange(indexed address,string,bool,uint256)
handler: handleGreetingChange
file: ./src/mapping.tsnameโ must match the contract name indeployedContracts.tsaddressโ auto-updated byabi-copyscript for localhosteventHandlersโ must match the exact Solidity event signatures (parameter names don't matter, types and order do)entitiesโ must match what's defined inschema.graphql
GraphQL schema (src/schema.graphql)
Define entities that represent your indexed data. Each entity maps to a table in the Graph Node's Postgres:
# packages/subgraph/src/schema.graphql
type Greeting @entity(immutable: true) {
id: ID!
sender: Sender!
greeting: String!
premium: Boolean
value: BigInt
createdAt: BigInt!
transactionHash: String!
}
type Sender @entity(immutable: false) {
id: ID!
address: Bytes!
greetings: [Greeting!] @derivedFrom(field: "sender")
createdAt: BigInt!
greetingCount: BigInt!
}AssemblyScript mappings (src/mapping.ts)
Mappings transform raw event data into entities. They're written in AssemblyScript (a TypeScript subset that compiles to WASM):
// packages/subgraph/src/mapping.ts
import { BigInt } from "@graphprotocol/graph-ts";
import { GreetingChange } from "../generated/YourContract/YourContract";
import { Greeting, Sender } from "../generated/schema";
export function handleGreetingChange(event: GreetingChange): void {
const senderString = event.params.greetingSetter.toHexString();
let sender = Sender.load(senderString);
if (sender === null) {
sender = new Sender(senderString);
sender.address = event.params.greetingSetter;
sender.createdAt = event.block.timestamp;
sender.greetingCount = BigInt.fromI32(1);
} else {
sender.greetingCount = sender.greetingCount.plus(BigInt.fromI32(1));
}
const greeting = new Greeting(
event.transaction.hash.toHex() + "-" + event.logIndex.toString(),
);
greeting.greeting = event.params.newGreeting;
greeting.sender = senderString;
greeting.premium = event.params.premium;
greeting.value = event.params.value;
greeting.createdAt = event.block.timestamp;
greeting.transactionHash = event.transaction.hash.toHex();
greeting.save();
sender.save();
}AssemblyScript compiles to WASM โ no closures, no Array.map/filter/reduce, no console.log. Use @graphprotocol/graph-ts utilities for logging (log.info()).
ABI Copy Bridge
The abi-copy script bridges SE-2's deployment output to the subgraph. It reads packages/nextjs/contracts/deployedContracts.ts, extracts ABIs and addresses for chain ID 31337 (localhost), and writes them to packages/subgraph/abis/ and networks.json.
Create packages/subgraph/scripts/abi_copy.ts โ this script parses the deployedContracts file, extracts contract data, and publishes it:
// packages/subgraph/scripts/abi_copy.ts
import * as fs from "fs";
import type { Abi } from "viem";
const DEPLOYED_CONTRACTS_FILE = "../nextjs/contracts/deployedContracts.ts";
const GRAPH_DIR = "./";
function publishContract(
contractName: string,
contractObject: { address: string; abi: Abi },
networkName: string,
) {
const graphConfigPath = `${GRAPH_DIR}/networks.json`;
let graphConfig = fs.existsSync(graphConfigPath)
? JSON.parse(fs.readFileSync(graphConfigPath, "utf8"))
: {};
if (!graphConfig[networkName]) graphConfig[networkName] = {};
graphConfig[networkName][contractName] = { address: contractObject.address };
fs.writeFileSync(graphConfigPath, JSON.stringify(graphConfig, null, 2));
if (!fs.existsSync(`${GRAPH_DIR}/abis`)) fs.mkdirSync(`${GRAPH_DIR}/abis`);
fs.writeFileSync(
`${GRAPH_DIR}/abis/${networkName}_${contractName}.json`,
JSON.stringify(contractObject.abi, null, 2),
);
}
async function main() {
const fileContent = fs.readFileSync(DEPLOYED_CONTRACTS_FILE, "utf8");
const match = fileContent.match(
/const deployedContracts = ({[^;]+}) as const;/s,
);
if (!match?.[1]) throw new Error("Failed to find deployedContracts");
// Parse the TS object literal as JSON (add quotes around keys, remove trailing commas)
let json = match[1]
.replace(/(\w+)(?=\s*:)/g, '"$1"')
.replace(/,(?=\s*[}\]])/g, "");
const contracts = JSON.parse(json);
const localContracts = contracts[31337];
if (!localContracts) {
console.error("No contracts for local network.");
return;
}
for (const name in localContracts) {
publishContract(name, localContracts[name], "localhost");
}
console.log("Published contracts to subgraph package.");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Graph Client (Frontend Queries)
Graph Client provides a typed GraphQL client with features like client-side composition and automatic pagination.
Configuration
# packages/nextjs/.graphclientrc.yml
sources:
- name: YourContract
handler:
graphql:
endpoint: http://localhost:8000/subgraphs/name/scaffold-eth/your-contract
documents:
- ./graphql/GetGreetings.gqlGraphQL queries
# packages/nextjs/graphql/GetGreetings.gql
query GetGreetings {
greetings(first: 25, orderBy: createdAt, orderDirection: desc) {
id
greeting
premium
value
createdAt
sender {
address
greetingCount
}
}
}Using in components
After running yarn graphclient:build, import the generated client. Use TanStack Query (already available in SE-2) for data fetching:
"use client";
import { useQuery } from "@tanstack/react-query";
import { GetGreetingsDocument, execute } from "~~/.graphclient";
async function fetchGreetings() {
const result = await execute(GetGreetingsDocument, {});
return result.data?.greetings ?? [];
}
const GreetingsTable = () => {
const {
data: greetings = [],
isLoading,
error,
} = useQuery({
queryKey: ["subgraph-greetings"],
queryFn: fetchGreetings,
});
// Render data...
};
~~/.graphclientis the generated runtime artifact. It only exists afteryarn graphclient:build. The.graphclient/directory should NOT be committed โ it's generated from.graphclientrc.ymland the GQL files.
Gotchas & Common Pitfalls
Docker must be running. The local Graph Node, IPFS, and Postgres all run in Docker. If Docker isn't running, yarn subgraph:run-node will fail.
yarn deploy must run before yarn subgraph:abi-copy. The ABI copy script reads from deployedContracts.ts which is generated by the deploy step. If you haven't deployed, there's nothing to copy.
local-ship does everything in one command. It runs abi-copy โ codegen โ build โ deploy-local sequentially. Use this instead of running each step manually.
create-local only needs to run once. It registers the subgraph name with the local Graph Node. Running it again will error with "subgraph already exists." Only re-run after clean-node.
Linux users need --hostname 0.0.0.0. The default Hardhat/Anvil config binds to 127.0.0.1, which Docker can't reach. Add --hostname 0.0.0.0 (Hardhat) or --host 0.0.0.0 (Anvil) to the chain command. You may also need sudo ufw allow 8545/tcp.
Graph Client artifacts must be regenerated after schema changes. Run yarn graphclient:build whenever you change the GraphQL schema or queries. The frontend imports from ~~/.graphclient which contains generated types.
Port conflicts with other services. The Graph Node stack uses ports 5001 (IPFS), 5432 (Postgres), 8000 (GraphQL), 8020 (admin). If you're also running the drizzle-neon extension (which uses port 5432 for its own Postgres), you'll have a conflict. Change one of the Postgres ports.
How to Test
yarn chainโ start local blockchainyarn deployโ deploy contracts (generatesdeployedContracts.ts)yarn subgraph:run-nodeโ start Docker Graph Node (keep this terminal open)yarn subgraph:create-localโ register subgraph (once only)yarn subgraph:local-shipโ copies ABIs, generates types, builds, and deploys- Visit
http://localhost:8000/subgraphs/name/scaffold-eth/your-contract/graphqlโ test GraphQL queries yarn graphclient:buildโ generate frontend client artifactsyarn startโ visit the subgraph page to see indexed datayarn subgraph:testโ run Matchstick unit tests
Deploying to Subgraph Studio
- Update
subgraph.yaml: changenetworkfromlocalhostto target network (e.g.,sepolia), add deployedaddressandstartBlock - Create a subgraph on Subgraph Studio
yarn graph auth --studio <DEPLOY_KEY>yarn graph deploy --studio <SUBGRAPH_SLUG>- Update the Graph Client endpoint in
.graphclientrc.ymlto point to the Studio URL
For the full list of supported networks, check The Graph docs.