Solana ShredStream Setup Guide: Stream Shreds Step by Step (2026)

Step-by-step ShredStream setup: choose between shared gRPC and direct UDP, connect with Rust or TypeScript, parse entries, and avoid the common pitfalls — with real endpoints.

  • July 7, 2026
  • Shreds
  • AllenHark Team
A quiet stack of pagesAn abstract drawing of light on the page's dark ground. A neat stack of thin, solid pages, the top one lit, with faint streams of light drifting toward it from the left.A quiet stack of pagesAn abstract drawing of light on the page's dark ground. A neat stack of thin, solid pages, the top one lit, with faint streams of light drifting toward it from the left.

ShredStream is the earliest way to read Solana: instead of waiting for a block to be assembled, propagated, and served over RPC, you tap the shreds — the packets validators exchange while the block is still being produced. Done right, your system sees transactions while the block is still being produced, before anything downstream of an RPC blockSubscribe does.

This guide takes you from zero to a working shred feed. If you want the conceptual background first, read What Are Solana Shreds? — this post is purely practical.

Step 1: Pick your delivery mode

AllenHark ShredStream ships in two flavors, and the right one depends on how much latency you're actually paying for elsewhere:

Shared gRPCDirect UDP
How it worksWe verify and reassemble the shreds and stream you decoded entries (and the transactions inside them) over gRPCThe same reconstructed entries, pushed as UDP datagrams from our nodes straight to your server's IP and port
What you receiveDecoded entries, not raw shredsDecoded entries, not raw shreds: an 8-byte slot, then bincode entries
Integration effortLow — a gRPC subscriptionLow — a UDP socket, no client and no auth
Best forMonitoring, standard bots, testing the edgeBots that want entries pushed without a gRPC client
PlansDay, week or month (pricing)Monthly (pricing)

If you want raw shreds — data and coding shreds, byte for byte — that is the separate Dedicated feed, pushed over UDP from a dedicated fan-out process in Frankfurt.

Regions are Frankfurt and Amsterdam. Two rules of thumb. First, start with shared gRPC — the daily pass exists precisely so you can test the edge cheaply before committing. Second, UDP delivery only pays off if your server is network-close to the source: if you're streaming across an ocean, the transit time swamps what you saved. That's what co-location is for.

There's also a free 1-hour trial, once per account, if you just want to see the feed.

Step 2: Get an API key

  1. Log in to the AllenHark Console
  2. Open ShredStream and choose a pass (daily/weekly/monthly)
  3. Copy your API key — it looks like ak_1234567890 and is sent as an x-api-key (or x-token) gRPC metadata entry on every connection

Step 3: Connect (shared gRPC)

The shared feed speaks the standard shredstream-proxy protocol on 185.191.117.89:9090. It is plaintext h2c — there is no TLS on this endpoint, so connect with http:// and insecure credentials. The service and messages are:

Protobuf
service ShredstreamProxy {
    rpc SubscribeEntries(SubscribeEntriesRequest) returns (stream Entry);
}

message SubscribeEntriesRequest {}

message Entry {
    uint64 slot = 1;
    bytes  entries = 2;
}

There are no server-side filters — you get every entry, votes included, in order. Filter client-side.

Rust

Rust
use tonic::{Request, transport::Channel, metadata::MetadataValue};
use shredstream_proxy_client::ShredstreamProxyClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let channel = Channel::from_static("http://185.191.117.89:9090")
        .connect()
        .await?;
    let mut client = ShredstreamProxyClient::new(channel);

    let mut request = Request::new(SubscribeEntriesRequest {});
    let api_key = MetadataValue::from_str("YOUR_API_KEY")?;
    request.metadata_mut().insert("x-api-key", api_key);

    let mut stream = client.subscribe_entries(request).await?.into_inner();

    while let Some(entry) = stream.message().await? {
        println!("slot={} entries={} bytes", entry.slot, entry.entries.len());
        // entry.entries is bincode-serialized Vec<solana_entry::entry::Entry>:
        // deserialize it to get the transactions.
    }
    Ok(())
}

TypeScript

TypeScript
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";

// shredstream.proto is published at https://allenhark.com/shredstream.proto
const def = protoLoader.loadSync("shredstream.proto");
const proto = grpc.loadPackageDefinition(def) as any;

// plaintext h2c — no TLS on this endpoint
const client = new proto.shredstream.ShredstreamProxy(
    "185.191.117.89:9090",
    grpc.credentials.createInsecure(),
    { "grpc.max_receive_message_length": 64 * 1024 * 1024 }
);

const metadata = new grpc.Metadata();
metadata.set("x-api-key", process.env.ALLENHARK_API_KEY!);

const stream = client.SubscribeEntries({}, metadata);
stream.on("data", (entry: { slot: number; entries: Buffer }) => {
    console.log(`slot=${entry.slot} ${entry.entries.length} bytes`);
});
stream.on("error", (err: Error) => {
    console.error("stream error, reconnecting:", err.message);
    // production systems: reconnect with backoff here
});

The proto file is served from allenhark.com/shredstream.proto.

Step 4: Parse entries into transactions

Each message's entries field is a bincode-serialized Vec<solana_entry::entry::Entry>, where each Entry is { num_hashes: u64, hash: [u8; 32], transactions: Vec<VersionedTransaction> }. In Rust:

Rust
let entries: Vec<solana_entry::entry::Entry> =
    bincode::deserialize(&entry.entries)?;

for e in entries {
    for tx in e.transactions {
        // VersionedTransaction — filter by program ID, account keys, etc.
    }
}

Filter as early as possible. A busy slot carries thousands of transactions; if you only care about one program (a DEX, pump.fun, a specific pool), drop everything else before it touches your strategy code. For a worked example of program-level filtering for launch sniping, see Best Shreds Setup for Pump.fun.

Step 5 (optional): Go UDP

When you've validated the edge and want the data pushed to you:

  1. Provision a server with a static IPv4 — ideally in the same city as the source (Frankfurt bare metal is the canonical choice)
  2. For Direct UDP, enter the IP and port in the console; we push the same reconstructed entries to it as UDP datagrams (large messages arrive in fragments you reassemble by message id)
  3. For raw shreds, take the Dedicated feed; ingest it with the jito-shredstream-proxy tooling or your own deshredder

With raw shreds you take over verification and reassembly — that's the deal: UDP gives you speed, you supply the reliability.

Common pitfalls

  • Streaming across the Atlantic and wondering where the edge went. The shred edge is small; a transatlantic round trip swamps it many times over. Co-locate or pick the nearest region.
  • Leaving the default 4 MiB gRPC message limit in place. A busy slot's entries blow straight through it. Raise the max receive message size on the client.
  • No reconnect logic. gRPC streams drop — network blips, deploys. Reconnect with exponential backoff and expect to miss a slot or two; design the strategy to tolerate it.
  • Parsing everything. Deserialize, filter by program ID, then process. The other order melts CPUs on busy slots.
  • Treating shreds as confirmed data. You're seeing block production, ahead of consensus. A slot can be skipped. Act on shreds, confirm on commitment levels.

Where ShredStream fits in the stack

Shreds are the read side. If the goal is end-to-end speed you also need the write side: AllenHark Relay or Slipstream for transaction landing, and Jito bundles when execution must be atomic. The full comparison of read paths — shreds vs gRPC vs RPC vs WebSockets — covers when each is the right tool.

Questions, or want the dedicated cluster? Join the Discord or head to the console and start with a day pass.