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.
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.
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. Set ethereum: "localhost:http://host.docker.internal:8545" to connect to the local chain. Add extra_hosts: ["host.docker.internal:host-gateway"].
ipfs: ipfs/kubo:v0.39.0 (not the legacy ipfs/go-ipfs) — port 5001, volume ./data/ipfs:/data/ipfs
postgres: postgres — port 5432, volume ./data/postgres:/var/lib/postgresql/data. Credentials: user graph-node, password let-me-in, db graph-node. Must set POSTGRES_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:
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.tsimport * as fs from"fs";
importtype { Abi } from"viem";
constDEPLOYED_CONTRACTS_FILE = "../nextjs/contracts/deployedContracts.ts";
constGRAPH_DIR = "./";
functionpublishContract(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),
);
}
asyncfunctionmain() {
const fileContent = fs.readFileSync(DEPLOYED_CONTRACTS_FILE, "utf8");
const match = fileContent.match(
/const deployedContracts = ({[^;]+}) as const;/s,
);
if (!match?.[1]) thrownewError("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.
~~/.graphclient is the generated runtime artifact. It only exists after yarn graphclient:build. The .graphclient/ directory should NOT be committed — it's generated from .graphclientrc.yml and 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.