useFeedHistory

Anchor-feed history across a range of voting rounds, where every round in the range comes back as its own point — including the ones that answered nothing.

import { useFeedHistory } from '@flarekit-dev/react'

useFeedHistory walks a range of voting rounds and classifies every one of them. A round that answered nothing is a point in the series, not a row that disappeared.

That exists because of a real, non-erroneous condition: the Relay's merkle root outlives the data. Measured on Coston2, the data-availability host served round 1130919 while round 1127919's root was still set on chain and its leaf was gone. The chain asserts a commitment for a round whose value can no longer be retrieved. Rendering that as an error would be wrong — nothing failed — and rendering it as a missing value would be wrong too, because the chain did commit to one. It gets its own status.

Where that edge sits is not a constant. Two bisections run minutes apart on 2026-08-05 returned boundaries 513576 rounds apart, so no single retention floor is reported. The boundary is whatever this query observed, per query.

Live#

This is the one hook on the site whose read leaves the chain: each round's leaf comes from the data-availability host over HTTP, and the hook takes no fetch implementation, so no mock can answer it and driving it here would mean a network call out of a documentation page. These pages make none.

So the range below is deliberately empty — toRound sits one round below fromRound, the walk runs zero times, and no host is contacted. What you are looking at is genuinely what the hook returned: the observation envelope, naming the provider it would have asked, around a point list with nothing in it. The populated table — retention boundary included — is on the FeedHistoryTable page, driven by the gallery's recorded Coston2 measurement.

empty range · no network
useFeedHistory — live return value
// reads on mount

Read from the running hook against the mock kit, on this render.

Usage#

import { FEED_CATEGORY, encodeFeedId } from '@flarekit-dev/core'
import { useFeedHistory } from '@flarekit-dev/react'
import { FeedHistoryTable } from '@flarekit-dev/react-ui'

const FEED_ID = encodeFeedId(FEED_CATEGORY.crypto, 'FLR/USD')

export function History({ reader }) {
  const { data, loading, refresh } = useFeedHistory({
    reader,
    chainId: 114,
    feedId: FEED_ID,
    fromRound: 1415856n,
    toRound: 1415859n,
  })

  return (
    <FeedHistoryTable
      history={data}
      feedName="FLR/USD"
      loading={loading}
      now={Date.now()}
      onRefresh={refresh}
    />
  )
}

The range is walked newest first, which is the order a surface reads in, and every round in it costs a request — so a range is a decision about how much you are asking the host for, not a window to widen for free.

Parameters#

PropTypeDefaultDescription
readerrequiredRoundReaderReads the chain, not the host. It is asked one thing: what root the Relay published for a round whose leaf did not come back.
chainIdrequirednumberWhich deployment. It selects both the Relay address and the data-availability host, from @flarekit-dev/contracts.
feedIdrequiredFeedIdOne feed. History is per feed because a leaf is per feed.
fromRoundrequiredbigintInclusive lower bound of the range.
toRoundrequiredbigintInclusive upper bound. The walk starts here and counts down.
apiKeystringA transport credential for the data-availability host, not a user identity. Omitted, the public key for the network is used.

Return type#

ObservedRead<Observation<FeedHistory>>, from useObservedRead.

PropTypeDefaultDescription
dataObservation<FeedHistory> | undefinedundefined until the walk finishes. Its source class is provider — the host serves the leaves, and only the root they hash to is on chain.
loadingbooleanTrue only while no history has ever arrived.
errorstring | undefinedThe walk threw. A round that could not be classified is a point with a status, not an error; this is the read itself failing.
refresh() => voidWalk the range again. The previous history stays on screen until the new one lands.

Inside data.value: feedId, points, and two figures discovered rather than assumed — oldestRetrievedRound, the oldest round in this range the host actually served, and retentionBoundary, the newest round that was committed and unretrievable. The boundary moves, so it is reported per query and never cached as a constant.

States#

Each point in points carries one of four statuses, and they are four different sentences:

  • retrieved — the leaf came back, with its proof. The point carries the AnchorFeedWithProof and its own exponent.
  • committed_not_retrievable — the Relay published a root for this round and the host no longer serves the leaf. The commitment exists; the value is gone. Not an error, and not an absence.
  • not_finalized — no root was published for this round under FTSO. Nothing was ever committed.
  • could_not_ask — the host could not be reached. This says nothing about the round either way, and it is never collapsed into one of the two above.

An absence is confirmed three times before it is believed. The host intermittently serves an empty 200 for a round it demonstrably holds — measured on 2026-08-05, where round 812988 answered empty and then returned a real value on three paced retries seconds later. An unconfirmed absence flowing into the root check would manufacture a permanent "this value is gone forever" claim out of a blip.

Mock to live#

reader swaps the way every other FTSO hook's does. The data-availability host does not: it is reached with the real fetch, at the URL @flarekit-dev/contracts holds for that network.

import { chainFor } from '@flarekit-dev/contracts'
import { createPublicClient, http } from 'viem'

const reader = createPublicClient({ transport: http(chainFor(114).rpcUrl) })

What it will not do#

It will not drop the rounds that answered nothing, and it will not draw a line between the two values either side of a gap — a rendered interpolation across a retention boundary is an invented price, which is why the spec forbids a chart here at all.

It will not conclude that a value is permanently gone from a single empty response, and it will not report a host it could not reach as a round that was never finalized. It will not carry a compiled-in retention floor: the boundary is whatever this query observed. It will not poll.