The Foundation reports that over 70% of subgraphs call eth_call. But most of those reads aren’t
special - they’re derivable from the events a nest already indexes. Subgraphs fetch them only because
they have no incremental-view engine. Nuthatch does.
A recipe is an authored SQL view (see Authored SQL views) that computes a read from indexed events instead of fetching it. Deterministic, free, no archive node - and a capability subgraphs structurally lack, because their storage isn’t content-addressed.
Use one
nuthatch recipe list
nuthatch recipe add total_supply # writes views/total_supply.sql
nuthatch sql "SELECT total_supply FROM usdc_total_supply"
recipe add drops a CREATE VIEW into the nest’s views/. It’s yours to read, edit, or extend - it’s
just SQL over your tables.
The library
| Recipe | Replaces | Derived from |
|---|---|---|
total_supply | ERC-20 totalSupply() | Σ minted − Σ burned (Transfers from/to 0x0) |
balances | balanceOf(addr) per address | Σ(in) − Σ(out) per address |
holder_count | - | count of non-zero holders |
reserves | Uniswap-V2 getReserves() | the latest Sync(uint112,uint112) per pair |
Requirements.
total_supply/balances/holder_countneed aTransferevent;reservesneeds the pair’sSyncevent. If your nest doesn’t index the source event yet, add it tonuthatch.tomlfirst.
How total_supply works
Under the hood it’s just this - Σ minted minus Σ burned, straight over the transfer table:
CREATE VIEW usdc_total_supply AS
SELECT
COALESCE(SUM(CASE WHEN lower("from") = '0x0000…0000' THEN TRY_CAST("value" AS HUGEINT) ELSE 0 END), 0)
- COALESCE(SUM(CASE WHEN lower("to") = '0x0000…0000' THEN TRY_CAST("value" AS HUGEINT) ELSE 0 END), 0)
AS total_supply
FROM "usdc__transfer";
reserves is a window over Sync - the current reserves are simply the most recent Sync per pair:
SELECT address, reserve0, reserve1 FROM (
SELECT address, reserve0, reserve1,
ROW_NUMBER() OVER (PARTITION BY address ORDER BY block_number DESC, log_index DESC) AS rn
FROM "pool__sync"
) WHERE rn = 1;
Derived vs eth_call
Every recipe is proven against what the chain’s own eth_call returns - the derived value is
byte-for-byte the fetched one, not merely plausible. Where a read genuinely can’t be derived (an
oracle price, an ungoverned param), that’s the irreducible residue a fallback handles - the derive-first
path here needs no archive node and no external dependency. It’s the differentiator, and it’s the
default.
What about metadata?
decimals / symbol / name never change, so they’re not derived - they’re fetched once and cached:
nuthatch metadata fetch
The result lands in metadata.json, keyed by address; a cached contract is never re-fetched.