Documentation Build a subgraph fallback

Build a subgraph fallback

Nuthatch is not automatically a drop-in GraphQL replacement. It indexes deterministic on-chain event data and exposes it as read-only SQL over HTTP. That is enough to make a useful fallback for many subgraph reads, provided the boundary is made explicit before an outage.

The useful question is not “can we replace this entire subgraph?” It is: which reads cannot afford to disappear, and are they derivable from chain logs?

1. Start with the reads that matter

Write down the GraphQL queries or dashboard panels that need a fallback. For each one, identify the entities and fields it uses, then identify how its mappings create them.

Mapping inputFallback statusWhat to do
Contract eventsGood fitIndex the contracts and write a SQL view for the query shape.
Deterministic transforms of eventsGood fitExpress them as a view or a first-party recipe.
Contract state via eth_callNot current parityKeep the subgraph for that field, or redesign around event-derived state.
IPFS or another off-chain fetchNot current parityState the omission explicitly; do not call the result a full port.
Bespoke off-chain state or external APIsNot a Nuthatch fallbackKeep the existing service or build a separate bounded adapter.

This classification is the work. It is far cheaper to say “token metadata is outside this fallback” in advance than to discover it when the GraphQL endpoint is unavailable.

2. Build the event surface

Create a nest from the deployment, then narrow it to the events the affected queries actually need:

nuthatch init 0xYourContract --chain arbitrum-one --alias protocol

In nuthatch.toml, use each contract’s events list to avoid indexing unrelated ABI events. Vendor the ABI with the nest. The ABI is part of the authored package, so anyone running the fallback decodes the same events in the same way.

[[contracts]]
alias = "protocol"
address = "0xYourContract"
abi = "abis/protocol.json"
events = ["PositionOpened", "PositionClosed", "FeesCollected"]

Run nuthatch schema after editing the configuration, then inspect /tables or schema.json. This is the exact SQL surface available to the fallback, not a hopeful reconstruction of the old schema.

3. Recreate only the query shape you need

Put derived reads in views/ as ordinary CREATE VIEW statements. A view can join event tables, aggregate them, and give columns names that make the consuming code simple.

CREATE VIEW protocol_recent_positions AS
SELECT owner, position_id, MAX(block_number) AS last_event_block
FROM protocol__position_opened
GROUP BY owner, position_id;

Views are query-time logic over the hot tip and sealed history. They do not change the deterministic decode path, and editing one does not require a raw-chain backfill. Add meaning to semantic.toml so both people and MCP clients know the view’s grain and its limits.

4. Prove the boundary before you need it

Backfill against an RPC endpoint that can serve the deployment block. Use nuthatch doctor first, then compare a fixed historical range with the subgraph while it is healthy.

nuthatch doctor --rpc https://your-rpc.example --address 0xYourContract
nuthatch dev --seal-direct --rpc https://your-rpc.example
nuthatch sql 'SELECT count(*) FROM protocol__position_opened'

Commit checks for the view where possible. The goal is not merely that a query returns rows; it is that the fallback’s stated event-derived answer agrees at a known watermark.

5. Put the switch in the consumer

Keep the GraphQL subgraph as the normal path. When the relevant query fails or becomes unhealthy, send only that read to the nest’s SQL endpoint or to a small adapter which translates the narrow result shape your application needs.

If the nest is already indexed, the switch is immediate. Keep the endpoint behind your own gateway: TLS, authentication, and a named-query allowlist where callers should not have free-form SQL. A public nest without an allowlist is an open query engine, which is a rather generous outage plan.

The fallback should carry its boundary in its UI and API documentation. “Event-derived position activity, not IPFS metadata” is a good contract. “Parity, probably” is not.

A useful handoff

If somebody else will operate the fallback, give them:

  • the deployment address and chain;
  • the pinned ABI and exact event list;
  • the GraphQL queries or panels being protected;
  • the SQL views and a fixed-watermark comparison;
  • the explicit list of fields that remain outside the fallback;
  • the gateway route and health signal the consumer uses to switch.

That is enough for another operator to run the same data path without having to reverse-engineer intent from a stale subgraph manifest during an incident.

Next