@validators-dao/solana-stream-sdk 1.0.0 → 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 +78 -162
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,12 +27,31 @@ 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.
|
|
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
|
|
34
|
+
- NAPI-powered Shreds client/decoder so TypeScript can tap near-native throughput
|
|
33
35
|
- Improved backpressure handling and up to 4x streaming efficiency (400% improvement)
|
|
34
36
|
- Faster real-time Geyser streams for TypeScript clients with lower overhead
|
|
35
37
|
|
|
38
|
+
## Production-Ready Geyser Client (TypeScript Best Practices)
|
|
39
|
+
|
|
40
|
+
- Ping/Pong handling to keep Yellowstone gRPC streams alive
|
|
41
|
+
- Exponential reconnect backoff plus `fromSlot` gap recovery
|
|
42
|
+
- Bounded in-memory queue with drop logging for backpressure safety
|
|
43
|
+
- Update subscriptions by writing a new request to the stream (no reconnect)
|
|
44
|
+
- Optional runtime metrics logging (rates, queue size, drops)
|
|
45
|
+
- Default filters drop vote/failed transactions to reduce traffic
|
|
46
|
+
|
|
47
|
+
Tip: start with slots, then add filters as needed. When resuming from `fromSlot`,
|
|
48
|
+
duplicates are expected.
|
|
49
|
+
|
|
50
|
+
## Performance Highlights
|
|
51
|
+
|
|
52
|
+
- NAPI-powered Geyser gRPC and Shreds client/decoder for high-throughput streaming
|
|
53
|
+
- TypeScript ergonomics with native performance under the hood
|
|
54
|
+
|
|
36
55
|
## Installation
|
|
37
56
|
|
|
38
57
|
```bash
|
|
@@ -47,69 +66,36 @@ pnpm add @validators-dao/solana-stream-sdk
|
|
|
47
66
|
|
|
48
67
|
## Usage
|
|
49
68
|
|
|
50
|
-
|
|
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):
|
|
51
75
|
|
|
52
76
|
```typescript
|
|
53
77
|
import {
|
|
54
|
-
GeyserClient,
|
|
55
|
-
bs58,
|
|
56
78
|
CommitmentLevel,
|
|
57
|
-
SubscribeRequestAccountsDataSlice,
|
|
58
|
-
SubscribeRequestFilterAccounts,
|
|
59
|
-
SubscribeRequestFilterBlocks,
|
|
60
|
-
SubscribeRequestFilterBlocksMeta,
|
|
61
|
-
SubscribeRequestFilterEntry,
|
|
62
|
-
SubscribeRequestFilterSlots,
|
|
63
79
|
SubscribeRequestFilterTransactions,
|
|
80
|
+
GeyserClient,
|
|
81
|
+
bs58,
|
|
64
82
|
} from '@validators-dao/solana-stream-sdk'
|
|
65
83
|
import 'dotenv/config'
|
|
66
84
|
|
|
67
|
-
interface SubscribeRequest {
|
|
68
|
-
accounts: {
|
|
69
|
-
[key: string]: SubscribeRequestFilterAccounts
|
|
70
|
-
}
|
|
71
|
-
slots: {
|
|
72
|
-
[key: string]: SubscribeRequestFilterSlots
|
|
73
|
-
}
|
|
74
|
-
transactions: {
|
|
75
|
-
[key: string]: SubscribeRequestFilterTransactions
|
|
76
|
-
}
|
|
77
|
-
transactionsStatus: {
|
|
78
|
-
[key: string]: SubscribeRequestFilterTransactions
|
|
79
|
-
}
|
|
80
|
-
blocks: {
|
|
81
|
-
[key: string]: SubscribeRequestFilterBlocks
|
|
82
|
-
}
|
|
83
|
-
blocksMeta: {
|
|
84
|
-
[key: string]: SubscribeRequestFilterBlocksMeta
|
|
85
|
-
}
|
|
86
|
-
entry: {
|
|
87
|
-
[key: string]: SubscribeRequestFilterEntry
|
|
88
|
-
}
|
|
89
|
-
commitment?: CommitmentLevel | undefined
|
|
90
|
-
accountsDataSlice: SubscribeRequestAccountsDataSlice[]
|
|
91
|
-
ping?: any
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// const PUMP_FUN_MINT_AUTHORITY = 'TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM'
|
|
95
85
|
const PUMP_FUN_PROGRAM_ID = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'
|
|
96
86
|
|
|
97
|
-
const
|
|
87
|
+
const pumpfun: SubscribeRequestFilterTransactions = {
|
|
88
|
+
vote: false,
|
|
89
|
+
failed: false,
|
|
98
90
|
accountInclude: [PUMP_FUN_PROGRAM_ID],
|
|
99
91
|
accountExclude: [],
|
|
100
92
|
accountRequired: [],
|
|
101
93
|
}
|
|
102
94
|
|
|
103
|
-
const request
|
|
104
|
-
accounts: {
|
|
105
|
-
pumpfun: {
|
|
106
|
-
account: [],
|
|
107
|
-
owner: [],
|
|
108
|
-
filters: [],
|
|
109
|
-
},
|
|
110
|
-
},
|
|
95
|
+
const request = {
|
|
96
|
+
accounts: {},
|
|
111
97
|
slots: {},
|
|
112
|
-
transactions: {
|
|
98
|
+
transactions: { pumpfun },
|
|
113
99
|
transactionsStatus: {},
|
|
114
100
|
blocks: {},
|
|
115
101
|
blocksMeta: {},
|
|
@@ -118,101 +104,55 @@ const request: SubscribeRequest = {
|
|
|
118
104
|
commitment: CommitmentLevel.PROCESSED,
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const createClient = () => {
|
|
126
|
-
const token = process.env.X_TOKEN?.trim()
|
|
127
|
-
if (!token) {
|
|
128
|
-
console.warn('X_TOKEN not set. Connecting without auth.')
|
|
129
|
-
}
|
|
130
|
-
const endpoint = `https://grpc-ams-3.erpc.global`
|
|
131
|
-
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)
|
|
132
111
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
112
|
+
await client.connect()
|
|
113
|
+
const stream = await client.subscribe()
|
|
136
114
|
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
115
|
+
stream.on('data', (data: any) => {
|
|
116
|
+
if (data?.ping != undefined) {
|
|
117
|
+
stream.write({ ping: { id: 1 } }, () => undefined)
|
|
118
|
+
return
|
|
140
119
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const client = createClient()
|
|
144
|
-
await client.connect()
|
|
145
|
-
const version = await client.getVersion()
|
|
146
|
-
console.log('version: ', version)
|
|
147
|
-
const stream = await client.subscribe()
|
|
148
|
-
stream.on('data', async (data: any) => {
|
|
149
|
-
if (data.transaction !== undefined) {
|
|
150
|
-
const transaction = data.transaction
|
|
151
|
-
const txnSignature = transaction.transaction.signature
|
|
152
|
-
const tx = bs58.encode(new Uint8Array(txnSignature))
|
|
153
|
-
console.log('tx:', tx)
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
if (data.account === undefined) {
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
// console.log('data:', JSON.stringify(data, null, 2))
|
|
160
|
-
|
|
161
|
-
const accounts = data.account
|
|
162
|
-
const rawPubkey = accounts.account.pubkey
|
|
163
|
-
const rawTxnSignature = accounts.account.txnSignature
|
|
164
|
-
const pubkey = bs58.encode(new Uint8Array(rawPubkey))
|
|
165
|
-
const txnSignature = bs58.encode(new Uint8Array(rawTxnSignature))
|
|
166
|
-
console.log('pubkey:', pubkey)
|
|
167
|
-
console.log('txnSignature:', txnSignature)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
stream.on('error', async (e: any) => {
|
|
171
|
-
console.error('Stream error:', e)
|
|
172
|
-
console.log(`Reconnecting ...`)
|
|
173
|
-
await connect(retries + 1)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
await new Promise<void>((resolve, reject) => {
|
|
177
|
-
stream.write(request, (err: any) => {
|
|
178
|
-
if (!err) {
|
|
179
|
-
resolve()
|
|
180
|
-
} else {
|
|
181
|
-
console.error('Request error:', err)
|
|
182
|
-
reject(err)
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
}).catch((reason) => {
|
|
186
|
-
console.error(reason)
|
|
187
|
-
throw reason
|
|
188
|
-
})
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error(`Connection failed. Retrying ...`, error)
|
|
191
|
-
await connect(retries + 1)
|
|
120
|
+
if (data?.pong != undefined) {
|
|
121
|
+
return
|
|
192
122
|
}
|
|
193
|
-
|
|
123
|
+
if (data?.transaction != undefined) {
|
|
124
|
+
const signature = data.transaction.transaction.signature
|
|
125
|
+
const txSignature = bs58.encode(new Uint8Array(signature))
|
|
194
126
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
})
|
|
204
142
|
}
|
|
205
143
|
|
|
206
|
-
main()
|
|
144
|
+
void main()
|
|
207
145
|
```
|
|
208
146
|
|
|
209
147
|
If your endpoint requires authentication, set the `X_TOKEN` environment variable with your gRPC token.
|
|
210
|
-
|
|
211
|
-
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.
|
|
212
149
|
|
|
213
150
|
### Shreds Client
|
|
214
151
|
|
|
215
|
-
|
|
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:
|
|
216
156
|
|
|
217
157
|
```typescript
|
|
218
158
|
import {
|
|
@@ -221,9 +161,6 @@ import {
|
|
|
221
161
|
// decodeSolanaEntries,
|
|
222
162
|
} from '@validators-dao/solana-stream-sdk'
|
|
223
163
|
import 'dotenv/config'
|
|
224
|
-
// import { logDecodedEntries } from '@/utils/logDecodedEntries'
|
|
225
|
-
|
|
226
|
-
import { receivedSlots, startLatencyCheck } from '@/utils/checkLatency'
|
|
227
164
|
|
|
228
165
|
const endpoint = process.env.SHREDS_ENDPOINT!
|
|
229
166
|
|
|
@@ -243,29 +180,20 @@ const connect = () => {
|
|
|
243
180
|
client.subscribeEntries(
|
|
244
181
|
JSON.stringify(request),
|
|
245
182
|
(_error: any, buffer: any) => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const {
|
|
249
|
-
slot,
|
|
250
|
-
// entries
|
|
251
|
-
} = JSON.parse(buffer)
|
|
252
|
-
|
|
253
|
-
// You can decode entries as needed
|
|
254
|
-
// const decodedEntries = decodeSolanaEntries(new Uint8Array(entries))
|
|
255
|
-
// logDecodedEntries(decodedEntries)
|
|
256
|
-
|
|
257
|
-
if (!receivedSlots.has(slot)) {
|
|
258
|
-
receivedSlots.set(slot, [{ receivedAt }])
|
|
259
|
-
} else {
|
|
260
|
-
receivedSlots.get(slot)!.push({ receivedAt })
|
|
261
|
-
}
|
|
183
|
+
if (!buffer) {
|
|
184
|
+
return
|
|
262
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)
|
|
263
192
|
},
|
|
264
193
|
)
|
|
265
194
|
}
|
|
266
195
|
|
|
267
196
|
connect()
|
|
268
|
-
startLatencyCheck()
|
|
269
197
|
```
|
|
270
198
|
|
|
271
199
|
Ensure the environment variable `SHREDS_ENDPOINT` is set correctly.
|
|
@@ -322,18 +250,6 @@ Other reports and suggestions are also highly appreciated.
|
|
|
322
250
|
You can also join discussions or share feedback on Validators DAO's Discord community:
|
|
323
251
|
https://discord.gg/C7ZQSrCkYR
|
|
324
252
|
|
|
325
|
-
## Shreds UDP configuration (Rust client)
|
|
326
|
-
|
|
327
|
-
- Load base config via `SHREDS_UDP_CONFIG=/path/to/config.{json,toml}`; env vars then override it.
|
|
328
|
-
- Core envs:
|
|
329
|
-
- `SOLANA_RPC_ENDPOINT`
|
|
330
|
-
- Logs: `SHREDS_UDP_LOG_RAW`, `SHREDS_UDP_LOG_SHREDS`, `SHREDS_UDP_LOG_ENTRIES`, `SHREDS_UDP_LOG_WATCH_HITS`, `SHREDS_UDP_LOG_DEFER`
|
|
331
|
-
- Watch lists: `SHREDS_UDP_WATCH_PROGRAM_IDS`, `SHREDS_UDP_WATCH_AUTHORITIES` (defaults to pump.fun program/authority)
|
|
332
|
-
- Hardening: `SHREDS_UDP_REQUIRE_CODE_MATCH`, `SHREDS_UDP_STRICT_FEC`, `SHREDS_UDP_STRICT_NUM_DATA`, `SHREDS_UDP_STRICT_NUM_CODING`
|
|
333
|
-
- Slot window: `SHREDS_UDP_ROOT_SLOT`, `SHREDS_UDP_MAX_FUTURE` (default 512)
|
|
334
|
-
- TTLs: `SHREDS_UDP_COMPLETED_TTL_MS` (default 30s), `SHREDS_UDP_EVICT_COOLDOWN_MS` (default 300ms), `SHREDS_UDP_WARN_ONCE` (default true)
|
|
335
|
-
- Pump.fun: program and mint-authority pubkeys are fixed; token mint addresses are discovered per transaction at runtime (not hardcoded).
|
|
336
|
-
|
|
337
253
|
## Repository
|
|
338
254
|
|
|
339
255
|
This package is part of the [Solana Stream](https://github.com/ValidatorsDAO/solana-stream) monorepo.
|