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.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- thegraph-subgraphs

Installs to ~/.agents/skills/thegraph-subgraphs/ for every project on this machine.

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

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.

~/.agents/skills/thegraph-subgraphs/SKILL.md

--- name: thegraph-subgraphs description: Builds and maintains The Graph subgraphs - subgraph.yaml manifests with correct event signatures and startBlock, schema.graphql entities (immutable,… license: MIT ---

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

  1. Read AGENTS.md, package.json, subgraph.yaml, schema.graphql and src/. Note the @graphprotocol/graph-cli and graph-ts versions; keep specVersion/apiVersion in the range that CLI generates.
  2. Get the contract ABI from the build output (out/Vault.sol/Vault.json in Foundry, artifacts/ in Hardhat). For Foundry artifacts extract the abi array into abis/Vault.json.
  3. Find the deployment block (explorer or deployment records). Indexing from block 0 can take days.
  4. 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 indexed and tuple types. Run graph codegen after every change; a mismatch fails there, not silently.
  • indexerHints.prune: auto keeps only recent history and syncs faster; set prune: never if you need time-travel queries (block: { number: N }) far into the past.
  • Avoid callHandlers and blockHandlers without 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 Bytes IDs (address, or txHash.concatI32(logIndex)): smaller and faster than strings.
  • immutable: true for 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 @derivedFrom on 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 @aggregation where 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.
  • BigInt arithmetic is via methods (plus, minus, times, div); + does not work. Use BigDecimal for prices and divide only at the end.
  • Nullable loads: Entity.load() returns T | null; check == null before use. Every non-nullable field must be set before save() or indexing fails.
  • load hits the store; loadInBlock is 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> then graph 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 }
}
  • first maxes out at 1000; skip gets slow beyond a few thousand. Paginate by id_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

  1. Read the failing block and handler from Studio logs or graph-node logs.
  2. Reproduce with a Matchstick test using that event’s parameters.
  3. Common causes: unset non-nullable field, load of a missing entity, reverted call without try_, ABI that does not match the deployed contract, wrong startBlock.
  4. 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem