// For Cursor, Claude Code and friends
Your agent writes a Yellowstone client that loses data. Here is the context that fixes it.
Ask any coding agent for a Solana gRPC client and you will get working code in a minute. It will also, almost every time, omit a read-idle timeout, reconnect at the live head instead of resuming, and skip deduplication. All three are invisible in a demo and expensive in production — we measured them. Below: prompt packs per stack, a droppable AGENTS.md section, and a read-only MCP server so your agent works from current facts instead of recalled ones.
The three defects, and why a demo never shows them
No read-idle timeout
A gRPC stream can stay open, healthy at the transport level, and simply stop delivering messages. Nothing throws. Your await sits there forever and your bot reports itself up. Only a timer you write catches it.
Reconnect at the head
Resubscribing to the live head is not resuming. Everything produced during the outage is gone, with no error and no log line. Recovery means tracking the last processed slot and asking for it back.
No deduplication
The moment you do resume, the boundary slot arrives twice. For a trading bot, acting twice on one signature is worse than missing it, so dedupe by signature before doing anything.
Measured with fault injection across four failure modes on the reconnect benchmark, with implementation patterns in auto-reconnect and gap recovery.
// Step 1
Give your agent current facts: @subglow/mcp
A model's memory of provider pricing and latency figures is stale by construction. This MCP server hands it live ones, plus a static validator for the filter mistakes that cost real money. Four read-only tools, zero dependencies, and it never accepts an API key — your editor config is not a place for a spending credential.
search_docsSearches the generated content export — plan limits, the provider matrix with a provenance label on every figure, benchmark results, glossary, full article text. Fetched live, so an assistant quotes current prices rather than remembered ones.
validate_filterStatic checks on a SubscribeRequest: filters with no account constraints, accountRequired used where accountInclude was meant, contradictory include/exclude lists, invalid base58, missing vote exclusion, and commitment levels that contradict the stated use case.
estimate_bandwidthSustained and peak bandwidth, monthly egress, and share of a CPU core spent decoding, for a program set and consumer language. Prints every assumption alongside the result.
subscribe_previewA few real recent Pump.fun events so an agent can see actual field shapes instead of inventing them. Rate limited, read-only, recent history rather than a live stream.
Cursor
~/.cursor/mcp.json{
"mcpServers": {
"subglow": {
"command": "npx",
"args": ["-y", "@subglow/mcp"]
}
}
}Claude Code
terminalclaude mcp add subglow -- npx -y @subglow/mcpClaude Desktop
claude_desktop_config.json{
"mcpServers": {
"subglow": {
"command": "npx",
"args": ["-y", "@subglow/mcp"]
}
}
}Requires Node 18.17 or newer. Source and tool schemas: github.com/subglow/subglow-mcp.
// Step 2
Drop this into AGENTS.md
Paste it into your project's AGENTS.md, CLAUDE.md, or a file under .cursor/rules/. Every later request inherits the endpoint, the filter semantics and the six stream-handling rules, so you stop re-explaining them and the agent stops inventing a vendor SDK.
## Solana streaming via Subglow gRPC
The project streams Solana data over Yellowstone gRPC from Subglow.
- Endpoint: `grpc.subglow.io:443` (TLS). Auth is an `x-api-key` metadata header.
- Read the key from the environment (`SUBGLOW_API_KEY`). Never inline it, never
commit it, never log it.
- Subglow speaks standard Yellowstone gRPC. Use the standard client for the
language (`@triton-one/yellowstone-grpc`, `yellowstone-grpc-client` for Rust,
`grpcio` with the Yellowstone protos for Python). Do not look for a
Subglow-specific SDK; there isn't one, and that is deliberate — the same code
works against any Yellowstone endpoint.
- Instruction data arrives pre-parsed as JSON on Subglow, so do not add Borsh
decoding for the programs Subglow parses (Pump.fun, Raydium, Jupiter).
### Subscription rules
- Always set `vote: false` and `failed: false` on transaction filters unless the
task explicitly needs them. Votes are most of Solana's traffic.
- Always constrain filters with `accountInclude`. A filter with no account
constraints subscribes to the whole network.
- `accountInclude` is any-of. `accountRequired` is all-of. Use `accountInclude`
unless every listed account must appear in the same transaction.
- Use `commitment: processed` for trading paths, `confirmed` or `finalized` for
anything that will be reconciled or reported on.
### Stream handling rules (non-negotiable)
1. Keep the read loop cheap: receive, enqueue, return. No RPC calls, no database
writes, no transaction submission inside the handler.
2. Implement a read-idle timeout (5s is a reasonable default). A gRPC stream can
stay open and silently stop delivering; nothing else detects that.
3. Reconnect with capped, jittered exponential backoff. Cap it — an uncapped
curve keeps sleeping after the endpoint has recovered.
4. Track the last *processed* slot and resubscribe from it. Reconnecting at the
live head discards everything produced during the outage, with no error.
5. Deduplicate by transaction signature before acting. Resuming re-delivers the
boundary slot, and a bot that buys twice on one signature is worse than one
that missed it.
6. Bound every queue and every dedupe set. Log dropped events as a metric, not a
debug line.// Step 3
Prompt packs, one per stack
Use one of these as your first request rather than “write me a Solana gRPC client”. Each specifies the library to use, the queue architecture, and the five reliability requirements — the things an agent omits when you do not ask.
TypeScript / Node
@triton-one/yellowstone-grpcThe most common stack for Solana bots, and the one where a naive agent-written client fails first, because a blocked event loop turns into backpressure under load.
Build a production Yellowstone gRPC client in TypeScript (Node 20+) that streams Pump.fun transactions from Subglow.
Requirements:
- Use the standard `@triton-one/yellowstone-grpc` client. Subglow is a standard
Yellowstone endpoint, so do not look for a vendor SDK.
- Endpoint `grpc.subglow.io:443` over TLS, auth via `x-api-key` metadata read
from `process.env.SUBGLOW_API_KEY`. Fail fast with a clear message if unset.
- Subscribe to transactions with `accountInclude: ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]`,
`vote: false`, `failed: false`, `commitment: processed`.
- The read loop must only receive and enqueue. Put handling in a bounded queue
(max 10k) drained by a pool of 8 async workers. Count and log drops when the
queue is full rather than growing it.
- Implement a 5 second read-idle watchdog that destroys the stream if no message
arrives. A Yellowstone stream can stay open and stop delivering, and nothing
else detects that failure mode.
- Reconnect with jittered exponential backoff capped at 5 seconds.
- Track the last successfully processed slot and pass it as `fromSlot` on
resubscribe. Deduplicate by signature using a bounded structure (last 10k
signatures) because resuming re-delivers the boundary slot.
- Expose a metrics object: events received, events processed, drops, reconnects,
last slot, current queue depth. Log it once a second.
- TypeScript strict mode, no `any`, graceful SIGINT shutdown.
Do not add Borsh decoding: Subglow delivers pre-parsed JSON for Pump.fun.Rust
yellowstone-grpc-clientFor latency-sensitive paths. An agent will usually get the tonic wiring right and the reconnect semantics wrong.
Build a production Yellowstone gRPC client in Rust that streams Pump.fun transactions from Subglow.
Requirements:
- Use `yellowstone-grpc-client` and `yellowstone-grpc-proto` with tokio and
tonic. Subglow is a standard Yellowstone endpoint; there is no vendor SDK.
- Connect to `https://grpc.subglow.io:443` with the `x-api-key` header from the
`SUBGLOW_API_KEY` environment variable.
- SubscribeRequest: transactions filter with
`account_include = ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]`,
`vote: Some(false)`, `failed: Some(false)`, commitment Processed.
- Separate the receive loop from processing with a bounded `tokio::sync::mpsc`
channel (capacity 10_000) and a fixed set of worker tasks. On a full channel,
increment a dropped counter — do not await, and do not use an unbounded channel.
- Wrap the receive loop in `tokio::time::timeout` with a 5 second read-idle
budget so a silently stalled stream is detected and torn down.
- Reconnect loop with jittered exponential backoff capped at 5 seconds.
- Track the last processed slot in an `AtomicU64` and set `from_slot` on
resubscribe. Deduplicate by signature with a bounded structure, since resuming
re-delivers the boundary slot.
- Structured logging via `tracing`, and a metrics snapshot every second:
received, processed, dropped, reconnects, last slot, channel depth.
- No `unwrap()` outside of startup validation. Handle SIGINT cleanly.
Subglow ships pre-parsed JSON for Pump.fun, so no Borsh decoding is needed for
these events.Python
grpcio + Yellowstone protosBest for research and indexing rather than the hot path — Python's decode cost per event is an order of magnitude above Rust's in our benchmark.
Build a production Yellowstone gRPC client in Python 3.11+ that streams Pump.fun transactions from Subglow.
Requirements:
- Use `grpcio` with the Yellowstone protobuf definitions and `grpc.aio` for
async streaming. Subglow is a standard Yellowstone endpoint, so no vendor SDK.
- Secure channel to `grpc.subglow.io:443`, `x-api-key` metadata from
`os.environ["SUBGLOW_API_KEY"]`.
- SubscribeRequest: transactions filter with
`account_include=["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]`,
`vote=False`, `failed=False`, commitment PROCESSED.
- The receive coroutine must only put messages on an `asyncio.Queue(maxsize=10000)`.
Run a fixed number of consumer tasks. On `QueueFull`, increment a drop counter
instead of awaiting — blocking the receiver is what turns a slow consumer into
a stalled stream.
- Wrap each stream read in `asyncio.wait_for` with a 5 second timeout so a
silently stalled connection is detected rather than waited on forever.
- Reconnect with jittered exponential backoff capped at 5 seconds.
- Track the last processed slot and pass it as `from_slot` when resubscribing.
Deduplicate by signature using a bounded `collections.deque` plus a set.
- Log a metrics line every second: received, processed, dropped, reconnects,
last slot, queue depth. Handle SIGINT for clean shutdown.
- Type hints throughout; no bare `except`.
Note on performance: base58-encoding pubkeys dominates client-side decode cost in
Python. Keep pubkeys as bytes internally and only encode them when rendering.Go
grpc-go + Yellowstone protosGood middle ground for indexers and services. Goroutine-per-event is the trap an agent will fall into.
Build a production Yellowstone gRPC client in Go (1.22+) that streams Pump.fun transactions from Subglow.
Requirements:
- Use `google.golang.org/grpc` with the Yellowstone protobuf definitions.
Subglow is a standard Yellowstone endpoint, so there is no vendor SDK.
- Dial `grpc.subglow.io:443` with TLS credentials and per-RPC `x-api-key`
metadata from `os.Getenv("SUBGLOW_API_KEY")`.
- SubscribeRequest: transactions filter with
`AccountInclude: []string{"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}`,
`Vote: proto.Bool(false)`, `Failed: proto.Bool(false)`, commitment Processed.
- Receive loop sends to a buffered channel (capacity 10000) consumed by a fixed
worker pool of 8 goroutines. Do NOT spawn a goroutine per event. On a full
channel, use a non-blocking send with a default branch that increments a
dropped counter.
- Detect silent stalls: a ticker that compares time since the last received
message against a 5 second budget and cancels the stream context when exceeded.
- Reconnect with jittered exponential backoff capped at 5 seconds.
- Track the last processed slot with `atomic.Uint64` and set `FromSlot` on
resubscribe. Deduplicate by signature with a bounded map plus ring buffer.
- Expose counters (received, processed, dropped, reconnects, last slot, channel
depth) and log them every second. Handle SIGINT with context cancellation.
- No panics on stream errors; return them up to the reconnect loop.
Subglow delivers pre-parsed JSON for Pump.fun, so no Borsh decoding is required.// Step 4
Review what it produced
A generated client that runs is not a client that survives. Check these six before you point real money at it — and if you cannot find the corresponding code, the agent skipped it.
- 1The read loop only receives and enqueues. No RPC calls, database writes or transaction submission inside it.
- 2There is a read-idle timeout that tears down a stream which stops delivering. Without this, a silent stall is an outage you never see.
- 3Reconnect backoff is capped and jittered. Uncapped exponential backoff sleeps long after the endpoint recovers.
- 4The last processed slot is tracked and sent on resubscribe. Not the last received slot — the last one you finished handling.
- 5Events are deduplicated by signature, with a bounded structure. Resuming guarantees duplicates at the boundary.
- 6Queues are bounded and drops are counted as a metric. An unbounded queue converts backpressure into a crash.
Then ask your agent to run its validate_filter tool on the SubscribeRequest before the first real run. It catches the any-of versus all-of mistake that quietly returns nothing.
Questions
Can I build a Solana trading bot without writing code?
Not responsibly, and we would rather say so than sell you the idea. A coding agent will produce a working Yellowstone client quickly, but the failure modes are invisible in a demo: a reconnect that resubscribes at the live head silently discards the outage window, and a stream that stays open while delivering nothing looks healthy from the outside. Both cost money in production. What this page gives you is the context that makes an agent write the correct version, plus the knowledge to review what it produced.
What does the Subglow MCP server do?
It gives your coding assistant four read-only tools: search_docs over Subglow's generated content export so it quotes current prices and benchmark figures rather than remembered ones, validate_filter to statically check a SubscribeRequest for the mistakes that cause firehoses and silent data loss, estimate_bandwidth for egress and decode cost, and subscribe_preview for a small sample of real Pump.fun events. It never accepts an API key.
Why doesn't the MCP server take an API key?
MCP server configuration lives in plaintext in your editor's settings file, often in a synced dotfiles repo. A documentation search tool has no business holding a credential that can spend money, so every tool reads public data only. When you are ready to open a real stream, the key goes in your project's environment, not in your editor config.
Do I need a Subglow-specific SDK?
No, and that is deliberate. Subglow serves standard Yellowstone gRPC, so the standard client for your language works unchanged — @triton-one/yellowstone-grpc, yellowstone-grpc-client for Rust, grpcio with the Yellowstone protos for Python. The practical benefit is that the same code runs against any Yellowstone endpoint, so switching provider is an endpoint and key change rather than a rewrite. Tell your agent this explicitly, because otherwise it will hallucinate a vendor SDK.
What are the three mistakes agents make most often?
First, no read-idle timeout, so a connection that stays open and stops delivering is never detected. Second, reconnecting at the live head instead of resuming from the last processed slot, which discards everything produced during the outage with no error raised. Third, no deduplication by signature, so once resuming is implemented the boundary slot gets processed twice — and for a trading bot, buying twice on one signature is worse than missing it. Our reconnect benchmark measures all three.
Get a key, then let the agent do the typing.
Standard Yellowstone gRPC with pre-parsed JSON, $99/mo flat and unmetered. Put SUBGLOW_API_KEY in your project environment — never in your editor config.