The Graph Subgraphs skill for Codex
Index contract events into queries that stay fast. Codex designs subgraph entities from the queries your frontend needs, starts indexing at the deployment block, marks event-log entities immutable and uses derivedFrom instead of hand-kept arrays. Mappings avoid slow eth_calls, factory contracts get templates, every handler has a Matchstick test, and publishing to the paid network waits for your go-ahead.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- thegraph-subgraphs Installs to ~/.agents/skills/thegraph-subgraphs/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install thegraph-subgraphs The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project thegraph-subgraphs Installs to .agents/skills/thegraph-subgraphs/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/thegraph-subgraphs.zipunzip -o thegraph-subgraphs.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) thegraph-subgraphs PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $thegraph-subgraphs, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds and maintains The Graph subgraphs - subgraph.yaml manifests with correct event signatures and startBlock, schema.graphql entities (immutable, derivedFrom, timeseries and aggregations), AssemblyScript mappings, data source templates for factory contracts, Matchstick unit tests, Subgraph Studio deploys and paginated GraphQL queries. Use when asked to index contract events, create or fix a subgraph, speed up a slow-syncing subgraph, add an entity, debug an indexing error, or query subgraph data from a frontend.
Use it when
- A dapp needs historical or aggregated on-chain data in queries
- A subgraph fails with an indexing error or syncs slowly
- A factory contract spawns child contracts whose events must be indexed
- Frontend queries hit pagination limits or stale data
Use something else when
- You only need a few live reads in the UI: use viem-wagmi
- The contract itself must change to emit the data you need: use solidity-contracts
How to check the result
graph codegen, build and test pass, the deployment reaches chain head in Studio with hasIndexingErrors false, and a sample query matches values read directly from the contract.
The mistake it prevents
Leaving startBlock at zero or calling the contract in every handler, which turns a minutes-long sync into days.
A subgraph is right when it starts at the contract’s deployment block, derives every entity
from event data rather than contract calls where possible, marks append-only entities
immutable, passes Matchstick tests for each handler, and syncs to head with
hasIndexingErrors: false.
Inspect first
- Read
AGENTS.md,package.json,subgraph.yaml,schema.graphqlandsrc/. Note the@graphprotocol/graph-cliandgraph-tsversions; keepspecVersion/apiVersionin the range that CLI generates. - Get the contract ABI from the build output (
out/Vault.sol/Vault.jsonin Foundry,artifacts/in Hardhat). For Foundry artifacts extract theabiarray intoabis/Vault.json. - Find the deployment block (explorer or deployment records). Indexing from block 0 can take days.
- Decide what the frontend needs to query. Design entities from queries backwards.
Installing the CLI (npm i -D @graphprotocol/graph-cli @graphprotocol/graph-ts), deploying
and Matchstick’s first run need network.
Manifest
specVersion: 1.2.0
indexerHints:
prune: auto
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: Vault
network: mainnet
source:
address: "0x1234..."
abi: Vault
startBlock: 19000000
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities: [Deposit, Account]
abis:
- name: Vault
file: ./abis/Vault.json
eventHandlers:
- event: Deposited(indexed address,uint256)
handler: handleDeposited
- event: Withdrawn(indexed address,uint256)
handler: handleWithdrawn
file: ./src/vault.ts
- Event signatures must match the ABI exactly, including
indexedand tuple types. Rungraph codegenafter every change; a mismatch fails there, not silently. indexerHints.prune: autokeeps only recent history and syncs faster; setprune: neverif you need time-travel queries (block: { number: N }) far into the past.- Avoid
callHandlersandblockHandlerswithout filters: they need trace support on some networks and slow indexing badly.
Schema
type Deposit @entity(immutable: true) {
id: Bytes!
account: Account!
amount: BigInt!
blockNumber: BigInt!
timestamp: BigInt!
txHash: Bytes!
}
type Account @entity(immutable: false) {
id: Bytes! # the user address
balance: BigInt!
depositCount: Int!
deposits: [Deposit!]! @derivedFrom(field: "account")
}
- Use
BytesIDs (address, ortxHash.concatI32(logIndex)): smaller and faster than strings. immutable: truefor event-log style entities that are never updated; they index and query faster.- One-to-many: store the reference on the “many” side and use
@derivedFromon the “one” side. Never maintain arrays of IDs by hand; they grow unboundedly and slow every save. - Time buckets (hourly volume, daily TVL): use timeseries entities with
@aggregationwhere your graph-node/CLI version supports them, instead of hand-rolled day entities.
Mapping
import { BigInt } from "@graphprotocol/graph-ts";
import { Deposited } from "../generated/Vault/Vault";
import { Account, Deposit } from "../generated/schema";
export function handleDeposited(event: Deposited): void {
const d = new Deposit(event.transaction.hash.concatI32(event.logIndex.toI32()));
d.account = event.params.user;
d.amount = event.params.amount;
d.blockNumber = event.block.number;
d.timestamp = event.block.timestamp;
d.txHash = event.transaction.hash;
d.save();
let acct = Account.load(event.params.user);
if (acct == null) {
acct = new Account(event.params.user);
acct.balance = BigInt.zero();
acct.depositCount = 0;
}
acct.balance = acct.balance.plus(event.params.amount);
acct.depositCount = acct.depositCount + 1;
acct.save();
}
AssemblyScript rules that bite:
- No closures, no
any, limited generics. Use plain loops. BigIntarithmetic is via methods (plus,minus,times,div);+does not work. UseBigDecimalfor prices and divide only at the end.- Nullable loads:
Entity.load()returnsT | null; check== nullbefore use. Every non-nullable field must be set beforesave()or indexing fails. loadhits the store;loadInBlockis cheaper when the entity can only have been created in the same block.
Contract calls
const vault = Vault.bind(event.address);
const res = vault.try_totalAssets();
if (!res.reverted) { stats.totalAssets = res.value; }
Each eth_call is a round trip to an archive node and is the most common cause of slow
syncs. Prefer emitting the needed values in events (change the contract if you own it). If a
call is unavoidable, use the try_ form so a revert does not halt indexing, and declare it
in the manifest (calls: on the handler, specVersion 1.2.0+) so graph-node can prefetch.
Factory contracts: templates
templates:
- kind: ethereum
name: Pool
network: mainnet
source: { abi: Pool }
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities: [Swap]
abis: [{ name: Pool, file: ./abis/Pool.json }]
eventHandlers:
- event: Swap(indexed address,int256,int256)
handler: handleSwap
file: ./src/pool.ts
import { Pool as PoolTemplate } from "../generated/templates";
export function handlePoolCreated(event: PoolCreated): void {
PoolTemplate.create(event.params.pool);
}
A template only indexes events from the block where create runs onward.
Tests (Matchstick)
import { assert, clearStore, describe, newMockEvent, test, afterEach } from "matchstick-as";
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
import { Deposited } from "../generated/Vault/Vault";
import { handleDeposited } from "../src/vault";
function mockDeposited(user: string, amount: i64): Deposited {
const e = changetype<Deposited>(newMockEvent());
e.parameters = [
new ethereum.EventParam("user", ethereum.Value.fromAddress(Address.fromString(user))),
new ethereum.EventParam("amount", ethereum.Value.fromUnsignedBigInt(BigInt.fromI64(amount))),
];
return e;
}
describe("handleDeposited", () => {
afterEach(() => { clearStore(); });
test("accumulates balance", () => {
const u = "0x00000000000000000000000000000000000000a1";
handleDeposited(mockDeposited(u, 100));
handleDeposited(mockDeposited(u, 50));
assert.fieldEquals("Account", u, "balance", "150");
assert.entityCount("Deposit", 2);
});
});
Run with graph test. Mock contract calls with createMockedFunction.
Build and deploy
graph codegen && graph build
graph test
graph auth <DEPLOY_KEY> # user runs this; never echo the key
graph deploy <subgraph-slug> --version-label v0.1.0
- Deploy to Subgraph Studio first and watch sync status and logs there. Publishing to The Graph Network is a separate, public, paid step (signalling GRT): get explicit user confirmation before publishing.
- Local graph-node via its docker compose is the fastest loop for heavy changes:
graph create --node http://localhost:8020/ <name>thengraph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 <name>.
Querying
query Deposits($user: Bytes!, $lastId: Bytes!) {
deposits(first: 1000, where: { account: $user, id_gt: $lastId }, orderBy: id) {
id amount timestamp
}
_meta { block { number } hasIndexingErrors }
}
firstmaxes out at 1000;skipgets slow beyond a few thousand. Paginate byid_gt.- Show data freshness using
_meta.block.number; a subgraph can lag behind head. - Keep the query API key on a server or restrict it by domain in Studio.
Debugging indexing errors
- Read the failing block and handler from Studio logs or graph-node logs.
- Reproduce with a Matchstick test using that event’s parameters.
- Common causes: unset non-nullable field,
loadof a missing entity, reverted call withouttry_, ABI that does not match the deployed contract, wrongstartBlock. - Fix, bump the version label, redeploy. Grafting onto the failed deployment speeds up recovery in development; avoid grafting for subgraphs you publish.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "The Graph Subgraphs"
short_description: "Index contract events into queries that stay fast"
default_prompt: "$thegraph-subgraphs Create a subgraph that indexes deposits and withdrawals from our vault" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.