The Data feeds screen has two halves. The top half monitors the three MoQT-published tracks that FirstByte runs for you (slots, blockhash, priorityFee). The bottom half — Private Geyser feed and Feed cadence — is per-organization configuration: you supply your own Yellowstone-Geyser gRPC endpoint, and you choose how often the engine publishes each field. This page documents that per-organization half.

What a private feed is

Normally mod_sol_feeds ingests Solana state and publishes it to shared tracks. With a private feed you give the module your own Yellowstone-Geyser gRPC endpoint instead. The engine (shard 0) ingests slots from that endpoint over HTTP/2 and republishes them to a MoQT track that belongs only to your org. Two things follow from that:
  • The data path is yours. Freshness and update rate depend on the Geyser provider you point at (Triton, Helius, or a self-hosted validator), not on the shared feed.
  • The track is namespaced under your org, so a relay token scoped to another org cannot subscribe to it.

Feed namespace and track names

A configured private feed occupies the namespace:
and the slot track inside it:
geyser.get returns the namespace as namespace, so the console renders the real value rather than deriving it. Before you sign in or before an org is selected, the screen falls back to the literal placeholder sol/feed/<org> — that is a display placeholder, not a usable namespace. The header pill reflects configured from geyser.get: live · <namespace> when an endpoint is stored, not configured otherwise.

Configuring a Yellowstone-Geyser endpoint

The panel is scoped to the active organization and is gated on sign-in. If you are not signed in, you get a Sign in to configure state with a link to the portal; the portal session is shared with the console. Two fields: Validation runs client-side before the mutation fires. An empty or whitespace-only grpc_url, or a URL whose protocol is not https, blocks the save and marks the field invalid — the request is not sent. Errors are only surfaced after the first save attempt. Saving calls:
On success the screen refetches geyser.get and geyser.verify, and clears the token input so the secret does not linger in the DOM. If the mutation itself fails, the server’s message is rendered under the fields verbatim.

Credential sealing and the unseal failure path

The x-token is sealed at rest under your org. The console never reads it back. geyser.get exposes only hasToken — whether a token is stored — and the input renders •••••••• (unchanged) as its placeholder in that case. Separately, geyser.verify answers a different question: can the engine-side KEK actually open the sealed credential? The Token stat combines both signals: When the state is unreadable, the panel shows:
The stored token can’t be unsealed — re-enter it to reseal.
Operationally, treat this as the endpoint is effectively unauthenticated right now. The ciphertext is present but the engine cannot recover the plaintext, so ingest cannot present a valid x-token to your Geyser provider. There is no repair action other than resealing: paste the token into the x-token field again and save. That runs geyser.set with a fresh seal, and the subsequent verify refetch should flip the stat back to sealed ✓. Because the old value is unrecoverable by design, you need the token from your Geyser provider, not from FirstByte.

Feed cadence: tick_ms, blockhash_every, fee_every

Cadence is stored in the same per-tenant geyser config that holds your endpoint, and it is read back by geyser.get as tickMs, blockhashEvery and feeEvery. The two *_every values are multiples of the tick, not independent intervals. The effective publish interval for a field is:
So the console defaults — tick_ms = 400, blockhash_every = 5, fee_every = 8 — mean the base poll runs every 400 ms, blockhash publishes every 2000 ms, and priorityFee publishes every 3200 ms. Lowering tick_ms speeds up everything downstream of it; raising a *_every thins out one field without touching the others. Slider hydration has one quirk worth knowing: when the config loads, the console accepts the stored tickMs only if it is at least 100, otherwise it displays 400. blockhashEvery and feeEvery fall back to 5 and 8 respectively when absent or zero. If you saved a cadence out-of-band and the slider shows a different number than you expect, that fallback is why. Saving calls:
Like the endpoint panel, the whole cadence panel is gated on sign-in — anonymous visitors see Sign in to configure, not live sliders.

When a cadence change takes effect

A saved cadence is written to your feed config immediately and geyser.get is refetched, so the sliders reflect the stored state as soon as the mutation resolves. Propagation to the publisher is separate from persistence:
  • mod_sol_feeds picks up a live cadence change within roughly five seconds. You do not restart anything and you do not resubscribe.
  • The per-tenant blockhash and priorityFee poll is still rolling out. Until it is enabled engine-side, a private feed is slots-only: your blockhash_every and fee_every values are persisted and validated, but no per-tenant blockhash or fee publishing happens yet. The success caption on the screen says exactly this.
So a cadence save today is best understood as a durable declaration of intent for blockhash / priorityFee, and a live change for the tick that drives slot publishing.

Subscribing to your private track

Private tracks are published over MoQT and subscribed over QUIC, the same as the shared tracks listed at the top of the screen. The name to subscribe to is the one shown in the Track stat, sol/feed/<orgId>/slots. Access is enforced by namespace scope on the relay token, not by the feed config. The token you present on the MoQT handshake must carry a namespace claim that covers sol/feed/<orgId> — see Authentication for how ns claims are minted and checked. A token scoped to a different org’s namespace is rejected at subscribe time.

Why freshness, rate and subscribers show a dash

On the track cards and in the selected-track detail, Freshness, Rate and Subscribers render — when the underlying value is unmeasured. The console deliberately does not substitute 0, because “no measurement” and “measured zero” are different conditions and conflating them hides a broken telemetry path. A dash means no sample has arrived for that counter. It does not by itself mean the track is down — the track’s own status pill and the push · WS / poll · HTTP mode pill are the signals for that.

Removing a private feed

The Remove button appears only when configured is true. It calls:
and then refetches both geyser.get and geyser.verify, which flips the header pill back to not configured and the Token stat back to —. Clearing removes the endpoint and the sealed token for the org. Ingest from your Geyser endpoint stops, and with it publishing to sol/feed/<orgId>/slots. Any subscriber still attached to that track stops receiving objects. Re-adding a feed later requires the x-token again, since the sealed copy is gone.

Procedures used by this screen

geyser.get, geyser.verify, geyser.set, geyser.setCadence and geyser.clear are all keyed on the active org, and every mutation is disabled until an org is selected.