The logsSubscribe method opens a WebSocket subscription that notifies you whenever a transaction produces log output matching your filter. Each notification carries three things: the slot, the transaction signature, an error field, and the array of log lines the runtime collected. It does not carry amounts, balances or decoded instructions, which is the single most important fact about it.

Used correctly, a log subscription is a trigger: it tells you which transaction to go and read. Used incorrectly, it becomes a data source, and every pipeline built that way eventually reports wrong numbers because a developer changed a string in a program upgrade.

What logsSubscribe actually is

During execution, Solana programs can write messages to a per-transaction log buffer. The runtime also writes its own lines around every program invocation: the program id being invoked, the depth of the invocation, the compute units consumed, and whether the call succeeded or failed. When the transaction finishes, that buffer is attached to the transaction result.

A log subscription streams that buffer to you as soon as the transaction is processed at your chosen commitment level. It is the same data you would see in the meta.logMessages field of a fetched transaction, delivered earlier and without you having to ask. The subscription itself is described in the Solana WebSocket API reference.

What makes it popular is the filter. You can ask for every transaction that mentions a specific address, which for market work usually means a program id such as an AMM, or a specific pool account. That gives you a narrow, high-signal feed without subscribing to the entire cluster, and it works on a standard RPC endpoint without any special product.

The parameters and what they constrain

The method takes a filter and an optional configuration object. The filter is where most of the design decisions live, and there are only three legal shapes.

FilterWhat you receivePractical use
"all"Every transaction except simple vote transactionsBroad scanning where you filter locally; heavy on mainnet
"allWithVotes"Everything, including consensus vote trafficValidator and consensus research, rarely market work
{ "mentions": [ address ] }Transactions that reference exactly that one accountWatching a program or a specific pool

The configuration object carries the commitment level. It is optional and defaults to the node's strictest setting, which is why a newcomer who omits it often reports that the stream feels slow: they are unknowingly consuming finalized data. Setting confirmed explicitly is the normal choice for a live panel, and the reasoning behind that is in confirmed versus finalized.

What a notification contains

Understanding the payload shape prevents most of the mistakes that follow. There are four fields worth knowing, and the table records what each one is good for and where it misleads.

FieldMeaningWhere it misleads
context.slotThe slot in which the transaction was processedIt is a slot, not a timestamp; converting it to a time needs a separate lookup
value.signatureThe transaction signature, base58 encodedOne signature can contain many countable events, not one
value.errNull on success, an error object on failureFailed transactions still notify; counting them as volume inflates every total
value.logsOrdered array of log lines from the runtime and the programsMay be truncated, may be reworded by an upgrade, never carries authoritative amounts

The error field deserves its own habit. On a busy launch a meaningful share of submitted transactions fail on slippage or on an exhausted compute budget, and a naive counter that treats every notification as an event will report activity that never touched a pool. Filter on err === null before anything else happens in your handler.

Log lines are program output, not ledger entries

This is the section to remember. A log line exists because a developer chose to print it. It is not part of the program's interface, it carries no version guarantee, and nothing in the runtime validates its contents. Two programs implementing the same market behaviour will print entirely different strings, and one program can change its strings between releases without any client noticing until the parser breaks.

The ledger, by contrast, records what the runtime observed: which accounts existed before and after, what their lamport balances were, and what token balances the SPL token programs reported. Those fields are produced by the runtime rather than by the program author, and they are the only defensible basis for a volume figure. The full method is set out in decoding a swap event.

The rule this desk applies

A log line may decide whether you look at a transaction. It may never decide how much that transaction moved. If your code parses a number out of a log string and writes it to a total, you have built a system whose correctness depends on somebody else's logging style, and you will not be told when that style changes.

Truncation, and what it silently removes

The runtime caps how much log output it collects for a single transaction. When a transaction exceeds that cap, collection stops and a truncation marker replaces the remainder. Nothing else signals the loss: the notification arrives normally, the earlier lines are intact, and the later ones simply are not there.

The bias this introduces is precisely the wrong way round. A simple transfer never truncates. A large aggregated swap that crosses several programs, emits an event from each, and reports compute usage at every level is exactly the kind of transaction that does. So the transactions most likely to matter for a volume figure are the transactions most likely to have incomplete logs.

There is no client-side setting that prevents this, because the limit belongs to the runtime rather than to your subscription. The only reliable mitigation is architectural: treat the log as a pointer, fetch the transaction, and read the amounts from metadata that is never truncated in the same way.

The mentions filter and the one address rule

The mentions filter accepts exactly one public key. Supplying an array with more than one entry is rejected rather than silently narrowed, which is at least honest. If you want to watch twelve pools, you open twelve subscriptions, and each one has its own id, its own confirmation and its own resubscription obligation after a reconnect.

That bookkeeping is the practical ceiling on the approach. A handful of addresses is comfortable. A hundred is a system in its own right, where a partial resubscribe after a reconnect leaves you silently blind on a subset of the watch list, which is far harder to notice than a total outage. At that scale, a program subscription or a filtered gRPC stream removes the per-address state entirely.

The most common single-address use is a launchpad or AMM program id rather than a pool, because one subscription then covers everything that program touches. Watching the Pump.fun program this way is how most people first see curve activity arrive in real time, and it is the same signal a Pump.fun volume bot tool has to consume before it can react to anything on the curve. The subscription tells you a transaction touched the program; everything after that is decoding.

Reading invoke depth and the call tree

Runtime lines follow a predictable grammar, and learning it turns an intimidating wall of text into a readable call tree. The pattern is an invoke line naming the program and its depth, then whatever that program logged, then either a success line or a failure line, with nested calls appearing between them at a greater depth.

Program <router> invoke [1]
  Program log: instruction: route
  Program <amm> invoke [2]
    Program log: instruction: swap
    Program <token> invoke [3]
    Program <token> success
  Program <amm> consumed 41,000 of 400,000 compute units
  Program <amm> success
Program <router> success

The depth numbers are the useful part. Depth one is what the user signed for; anything deeper is a cross-program invocation made on their behalf. When you are deciding whether an event is a user trade or a pool fill, depth is the fastest first signal, and it is the reason the same transaction can legitimately produce one count or three depending on the question. The compute figures shown above are placeholders for shape, not measurements.

Failure lines carry the reason in text, which is genuinely useful for diagnosis even though it is unusable as data. A transaction that failed on a slippage tolerance and a transaction that ran out of compute budget look identical in the error field of the notification and completely different in the log, which is why the log remains worth keeping even when you never parse it programmatically.

Anchor events hiding in the log

Programs built with the Anchor framework can emit structured events. The classic mechanism serialises the event and writes it as a base64 payload on a line beginning with a program data marker, prefixed by an eight-byte discriminator derived from a hash of the event name. If you have the program's IDL you can decode that payload into typed fields.

This is more trustworthy than parsing free text, because the layout is generated from the program's own type definitions rather than written by hand. It is still not a ledger entry. The program decides what to emit and can emit something inaccurate, and the line is subject to the same truncation as every other log line. Treat a decoded event as a strong hint with structure, not as settlement.

Newer Anchor code often emits events through a self-invocation instead, which places the data in the inner instructions of the transaction rather than in the log text. That is better for reliability and worse for log subscribers, because the payload no longer appears in the stream you are watching. It is one more reason the log is a trigger and the fetched transaction is the source.

From notification to countable event

The sequence below is the whole pattern, and it is deliberately short. Everything expensive happens after the cheap filter.

  1. Drop failures immediately. If the error field is not null, record it for diagnostics and stop. Nothing moved.
  2. Check the signature against your store. If you have already processed it, stop. Duplicate delivery is normal across reconnects and parallel consumers.
  3. Fetch the transaction by signature. Request the versioned form so that address lookup tables do not cause a parse failure, and take the metadata with it.
  4. Read balances, not logs. Compute deltas from the pre and post token balance arrays, and from the lamport balances for the native side.
  5. Key the result. Signature plus instruction index identifies one countable fill exactly once, whatever route your decoder followed to reach it.
  6. Record the slot. Store it alongside the event so the ingestion watermark advances and a later gap check has something to compare against.

Step three is where the subscription stops paying for itself and the request budget starts. On a quiet address that is negligible. During a launch it is the dominant cost of the whole pipeline, and it is worth measuring before you promise anyone a live panel.

When another subscription is the right one

You want to knowBetter subscriptionWhy
That a specific pool's reserves changedaccountSubscribeDelivers the account data itself, so you see state rather than an event
About every account a program ownsprogramSubscribeOne subscription covers a whole class of accounts, with size and memcmp filters
Whether one transaction landedsignatureSubscribeFires once for that signature and then cancels itself
Whether the chain is still movingslotSubscribeThe cheapest possible liveness heartbeat for your consumer
Everything, at cluster scaleGeyser or gRPC streamPer-address subscriptions stop being manageable long before this point

The account subscription deserves a second look for volume work specifically. Watching a pool's vault accounts gives you reserve balances directly, which is a different and sometimes better view than reconstructing them from trades. The trade-off is that you see the net effect of a slot rather than each individual fill inside it.

A log subscription remains the right default for a small watch list, and it remains a trigger rather than a source no matter how the rest of the design evolves. That framing is also what separates a monitoring pipeline from an execution one: reading a stream is enough to build a panel, while anything that has to act on the same signal, including a volume bot for Solana, needs a submission path and a landing strategy that no subscription can provide. The next note in this section, webhooks and push data, covers what happens when you hand the whole listening problem to somebody else.

Questions this desk keeps getting

What does logsSubscribe return?

Each notification carries a context object with the slot, and a value object with the transaction signature, an error field that is null on success, and an array of log strings the runtime collected while executing the transaction. It does not carry balances, instruction data, or account keys. To learn what a transaction actually moved you fetch it separately by signature.

Can logsSubscribe watch more than one address?

Not in one subscription. The mentions filter accepts exactly one public key per call, and supplying more than one is rejected. Watching several addresses means several subscriptions, or a broader filter with local filtering. Once the watch list grows past what per-address subscriptions can comfortably manage, a program subscription or a Geyser-based stream is usually the better shape.

Why are some log lines missing?

The runtime limits how much log output it collects per transaction. When a transaction produces more than the limit, the collection stops and a truncation marker appears in place of the rest. A busy multi-hop route is exactly the kind of transaction that hits this, which means the log for the most interesting transactions is the log most likely to be incomplete.

Should I parse the log text to detect swaps?

Only as a trigger, never as a source of amounts. Log strings are written by program authors, are not part of any interface contract, and can change in a routine upgrade without anything else changing. Use the log to learn that a transaction touched your program, then read the amounts from the transaction metadata where the runtime, not the developer, decided what to record.

What is the difference between all and allWithVotes?

The all filter delivers transactions excluding simple vote transactions, which is what almost everyone wants because votes dominate the transaction count and carry no market information. The allWithVotes filter includes them. Choosing allWithVotes on mainnet without a very specific reason means paying to receive consensus traffic you will immediately discard.

Which commitment level should a log subscription use?

Most streaming consumers set confirmed, because it means the block already carries a supermajority vote while still arriving well ahead of finalized. Processed is available and is genuinely earlier, at the cost of occasionally showing you activity on a fork that loses. Whatever you choose, record it, because a figure produced at one level cannot be compared with a figure produced at another.

Does a failed transaction still produce a log notification?

Yes, and this is useful rather than noise. The error field is populated and the log lines usually show how far execution got before the failure. For anyone studying why activity did not land, that is the primary evidence. For anyone counting volume, it is the thing you must exclude, because a failed transaction moved nothing.