@validators-dao/solana-stream-sdk 1.0.1 → 1.1.0
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.
- package/README.md +63 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,10 +27,11 @@ Solana Stream SDK by Validators DAO - A TypeScript SDK for streaming Solana bloc
|
|
|
27
27
|
<img src="https://storage.slv.dev/PoweredBySolana.svg" alt="Powered By Solana" width="200px" height="95px">
|
|
28
28
|
</a>
|
|
29
29
|
|
|
30
|
-
## What's New in v1.0
|
|
30
|
+
## What's New in v1.1.0
|
|
31
31
|
|
|
32
|
+
- Refreshed starter layout and docs to highlight trading hooks
|
|
32
33
|
- Yellowstone Geyser gRPC connection upgraded to an NAPI-RS-powered client for better backpressure
|
|
33
|
-
- NAPI-powered Shreds client/decoder so TypeScript can tap
|
|
34
|
+
- NAPI-powered Shreds client/decoder so TypeScript can tap near-native throughput
|
|
34
35
|
- Improved backpressure handling and up to 4x streaming efficiency (400% improvement)
|
|
35
36
|
- Faster real-time Geyser streams for TypeScript clients with lower overhead
|
|
36
37
|
|
|
@@ -39,7 +40,7 @@ Solana Stream SDK by Validators DAO - A TypeScript SDK for streaming Solana bloc
|
|
|
39
40
|
- Ping/Pong handling to keep Yellowstone gRPC streams alive
|
|
40
41
|
- Exponential reconnect backoff plus `fromSlot` gap recovery
|
|
41
42
|
- Bounded in-memory queue with drop logging for backpressure safety
|
|
42
|
-
-
|
|
43
|
+
- Update subscriptions by writing a new request to the stream (no reconnect)
|
|
43
44
|
- Optional runtime metrics logging (rates, queue size, drops)
|
|
44
45
|
- Default filters drop vote/failed transactions to reduce traffic
|
|
45
46
|
|
|
@@ -49,9 +50,7 @@ duplicates are expected.
|
|
|
49
50
|
## Performance Highlights
|
|
50
51
|
|
|
51
52
|
- NAPI-powered Geyser gRPC and Shreds client/decoder for high-throughput streaming
|
|
52
|
-
- TypeScript ergonomics with
|
|
53
|
-
- For the absolute fastest signal path, see Rust UDP Shreds in the repo:
|
|
54
|
-
https://github.com/ValidatorsDAO/solana-stream#shreds-udp-pumpfun-watcher-rust
|
|
53
|
+
- TypeScript ergonomics with native performance under the hood
|
|
55
54
|
|
|
56
55
|
## Installation
|
|
57
56
|
|
|
@@ -67,69 +66,36 @@ pnpm add @validators-dao/solana-stream-sdk
|
|
|
67
66
|
|
|
68
67
|
## Usage
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
### Geyser Client (TypeScript)
|
|
70
|
+
|
|
71
|
+
For a production-ready runner (reconnects, backpressure handling, metrics),
|
|
72
|
+
see https://github.com/ValidatorsDAO/solana-stream/tree/main/client/geyser-ts.
|
|
73
|
+
|
|
74
|
+
Minimal SDK usage (filters defined in code):
|
|
71
75
|
|
|
72
76
|
```typescript
|
|
73
77
|
import {
|
|
74
|
-
GeyserClient,
|
|
75
|
-
bs58,
|
|
76
78
|
CommitmentLevel,
|
|
77
|
-
SubscribeRequestAccountsDataSlice,
|
|
78
|
-
SubscribeRequestFilterAccounts,
|
|
79
|
-
SubscribeRequestFilterBlocks,
|
|
80
|
-
SubscribeRequestFilterBlocksMeta,
|
|
81
|
-
SubscribeRequestFilterEntry,
|
|
82
|
-
SubscribeRequestFilterSlots,
|
|
83
79
|
SubscribeRequestFilterTransactions,
|
|
80
|
+
GeyserClient,
|
|
81
|
+
bs58,
|
|
84
82
|
} from '@validators-dao/solana-stream-sdk'
|
|
85
83
|
import 'dotenv/config'
|
|
86
84
|
|
|
87
|
-
interface SubscribeRequest {
|
|
88
|
-
accounts: {
|
|
89
|
-
[key: string]: SubscribeRequestFilterAccounts
|
|
90
|
-
}
|
|
91
|
-
slots: {
|
|
92
|
-
[key: string]: SubscribeRequestFilterSlots
|
|
93
|
-
}
|
|
94
|
-
transactions: {
|
|
95
|
-
[key: string]: SubscribeRequestFilterTransactions
|
|
96
|
-
}
|
|
97
|
-
transactionsStatus: {
|
|
98
|
-
[key: string]: SubscribeRequestFilterTransactions
|
|
99
|
-
}
|
|
100
|
-
blocks: {
|
|
101
|
-
[key: string]: SubscribeRequestFilterBlocks
|
|
102
|
-
}
|
|
103
|
-
blocksMeta: {
|
|
104
|
-
[key: string]: SubscribeRequestFilterBlocksMeta
|
|
105
|
-
}
|
|
106
|
-
entry: {
|
|
107
|
-
[key: string]: SubscribeRequestFilterEntry
|
|
108
|
-
}
|
|
109
|
-
commitment?: CommitmentLevel | undefined
|
|
110
|
-
accountsDataSlice: SubscribeRequestAccountsDataSlice[]
|
|
111
|
-
ping?: any
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// const PUMP_FUN_MINT_AUTHORITY = 'TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM'
|
|
115
85
|
const PUMP_FUN_PROGRAM_ID = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'
|
|
116
86
|
|
|
117
|
-
const
|
|
87
|
+
const pumpfun: SubscribeRequestFilterTransactions = {
|
|
88
|
+
vote: false,
|
|
89
|
+
failed: false,
|
|
118
90
|
accountInclude: [PUMP_FUN_PROGRAM_ID],
|
|
119
91
|
accountExclude: [],
|
|
120
92
|
accountRequired: [],
|
|
121
93
|
}
|
|
122
94
|
|
|
123
|
-
const request
|
|
124
|
-
accounts: {
|
|
125
|
-
pumpfun: {
|
|
126
|
-
account: [],
|
|
127
|
-
owner: [],
|
|
128
|
-
filters: [],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
95
|
+
const request = {
|
|
96
|
+
accounts: {},
|
|
131
97
|
slots: {},
|
|
132
|
-
transactions: {
|
|
98
|
+
transactions: { pumpfun },
|
|
133
99
|
transactionsStatus: {},
|
|
134
100
|
blocks: {},
|
|
135
101
|
blocksMeta: {},
|
|
@@ -138,101 +104,55 @@ const request: SubscribeRequest = {
|
|
|
138
104
|
commitment: CommitmentLevel.PROCESSED,
|
|
139
105
|
}
|
|
140
106
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const createClient = () => {
|
|
146
|
-
const token = process.env.X_TOKEN?.trim()
|
|
147
|
-
if (!token) {
|
|
148
|
-
console.warn('X_TOKEN not set. Connecting without auth.')
|
|
149
|
-
}
|
|
150
|
-
const endpoint = `https://grpc-ams-3.erpc.global`
|
|
151
|
-
console.log('Connecting to', endpoint)
|
|
107
|
+
const main = async () => {
|
|
108
|
+
const endpoint = process.env.GEYSER_ENDPOINT || 'http://localhost:10000'
|
|
109
|
+
const token = process.env.X_TOKEN?.trim()
|
|
110
|
+
const client = new GeyserClient(endpoint, token || undefined, undefined)
|
|
152
111
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
112
|
+
await client.connect()
|
|
113
|
+
const stream = await client.subscribe()
|
|
156
114
|
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
115
|
+
stream.on('data', (data: any) => {
|
|
116
|
+
if (data?.ping != undefined) {
|
|
117
|
+
stream.write({ ping: { id: 1 } }, () => undefined)
|
|
118
|
+
return
|
|
160
119
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const client = createClient()
|
|
164
|
-
await client.connect()
|
|
165
|
-
const version = await client.getVersion()
|
|
166
|
-
console.log('version: ', version)
|
|
167
|
-
const stream = await client.subscribe()
|
|
168
|
-
stream.on('data', async (data: any) => {
|
|
169
|
-
if (data.transaction !== undefined) {
|
|
170
|
-
const transaction = data.transaction
|
|
171
|
-
const txnSignature = transaction.transaction.signature
|
|
172
|
-
const tx = bs58.encode(new Uint8Array(txnSignature))
|
|
173
|
-
console.log('tx:', tx)
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
if (data.account === undefined) {
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
// console.log('data:', JSON.stringify(data, null, 2))
|
|
180
|
-
|
|
181
|
-
const accounts = data.account
|
|
182
|
-
const rawPubkey = accounts.account.pubkey
|
|
183
|
-
const rawTxnSignature = accounts.account.txnSignature
|
|
184
|
-
const pubkey = bs58.encode(new Uint8Array(rawPubkey))
|
|
185
|
-
const txnSignature = bs58.encode(new Uint8Array(rawTxnSignature))
|
|
186
|
-
console.log('pubkey:', pubkey)
|
|
187
|
-
console.log('txnSignature:', txnSignature)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
stream.on('error', async (e: any) => {
|
|
191
|
-
console.error('Stream error:', e)
|
|
192
|
-
console.log(`Reconnecting ...`)
|
|
193
|
-
await connect(retries + 1)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
await new Promise<void>((resolve, reject) => {
|
|
197
|
-
stream.write(request, (err: any) => {
|
|
198
|
-
if (!err) {
|
|
199
|
-
resolve()
|
|
200
|
-
} else {
|
|
201
|
-
console.error('Request error:', err)
|
|
202
|
-
reject(err)
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
}).catch((reason) => {
|
|
206
|
-
console.error(reason)
|
|
207
|
-
throw reason
|
|
208
|
-
})
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error(`Connection failed. Retrying ...`, error)
|
|
211
|
-
await connect(retries + 1)
|
|
120
|
+
if (data?.pong != undefined) {
|
|
121
|
+
return
|
|
212
122
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
123
|
+
if (data?.transaction != undefined) {
|
|
124
|
+
const signature = data.transaction.transaction.signature
|
|
125
|
+
const txSignature = bs58.encode(new Uint8Array(signature))
|
|
217
126
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
127
|
+
// TODO: Add your trade logic here.
|
|
128
|
+
console.log('tx:', txSignature)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
await new Promise<void>((resolve, reject) => {
|
|
133
|
+
stream.write(request, (err: any) => {
|
|
134
|
+
if (!err) {
|
|
135
|
+
resolve()
|
|
136
|
+
} else {
|
|
137
|
+
console.error('Request error:', err)
|
|
138
|
+
reject(err)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
})
|
|
224
142
|
}
|
|
225
143
|
|
|
226
|
-
main()
|
|
144
|
+
void main()
|
|
227
145
|
```
|
|
228
146
|
|
|
229
147
|
If your endpoint requires authentication, set the `X_TOKEN` environment variable with your gRPC token.
|
|
230
|
-
|
|
231
|
-
Please note that the url endpoint in the example is for demonstration purposes. You should replace it with the actual endpoint you are using.
|
|
148
|
+
Please note that the endpoint in the example is for demonstration purposes. Replace it with your actual endpoint.
|
|
232
149
|
|
|
233
150
|
### Shreds Client
|
|
234
151
|
|
|
235
|
-
|
|
152
|
+
For a working starter that includes latency checks, see
|
|
153
|
+
https://github.com/ValidatorsDAO/solana-stream/tree/main/client/shreds-ts.
|
|
154
|
+
|
|
155
|
+
Here's a minimal example:
|
|
236
156
|
|
|
237
157
|
```typescript
|
|
238
158
|
import {
|
|
@@ -241,9 +161,6 @@ import {
|
|
|
241
161
|
// decodeSolanaEntries,
|
|
242
162
|
} from '@validators-dao/solana-stream-sdk'
|
|
243
163
|
import 'dotenv/config'
|
|
244
|
-
// import { logDecodedEntries } from '@/utils/logDecodedEntries'
|
|
245
|
-
|
|
246
|
-
import { receivedSlots, startLatencyCheck } from '@/utils/checkLatency'
|
|
247
164
|
|
|
248
165
|
const endpoint = process.env.SHREDS_ENDPOINT!
|
|
249
166
|
|
|
@@ -263,29 +180,20 @@ const connect = () => {
|
|
|
263
180
|
client.subscribeEntries(
|
|
264
181
|
JSON.stringify(request),
|
|
265
182
|
(_error: any, buffer: any) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const {
|
|
269
|
-
slot,
|
|
270
|
-
// entries
|
|
271
|
-
} = JSON.parse(buffer)
|
|
272
|
-
|
|
273
|
-
// You can decode entries as needed
|
|
274
|
-
// const decodedEntries = decodeSolanaEntries(new Uint8Array(entries))
|
|
275
|
-
// logDecodedEntries(decodedEntries)
|
|
276
|
-
|
|
277
|
-
if (!receivedSlots.has(slot)) {
|
|
278
|
-
receivedSlots.set(slot, [{ receivedAt }])
|
|
279
|
-
} else {
|
|
280
|
-
receivedSlots.get(slot)!.push({ receivedAt })
|
|
281
|
-
}
|
|
183
|
+
if (!buffer) {
|
|
184
|
+
return
|
|
282
185
|
}
|
|
186
|
+
const { slot } = JSON.parse(buffer)
|
|
187
|
+
|
|
188
|
+
// You can decode entries as needed
|
|
189
|
+
// const decodedEntries = decodeSolanaEntries(new Uint8Array(entries))
|
|
190
|
+
|
|
191
|
+
console.log('slot:', slot)
|
|
283
192
|
},
|
|
284
193
|
)
|
|
285
194
|
}
|
|
286
195
|
|
|
287
196
|
connect()
|
|
288
|
-
startLatencyCheck()
|
|
289
197
|
```
|
|
290
198
|
|
|
291
199
|
Ensure the environment variable `SHREDS_ENDPOINT` is set correctly.
|
|
@@ -342,18 +250,6 @@ Other reports and suggestions are also highly appreciated.
|
|
|
342
250
|
You can also join discussions or share feedback on Validators DAO's Discord community:
|
|
343
251
|
https://discord.gg/C7ZQSrCkYR
|
|
344
252
|
|
|
345
|
-
## Shreds UDP configuration (Rust client)
|
|
346
|
-
|
|
347
|
-
- Load base config via `SHREDS_UDP_CONFIG=/path/to/config.{json,toml}`; env vars then override it.
|
|
348
|
-
- Core envs:
|
|
349
|
-
- `SOLANA_RPC_ENDPOINT`
|
|
350
|
-
- Logs: `SHREDS_UDP_LOG_RAW`, `SHREDS_UDP_LOG_SHREDS`, `SHREDS_UDP_LOG_ENTRIES`, `SHREDS_UDP_LOG_WATCH_HITS`, `SHREDS_UDP_LOG_DEFER`
|
|
351
|
-
- Watch lists: `SHREDS_UDP_WATCH_PROGRAM_IDS`, `SHREDS_UDP_WATCH_AUTHORITIES` (defaults to pump.fun program/authority)
|
|
352
|
-
- Hardening: `SHREDS_UDP_REQUIRE_CODE_MATCH`, `SHREDS_UDP_STRICT_FEC`, `SHREDS_UDP_STRICT_NUM_DATA`, `SHREDS_UDP_STRICT_NUM_CODING`
|
|
353
|
-
- Slot window: `SHREDS_UDP_ROOT_SLOT`, `SHREDS_UDP_MAX_FUTURE` (default 512)
|
|
354
|
-
- TTLs: `SHREDS_UDP_COMPLETED_TTL_MS` (default 30s), `SHREDS_UDP_EVICT_COOLDOWN_MS` (default 300ms), `SHREDS_UDP_WARN_ONCE` (default true)
|
|
355
|
-
- Pump.fun: program and mint-authority pubkeys are fixed; token mint addresses are discovered per transaction at runtime (not hardcoded).
|
|
356
|
-
|
|
357
253
|
## Repository
|
|
358
254
|
|
|
359
255
|
This package is part of the [Solana Stream](https://github.com/ValidatorsDAO/solana-stream) monorepo.
|