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.
- shredstream-1.0.0/LICENSE +21 -0
- shredstream-1.0.0/PKG-INFO +173 -0
- shredstream-1.0.0/README.md +155 -0
- shredstream-1.0.0/pyproject.toml +26 -0
- shredstream-1.0.0/setup.cfg +4 -0
- shredstream-1.0.0/src/shredstream/__init__.py +3 -0
- shredstream-1.0.0/src/shredstream/accumulator.py +71 -0
- shredstream-1.0.0/src/shredstream/decoder.py +203 -0
- shredstream-1.0.0/src/shredstream/listener.py +80 -0
- shredstream-1.0.0/src/shredstream/parser.py +50 -0
- shredstream-1.0.0/src/shredstream.egg-info/PKG-INFO +173 -0
- shredstream-1.0.0/src/shredstream.egg-info/SOURCES.txt +13 -0
- shredstream-1.0.0/src/shredstream.egg-info/dependency_links.txt +1 -0
- shredstream-1.0.0/src/shredstream.egg-info/requires.txt +1 -0
- shredstream-1.0.0/src/shredstream.egg-info/top_level.txt +1 -0
|
@@ -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)
|
|
26
|
+
[](#)
|
|
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)
|
|
8
|
+
[](#)
|
|
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,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)
|
|
26
|
+
[](#)
|
|
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
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
solders>=0.21
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shredstream
|