Build a replay-safe Solana wallet monitor with Yellowstone filter shards, complete account-key resolution, bounded queues, and reconciliation.
Rrpc edge Team·Jul 22, 2026·13 min read
Frequently asked questions
How many Solana wallets can one Yellowstone gRPC stream monitor?
There is no provider-independent number. The usable count depends on filter limits, wallet activity, transaction overlap, selected commitment, update size, regional bandwidth, and consumer speed. Treat 500-plus wallets as an architecture scenario and measure the actual workload.
Does accountInclude mean a monitored wallet signed the transaction?
No. It means the transaction mentions at least one included account. Resolve the full account-key list and inspect the message header to determine signer and fee-payer roles.
Should I store one copy of a transaction for every matched wallet?
Store the canonical transaction once by signature, then store a separate edge for each matched wallet and role. This supports one transaction that mentions several monitored wallets without duplicating the payload.
How should a wallet monitor handle Yellowstone reconnects?
Give every shard a reconnect generation and durable progress record, request replay where the provider supports it, deduplicate before side effects, and reconcile the uncertain interval with bounded RPC backfill.
Is getSignaturesForAddress enough for production wallet monitoring?
It is useful for bounded reconciliation and lower-volume indexing. At larger scale, a polling loop per wallet increases request volume and can delay detection, so streaming should carry the live path while RPC repairs known gaps.
A wallet monitor stops being simple when one transaction can match several customers, one customer
can own several addresses, and a reconnect can replay an event already processed.
The answer is not one polling loop per address. Yellowstone gRPC carries the live path; resolve each
transaction completely, store one canonical event, and attach every relevant wallet through a
separate match record.
"Watch this wallet" is not a complete requirement. A transaction can involve an address as:
Fee payer.
Transaction signer.
Writable account.
Read-only account.
Token-account owner discovered from state.
Authority referenced by an instruction.
Address loaded through an address lookup table.
Decide which roles the product exposes. A copy-trading product may care about signers and decoded
swaps. A payment product may care about token-account owners and settlement. A risk monitor may care
about any mention but label the role precisely.
Yellowstone's transaction filter can narrow traffic by accounts. In the upstream
SubscribeRequestFilterTransactions,
account_include, account_exclude, and account_required are separate fields. Inclusion is a
transport filter. It is not proof that the included address signed or initiated the transaction.
Providers may cap accounts per filter, filters per request, streams per connection, message size, or
replay depth. Do not embed one vendor's current limit into the architecture.
Start with a configurable shard size and keep stable wallet assignment. Stable shards make reconnect
and incident traces easier to understand.
wallet-shards.ts
export type WalletShard = { id: string; wallets: readonly string[];};export function buildWalletShards( wallets: readonly string[], maxWalletsPerShard: number,): WalletShard[] { if (!Number.isInteger(maxWalletsPerShard) || maxWalletsPerShard < 1) { throw new Error("maxWalletsPerShard must be a positive integer"); } const unique = [...new Set(wallets)].sort(); const shards
Sorted fixed-size chunks are sufficient for an initial deployment. High-volume wallets can later
move to dedicated shards, but only after telemetry shows that one address dominates bytes or queue
work.
For each shard, track:
Connected state and reconnect generation.
Last update receive time.
Highest observed slot by commitment.
Updates and bytes per second.
Queue depth and age of the oldest item.
Decode errors, duplicate observations, and dropped work.
Wallet count and the configuration version that produced the filter.
Wallet count alone is a weak capacity signal. Five hundred dormant addresses may be cheaper than a
small group of active program or routing accounts.
The upstream Yellowstone gRPC README documents the
subscription client, filter model, pings, and replay-related interfaces. Confirm the generated
client version and the provider's supported schema before deploying copied code.
processed gives the live path an early view. It also means the application needs a policy for
commitment upgrades and fork reconciliation. Products that can wait for confirmed or finalized
state should choose that commitment deliberately instead of inheriting the example.
Versioned Solana transactions can load addresses from lookup tables. Matching only static message
keys can miss a monitored wallet or assign the wrong index to an instruction.
Then derive roles from the message header and instruction account indices. Do not label every
included wallet as a signer. The first static account is generally the fee payer, while the message
header defines how many leading static keys are required signers. Loaded addresses are not
transaction signers.
For token transfers, the token account in the transaction may differ from the customer wallet stored
in your registry. Resolve and cache token-account ownership from account state, invalidate the cache
when ownership or account lifecycle changes, and retain the evidence used for the match.
Make the canonical transaction key the signature. Make the match key at least
(signature, wallet, role). Instruction-level events should also include the outer and
inner instruction positions in that event key.
Upserts should allow a later observation to strengthen commitment without repeating downstream
actions. Put notification or strategy execution behind its own idempotency key.
The stream reader should not perform database writes, remote metadata requests, and webhooks inside
the receive callback. Decode the envelope, add it to a bounded queue, and return to reading.
Backpressure policy must be explicit:
Alert on queue age before memory pressure becomes critical.
Slow or pause optional enrichments first.
Preserve the raw observation or a recoverable cursor when possible.
Reconnect only when the client or transport requires it, not as a queue-clearing strategy.
Mark the uncertain slot interval for reconciliation if any update may have been lost.
Scaling consumers only helps when work can be partitioned without violating order. Route the same
signature to one partition so duplicate observations serialize against the same idempotency key.
Our Yellowstone bandwidth guide shows how update size
and rate turn into network cost. Record bytes after transport decoding as well as the size of stored
and enriched records. All three affect the capacity plan.
A reconnect creates a new observation generation. It does not create a new business event.
Persist per-shard state with the last observed slot, last processed signature, reconnect generation,
disconnect time, and configuration version. Request replay from the earliest uncertain slot where
the provider supports it. Deduplicate everything the replay returns.
RPC remains useful for repair. Solana's official
getSignaturesForAddress returns confirmed
signatures for an address in reverse chronological order. Apply it to a bounded wallet and time or
slot interval, then fetch and compare the missing transactions.
Solana's payment indexing guide describes
address-signature lookup followed by transaction retrieval for indexing payment activity. That is a
reasonable repair primitive. Running it continuously for every wallet turns the live architecture
back into a polling fleet.
Reconciliation should produce an auditable result:
Expected signatures in the interval.
Signatures already stored.
Missing records recovered.
Stored records invalidated or updated after stronger commitment.
Solana addresses are public. The mapping between an address, a customer, notification preferences,
and internal risk labels is not.
Provider filter logs should omit customer identifiers. Restrict registry access, encrypt secrets,
and apply retention limits to raw payloads and enriched activity. Customer removal of an address must
propagate the configuration change to streams, caches, backfill jobs, and notification routes.
Version the registry. Every stream observation should be attributable to the shard configuration
that admitted it, especially when a wallet moves between shards during a rebalance.
Fill one row per shard under a production-shaped load:
Measure
Observed value
Acceptance limit
Action when exceeded
Wallets and active wallets
Split or isolate a hot wallet
Updates per second, p50 and peak
Add consumer capacity or reduce scope
Inbound bytes per second
Review filters, region, and plan
Oldest queue item age
Pause enrichment or add workers
Decode error rate
Quarantine payload and page owner
Duplicate observation rate
Inspect reconnect or overlapping shards
Stream-to-store delay
Find transport, queue, or database bottleneck
Reconciliation duration
Reduce interval or increase repair capacity
Missing records after repair
Keep shard degraded and investigate
Run the sheet at average and burst traffic. Repeat after adding a new wallet cohort, changing a
filter, moving regions, or altering decoded payloads. The right shard size is the one that stays
inside the application's budgets with room for reconnect and repair.
To test this design against RPCEdge Yellowstone streams, choose an RPCEdge plan
and bring a representative wallet set, expected activity mix, and acceptance sheet.