shredstream 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShredStream.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: shredstream
3
+ Version: 1.0.0
4
+ Summary: Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from https://www.shredstream.com
5
+ Author: ShredStream.com
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://shredstream.com
8
+ Project-URL: Repository, https://github.com/shredstream/shredstream-sdk-python
9
+ Keywords: solana,shredstream,shred,stream,decoder,parser,transactions,grpc,rpc,websocket,udp,sdk,mev,hft,bot,defi,sniping,pumpfun,copytrading,blockchain,on-chain,jito,crypto,detection,trading,latency,speed,performance
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Classifier: Topic :: System :: Networking
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: solders>=0.21
17
+ Dynamic: license-file
18
+
19
+ # Solana ShredStream SDK for Python
20
+
21
+ Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from ShredStream.com
22
+
23
+ > Part of the [ShredStream.com](https://shredstream.com) ecosystem — ultra-low latency [Solana shred streaming](https://shredstream.com) via UDP.
24
+
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
26
+ [![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python&logoColor=white)](#)
27
+
28
+ ## 📋 Prerequisites
29
+
30
+ 1. **Create an account** on [ShredStream.com](https://shredstream.com)
31
+ 2. **Launch a Shred Stream** and pick your region (Frankfurt, Amsterdam, Singapore, Chicago, and more)
32
+ 3. **Enter your server's IP address** and the UDP port where you want to receive shreds
33
+ 4. **Open your firewall** for inbound UDP traffic on that port (e.g. configure your cloud provider's security group)
34
+ 5. Install [Python 3.10+](https://python.org) and pip
35
+
36
+ > 🎁 Want to try before you buy? Open a ticket on our [Discord](https://discord.gg/4w2DNbTaWD) to request a free trial.
37
+
38
+ ## 📦 Installation
39
+
40
+ ```bash
41
+ pip install shredstream
42
+ ```
43
+
44
+ ## ⚡ Quick Start
45
+
46
+ ```python
47
+ from shredstream import ShredListener
48
+ import os
49
+
50
+ # Bind to the UDP port configured on ShredStream.com
51
+ PORT = int(os.environ.get("SHREDSTREAM_PORT", 8001))
52
+ listener = ShredListener(port=PORT)
53
+
54
+ # Raw shreds — lowest latency, arrives before block assembly
55
+ for shred in listener.shreds():
56
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
57
+
58
+ # Decoded transactions — ready-to-use Solana transactions
59
+ for slot, transactions in ShredListener(port=PORT):
60
+ for tx in transactions:
61
+ print(tx.signature)
62
+ ```
63
+
64
+ ## 📖 API Reference
65
+
66
+ ### `ShredListener`
67
+
68
+ ```python
69
+ ShredListener(port=8001, recv_buf=25*1024*1024, max_age=10)
70
+ ```
71
+
72
+ | Parameter | Type | Default | Description |
73
+ |------------|-------|---------|----------------------------------|
74
+ | `port` | `int` | 8001 | UDP port to bind |
75
+ | `recv_buf` | `int` | 25 MB | Socket receive buffer size |
76
+ | `max_age` | `int` | 10 | Maximum slot age before eviction |
77
+
78
+ #### Methods
79
+
80
+ - `listener.shreds()` -- Generator yielding individual `ParsedShred` objects.
81
+ - **Iterator protocol** -- `for slot, transactions in listener:` yields decoded transactions per slot.
82
+ - `listener.active_slots()` -- Number of slots currently being accumulated.
83
+ - `listener.stop()` -- Closes the UDP socket.
84
+
85
+ ### `ParsedShred`
86
+
87
+ | Field | Type | Description |
88
+ |------------------|---------|----------------------------------------|
89
+ | `slot` | `int` | Slot number |
90
+ | `index` | `int` | Shred index within the slot |
91
+ | `payload` | `bytes` | Raw shred payload (after header) |
92
+ | `batch_complete` | `bool` | True if this shred ends an entry batch |
93
+ | `last_in_slot` | `bool` | True if this is the last shred in slot |
94
+
95
+ ### `Transaction`
96
+
97
+ | Field | Type | Description |
98
+ |--------------|------------------|---------------------------------------------------|
99
+ | `signatures` | `list[bytes]` | Raw 64-byte signatures |
100
+ | `raw` | `bytes` | Full wire-format transaction bytes |
101
+ | `signature` | `str` (property) | First signature as base58 (lazy, via `solders`) |
102
+
103
+ ## 🎯 Use Cases
104
+
105
+ 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.
106
+
107
+ ### 💎 PumpFun Token Sniping
108
+
109
+ ShredStream.com SDK detects PumpFun token creations **~499ms before they appear on PumpFun's live feed** — tested across 25 consecutive detections:
110
+
111
+ <img src="assets/shredstream.com_sdk_vs_pumpfun_live_feed.gif" alt="ShredStream.com SDK vs PumpFun live feed — ~499ms advantage" width="600">
112
+
113
+ > [ShredStream.com](https://shredstream.com) provides a complete, optimized PumpFun token creation detection code to all subscribers on a monthly plan. Battle-tested, high-performance, ready to plug into your sniping pipeline. To get access, open a ticket on [Discord](https://discord.gg/4w2DNbTaWD) or reach out on Telegram [@shredstream](https://t.me/shredstream).
114
+
115
+ ## ⚙️ Configuration
116
+
117
+ ### OS Tuning
118
+
119
+ ```bash
120
+ # Linux -- increase max receive buffer
121
+ sudo sysctl -w net.core.rmem_max=33554432
122
+
123
+ # macOS
124
+ sudo sysctl -w kern.ipc.maxsockbuf=33554432
125
+ ```
126
+
127
+ ### Dependencies
128
+
129
+ - `solders>=0.21` -- Required for base58 signature encoding (`tx.signature` property). Imported lazily on first access.
130
+
131
+ ## 💡 Examples
132
+
133
+ ### Filter by program
134
+
135
+ ```python
136
+ from shredstream import ShredListener
137
+
138
+ PUMP_FUN = bytes.fromhex("6ef8...") # program address bytes
139
+
140
+ for slot, txs in ShredListener(port=8001):
141
+ for tx in txs:
142
+ if PUMP_FUN in tx.raw:
143
+ print(f"slot {slot}: {tx.signature}")
144
+ ```
145
+
146
+ ### Raw shred access
147
+
148
+ ```python
149
+ from shredstream import ShredListener
150
+
151
+ listener = ShredListener(port=8001)
152
+ for shred in listener.shreds():
153
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
154
+ ```
155
+
156
+ ## 🚀 Launch a Shred Stream
157
+
158
+ Need a feed? **[Launch a Solana Shred Stream on ShredStream.com](https://shredstream.com)** — sub-millisecond delivery, multiple global regions, 5-minute setup.
159
+
160
+ ## 🔗 Links
161
+
162
+ - 🌐 Website: https://www.shredstream.com/
163
+ - 📖 Documentation: https://docs.shredstream.com/
164
+ - 🐦 X (Twitter): https://x.com/ShredStream
165
+ - 🎮 Discord: https://discord.gg/4w2DNbTaWD
166
+ - 💬 Telegram: https://t.me/ShredStream
167
+ - 💻 GitHub: https://github.com/ShredStream
168
+ - 🎫 Support: [Discord](https://discord.gg/4w2DNbTaWD)
169
+ - 📊 Benchmarks: [Discord](https://discord.gg/4w2DNbTaWD)
170
+
171
+ ## 📄 License
172
+
173
+ MIT — [ShredStream.com](https://shredstream.com)
@@ -0,0 +1,155 @@
1
+ # Solana ShredStream SDK for Python
2
+
3
+ Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from ShredStream.com
4
+
5
+ > Part of the [ShredStream.com](https://shredstream.com) ecosystem — ultra-low latency [Solana shred streaming](https://shredstream.com) via UDP.
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+ [![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python&logoColor=white)](#)
9
+
10
+ ## 📋 Prerequisites
11
+
12
+ 1. **Create an account** on [ShredStream.com](https://shredstream.com)
13
+ 2. **Launch a Shred Stream** and pick your region (Frankfurt, Amsterdam, Singapore, Chicago, and more)
14
+ 3. **Enter your server's IP address** and the UDP port where you want to receive shreds
15
+ 4. **Open your firewall** for inbound UDP traffic on that port (e.g. configure your cloud provider's security group)
16
+ 5. Install [Python 3.10+](https://python.org) and pip
17
+
18
+ > 🎁 Want to try before you buy? Open a ticket on our [Discord](https://discord.gg/4w2DNbTaWD) to request a free trial.
19
+
20
+ ## 📦 Installation
21
+
22
+ ```bash
23
+ pip install shredstream
24
+ ```
25
+
26
+ ## ⚡ Quick Start
27
+
28
+ ```python
29
+ from shredstream import ShredListener
30
+ import os
31
+
32
+ # Bind to the UDP port configured on ShredStream.com
33
+ PORT = int(os.environ.get("SHREDSTREAM_PORT", 8001))
34
+ listener = ShredListener(port=PORT)
35
+
36
+ # Raw shreds — lowest latency, arrives before block assembly
37
+ for shred in listener.shreds():
38
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
39
+
40
+ # Decoded transactions — ready-to-use Solana transactions
41
+ for slot, transactions in ShredListener(port=PORT):
42
+ for tx in transactions:
43
+ print(tx.signature)
44
+ ```
45
+
46
+ ## 📖 API Reference
47
+
48
+ ### `ShredListener`
49
+
50
+ ```python
51
+ ShredListener(port=8001, recv_buf=25*1024*1024, max_age=10)
52
+ ```
53
+
54
+ | Parameter | Type | Default | Description |
55
+ |------------|-------|---------|----------------------------------|
56
+ | `port` | `int` | 8001 | UDP port to bind |
57
+ | `recv_buf` | `int` | 25 MB | Socket receive buffer size |
58
+ | `max_age` | `int` | 10 | Maximum slot age before eviction |
59
+
60
+ #### Methods
61
+
62
+ - `listener.shreds()` -- Generator yielding individual `ParsedShred` objects.
63
+ - **Iterator protocol** -- `for slot, transactions in listener:` yields decoded transactions per slot.
64
+ - `listener.active_slots()` -- Number of slots currently being accumulated.
65
+ - `listener.stop()` -- Closes the UDP socket.
66
+
67
+ ### `ParsedShred`
68
+
69
+ | Field | Type | Description |
70
+ |------------------|---------|----------------------------------------|
71
+ | `slot` | `int` | Slot number |
72
+ | `index` | `int` | Shred index within the slot |
73
+ | `payload` | `bytes` | Raw shred payload (after header) |
74
+ | `batch_complete` | `bool` | True if this shred ends an entry batch |
75
+ | `last_in_slot` | `bool` | True if this is the last shred in slot |
76
+
77
+ ### `Transaction`
78
+
79
+ | Field | Type | Description |
80
+ |--------------|------------------|---------------------------------------------------|
81
+ | `signatures` | `list[bytes]` | Raw 64-byte signatures |
82
+ | `raw` | `bytes` | Full wire-format transaction bytes |
83
+ | `signature` | `str` (property) | First signature as base58 (lazy, via `solders`) |
84
+
85
+ ## 🎯 Use Cases
86
+
87
+ 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.
88
+
89
+ ### 💎 PumpFun Token Sniping
90
+
91
+ ShredStream.com SDK detects PumpFun token creations **~499ms before they appear on PumpFun's live feed** — tested across 25 consecutive detections:
92
+
93
+ <img src="assets/shredstream.com_sdk_vs_pumpfun_live_feed.gif" alt="ShredStream.com SDK vs PumpFun live feed — ~499ms advantage" width="600">
94
+
95
+ > [ShredStream.com](https://shredstream.com) provides a complete, optimized PumpFun token creation detection code to all subscribers on a monthly plan. Battle-tested, high-performance, ready to plug into your sniping pipeline. To get access, open a ticket on [Discord](https://discord.gg/4w2DNbTaWD) or reach out on Telegram [@shredstream](https://t.me/shredstream).
96
+
97
+ ## ⚙️ Configuration
98
+
99
+ ### OS Tuning
100
+
101
+ ```bash
102
+ # Linux -- increase max receive buffer
103
+ sudo sysctl -w net.core.rmem_max=33554432
104
+
105
+ # macOS
106
+ sudo sysctl -w kern.ipc.maxsockbuf=33554432
107
+ ```
108
+
109
+ ### Dependencies
110
+
111
+ - `solders>=0.21` -- Required for base58 signature encoding (`tx.signature` property). Imported lazily on first access.
112
+
113
+ ## 💡 Examples
114
+
115
+ ### Filter by program
116
+
117
+ ```python
118
+ from shredstream import ShredListener
119
+
120
+ PUMP_FUN = bytes.fromhex("6ef8...") # program address bytes
121
+
122
+ for slot, txs in ShredListener(port=8001):
123
+ for tx in txs:
124
+ if PUMP_FUN in tx.raw:
125
+ print(f"slot {slot}: {tx.signature}")
126
+ ```
127
+
128
+ ### Raw shred access
129
+
130
+ ```python
131
+ from shredstream import ShredListener
132
+
133
+ listener = ShredListener(port=8001)
134
+ for shred in listener.shreds():
135
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
136
+ ```
137
+
138
+ ## 🚀 Launch a Shred Stream
139
+
140
+ Need a feed? **[Launch a Solana Shred Stream on ShredStream.com](https://shredstream.com)** — sub-millisecond delivery, multiple global regions, 5-minute setup.
141
+
142
+ ## 🔗 Links
143
+
144
+ - 🌐 Website: https://www.shredstream.com/
145
+ - 📖 Documentation: https://docs.shredstream.com/
146
+ - 🐦 X (Twitter): https://x.com/ShredStream
147
+ - 🎮 Discord: https://discord.gg/4w2DNbTaWD
148
+ - 💬 Telegram: https://t.me/ShredStream
149
+ - 💻 GitHub: https://github.com/ShredStream
150
+ - 🎫 Support: [Discord](https://discord.gg/4w2DNbTaWD)
151
+ - 📊 Benchmarks: [Discord](https://discord.gg/4w2DNbTaWD)
152
+
153
+ ## 📄 License
154
+
155
+ MIT — [ShredStream.com](https://shredstream.com)
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shredstream"
7
+ version = "1.0.0"
8
+ description = "Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from https://www.shredstream.com"
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ authors = [{ name = "ShredStream.com" }]
12
+ readme = "README.md"
13
+ dependencies = ["solders>=0.21"]
14
+ keywords = ["solana", "shredstream", "shred", "stream", "decoder", "parser", "transactions", "grpc", "rpc", "websocket", "udp", "sdk", "mev", "hft", "bot", "defi", "sniping", "pumpfun", "copytrading", "blockchain", "on-chain", "jito", "crypto", "detection", "trading", "latency", "speed", "performance"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Software Development :: Libraries",
18
+ "Topic :: System :: Networking",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://shredstream.com"
23
+ Repository = "https://github.com/shredstream/shredstream-sdk-python"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from shredstream.listener import ShredListener
2
+
3
+ __all__ = ["ShredListener"]
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from shredstream.decoder import BatchDecoder, Transaction
4
+
5
+ _GAP_SKIP_THRESHOLD = 5
6
+
7
+
8
+ class SlotAccumulator:
9
+
10
+ def __init__(self) -> None:
11
+ self._pending: dict[int, tuple[bytes, bool, bool]] = {}
12
+ self._next_index: int = 0
13
+ self._decoder = BatchDecoder()
14
+ self._slot_complete: bool = False
15
+ self._stall_count: int = 0
16
+ self._decode_errors: int = 0
17
+
18
+ @property
19
+ def slot_complete(self) -> bool:
20
+ return self._slot_complete
21
+
22
+ @property
23
+ def decode_errors(self) -> int:
24
+ return self._decode_errors
25
+
26
+ def push(
27
+ self,
28
+ index: int,
29
+ payload: bytes,
30
+ batch_complete: bool,
31
+ last_in_slot: bool,
32
+ ) -> list[Transaction]:
33
+ if index in self._pending or index < self._next_index:
34
+ return []
35
+
36
+ self._pending[index] = (payload, batch_complete, last_in_slot)
37
+ return self._drain()
38
+
39
+ def _drain(self) -> list[Transaction]:
40
+ all_txs: list[Transaction] = []
41
+ drained_any = False
42
+
43
+ while self._next_index in self._pending:
44
+ drained_any = True
45
+ payload, batch_complete, last_in_slot = self._pending.pop(self._next_index)
46
+ self._next_index += 1
47
+
48
+ txs = self._decoder.push(payload)
49
+ if self._decoder.had_error:
50
+ self._decode_errors += 1
51
+ return all_txs
52
+
53
+ all_txs.extend(txs)
54
+
55
+ if last_in_slot:
56
+ self._slot_complete = True
57
+
58
+ if batch_complete:
59
+ self._decoder.reset()
60
+
61
+ if drained_any:
62
+ self._stall_count = 0
63
+ else:
64
+ self._stall_count += 1
65
+ if self._stall_count >= _GAP_SKIP_THRESHOLD and self._pending:
66
+ self._next_index = min(self._pending)
67
+ self._stall_count = 0
68
+ self._decoder.reset()
69
+ return self._drain()
70
+
71
+ return all_txs
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+
3
+ import struct
4
+ from dataclasses import dataclass, field
5
+
6
+
7
+ @dataclass(slots=True)
8
+ class Transaction:
9
+ signatures: list[bytes]
10
+ raw: bytes
11
+
12
+ @property
13
+ def signature(self) -> str:
14
+ from solders.signature import Signature
15
+
16
+ return str(Signature.from_bytes(self.signatures[0]))
17
+
18
+
19
+ def _read_compact_u16(buf: bytes | memoryview, off: int) -> tuple[int, int]:
20
+ b0 = buf[off]
21
+ if b0 < 0x80:
22
+ return b0, 1
23
+ b1 = buf[off + 1]
24
+ if b1 < 0x80:
25
+ return (b0 & 0x7F) | (b1 << 7), 2
26
+ b2 = buf[off + 2]
27
+ return (b0 & 0x7F) | ((b1 & 0x7F) << 7) | (b2 << 14), 3
28
+
29
+
30
+ def _try_read_compact_u16(
31
+ buf: bytes | memoryview, off: int, length: int
32
+ ) -> tuple[int | None, int]:
33
+ if off >= length:
34
+ return None, 0
35
+ b0 = buf[off]
36
+ if b0 < 0x80:
37
+ return b0, 1
38
+ if off + 1 >= length:
39
+ return None, 0
40
+ b1 = buf[off + 1]
41
+ if b1 < 0x80:
42
+ return (b0 & 0x7F) | (b1 << 7), 2
43
+ if off + 2 >= length:
44
+ return None, 0
45
+ b2 = buf[off + 2]
46
+ return (b0 & 0x7F) | ((b1 & 0x7F) << 7) | (b2 << 14), 3
47
+
48
+
49
+ def _try_parse_transaction(
50
+ buf: bytes | memoryview, off: int, length: int
51
+ ) -> tuple[int, list[bytes]] | None:
52
+ sig_count, consumed = _try_read_compact_u16(buf, off, length)
53
+ if sig_count is None:
54
+ return None
55
+ off += consumed
56
+
57
+ sigs: list[bytes] = []
58
+ for _ in range(sig_count):
59
+ if off + 64 > length:
60
+ return None
61
+ sigs.append(bytes(buf[off : off + 64]))
62
+ off += 64
63
+
64
+ if off >= length:
65
+ return None
66
+ first_byte = buf[off]
67
+ is_v0 = first_byte >= 0x80
68
+ if is_v0:
69
+ off += 1
70
+
71
+ off += 3
72
+ if off > length:
73
+ return None
74
+
75
+ acct_count, consumed = _try_read_compact_u16(buf, off, length)
76
+ if acct_count is None:
77
+ return None
78
+ off += consumed
79
+ off += acct_count * 32
80
+ if off > length:
81
+ return None
82
+
83
+ off += 32
84
+ if off > length:
85
+ return None
86
+
87
+ ix_count, consumed = _try_read_compact_u16(buf, off, length)
88
+ if ix_count is None:
89
+ return None
90
+ off += consumed
91
+ for _ in range(ix_count):
92
+ off += 1
93
+ if off > length:
94
+ return None
95
+ accts_len, consumed = _try_read_compact_u16(buf, off, length)
96
+ if accts_len is None:
97
+ return None
98
+ off += consumed + accts_len
99
+ if off > length:
100
+ return None
101
+ data_len, consumed = _try_read_compact_u16(buf, off, length)
102
+ if data_len is None:
103
+ return None
104
+ off += consumed + data_len
105
+ if off > length:
106
+ return None
107
+
108
+ if is_v0:
109
+ lookups_count, consumed = _try_read_compact_u16(buf, off, length)
110
+ if lookups_count is None:
111
+ return None
112
+ off += consumed
113
+ for _ in range(lookups_count):
114
+ off += 32
115
+ if off > length:
116
+ return None
117
+ writable_len, consumed = _try_read_compact_u16(buf, off, length)
118
+ if writable_len is None:
119
+ return None
120
+ off += consumed + writable_len
121
+ if off > length:
122
+ return None
123
+ readonly_len, consumed = _try_read_compact_u16(buf, off, length)
124
+ if readonly_len is None:
125
+ return None
126
+ off += consumed + readonly_len
127
+ if off > length:
128
+ return None
129
+
130
+ return off, sigs
131
+
132
+
133
+ _parse_transaction = _try_parse_transaction
134
+
135
+
136
+ class BatchDecoder:
137
+
138
+ def __init__(self) -> None:
139
+ self._buf = bytearray()
140
+ self._cursor: int = 0
141
+ self._expected_count: int | None = None
142
+ self._entries_yielded: int = 0
143
+ self._last_error: bool = False
144
+
145
+ def push(self, payload: bytes) -> list[Transaction]:
146
+ self._last_error = False
147
+ self._buf.extend(payload)
148
+ return self._drain()
149
+
150
+ @property
151
+ def had_error(self) -> bool:
152
+ return self._last_error
153
+
154
+ def reset(self) -> None:
155
+ self._buf.clear()
156
+ self._cursor = 0
157
+ self._expected_count = None
158
+ self._entries_yielded = 0
159
+ self._last_error = False
160
+
161
+ def _drain(self) -> list[Transaction]:
162
+ buf = memoryview(self._buf)
163
+ length = len(buf)
164
+
165
+ if self._expected_count is None:
166
+ if length < self._cursor + 8:
167
+ return []
168
+ count = struct.unpack_from("<Q", buf, self._cursor)[0]
169
+ self._cursor += 8
170
+ if count > 100_000:
171
+ self._last_error = True
172
+ return []
173
+ self._expected_count = count
174
+
175
+ transactions: list[Transaction] = []
176
+
177
+ while self._entries_yielded < self._expected_count:
178
+ if self._cursor + 48 > length:
179
+ break
180
+
181
+ off = self._cursor
182
+ off += 8 + 32
183
+ if off + 8 > length:
184
+ break
185
+ tx_count = struct.unpack_from("<Q", buf, off)[0]
186
+ off += 8
187
+
188
+ entry_txs: list[Transaction] = []
189
+ for _ in range(tx_count):
190
+ tx_start = off
191
+ result = _try_parse_transaction(buf, off, length)
192
+ if result is None:
193
+ return transactions
194
+ off, sigs = result
195
+ entry_txs.append(
196
+ Transaction(signatures=sigs, raw=bytes(buf[tx_start:off]))
197
+ )
198
+
199
+ transactions.extend(entry_txs)
200
+ self._cursor = off
201
+ self._entries_yielded += 1
202
+
203
+ return transactions
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import socket
4
+ from typing import Iterator
5
+
6
+ from shredstream.accumulator import SlotAccumulator
7
+ from shredstream.decoder import Transaction
8
+ from shredstream.parser import ParsedShred, parse_shred
9
+
10
+
11
+ class ShredListener:
12
+
13
+ def __init__(
14
+ self,
15
+ port: int = 8001,
16
+ recv_buf: int = 25 * 1024 * 1024,
17
+ max_age: int = 10,
18
+ ) -> None:
19
+ self._port = port
20
+ self._max_age = max_age
21
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
22
+ self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, recv_buf)
23
+ self._sock.bind(("0.0.0.0", port))
24
+
25
+ self._slots: dict[int, SlotAccumulator] = {}
26
+ self._highest_slot: int = 0
27
+
28
+ def __iter__(self) -> ShredListener:
29
+ return self
30
+
31
+ def __next__(self) -> tuple[int, list[Transaction]]:
32
+ while True:
33
+ data = self._sock.recv(2048)
34
+
35
+ shred = parse_shred(data)
36
+ if shred is None:
37
+ continue
38
+
39
+ txs = self._process_shred(shred)
40
+ if txs:
41
+ return shred.slot, txs
42
+
43
+ def shreds(self) -> Iterator[ParsedShred]:
44
+ while True:
45
+ data = self._sock.recv(2048)
46
+ shred = parse_shred(data)
47
+ if shred is not None:
48
+ yield shred
49
+
50
+ def stop(self) -> None:
51
+ self._sock.close()
52
+
53
+ def _process_shred(self, shred: ParsedShred) -> list[Transaction]:
54
+ slot = shred.slot
55
+
56
+ if slot > self._highest_slot:
57
+ self._highest_slot = slot
58
+ self._evict_old_slots()
59
+
60
+ if slot not in self._slots:
61
+ self._slots[slot] = SlotAccumulator()
62
+
63
+ acc = self._slots[slot]
64
+ prev_errors = acc.decode_errors
65
+ txs = acc.push(shred.index, shred.payload, shred.batch_complete, shred.last_in_slot)
66
+ new_errors = acc.decode_errors - prev_errors
67
+ if new_errors > 0:
68
+ del self._slots[slot]
69
+ return txs
70
+
71
+ if acc.slot_complete:
72
+ del self._slots[slot]
73
+
74
+ return txs
75
+
76
+ def _evict_old_slots(self) -> None:
77
+ cutoff = self._highest_slot - self._max_age
78
+ stale = [s for s in self._slots if s < cutoff]
79
+ for s in stale:
80
+ del self._slots[s]
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import struct
4
+ from dataclasses import dataclass
5
+
6
+ _SLOT_OFF = 0x41
7
+ _INDEX_OFF = 0x49
8
+ _FLAGS_OFF = 0x55
9
+ _SIZE_OFF = 0x56
10
+ DATA_HEADER_SIZE = 88
11
+
12
+ _DATA_COMPLETE = 0x40
13
+ _LAST_IN_SLOT = 0xC0
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class ParsedShred:
18
+ slot: int
19
+ index: int
20
+ payload: bytes
21
+ batch_complete: bool
22
+ last_in_slot: bool
23
+
24
+
25
+ def parse_shred(raw: bytes) -> ParsedShred | None:
26
+ if len(raw) < DATA_HEADER_SIZE:
27
+ return None
28
+
29
+ slot = struct.unpack_from("<Q", raw, _SLOT_OFF)[0]
30
+ index = struct.unpack_from("<I", raw, _INDEX_OFF)[0]
31
+ flags = raw[_FLAGS_OFF]
32
+ size = struct.unpack_from("<H", raw, _SIZE_OFF)[0]
33
+
34
+ if size < DATA_HEADER_SIZE:
35
+ return None
36
+
37
+ if size > len(raw):
38
+ return None
39
+ payload = raw[DATA_HEADER_SIZE:size]
40
+
41
+ last_in_slot = (flags & _LAST_IN_SLOT) == _LAST_IN_SLOT
42
+ batch_complete = last_in_slot or (flags & _DATA_COMPLETE) != 0
43
+
44
+ return ParsedShred(
45
+ slot=slot,
46
+ index=index,
47
+ payload=payload,
48
+ batch_complete=batch_complete,
49
+ last_in_slot=last_in_slot,
50
+ )
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: shredstream
3
+ Version: 1.0.0
4
+ Summary: Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from https://www.shredstream.com
5
+ Author: ShredStream.com
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://shredstream.com
8
+ Project-URL: Repository, https://github.com/shredstream/shredstream-sdk-python
9
+ Keywords: solana,shredstream,shred,stream,decoder,parser,transactions,grpc,rpc,websocket,udp,sdk,mev,hft,bot,defi,sniping,pumpfun,copytrading,blockchain,on-chain,jito,crypto,detection,trading,latency,speed,performance
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Classifier: Topic :: System :: Networking
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: solders>=0.21
17
+ Dynamic: license-file
18
+
19
+ # Solana ShredStream SDK for Python
20
+
21
+ Solana ShredStream SDK/Decoder for Python, enabling ultra-low latency Solana transaction streaming via UDP shreds from ShredStream.com
22
+
23
+ > Part of the [ShredStream.com](https://shredstream.com) ecosystem — ultra-low latency [Solana shred streaming](https://shredstream.com) via UDP.
24
+
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
26
+ [![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python&logoColor=white)](#)
27
+
28
+ ## 📋 Prerequisites
29
+
30
+ 1. **Create an account** on [ShredStream.com](https://shredstream.com)
31
+ 2. **Launch a Shred Stream** and pick your region (Frankfurt, Amsterdam, Singapore, Chicago, and more)
32
+ 3. **Enter your server's IP address** and the UDP port where you want to receive shreds
33
+ 4. **Open your firewall** for inbound UDP traffic on that port (e.g. configure your cloud provider's security group)
34
+ 5. Install [Python 3.10+](https://python.org) and pip
35
+
36
+ > 🎁 Want to try before you buy? Open a ticket on our [Discord](https://discord.gg/4w2DNbTaWD) to request a free trial.
37
+
38
+ ## 📦 Installation
39
+
40
+ ```bash
41
+ pip install shredstream
42
+ ```
43
+
44
+ ## ⚡ Quick Start
45
+
46
+ ```python
47
+ from shredstream import ShredListener
48
+ import os
49
+
50
+ # Bind to the UDP port configured on ShredStream.com
51
+ PORT = int(os.environ.get("SHREDSTREAM_PORT", 8001))
52
+ listener = ShredListener(port=PORT)
53
+
54
+ # Raw shreds — lowest latency, arrives before block assembly
55
+ for shred in listener.shreds():
56
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
57
+
58
+ # Decoded transactions — ready-to-use Solana transactions
59
+ for slot, transactions in ShredListener(port=PORT):
60
+ for tx in transactions:
61
+ print(tx.signature)
62
+ ```
63
+
64
+ ## 📖 API Reference
65
+
66
+ ### `ShredListener`
67
+
68
+ ```python
69
+ ShredListener(port=8001, recv_buf=25*1024*1024, max_age=10)
70
+ ```
71
+
72
+ | Parameter | Type | Default | Description |
73
+ |------------|-------|---------|----------------------------------|
74
+ | `port` | `int` | 8001 | UDP port to bind |
75
+ | `recv_buf` | `int` | 25 MB | Socket receive buffer size |
76
+ | `max_age` | `int` | 10 | Maximum slot age before eviction |
77
+
78
+ #### Methods
79
+
80
+ - `listener.shreds()` -- Generator yielding individual `ParsedShred` objects.
81
+ - **Iterator protocol** -- `for slot, transactions in listener:` yields decoded transactions per slot.
82
+ - `listener.active_slots()` -- Number of slots currently being accumulated.
83
+ - `listener.stop()` -- Closes the UDP socket.
84
+
85
+ ### `ParsedShred`
86
+
87
+ | Field | Type | Description |
88
+ |------------------|---------|----------------------------------------|
89
+ | `slot` | `int` | Slot number |
90
+ | `index` | `int` | Shred index within the slot |
91
+ | `payload` | `bytes` | Raw shred payload (after header) |
92
+ | `batch_complete` | `bool` | True if this shred ends an entry batch |
93
+ | `last_in_slot` | `bool` | True if this is the last shred in slot |
94
+
95
+ ### `Transaction`
96
+
97
+ | Field | Type | Description |
98
+ |--------------|------------------|---------------------------------------------------|
99
+ | `signatures` | `list[bytes]` | Raw 64-byte signatures |
100
+ | `raw` | `bytes` | Full wire-format transaction bytes |
101
+ | `signature` | `str` (property) | First signature as base58 (lazy, via `solders`) |
102
+
103
+ ## 🎯 Use Cases
104
+
105
+ 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.
106
+
107
+ ### 💎 PumpFun Token Sniping
108
+
109
+ ShredStream.com SDK detects PumpFun token creations **~499ms before they appear on PumpFun's live feed** — tested across 25 consecutive detections:
110
+
111
+ <img src="assets/shredstream.com_sdk_vs_pumpfun_live_feed.gif" alt="ShredStream.com SDK vs PumpFun live feed — ~499ms advantage" width="600">
112
+
113
+ > [ShredStream.com](https://shredstream.com) provides a complete, optimized PumpFun token creation detection code to all subscribers on a monthly plan. Battle-tested, high-performance, ready to plug into your sniping pipeline. To get access, open a ticket on [Discord](https://discord.gg/4w2DNbTaWD) or reach out on Telegram [@shredstream](https://t.me/shredstream).
114
+
115
+ ## ⚙️ Configuration
116
+
117
+ ### OS Tuning
118
+
119
+ ```bash
120
+ # Linux -- increase max receive buffer
121
+ sudo sysctl -w net.core.rmem_max=33554432
122
+
123
+ # macOS
124
+ sudo sysctl -w kern.ipc.maxsockbuf=33554432
125
+ ```
126
+
127
+ ### Dependencies
128
+
129
+ - `solders>=0.21` -- Required for base58 signature encoding (`tx.signature` property). Imported lazily on first access.
130
+
131
+ ## 💡 Examples
132
+
133
+ ### Filter by program
134
+
135
+ ```python
136
+ from shredstream import ShredListener
137
+
138
+ PUMP_FUN = bytes.fromhex("6ef8...") # program address bytes
139
+
140
+ for slot, txs in ShredListener(port=8001):
141
+ for tx in txs:
142
+ if PUMP_FUN in tx.raw:
143
+ print(f"slot {slot}: {tx.signature}")
144
+ ```
145
+
146
+ ### Raw shred access
147
+
148
+ ```python
149
+ from shredstream import ShredListener
150
+
151
+ listener = ShredListener(port=8001)
152
+ for shred in listener.shreds():
153
+ print(f"slot={shred.slot} index={shred.index} len={len(shred.payload)}")
154
+ ```
155
+
156
+ ## 🚀 Launch a Shred Stream
157
+
158
+ Need a feed? **[Launch a Solana Shred Stream on ShredStream.com](https://shredstream.com)** — sub-millisecond delivery, multiple global regions, 5-minute setup.
159
+
160
+ ## 🔗 Links
161
+
162
+ - 🌐 Website: https://www.shredstream.com/
163
+ - 📖 Documentation: https://docs.shredstream.com/
164
+ - 🐦 X (Twitter): https://x.com/ShredStream
165
+ - 🎮 Discord: https://discord.gg/4w2DNbTaWD
166
+ - 💬 Telegram: https://t.me/ShredStream
167
+ - 💻 GitHub: https://github.com/ShredStream
168
+ - 🎫 Support: [Discord](https://discord.gg/4w2DNbTaWD)
169
+ - 📊 Benchmarks: [Discord](https://discord.gg/4w2DNbTaWD)
170
+
171
+ ## 📄 License
172
+
173
+ MIT — [ShredStream.com](https://shredstream.com)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/shredstream/__init__.py
5
+ src/shredstream/accumulator.py
6
+ src/shredstream/decoder.py
7
+ src/shredstream/listener.py
8
+ src/shredstream/parser.py
9
+ src/shredstream.egg-info/PKG-INFO
10
+ src/shredstream.egg-info/SOURCES.txt
11
+ src/shredstream.egg-info/dependency_links.txt
12
+ src/shredstream.egg-info/requires.txt
13
+ src/shredstream.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ solders>=0.21
@@ -0,0 +1 @@
1
+ shredstream