Worked example
From subgraph to nest: indexing Graph Horizon
Most of this runs on v0.6.2. Multi-contract nests, Arbitrum One, per-event
tables, user-authored views/*.sql, init --from, and the
nuthatch check parity framework all ship today. Still design targets - marked
where they appear - are the auto-generated GraphQL layer, SSE streams, and AI-assisted schema
and test generation from an ABI or a subgraph manifest. See the roadmap
for the dividing line.
This example indexes The Graph's own network activity - operator mappings, allocations, indexing rewards and query fees, delegation balances - without using The Graph to do it. It runs on your hardware, against Arbitrum One, from three contracts.
It is a real community subgraph, not a toy. It is based on Paul Barba's Horizon Indexer Performance subgraph, and it was chosen precisely because it is awkward: three contracts, state derived across their events, delegation balances that fold deposits and withdrawals together, and rewards and fees bucketed by hour and by day. An ERC-20 transfer counter this is not.
One command, three contracts
nuthatch init --chain arbitrum-one \
0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03 \
0xb2Bb92d0DE618878E438b55D5846cfecD9301105 \
0x3bE385576d7C282070Ad91BF94366de9f9ba3571
The ABIs come from Sourcify - all three contracts are verified - and each contract's
deployment block is detected automatically, so the backfill knows where to start.
What you get back is a nest: a scaffolded project with
nuthatch.toml, the resolved abis/, and a schema.json,
ready for your views/ and checks/.
A nest is the unit of indexing definition - a directory of config and views you can
commit, share, and re-run anywhere. A published nest is reusable directly:
nuthatch init --from <git-url> pulls someone else's and runs it
against your own node.
Queryable before you model anything
Every event in those ABIs is a table the moment the nest exists -
allocation_created, stake_delegated,
query_fees_collected, and the rest - filling in as the backfill streams
through history. Before you write a single line of modelling, you can ask real
questions of the raw layer:
select subgraph_deployment_id,
indexer,
tokens / 1e18 as grt
from allocation_created
where block_number > (select max(block_number) from allocation_created) - 10000
order by tokens desc
limit 10; In a subgraph you get nothing until the handlers are written and the thing has synced. Here the raw event layer is free, and it is queryable while you are still deciding what the entities should be.
Entities are views, not handlers
The subgraph expresses its entities as AssemblyScript handlers: imperative read-modify-write against an entity store, one function per event, mutating rows by hand. In the nest those become declarative incremental views. The engine maintains them as events arrive, and on a reorg it retracts the affected rows and lets every view that depends on them correct itself. There is no rollback code, because there is nothing to roll back by hand.
create view indexers as
select indexer,
sum(tokens_indexer_rewards) as total_rewards_earned,
sum(query_fees) as total_query_fees,
count(*) filter (where status = 'Active') as active_allocation_count
from allocations
left join reward_events using (allocation_id)
group by indexer; The rest of the subgraph maps across the same way:
- operators - the latest
OperatorSetper indexer. - allocations -
Created,Resized, andClosedfolded into current state and status. - delegations -
Delegated,Locked, andWithdrawnfolded per delegator into a running balance. - daily and hourly aggregations - ordinary time-bucketed views of rewards, fees, and delegation events, maintained incrementally.
- global stats - a view over the views above.
The global-singleton entity is a well-known subgraph anti-pattern: every event contends on one hot row, and it serialises writes across the whole indexer. As an incremental aggregate it costs nothing - a running total the engine already keeps.
Parity you can check, not just claim
A nest ships checks/*.sql - read-only queries over its sealed data - and
nuthatch check compares each result to a recorded fixture, printing a row-level
diff and exiting non-zero on a mismatch. For this nest the fixtures are the deployed
subgraph's own answers at a pinned block, so nuthatch check is the
parity test. It is hermetic - committed fixtures, no live endpoint - so it runs in CI with no
network.
nuthatch check Planned: generating a schema, views, and a golden test suite from an ABI, or
translating a subgraph manifest with init --from-subgraph, with every proposed
view flagged for human review. Today you author the views; nuthatch check is
what proves them.
Serve it
nuthatch dev serves one local endpoint: /tables and
/table/{name}, read-only /sql, balances,
/metrics, and the built-in MCP server. An auto-generated GraphQL layer and SSE
streams - shaped so queries written against the original subgraph carry over with little
change - are planned:
{
indexers(first: 10, orderBy: total_rewards_earned, orderDirection: desc) {
indexer
total_rewards_earned
total_query_fees
active_allocation_count
}
} The MCP server is the same endpoint from an agent's side. Point Claude, or any client, at your own instance and ask which indexers earned the most this week - schema discovery is built in, so it knows the shape without being told, and nothing leaves your machine.
So this page described indexing The Graph's network without The Graph - on your own hardware, with no deploy key, no gateway, and no query fees. The finished nest is published as horizon-nest, and the subgraph it is based on is worth reading. Read the manifesto, or star the repo.