Geyser · Solana Streaming

Geyser concepts

Geyser is a single bidirectional gRPC stream. You send one SubscribeRequest describing what you want, and the server pushes SubscribeUpdate messages back as state changes — no polling, no per-call round trips. The proxy forwards your request to Solana's Yellowstone endpoint unchanged, so the full Yellowstone filter surface is yours.

Push, not poll

JSON-RPC makes you ask. You call getAccountInfo on a timer and diff the result, paying a full request round trip every poll and learning about a change only on your next tick. A WebSocket logsSubscribe pushes, but it speaks a narrow slice of the firehose and filters thinly.

Geyser inverts that. The match runs on the server: you declare the accounts, programs, and transaction shapes you care about, and only matching updates cross the wire — the moment they happen, at the commitment you chose. One TCP/TLS session carries every subscription type at once.

What you can subscribe to

A SubscribeRequest is a set of named filter maps. Older write-ups call Geyser a “four types” stream; the real proto surface is wider. Every map below ships in one request, and you mix as many as you need.

fieldbandwidthwhat it streams
slotslowSlot status as the chain advances — one tiny message per slot. The cheapest heartbeat for tracking chain head.
accountsmediumAccount writes, filtered by explicit pubkeys, program owner, or memcmp/datasize predicates. Cost scales with how much data the matched accounts carry.
transactionshighFull transactions matching account include/exclude/required, vote and failed flags. The whole message + meta lands on the wire.
transactionsStatuslowSignature + status only — no instructions, no meta. Use it when all you need is "did tx X land, and in which slot".
blockshighWhole blocks: every transaction and account update inside them. The heaviest subscription — filter it down or avoid it.
blocksMetalowBlock headers without the per-transaction payload — blockhash, parent slot, block time, tx counts. A fraction of a full blocks stream.
entrymediumProof-of-history entries as the leader produces them — sub-slot granularity below the block boundary.
accountsDataSlicelowNot a filter but a shaper: an array of {offset, length} ranges that trims account data down to the bytes you actually read.
pinglowIn-stream ping toggle the server echoes back — a way to keep a quiet stream warm without opening a side channel.

Reach for blocksMeta over blocks, and transactionsStatus over transactions, whenever the lighter feed answers your question — the bandwidth gap is large.

Commitment levels

Commitment is the trade between speed and certainty. Set it once per request via CommitmentLevel. When you leave it unset the stream runs at PROCESSED.

levellatencyrollback risk
PROCESSEDLowest — the node has seen the slot but the cluster has not voted on it yet.Can be dropped on a fork. Treat its data as provisional.
CONFIRMEDA supermajority has voted for the slot. A few hundred ms behind PROCESSED.Reorg is very unlikely but not formally impossible.
FINALIZEDHighest — the slot is rooted, roughly 30+ slots back.Irreversible. Use it when a rollback would corrupt your state.
Choosing a commitment level is a keyed feature. Public, keyless requests that set the commitment filter are rejected with gRPC PermissionDenied; attach a TheRPC API key to unlock CONFIRMED and FINALIZED. See Resilience & keepalive for the exact error and how to handle it.

Filters combine

Each map is keyed by a name you pick. Put several named entries under one map to watch unrelated targets in parallel — say raydium_pools and spl_token side by side under accounts. When an update arrives it carries the filter names it matched, so your handler can route on the name instead of re-deriving why the message showed up.

TypeScript
import Client, {
CommitmentLevel,
SubscribeRequest,
} from "@triton-one/yellowstone-grpc";
const client = new Client(
"https://geyser.therpc.io:443",
"<YOUR_API_KEY>",
undefined
);
const request: SubscribeRequest = {
accounts: {
// one map can hold several named filters
spl_token: {
account: [],
owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
filters: [],
nonemptyTxnSignature: undefined,
},
},
slots: { head: { filterByCommitment: undefined } },
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
// commitment selection needs an API key — keyless requests default to PROCESSED
commitment: CommitmentLevel.CONFIRMED,
};

Ready to write filters? The Filters & data slices page covers memcmp, owner predicates, and accountsDataSlice with worked code.