@triton-one/yellowstone-grpc 5.0.2 → 5.0.4

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/dist/esm/index.js CHANGED
@@ -1,10 +1,31 @@
1
1
  /** TypeScript/JavaScript client for gRPC Geyser. */
2
2
  // Import generated gRPC client and types.
3
- import { SubscribeUpdateTransactionInfo } from "./grpc/geyser.js";
3
+ import { GetBlockHeightResponse as GetBlockHeightResponseMessage, GetLatestBlockhashResponse as GetLatestBlockhashResponseMessage, GetSlotResponse as GetSlotResponseMessage, GetVersionResponse as GetVersionResponseMessage, IsBlockhashValidResponse as IsBlockhashValidResponseMessage, PongResponse as PongResponseMessage, SubscribeReplayInfoResponse as SubscribeReplayInfoResponseMessage, SubscribeUpdateTransactionInfo, } from "./grpc/geyser.js";
4
4
  // Reexport automatically generated types
5
5
  export { CommitmentLevel, SubscribeRequest, SubscribeRequest_AccountsEntry, SubscribeRequest_BlocksEntry, SubscribeRequest_BlocksMetaEntry, SubscribeRequest_SlotsEntry, SubscribeRequest_TransactionsEntry, SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdatePing, SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, } from "./grpc/geyser.js";
6
6
  import { Duplex } from "stream";
7
7
  import * as napi from "./napi/index.js";
8
+ /**
9
+ * Convert N-API `JsSubscribeUpdate` shape (with `updateOneof`) into the
10
+ * generated protobuf-friendly SDK shape (`SubscribeUpdate`) where the oneof
11
+ * variants are top-level optional fields.
12
+ */
13
+ function fromJsSubscribeUpdate(update) {
14
+ const oneof = update.updateOneof ?? {};
15
+ return {
16
+ filters: update.filters ?? [],
17
+ createdAt: update.createdAt,
18
+ account: oneof.account,
19
+ slot: oneof.slot,
20
+ transaction: oneof.transaction,
21
+ transactionStatus: oneof.transactionStatus,
22
+ block: oneof.block,
23
+ ping: oneof.ping,
24
+ pong: oneof.pong,
25
+ blockMeta: oneof.blockMeta,
26
+ entry: oneof.entry,
27
+ };
28
+ }
8
29
  export default class Client {
9
30
  _insecureEndpoint;
10
31
  _insecureXToken;
@@ -15,71 +36,88 @@ export default class Client {
15
36
  this._insecureXToken = xToken;
16
37
  this._channelOptions = channel_options;
17
38
  }
39
+ _connectedGrpcClient() {
40
+ if (!this._grpcClient) {
41
+ throw new Error("Client not connected. Call connect() first");
42
+ }
43
+ return this._grpcClient;
44
+ }
18
45
  async connect() {
19
- // Use the factory method to create the client
46
+ // Establish one persistent native gRPC client reused by all calls.
20
47
  this._grpcClient = await napi.GrpcClient.new(this._insecureEndpoint, this._insecureXToken, this._channelOptions);
21
48
  }
22
49
  async getLatestBlockhash(commitment) {
23
- if (!this._grpcClient) {
24
- throw new Error("Client not connected. Call connect() first");
25
- }
50
+ const grpcClient = this._connectedGrpcClient();
26
51
  const request = {
27
52
  commitment: commitment ?? null,
28
53
  };
29
- return await this._grpcClient.getLatestBlockhash(request);
54
+ const response = await grpcClient.getLatestBlockhash(request);
55
+ return GetLatestBlockhashResponseMessage.fromPartial({
56
+ slot: response.slot,
57
+ blockhash: response.blockhash,
58
+ lastValidBlockHeight: response.lastValidBlockHeight,
59
+ });
30
60
  }
31
61
  async ping(count) {
32
- if (!this._grpcClient) {
33
- throw new Error("Client not connected. Call connect() first");
34
- }
62
+ const grpcClient = this._connectedGrpcClient();
35
63
  const request = {
36
64
  count,
37
65
  };
38
- return (await this._grpcClient.ping(request)).count;
66
+ const response = await grpcClient.ping(request);
67
+ return PongResponseMessage.fromPartial({
68
+ count: response.count,
69
+ });
39
70
  }
40
71
  async getBlockHeight(commitment) {
41
- if (!this._grpcClient) {
42
- throw new Error("Client not connected. Call connect() first");
43
- }
72
+ const grpcClient = this._connectedGrpcClient();
44
73
  const request = {
45
74
  commitment,
46
75
  };
47
- return (await this._grpcClient.getBlockHeight(request)).blockHeight;
76
+ const response = await grpcClient.getBlockHeight(request);
77
+ return GetBlockHeightResponseMessage.fromPartial({
78
+ blockHeight: response.blockHeight,
79
+ });
48
80
  }
49
81
  async getSlot(commitment) {
50
- if (!this._grpcClient) {
51
- throw new Error("Client not connected. Call connect() first");
52
- }
82
+ const grpcClient = this._connectedGrpcClient();
53
83
  const request = {
54
84
  commitment,
55
85
  };
56
- return (await this._grpcClient.getSlot(request)).slot;
86
+ const response = await grpcClient.getSlot(request);
87
+ return GetSlotResponseMessage.fromPartial({
88
+ slot: response.slot,
89
+ });
57
90
  }
58
91
  async isBlockhashValid(blockhash, commitment) {
59
- if (!this._grpcClient) {
60
- throw new Error("Client not connected. Call connect() first");
61
- }
92
+ const grpcClient = this._connectedGrpcClient();
62
93
  const request = {
63
94
  blockhash,
64
95
  commitment,
65
96
  };
66
- return await this._grpcClient.isBlockhashValid(request);
97
+ const response = await grpcClient.isBlockhashValid(request);
98
+ return IsBlockhashValidResponseMessage.fromPartial({
99
+ slot: response.slot,
100
+ valid: response.valid,
101
+ });
67
102
  }
68
103
  async getVersion() {
69
- if (!this._grpcClient) {
70
- throw new Error("Client not connected. Call connect() first");
71
- }
104
+ const grpcClient = this._connectedGrpcClient();
72
105
  const request = {};
73
- return (await this._grpcClient.getVersion(request)).version;
106
+ const response = await grpcClient.getVersion(request);
107
+ return GetVersionResponseMessage.fromPartial({
108
+ version: response.version,
109
+ });
74
110
  }
75
111
  async subscribeReplayInfo() {
76
- if (!this._grpcClient) {
77
- throw new Error("Client not connected. Call connect() first");
78
- }
112
+ const grpcClient = this._connectedGrpcClient();
79
113
  const request = {};
80
- return await this._grpcClient.subscribeReplayInfo(request);
114
+ const response = await grpcClient.subscribeReplayInfo(request);
115
+ return SubscribeReplayInfoResponseMessage.fromPartial({
116
+ firstAvailable: response.firstAvailable,
117
+ });
81
118
  }
82
119
  async subscribe() {
120
+ const grpcClient = this._connectedGrpcClient();
83
121
  // Inner stream.Duplex config passed to both stream.Readable and Writable.
84
122
  // See: https://nodejs.org/en/blog/feature/streams2#new-streamduplexoptions
85
123
  const options = {
@@ -90,7 +128,9 @@ export default class Client {
90
128
  // TODO: Fine tune high watermark for performance and backpressure.
91
129
  // highWaterMark: 16
92
130
  };
93
- const stream = this._grpcClient.subscribe();
131
+ // Native stream produces N-API generated JS objects; wrapper below adapts
132
+ // to public protobuf-generated SDK shapes and Node stream semantics.
133
+ const stream = grpcClient.subscribe();
94
134
  return new Promise((resolve, reject) => {
95
135
  try {
96
136
  resolve(new ClientDuplexStream(stream, options));
@@ -101,23 +141,102 @@ export default class Client {
101
141
  });
102
142
  }
103
143
  }
104
- class ClientDuplexStream extends Duplex {
144
+ export class ClientDuplexStream extends Duplex {
105
145
  _napiDuplexStream;
146
+ // Prevent overlapping native reads: a single pending read at a time.
147
+ _readInFlight;
148
+ // Closed once Node emits `close`.
149
+ _isClosed;
150
+ // Set during destroy path to short-circuit late async completions.
151
+ _isDestroying;
152
+ // Ensure we surface at most one terminal error per stream instance.
153
+ _terminalErrorSeen;
106
154
  constructor(stream, options) {
107
155
  super({ ...options });
108
156
  this._napiDuplexStream = stream;
157
+ this._readInFlight = false;
158
+ this._isClosed = false;
159
+ this._isDestroying = false;
160
+ this._terminalErrorSeen = false;
161
+ this.once("close", () => {
162
+ this._isClosed = true;
163
+ });
109
164
  }
110
- async _read(_size) {
111
- try {
112
- const update = await this._napiDuplexStream.read();
113
- this.push(update);
165
+ _pullNextUpdate() {
166
+ if (this._isClosed || this._isDestroying || this._readInFlight) {
167
+ return;
114
168
  }
115
- catch (err) {
169
+ this._readInFlight = true;
170
+ this._napiDuplexStream
171
+ .read()
172
+ .then((update) => {
173
+ this._readInFlight = false;
174
+ if (this._isClosed || this._isDestroying) {
175
+ return;
176
+ }
177
+ // Native side can signal EOF by returning no update.
178
+ if (update == null) {
179
+ this.push(null);
180
+ this.destroy();
181
+ return;
182
+ }
183
+ const grpcUpdate = fromJsSubscribeUpdate(update);
184
+ // Respect backpressure: only pull again if consumer accepted push.
185
+ const canContinue = this.push(grpcUpdate);
186
+ if (canContinue) {
187
+ this._pullNextUpdate();
188
+ }
189
+ })
190
+ .catch((err) => {
191
+ this._readInFlight = false;
192
+ if (this._isClosed || this._isDestroying) {
193
+ return;
194
+ }
195
+ if (this._terminalErrorSeen) {
196
+ return;
197
+ }
198
+ this._terminalErrorSeen = true;
199
+ // Detect normal end-of-stream / "no update available" sentinel from native side.
200
+ const e = err;
201
+ const isNormalEof = err == null ||
202
+ (e && typeof e.code === "string" && e.code === "NO_UPDATE_AVAILABLE") ||
203
+ (e &&
204
+ typeof e.message === "string" &&
205
+ e.message.toLowerCase().includes("no update available"));
116
206
  this.push(null); // Signal end of stream
117
- this.destroy(err); // Handle resource cleanup
207
+ if (isNormalEof) {
208
+ // Graceful shutdown: end without emitting an error event.
209
+ this.destroy();
210
+ }
211
+ else {
212
+ // Real error: propagate as a stream error.
213
+ this.destroy(err);
214
+ }
215
+ });
216
+ }
217
+ _read(_size) {
218
+ this._pullNextUpdate();
219
+ }
220
+ _destroy(error, callback) {
221
+ // Mark terminal state first so late read completions are ignored.
222
+ this._isDestroying = true;
223
+ this._isClosed = true;
224
+ this._terminalErrorSeen = true;
225
+ // Explicitly stop the native worker so it does not outlive JS stream state.
226
+ try {
227
+ const nativeStream = this._napiDuplexStream;
228
+ if (typeof nativeStream.close === "function") {
229
+ nativeStream.close();
230
+ }
118
231
  }
232
+ catch { }
233
+ callback(error);
119
234
  }
120
235
  _write(chunk, _encoding, callback) {
236
+ if (this._isClosed || this._isDestroying) {
237
+ callback(new Error("Cannot write to a closed subscription stream"));
238
+ return;
239
+ }
121
240
  try {
122
241
  this._napiDuplexStream.write(chunk);
123
242
  callback();
@@ -122,6 +122,19 @@ export interface SubscribeRequestFilterBlocksMeta {
122
122
  }
123
123
  export interface SubscribeRequestFilterEntry {
124
124
  }
125
+ /**
126
+ * Filter for deshred transactions (transactions received before execution).
127
+ * Deshred transactions are received when entries are formed from shreds,
128
+ * BEFORE any execution occurs. No TransactionStatusMeta is available.
129
+ * Address lookup tables are resolved, so both static account keys and
130
+ * dynamically loaded addresses (from ALTs) are available for filtering.
131
+ */
132
+ export interface SubscribeRequestFilterDeshredTransactions {
133
+ vote?: boolean | undefined;
134
+ accountInclude: string[];
135
+ accountExclude: string[];
136
+ accountRequired: string[];
137
+ }
125
138
  export interface SubscribeRequestAccountsDataSlice {
126
139
  offset: string;
127
140
  length: string;
@@ -129,6 +142,24 @@ export interface SubscribeRequestAccountsDataSlice {
129
142
  export interface SubscribeRequestPing {
130
143
  id: number;
131
144
  }
145
+ /**
146
+ * Request message for the SubscribeDeshred RPC.
147
+ * Subscribes to deshred transactions (transactions received before execution).
148
+ * Deshred transactions are received when entries are formed from shreds,
149
+ * BEFORE any execution occurs. No TransactionStatusMeta is available.
150
+ * Address lookup tables are resolved, so both static and loaded addresses
151
+ * are available for filtering.
152
+ */
153
+ export interface SubscribeDeshredRequest {
154
+ deshredTransactions: {
155
+ [key: string]: SubscribeRequestFilterDeshredTransactions;
156
+ };
157
+ ping?: SubscribeRequestPing | undefined;
158
+ }
159
+ export interface SubscribeDeshredRequest_DeshredTransactionsEntry {
160
+ key: string;
161
+ value: SubscribeRequestFilterDeshredTransactions | undefined;
162
+ }
132
163
  export interface SubscribeUpdate {
133
164
  filters: string[];
134
165
  account?: SubscribeUpdateAccount | undefined;
@@ -216,11 +247,29 @@ export interface SubscribeUpdateEntry {
216
247
  /** added in v1.18, for solana 1.17 value is always 0 */
217
248
  startingTransactionIndex: string;
218
249
  }
250
+ export interface SubscribeUpdateDeshredTransaction {
251
+ transaction: SubscribeUpdateDeshredTransactionInfo | undefined;
252
+ slot: string;
253
+ }
254
+ export interface SubscribeUpdateDeshredTransactionInfo {
255
+ signature: Uint8Array;
256
+ isVote: boolean;
257
+ transaction: Transaction | undefined;
258
+ loadedWritableAddresses: Uint8Array[];
259
+ loadedReadonlyAddresses: Uint8Array[];
260
+ }
219
261
  export interface SubscribeUpdatePing {
220
262
  }
221
263
  export interface SubscribeUpdatePong {
222
264
  id: number;
223
265
  }
266
+ export interface SubscribeUpdateDeshred {
267
+ filters: string[];
268
+ deshredTransaction?: SubscribeUpdateDeshredTransaction | undefined;
269
+ ping?: SubscribeUpdatePing | undefined;
270
+ pong?: SubscribeUpdatePong | undefined;
271
+ createdAt: Date | undefined;
272
+ }
224
273
  export interface SubscribeReplayInfoRequest {
225
274
  }
226
275
  export interface SubscribeReplayInfoResponse {
@@ -282,8 +331,11 @@ export declare const SubscribeRequestFilterTransactions: MessageFns<SubscribeReq
282
331
  export declare const SubscribeRequestFilterBlocks: MessageFns<SubscribeRequestFilterBlocks>;
283
332
  export declare const SubscribeRequestFilterBlocksMeta: MessageFns<SubscribeRequestFilterBlocksMeta>;
284
333
  export declare const SubscribeRequestFilterEntry: MessageFns<SubscribeRequestFilterEntry>;
334
+ export declare const SubscribeRequestFilterDeshredTransactions: MessageFns<SubscribeRequestFilterDeshredTransactions>;
285
335
  export declare const SubscribeRequestAccountsDataSlice: MessageFns<SubscribeRequestAccountsDataSlice>;
286
336
  export declare const SubscribeRequestPing: MessageFns<SubscribeRequestPing>;
337
+ export declare const SubscribeDeshredRequest: MessageFns<SubscribeDeshredRequest>;
338
+ export declare const SubscribeDeshredRequest_DeshredTransactionsEntry: MessageFns<SubscribeDeshredRequest_DeshredTransactionsEntry>;
287
339
  export declare const SubscribeUpdate: MessageFns<SubscribeUpdate>;
288
340
  export declare const SubscribeUpdateAccount: MessageFns<SubscribeUpdateAccount>;
289
341
  export declare const SubscribeUpdateAccountInfo: MessageFns<SubscribeUpdateAccountInfo>;
@@ -294,8 +346,11 @@ export declare const SubscribeUpdateTransactionStatus: MessageFns<SubscribeUpdat
294
346
  export declare const SubscribeUpdateBlock: MessageFns<SubscribeUpdateBlock>;
295
347
  export declare const SubscribeUpdateBlockMeta: MessageFns<SubscribeUpdateBlockMeta>;
296
348
  export declare const SubscribeUpdateEntry: MessageFns<SubscribeUpdateEntry>;
349
+ export declare const SubscribeUpdateDeshredTransaction: MessageFns<SubscribeUpdateDeshredTransaction>;
350
+ export declare const SubscribeUpdateDeshredTransactionInfo: MessageFns<SubscribeUpdateDeshredTransactionInfo>;
297
351
  export declare const SubscribeUpdatePing: MessageFns<SubscribeUpdatePing>;
298
352
  export declare const SubscribeUpdatePong: MessageFns<SubscribeUpdatePong>;
353
+ export declare const SubscribeUpdateDeshred: MessageFns<SubscribeUpdateDeshred>;
299
354
  export declare const SubscribeReplayInfoRequest: MessageFns<SubscribeReplayInfoRequest>;
300
355
  export declare const SubscribeReplayInfoResponse: MessageFns<SubscribeReplayInfoResponse>;
301
356
  export declare const PingRequest: MessageFns<PingRequest>;
@@ -1,30 +1,44 @@
1
1
  /** TypeScript/JavaScript client for gRPC Geyser. */
2
2
  import { SubscribeUpdateTransactionInfo } from "./grpc/geyser";
3
+ import type { CommitmentLevel, GetBlockHeightResponse, GetLatestBlockhashResponse, GetSlotResponse, GetVersionResponse, IsBlockhashValidResponse, PongResponse, SubscribeReplayInfoResponse, SubscribeRequest } from "./grpc/geyser";
3
4
  export { CommitmentLevel, SubscribeRequest, SubscribeRequest_AccountsEntry, SubscribeRequest_BlocksEntry, SubscribeRequest_BlocksMetaEntry, SubscribeRequest_SlotsEntry, SubscribeRequest_TransactionsEntry, SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterLamports, SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterEntry, SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeUpdate, SubscribeUpdateAccount, SubscribeUpdateAccountInfo, SubscribeUpdateBlock, SubscribeUpdateBlockMeta, SubscribeUpdatePing, SubscribeUpdateSlot, SubscribeUpdateTransaction, SubscribeUpdateTransactionInfo, } from "./grpc/geyser";
4
5
  import type { TransactionErrorSolana, MapTransactionEncodingToReturnType } from "./types";
5
6
  import { Duplex } from "stream";
6
7
  import * as napi from "./napi/index";
8
+ /**
9
+ * Public channel options accepted by the SDK constructor.
10
+ * This is sourced from the native N-API constructor signature to avoid
11
+ * duplicating option fields in this file.
12
+ */
13
+ export type ChannelOptions = NonNullable<Parameters<typeof napi.GrpcClient.new>[2]>;
7
14
  export default class Client {
8
- _insecureEndpoint: string;
9
- _insecureXToken: string | undefined;
10
- _channelOptions: napi.JsChannelOptions | undefined;
11
- _grpcClient: napi.GrpcClient | null;
12
- constructor(endpoint: string, xToken: string | undefined, channel_options: napi.JsChannelOptions | undefined);
15
+ private _insecureEndpoint;
16
+ private _insecureXToken;
17
+ private _channelOptions;
18
+ private _grpcClient;
19
+ constructor(endpoint: string, xToken: string | undefined, channel_options: ChannelOptions | undefined);
20
+ private _connectedGrpcClient;
13
21
  connect(): Promise<void>;
14
- getLatestBlockhash(commitment?: number): Promise<napi.JsGetLatestBlockhashResponse>;
15
- ping(count: number): Promise<number>;
16
- getBlockHeight(commitment?: number): Promise<string>;
17
- getSlot(commitment?: number): Promise<string>;
18
- isBlockhashValid(blockhash: string, commitment?: number): Promise<napi.JsIsBlockhashValidResponse>;
19
- getVersion(): Promise<string>;
20
- subscribeReplayInfo(): Promise<napi.JsSubscribeReplayInfoResponse>;
22
+ getLatestBlockhash(commitment?: CommitmentLevel): Promise<GetLatestBlockhashResponse>;
23
+ ping(count: number): Promise<PongResponse>;
24
+ getBlockHeight(commitment?: CommitmentLevel): Promise<GetBlockHeightResponse>;
25
+ getSlot(commitment?: CommitmentLevel): Promise<GetSlotResponse>;
26
+ isBlockhashValid(blockhash: string, commitment?: CommitmentLevel): Promise<IsBlockhashValidResponse>;
27
+ getVersion(): Promise<GetVersionResponse>;
28
+ subscribeReplayInfo(): Promise<SubscribeReplayInfoResponse>;
21
29
  subscribe(): Promise<ClientDuplexStream>;
22
30
  }
23
- declare class ClientDuplexStream extends Duplex {
24
- _napiDuplexStream: napi.DuplexStream;
25
- constructor(stream: napi.DuplexStream, options: object | undefined);
26
- _read(_size: number): Promise<void>;
27
- _write(chunk: object, _encoding: any, callback: any): void;
31
+ export declare class ClientDuplexStream extends Duplex {
32
+ private _napiDuplexStream;
33
+ private _readInFlight;
34
+ private _isClosed;
35
+ private _isDestroying;
36
+ private _terminalErrorSeen;
37
+ constructor(stream: unknown, options: object | undefined);
38
+ _pullNextUpdate(): void;
39
+ _read(_size: number): void;
40
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
41
+ _write(chunk: SubscribeRequest, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
28
42
  }
29
43
  export declare const txEncode: {
30
44
  encoding: any;