decoded-shredstream
v0.1.1
Published
Official TypeScript/Node client for Decoded ShredStream by ShredStream.com — pre-execution Solana transactions decoded from shreds, over UDP push or gRPC.
Maintainers
Readme
Decoded ShredStream — TypeScript / Node client
TypeScript client for the Decoded ShredStream of ShredStream.com: pre-execution
Solana transactions, decoded from shreds — the serialized
VersionedTransaction, its signatures and its slot, delivered over gRPC or
UDP push the moment they propagate.
Before execution — transactions carry no status, logs, balance changes or inner instructions, and some will fail on-chain. Use a post-execution source to confirm.
npm install decoded-shredstreamimport { DecodedShredStream, FilterAll } from "decoded-shredstream";
const client = await DecodedShredStream.grpc({ endpoint, token, filters: { all: FilterAll } });
for await (const tx of client.transactions()) {
console.log(tx.slot, tx.signature.toBase58());
}Requirements — Node.js 20 or later, and a Decoded ShredStream subscription on ShredStream.com.
🔑 Access
Decoded ShredStream is a subscription product, available from ShredStream.com. One subscription covers both transports, and you can move from one to the other whenever you need to.
- gRPC — you receive an endpoint and an access token. Use the endpoint exactly as issued.
- UDP — you register your server's IP and port; datagrams are pushed to it.
Choosing a transport
Both carry the same data; they differ on what the protocol guarantees.
| | gRPC | UDP | |---|---|---| | Latency | higher | lowest | | Delivery | ordered, retransmitted | best-effort, no retransmission | | Server-side filters | yes | no — you receive the full stream |
⚡ Quickstart — gRPC
import { DecodedShredStream, FilterAll } from "decoded-shredstream";
const client = await DecodedShredStream.grpc({
endpoint: "your-endpoint.shredstream.com:PORT",
token: process.env.DECODED_SHREDSTREAM_TOKEN!,
filters: { all: FilterAll },
});
for await (const tx of client.transactions()) {
console.log(`slot=${tx.slot} sig=${tx.signature.toBase58()} ${tx.bytes.length} bytes`);
}DecodedShredStream.grpc() opens the stream and subscribes before it
resolves.
📡 Quickstart — UDP
import { DecodedShredStream } from "decoded-shredstream";
const client = await DecodedShredStream.udp({ port: 8002 });
console.log(`listening on ${client.localAddr}`);
client.on("transaction", (tx) => {
console.log(`slot=${tx.slot} sig=${tx.signature.toBase58()} ${tx.bytes.length} bytes`);
});8002 is only an example: bind whichever port you registered in your
account.
🔍 Transaction parsing
Every transaction exposes tx.bytes, in the standard Solana wire format, and
its signatures without any decoding:
tx.signature.toBase58(); // fee payer signature
tx.signatures.map((s) => s.toBase58()); // every signatureEverything else is available through parse():
client.on("transaction", (tx) => {
const { message } = tx.parse();
message.version; // 0, or "legacy"
message.staticAccounts; // Pubkey[]
message.lifetimeToken; // recent blockhash, or a durable nonce
message.instructions; // { programAddressIndex, accountIndices, data }
message.addressTableLookups; // versioned messages only
for (const ix of message.instructions) {
const program = message.staticAccounts[ix.programAddressIndex]!;
console.log(program.toBase58(), ix.data.length, "bytes of payload");
}
});🎯 Filters
Filters exist on the gRPC transport only. They are evaluated by the server; the client never filters locally. UDP delivers the full stream.
A subscription carries a map of named filters. Each filter has three optional lists of base58 account addresses, combined with a logical AND:
| list | semantics |
|---|---|
| include | the transaction touches at least one of these accounts |
| exclude | the transaction touches none of these accounts |
| required | the transaction touches all of these accounts |
Matching uses the account keys carried in the transaction, signers included. Addresses resolved through a lookup table cannot be filtered on.
An omitted or empty list is not a constraint, so a filter with no list at all
matches every transaction. FilterAll is exactly that filter:
import { DecodedShredStream, FilterAll } from "decoded-shredstream";
const client = await DecodedShredStream.grpc({
endpoint,
token,
filters: {
"watched-wallet": { include: [wallet] },
"amm-only": { required: [ammProgram], exclude: [blockedAccount] },
everything: FilterAll,
},
});Every update reports which filters matched it, and a transaction matching several named filters is delivered once:
client.on("transaction", (tx) => {
if (tx.filters.includes("watched-wallet")) { /* … */ }
});The whole map is replaced on a live stream, atomically and without a gap in the data:
await client.updateFilters({ "watched-wallet": { include: [wallet] } });The new map is also the one re-sent by any later reconnection.
🔄 Errors & reconnection
Recoverable interruptions never reach you: the client reconnects on its own and
re-sends the current filter map. Only a refused token, a session closed by the
server and a rejected filter map end the stream, once, through error:
client.on("error", (err) => {
console.error(`${err.name}: ${err.message}`);
});Every error class, the backoff policy and the telemetry notices are in docs/errors.md.
📖 Documentation
This README is what you need to receive transactions. The rest lives beside it:
| Document | Contents | |---|---| | docs/api.md | Every type and method: clients, configuration, filters, updates, UDP codec, performance notes and counters | | docs/errors.md | Error types, reconnection policy, telemetry notices |
💡 Examples
The examples/ directory holds runnable programs. Run them with
tsx:
DECODED_SHREDSTREAM_UDP_PORT=8002 npx tsx examples/udp_quickstart.ts
DECODED_SHREDSTREAM_ENDPOINT=your-endpoint.shredstream.com:PORT DECODED_SHREDSTREAM_TOKEN=... npx tsx examples/grpc_quickstart.ts| example | shows |
|---|---|
| udp_quickstart | binding the port and printing transactions |
| grpc_quickstart | connecting, subscribing to everything, iterating |
| grpc_filters | named filters and replacing the map on a live stream |
| parse_transaction | cheap accessors first, then parse() for the message |
| raw_bytes_pipeline | forwarding raw bytes as NDJSON without decoding |
| low_latency | synchronous handlers and receive buffer sizing |
⚖️ License
Apache-2.0. See LICENSE.
