@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.
Files changed (2) hide show
  1. package/README.md +78 -162
  2. 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.0.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
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
- Example of using the GeyserClient to subscribe to Solana Pump Fun transactions and accounts:
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 tran: SubscribeRequestFilterTransactions = {
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: SubscribeRequest = {
104
- accounts: {
105
- pumpfun: {
106
- account: [],
107
- owner: [],
108
- filters: [],
109
- },
110
- },
95
+ const request = {
96
+ accounts: {},
111
97
  slots: {},
112
- transactions: { elsol: tran },
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 geyser = async () => {
122
- console.log('Starting geyser client...')
123
- const maxRetries = 2000000
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
- // @ts-ignore ignore
134
- return new GeyserClient(endpoint, token || undefined, undefined)
135
- }
112
+ await client.connect()
113
+ const stream = await client.subscribe()
136
114
 
137
- const connect = async (retries: number = 0): Promise<void> => {
138
- if (retries > maxRetries) {
139
- throw new Error('Max retries reached')
115
+ stream.on('data', (data: any) => {
116
+ if (data?.ping != undefined) {
117
+ stream.write({ ping: { id: 1 } }, () => undefined)
118
+ return
140
119
  }
141
-
142
- try {
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
- await connect()
196
- }
197
-
198
- const main = async () => {
199
- try {
200
- await geyser()
201
- } catch (error) {
202
- console.log(error)
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
- Here's how to use the SDK to subscribe to Solana Shreds and decode entries:
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
- const receivedAt = new Date()
247
- if (buffer) {
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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@validators-dao/solana-stream-sdk",
3
3
  "description": "Solana Stream SDK by Validators DAO",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",