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 input | Fallback status | What to do |
|---|---|---|
| Contract events | Good fit | Index the contracts and write a SQL view for the query shape. |
| Deterministic transforms of events | Good fit | Express them as a view or a first-party recipe. |
Contract state via eth_call | Not current parity | Keep the subgraph for that field, or redesign around event-derived state. |
| IPFS or another off-chain fetch | Not current parity | State the omission explicitly; do not call the result a full port. |
| Bespoke off-chain state or external APIs | Not a Nuthatch fallback | Keep 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
- ABIs, events & tables - what the event surface looks like in SQL
- Authored SQL views - write the narrow derived reads
- Run it in production - backfill, expose, monitor, and back it up
- Nest identity & reuse - package versions and inexpensive updates