Documentation menu

Shreds (Early Data)

Shreds are the fragments into which a slot leader splits the block it is producing. The leader propagates them through the network via the Turbine protocol while the slot is still in progress. MEV Alpha Store delivers shreds directly from validator infrastructure, before they undergo replay and before the data becomes available through standard RPC nodes and WebSocket subscriptions.

Delivery

PropertyValue
ProtocolsgRPC or WebSocket
EndpointsSee Regions & endpoints
RegionsAll 7 regions
Latency3 ms at the median (p50) to a server in the same region
Payloadentries reconstructed from shreds; raw shreds on request
gRPC messageslot number and bincode-serialized entries, see the definition below
WebSocket messageJSON with base64-encoded transactions
FilteringNone: you receive the complete stream and filter it on your side

We reconstruct entries from shreds for you, including recovery of missing data shreds from coding shreds. You receive the transactions of the block in the order the leader recorded them.

How shreds are organized

  • The leader turns the transactions it executes into entries and splits the entries into data shreds.
  • Data shreds are grouped into FEC sets. Each FEC set also has coding shreds (erasure codes), which allow missing data shreds to be recovered.
  • Each shred fits into a single network packet and carries its slot and index.

An entry contains a hash, the number of hashes since the previous entry and a list of transactions. Entries without transactions ("ticks") mark the passage of time and are also delivered.

gRPC

The protocol definition is provided with your credentials.

protobuf
syntax = "proto3";

package mevalpha.shreds.v1;

service ShredStream {
  // Streams entries as soon as they are reconstructed from shreds.
  rpc SubscribeEntries(SubscribeEntriesRequest) returns (stream EntryBatch);
}

message SubscribeEntriesRequest {}

message EntryBatch {
  // Slot the entries belong to.
  uint64 slot = 1;
  // bincode-serialized Vec<solana_entry::entry::Entry>.
  bytes entries = 2;
  // Time the entries were reconstructed on our side, Unix epoch in microseconds.
  uint64 created_at_us = 3;
}

Rust consumer

rust
use std::time::Duration;

use solana_entry::entry::Entry;
use tonic::transport::{Channel, ClientTlsConfig};

pub mod pb {
    tonic::include_proto!("mevalpha.shreds.v1");
}
use pb::{shred_stream_client::ShredStreamClient, SubscribeEntriesRequest};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // The gRPC endpoint from your credentials, including https:// and the port.
    let channel = Channel::from_shared(std::env::var("MEV_SHREDS_GRPC")?)?
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .tcp_nodelay(true)
        .http2_keep_alive_interval(Duration::from_secs(10))
        .keep_alive_timeout(Duration::from_secs(5))
        .keep_alive_while_idle(true)
        .connect()
        .await?;

    // ApiKey is the interceptor from the Authentication page.
    let mut client = ShredStreamClient::with_interceptor(channel, ApiKey::from_env());
    let mut stream = client
        .subscribe_entries(SubscribeEntriesRequest {})
        .await?
        .into_inner();

    while let Some(batch) = stream.message().await? {
        let entries: Vec<Entry> = bincode::deserialize(&batch.entries)?;
        for entry in &entries {
            for tx in &entry.transactions {
                // Hand off to your strategy workers; keep this loop fast.
                println!("slot {} tx {}", batch.slot, tx.signatures[0]);
            }
        }
    }
    Ok(())
}

Dependencies: tonic (with the tls-native-roots feature), prost, tonic-build as a build dependency, solana-entry, bincode, tokio and anyhow. Use the same major version of solana-entry as the solana-sdk in the rest of your project.

The loop above reconnects only when the process restarts. In production, wrap it in a reconnect loop with exponential backoff and jitter.

WebSocket

After the connection opens, the server sends one message per batch of entries:

json
{
  "slot": 372418559,
  "created_at_us": 1761490800123456,
  "entries": [
    {
      "num_hashes": 12500,
      "hash": "8tHkPqXg...base58",
      "transactions": ["AV8x...base64-encoded transaction...AAA="]
    }
  ]
}

TypeScript consumer

ts
import WebSocket from "ws";
import bs58 from "bs58";
import { VersionedTransaction } from "@solana/web3.js";

const ENDPOINT = process.env.MEV_SHREDS_WS!;

function connect(attempt = 0): void {
  const ws = new WebSocket(ENDPOINT, {
    headers: { "x-api-key": process.env.MEV_ALPHA_KEY! },
  });

  // Reset the backoff only after the connection has been stable for a minute.
  let stable: NodeJS.Timeout | undefined;
  ws.on("open", () => {
    stable = setTimeout(() => (attempt = 0), 60_000);
  });

  ws.on("message", (data) => {
    const batch = JSON.parse(data.toString());
    for (const entry of batch.entries) {
      for (const b64 of entry.transactions) {
        const tx = VersionedTransaction.deserialize(Buffer.from(b64, "base64"));
        console.log(batch.slot, bs58.encode(tx.signatures[0]));
      }
    }
  });

  ws.on("close", (code, reason) => {
    clearTimeout(stable);
    console.log("closed", code, reason.toString());
    const delay = Math.min(30_000, 500 * 2 ** attempt) + Math.random() * 250;
    setTimeout(() => connect(attempt + 1), delay);
  });

  ws.on("error", () => ws.close());
}

connect();

The server sends WebSocket ping frames every 10 seconds. The ws library answers them automatically. If you see no message and no ping for 30 seconds, close the connection and reconnect.

Raw shreds

If your offer includes raw shreds instead of entries, you reconstruct the entries yourself:

  1. Group shreds by slot and FEC set. Discard duplicates.
  2. Recover missing data shreds from the coding shreds of the same FEC set.
  3. Reassemble ("deshred") the data shreds into entries.
  4. Deserialize the entries. Each entry contains a list of transactions.

In Rust, the solana-ledger crate provides shred parsing and deshredding, and the solana-entry crate provides the Entry type.

Guidelines

  • A slot arrives in parts. Entries of one slot come in several messages. There is no end-of-slot message: treat a slot as complete when batches for a later slot arrive, and confirm the outcome through your RPC.
  • Expect imperfect delivery. After a reconnect, or when a leader produces a block on a fork, you can see the same transaction more than once. Deduplicate by signature where it matters.
  • Do not treat a slot as final. Blocks can be skipped or end up on a minority fork. Confirm the outcome through your RPC at the commitment level your strategy needs.
  • Entries do not carry execution results. A transaction in an entry was processed by the leader, but it may have failed. Failed transactions are recorded in the block too, because they pay fees. Check the status through your RPC if your strategy depends on it.
  • Keep the receive loop fast. Queue batches and decode them on separate workers. See Limits.
  • Use the head start wisely. Shreds show what the leader is placing in the current block before most of the network has replayed it. Combine them with Private Orderflow to see transactions both before and after execution.

Need help or access? Contact our support on Telegram.

Contact