Solana ShredStream SDK/Decoder for JavaScript/TypeScript, enabling ultra-low latency Solana transaction streaming via UDP shreds from ShredStream.com
Part of the ShredStream.com ecosystem โ ultra-low latency Solana shred streaming via UDP.
- Create an account on ShredStream.com
- Launch a Shred Stream and pick your region (Frankfurt, Amsterdam, Singapore, Chicago, and more)
- Enter your server's IP address and the UDP port where you want to receive shreds
- Open your firewall for inbound UDP traffic on that port (e.g. configure your cloud provider's security group)
- Install Node.js 18+ and npm:
# Linux (Ubuntu/Debian) curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs # macOS brew install node
๐ Want to try before you buy? Open a ticket on our Discord to request a free trial.
npm install shredstreamCreate a file index.ts:
import { ShredListener } from 'shredstream';
import { VersionedTransaction } from '@solana/web3.js';
const PORT = parseInt(process.env.SHREDSTREAM_PORT || '8001');
const listener = ShredListener.bind(PORT);
while (true) {
const batch = listener.nextTransactionSync();
if (batch === null) {
throw new Error(`shredstream listener stopped: ${listener.lastIoErrorKind ?? 'unknown'}`);
}
for (const raw of batch.transactions) {
const tx = VersionedTransaction.deserialize(new Uint8Array(raw));
console.log(`slot ${batch.slot}: ${tx.signatures[0]}`);
}
}Transactions may be version 1 (leading byte
0x81); decode them with a library that supports it.
nextTransactionSync()is the lowest-latency path โ recommended for dedicated MEV / sniping consumers. Anullreturn is a terminal state (socket closed, fatal I/O likeBrokenPipeorNetworkDown); inspectlistener.lastIoErrorKindand let your process supervisor (systemd, Docker, k8s) restart you. If you need to mix with other Node async I/O (dgram.send, HTTP, timers), usefor await (const batch of listener)instead โ see Performance.
Run it:
npx tsx index.tsShredListener.bind(port)โ Bind with defaults (64 MB recv buf, 3 slot window, FEC enabled)ShredListener.bindWithOptions(port, opts)โ Custom configurationShredListener.offline()โ Bind on ephemeral port; drive viahandlePacket()ShredListener.fromFd(fd, opts)โ Adopt an existing UDP file descriptorlistener.nextTransactionSync()โ Sync single-pull (lowest latency, MEV-grade โ see Performance)for await (const { slot, transactions } of listener)โ Async iterator, compatible with other Node async I/Oawait listener.nextTransaction()โ Async single-pull, resolves to{ slot, transactions } | nullawait listener.nextShred()โ Raw shred header{ slot, index, payloadLen }listener.handlePacket(buf: Buffer)โ Inject an externally-received UDP datagramlistener.close()โ Release the socket
interface ListenerOptions {
recvBuf?: number;
maxAge?: bigint;
busyPollUs?: number;
disableBusyPoll?: boolean;
poolSize?: number;
enableFec?: boolean;
disableSalvageDelivery?: boolean;
accumulator?: { maxFecSetsPerSlot?: number; stuckBatchTimeoutMs?: number };
}| Field | Default | Description |
|---|---|---|
recvBuf |
64 MB |
SO_RCVBUF size. Kernel rmem_max must allow it |
maxAge |
3n |
Slot retention window |
busyPollUs |
200 |
Linux SO_BUSY_POLL ยตs (best-effort) |
disableBusyPoll |
false |
Pass true to disable busy poll explicitly |
poolSize |
4096 |
Number of 2 KiB buffers in the zero-copy pool |
enableFec |
true |
Reed-Solomon recovery on dropped data shreds |
disableSalvageDelivery |
false |
Drop salvaged tail txs for lowest p99 |
accumulator.maxFecSetsPerSlot |
32 |
Per-slot FEC buffer cap |
accumulator.stuckBatchTimeoutMs |
50 |
Force-finalize a stuck batch after this delay |
Read-only getters mirroring the Rust crate. All u64 counters are bigint:
| Group | Getters |
|---|---|
| Throughput | dataShredCountTotal, codeShredCountTotal, bytesReceived, slotCount |
| Decoder | batchesDecodedStreamingTotal, batchesDecodedFallbackTotal, batchesSkippedTotal, decodeErrorsTotal |
| FEC | fecRecoveriesTotal, fecRecoveryFailuresTotal, fecSetsDiscardedUnusedTotal, fecSetsEvictedEarlyTotal |
| Unparseable | unparseablePackets, unparseableTooShort, unparseableVariant, unparseablePayload, unparseableSlotRange |
| Slot lifecycle | slotsCompletedTotal, slotsEvictedByAge, droppedKnownSlots, harvestedBatchesTotal, salvagedTailTxTotal |
| Tail control | batchesForceFinalizedCorruptedTotal, batchesForceFinalizedTimeoutTotal |
| Pool / I-O | poolExhaustedCount, lastIoErrorKind, busyPollActive, localAddress |
classifyVariant(byte: number)โ'DataLegacy' | 'CodeLegacy' | 'DataMerkleUnchained' | 'DataMerkleResigned' | 'CodeMerkleUnchained' | 'CodeMerkleResigned' | nullvariantProofSize(byte)/variantResigned(byte)/variantMerkleSuffix(byte)โ Merkle metadatapinCurrentThreadToCpu(cpuId: number)โ Best-effort thread pinning. Linux:sched_setaffinity; macOS: hint; other: no-op
ShredStream.com shred data powers a wide range of latency-sensitive strategies โ HFT, MEV extraction, token sniping, copy trading, liquidation bots, on-chain analytics, and more.
ShredStream.com SDK detects PumpFun token creations ~499ms before they appear on PumpFun's live feed โ tested across 25 consecutive detections:
Ready-to-run example included: see
examples/pumpfun_creates.ts. Run withnpx tsx examples/pumpfun_creates.ts [port].
Two delivery APIs, depending on what your process does between batches.
Synchronous pull. Returns { slot, transactions } | null directly โ no Promise allocation, no Buffer.from round-trip, no event-loop hop. Fastest possible delivery.
while (true) {
const batch = listener.nextTransactionSync();
if (batch === null) {
throw new Error(`shredstream listener stopped: ${listener.lastIoErrorKind ?? 'unknown'}`);
}
for (const raw of batch.transactions) {
// synchronous decode + outbound send (e.g. raw-socket, RDMA, etc.)
}
}
โ ๏ธ Sync mode blocks the libuv event loop while parked on UDPrecv. Any other async I/O in this process (dgram.send, HTTP,fs.readFile,setTimeout) will not progress while sync mode is parked. Use sync mode only in a dedicated process or aworker_threadwhose only job is to consume shreds. For mixed workloads, switch tofor awaitbelow.
Async-await flow. Compatible with any other Node async I/O (dgram, fs, HTTP clients, timers). The iterator parks on the libuv event loop while waiting on UDP.
for await (const { slot, transactions } of listener) {
// safe to await other async work here
}# Linux
sudo sysctl -w net.core.rmem_max=67108864
sudo sysctl -w net.core.busy_read=200
# macOS
sudo sysctl -w kern.ipc.maxsockbuf=67108864Need a feed? Launch a Solana Shred Stream on ShredStream.com โ sub-millisecond delivery, multiple global regions, 5-minute setup.
- ๐ Website: https://www.shredstream.com/
- ๐ Documentation: https://docs.shredstream.com/
- ๐ฆ X (Twitter): https://x.com/ShredStream
- ๐ฎ Discord: https://discord.gg/4w2DNbTaWD
- ๐ฌ Telegram: https://t.me/ShredStream
- ๐ป GitHub: https://github.com/ShredStream
- ๐ซ Support: Discord
- ๐ Benchmarks: Discord
MIT โ ShredStream.com
