vaultsDocumentation

Indexer

Implemented

Historical reads come from an indexer. Live state never does.

#The split

DataSourceIf it is down
Vault balances, snapshots, pause flags, NAVChain, every requestNothing to be down
Positions and divergenceChain, every requestNothing to be down
Vault activity and 24-hour volumeChain, into an in-memory logFalls back to the last good read
Receipt historyIndexer, falling back to the chain logWindow shortens to 24 hours, and the response says so
Returns (24H / 7D / 30D)Indexer NAV seriesNo return is shown, and it says why

The database is a read model. It is never authoritative: no vault decision, no share price, and no eligibility result is ever taken from it.

#What serves history

Robinhood Chain produces a block roughly every 100 ms, so scanning from genesis on demand is not viable. Two things answer instead, and a caller is always told which one did.

The chain log. The API keeps a rolling 24-hour window in memory, back-filled once at startup in chunks small enough for a provider to serve, then tailed as new blocks arrive. It needs no infrastructure and cannot be stale. It also cannot answer for anything older, because nothing is retained that long.

The window is measured in seconds, not blocks. A block-counted window is only equivalent to "24 hours" while blocks arrive on schedule, and the count it produces feeds source eligibility — so a window that quietly stretched would also make accounts look eligible when they are not.

The indexer. Where it is running, /v1/receipts is served from PostgreSQL and reaches back as far as ingestion has got. The API prefers it and falls back to the chain log when it cannot be read.

#Why every response says where it came from

The fallback has a failure mode worth naming: when the database goes away, the answer becomes shorter, not absent. A caller asking for six months would get 24 hours, with a 200 and no error — and a short list is indistinguishable from a complete one. "The database is down" would read as "nothing happened".

So every receipts response carries coverage:

json
{
  "source": "indexer",
  "windowSeconds": null,
  "oldestBlock": "44",
  "degraded": false,
  "reason": null
}

windowSeconds is null for the indexer, which is bounded by how far it has ingested rather than by a configured window — oldestBlock is the real answer to "how far back". When the chain log answers instead, windowSeconds is 86400.

degraded is the load-bearing field. It is true only when the indexer should have answered and could not, so a stack deliberately running without a database is never flagged, while one whose database has failed always is. The interface shows that state rather than quietly rendering a shorter list.

Running without DATABASE_URL is a supported configuration. It costs history beyond a day, and nothing else.

#Reorganisation handling

Orbit chains give fast soft confirmation from the sequencer, but an L1 reorganisation can still reorganise unfinalised L2 blocks.

INDEXER_CONFIRMATIONS sets the depth treated as final. Rows shallower than that are ingested and marked soft, and every surface that displays them labels them unfinalised.

#Configuration

SettingPurpose
INDEXER_START_BLOCKWhere ingestion begins
INDEXER_CONFIRMATIONSDepth treated as final
INDEXER_BATCH_SIZELogs per range request
DATABASE_URLPostgreSQL connection

Note. With no DATABASE_URL, the API starts anyway and reports the database as not configured in /v1/status. Live vault state and 24-hour activity are unaffected, because they are read from chain. Receipt history is served from the chain log instead, and its coverage says so. A missing read model degrades the product; it does not break it.

#The NAV series

A return needs two points in time, and the chain only ever shows one. The indexer records what one share is worth on a cadence (INDEXER_NAV_INTERVAL_SECONDS), using the vault's own convertToAssets(1e18) — the contract's answer to what a redeemer would get, virtual offset included, rather than a ratio computed here that could disagree with it.

Per share, not per vault. A vault that doubles its TVL through deposits has returned nothing to anyone. A series built on total value would report a 100% gain for taking someone's money, which is the single most misleading number this product could publish.

Gaps are recorded as gaps. When an asset has no trustworthy price the vault refuses to report NAV, and the snapshot is written with a null value and the contract's reason rather than a zero. Returns skip those points instead of measuring from them: a return computed from a fabricated zero would show a total loss followed by a total recovery, neither of which happened.