Geyser · Solana Streaming

Filters & data slices

The point of Geyser is to make the server do the narrowing. The tighter your filters, the less data crosses the wire — which keeps you comfortably inside flat fair-use. Account filters, transaction filters, and accountsDataSlice are the three levers, and they stack.

Account filters

An entry under accounts narrows by four things, ANDed together:

  • ·account — a list of explicit pubkeys. Watch a handful of known accounts.
  • ·owner — the program that owns the account. Stream every account a program controls.
  • ·filters with memcmp ({ offset, base58/bytes }) to match bytes at a position, and datasize to match exact account length.

SPL token accounts make a clean example. Every one is owned by the Token program and is exactly 165 bytes, with the mint pubkey at offset 0. A memcmp at offset 0 plus a 165-byte datasize gives you a live feed of just one mint's token accounts.

TypeScript
import Client, {
CommitmentLevel,
SubscribeRequest,
} from "@triton-one/yellowstone-grpc";
const client = new Client(
"https://geyser.therpc.io:443",
"<YOUR_API_KEY>",
undefined
);
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const request: SubscribeRequest = {
accounts: {
usdc_token_accounts: {
account: [],
// SPL Token program owns every token account
owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
filters: [
// token account layout: mint pubkey sits in the first 32 bytes
{ memcmp: { offset: 0, base58: USDC_MINT } },
// and the struct is exactly 165 bytes
{ datasize: 165 },
],
nonemptyTxnSignature: undefined,
},
},
slots: {},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
};

Trim the payload with accountsDataSlice

Matching the right accounts is half the job; you rarely need every byte they carry. accountsDataSlice takes an array of { offset, length } ranges and the server returns only those byte windows. For a token account you might want the owner (offset 32, 32 bytes) and the amount (offset 64, 8 bytes) and nothing else — 40 bytes instead of 165 on every update.

TypeScript
const request: SubscribeRequest = {
accounts: {
usdc_token_accounts: {
account: [],
owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
filters: [
{ memcmp: { offset: 0, base58: USDC_MINT } },
{ datasize: 165 },
],
nonemptyTxnSignature: undefined,
},
},
// ship only the owner (offset 32, 32 bytes) and amount (offset 64, 8 bytes)
// instead of all 165 bytes of every token account
accountsDataSlice: [
{ offset: 32, length: 32 },
{ offset: 64, length: 8 },
],
slots: {},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
commitment: CommitmentLevel.PROCESSED,
};

Transaction filters

Entries under transactions select by the accounts a transaction touches and by two boolean flags:

  • ·accountInclude — the tx must reference at least one of these keys (a program id works here).
  • ·accountExclude — drop the tx if it touches any of these.
  • ·accountRequired — every listed key must be present, not just one.
  • ·vote and failed — set both to false to drop the vote chatter and errored attempts most consumers ignore.

Streaming every successful transaction that hits a given program is the common case:

TypeScript
const PUMP_FUN = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
const request: SubscribeRequest = {
accounts: {},
slots: {},
transactions: {
pump_fun_trades: {
vote: false, // skip vote transactions
failed: false, // skip transactions that errored
signature: undefined,
accountInclude: [PUMP_FUN], // must touch this program
accountExclude: [],
accountRequired: [], // every listed key must appear
},
},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
commitment: CommitmentLevel.CONFIRMED,
};

transactionsStatus vs transactions

Both maps take the same filter shape, but they answer different questions. transactions ships the whole transaction and its metadata — instructions, inner instructions, balances, logs. transactionsStatus ships the signature and whether it succeeded, and not much more. If you are confirming that a transaction you already know about landed, the status feed costs a sliver of the bandwidth.

TypeScript
const request: SubscribeRequest = {
accounts: {},
slots: {},
transactions: {},
// same filter shape, a fraction of the payload — signature + status only
transactionsStatus: {
my_program_acks: {
vote: false,
failed: false,
signature: undefined,
accountInclude: [PUMP_FUN],
accountExclude: [],
accountRequired: [],
},
},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
commitment: CommitmentLevel.CONFIRMED,
};

Staying inside fair-use

Narrow filters and a tight accountsDataSlice are how you keep volume sane on flat pricing. A full blocks subscription is the highest-bandwidth thing you can ask for — prefer blocksMeta or a filtered transactions feed unless you genuinely need every byte of every block. Operational limits and packs live on the product page.