sol-parser-sdk-nodejs 0.3.0 → 0.4.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.
Files changed (114) hide show
  1. package/.env.example +24 -0
  2. package/README.md +100 -352
  3. package/README_CN.md +107 -253
  4. package/dist/accounts/mod.d.ts +3 -0
  5. package/dist/accounts/mod.js +18 -1
  6. package/dist/accounts/pumpfun.d.ts +6 -0
  7. package/dist/accounts/pumpfun.js +157 -0
  8. package/dist/accounts/rpc_wallet.d.ts +5 -0
  9. package/dist/accounts/rpc_wallet.js +18 -0
  10. package/dist/accounts/rust_aliases.d.ts +10 -0
  11. package/dist/accounts/rust_aliases.js +21 -0
  12. package/dist/accounts/wallet_resolve.d.ts +1 -0
  13. package/dist/accounts/wallet_resolve.js +28 -0
  14. package/dist/common/constants.d.ts +10 -0
  15. package/dist/common/constants.js +13 -0
  16. package/dist/core/account_dispatcher_rpc.js +26 -5
  17. package/dist/core/account_fill_meteora.d.ts +4 -2
  18. package/dist/core/account_fill_meteora.js +5 -2
  19. package/dist/core/account_fill_pumpfun.js +17 -0
  20. package/dist/core/account_pubkey_cache.d.ts +12 -0
  21. package/dist/core/account_pubkey_cache.js +26 -0
  22. package/dist/core/clock.d.ts +6 -0
  23. package/dist/core/clock.js +13 -0
  24. package/dist/core/dex_event.d.ts +172 -44
  25. package/dist/core/metadata.d.ts +1 -0
  26. package/dist/core/pumpfun_fee_enrich.d.ts +4 -0
  27. package/dist/core/pumpfun_fee_enrich.js +80 -0
  28. package/dist/core/unified_parser.d.ts +2 -2
  29. package/dist/core/unified_parser.js +8 -6
  30. package/dist/grpc/client.d.ts +32 -1
  31. package/dist/grpc/client.js +400 -64
  32. package/dist/grpc/event_parser.d.ts +6 -0
  33. package/dist/grpc/event_parser.js +15 -0
  34. package/dist/grpc/geyser_connect.d.ts +30 -0
  35. package/dist/grpc/geyser_connect.js +40 -0
  36. package/dist/grpc/log_instr_dedup.d.ts +2 -0
  37. package/dist/grpc/log_instr_dedup.js +330 -0
  38. package/dist/grpc/order_buffer.d.ts +27 -0
  39. package/dist/grpc/order_buffer.js +166 -0
  40. package/dist/grpc/program_ids.d.ts +26 -0
  41. package/dist/grpc/program_ids.js +55 -0
  42. package/dist/grpc/rpc_to_grpc.d.ts +18 -0
  43. package/dist/grpc/rpc_to_grpc.js +127 -0
  44. package/dist/grpc/subscribe_builder.d.ts +13 -0
  45. package/dist/grpc/subscribe_builder.js +66 -0
  46. package/dist/grpc/transaction_meta.d.ts +29 -0
  47. package/dist/grpc/transaction_meta.js +208 -0
  48. package/dist/grpc/types.d.ts +57 -2
  49. package/dist/grpc/types.js +199 -7
  50. package/dist/grpc/yellowstone_parse.d.ts +8 -1
  51. package/dist/grpc/yellowstone_parse.js +27 -11
  52. package/dist/index.d.ts +39 -8
  53. package/dist/index.js +184 -2
  54. package/dist/instr/bonk_ix.d.ts +4 -1
  55. package/dist/instr/bonk_ix.js +106 -27
  56. package/dist/instr/meteora_damm_ix.d.ts +4 -2
  57. package/dist/instr/meteora_damm_ix.js +248 -13
  58. package/dist/instr/mod.d.ts +1 -0
  59. package/dist/instr/mod.js +16 -3
  60. package/dist/instr/orca_whirlpool_ix.d.ts +4 -1
  61. package/dist/instr/orca_whirlpool_ix.js +45 -16
  62. package/dist/instr/program_ids.d.ts +7 -13
  63. package/dist/instr/program_ids.js +20 -15
  64. package/dist/instr/pump_fees_ix.d.ts +2 -0
  65. package/dist/instr/pump_fees_ix.js +166 -0
  66. package/dist/instr/pumpfun_ix.js +57 -0
  67. package/dist/instr/pumpswap_ix.d.ts +1 -1
  68. package/dist/instr/pumpswap_ix.js +78 -57
  69. package/dist/instr/raydium_amm_v4_ix.d.ts +1 -1
  70. package/dist/instr/raydium_amm_v4_ix.js +94 -28
  71. package/dist/instr/raydium_clmm_ix.d.ts +1 -1
  72. package/dist/instr/raydium_clmm_ix.js +84 -30
  73. package/dist/instr/raydium_cpmm_ix.d.ts +1 -1
  74. package/dist/instr/raydium_cpmm_ix.js +46 -12
  75. package/dist/instr/rust_aliases.d.ts +8 -0
  76. package/dist/instr/rust_aliases.js +16 -0
  77. package/dist/instr/utils.d.ts +1 -1
  78. package/dist/instr/utils.js +2 -1
  79. package/dist/logs/discriminator_lut.d.ts +19 -0
  80. package/dist/logs/discriminator_lut.js +62 -0
  81. package/dist/logs/meteora_damm.d.ts +3 -4
  82. package/dist/logs/meteora_damm.js +3 -369
  83. package/dist/logs/optimized_matcher.d.ts +2 -2
  84. package/dist/logs/optimized_matcher.js +123 -23
  85. package/dist/logs/program_log_discriminators.d.ts +10 -0
  86. package/dist/logs/program_log_discriminators.js +10 -0
  87. package/dist/logs/pump.d.ts +2 -0
  88. package/dist/logs/pump.js +51 -4
  89. package/dist/logs/pump_fees.d.ts +23 -0
  90. package/dist/logs/pump_fees.js +364 -0
  91. package/dist/logs/rust_aliases.d.ts +6 -0
  92. package/dist/logs/rust_aliases.js +13 -0
  93. package/dist/rpc_parser.d.ts +1 -0
  94. package/dist/rpc_parser.js +4 -1
  95. package/dist/rpc_transaction.d.ts +2 -0
  96. package/dist/rpc_transaction.js +14 -6
  97. package/dist/shredstream/alt_lookup.d.ts +9 -0
  98. package/dist/shredstream/alt_lookup.js +70 -0
  99. package/dist/shredstream/client.d.ts +62 -0
  100. package/dist/shredstream/client.js +399 -0
  101. package/dist/shredstream/config.d.ts +30 -0
  102. package/dist/shredstream/config.js +34 -0
  103. package/dist/shredstream/entries_decode.d.ts +28 -0
  104. package/dist/shredstream/entries_decode.js +251 -0
  105. package/dist/shredstream/index.d.ts +17 -0
  106. package/dist/shredstream/index.js +33 -0
  107. package/dist/shredstream/instruction_parse.d.ts +34 -0
  108. package/dist/shredstream/instruction_parse.js +49 -0
  109. package/dist/shredstream/proto_types.d.ts +9 -0
  110. package/dist/shredstream/proto_types.js +2 -0
  111. package/dist/shredstream/shredstream.proto +15 -0
  112. package/dist/shredstream/wire_to_shred_tx.d.ts +2 -0
  113. package/dist/shredstream/wire_to_shred_tx.js +59 -0
  114. package/package.json +28 -11
@@ -32,22 +32,80 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.YellowstoneGrpc = void 0;
37
40
  /**
38
41
  * Yellowstone gRPC 客户端实现 - 基于 @triton-one/yellowstone-grpc
39
42
  */
40
43
  const yellowstone_grpc_1 = __importStar(require("@triton-one/yellowstone-grpc"));
44
+ const bs58_1 = __importDefault(require("bs58"));
45
+ const metadata_js_1 = require("../core/metadata.js");
46
+ const mod_js_1 = require("../accounts/mod.js");
47
+ const geyser_connect_js_1 = require("./geyser_connect.js");
48
+ const order_buffer_js_1 = require("./order_buffer.js");
49
+ const subscribe_builder_js_1 = require("./subscribe_builder.js");
50
+ const yellowstone_parse_js_1 = require("./yellowstone_parse.js");
41
51
  const types_js_1 = require("./types.js");
52
+ class AsyncEventQueue {
53
+ maxSize;
54
+ items = [];
55
+ waiters = [];
56
+ closed = false;
57
+ constructor(maxSize) {
58
+ this.maxSize = maxSize;
59
+ }
60
+ push(item) {
61
+ if (this.closed)
62
+ return;
63
+ const waiter = this.waiters.shift();
64
+ if (waiter) {
65
+ waiter({ value: item, done: false });
66
+ return;
67
+ }
68
+ if (this.items.length < this.maxSize) {
69
+ this.items.push(item);
70
+ }
71
+ }
72
+ close() {
73
+ if (this.closed)
74
+ return;
75
+ this.closed = true;
76
+ for (const waiter of this.waiters.splice(0)) {
77
+ waiter({ value: undefined, done: true });
78
+ }
79
+ }
80
+ next() {
81
+ const item = this.items.shift();
82
+ if (item !== undefined) {
83
+ return Promise.resolve({ value: item, done: false });
84
+ }
85
+ if (this.closed) {
86
+ return Promise.resolve({ value: undefined, done: true });
87
+ }
88
+ return new Promise((resolve) => this.waiters.push(resolve));
89
+ }
90
+ [Symbol.asyncIterator]() {
91
+ return this;
92
+ }
93
+ }
42
94
  /** Yellowstone gRPC 客户端包装器 */
43
95
  class YellowstoneGrpc {
44
96
  client;
45
97
  config;
46
98
  connected = false;
47
99
  subscribers = new Map();
100
+ dexSubscribers = new Map();
48
101
  constructor(endpoint, xToken, config = (0, types_js_1.defaultClientConfig)()) {
49
102
  this.config = config;
50
- this.client = new yellowstone_grpc_1.default(endpoint, xToken || undefined, undefined);
103
+ const gc = (0, geyser_connect_js_1.defaultGeyserConnectConfig)();
104
+ this.client = new yellowstone_grpc_1.default(endpoint, xToken || undefined, (0, geyser_connect_js_1.geyserGrpcChannelOptions)({
105
+ ...gc,
106
+ keepAliveIntervalMs: config.keep_alive_interval_ms,
107
+ keepAliveTimeoutMs: config.keep_alive_timeout_ms,
108
+ }));
51
109
  }
52
110
  /** 连接到 gRPC 服务器 */
53
111
  async connect() {
@@ -62,6 +120,10 @@ class YellowstoneGrpc {
62
120
  sub.cancel();
63
121
  }
64
122
  this.subscribers.clear();
123
+ for (const [, sub] of this.dexSubscribers) {
124
+ sub.cancel();
125
+ }
126
+ this.dexSubscribers.clear();
65
127
  this.connected = false;
66
128
  }
67
129
  /** 检查是否已连接 */
@@ -90,6 +152,9 @@ class YellowstoneGrpc {
90
152
  const result = {
91
153
  filters: update.filters,
92
154
  };
155
+ const createdAtUs = YellowstoneGrpc.timestampToMicros(update.createdAt ?? update.created_at);
156
+ if (createdAtUs !== undefined)
157
+ result.createdAtUs = createdAtUs;
93
158
  if (update.account) {
94
159
  const acc = update.account;
95
160
  result.account = {
@@ -167,89 +232,192 @@ class YellowstoneGrpc {
167
232
  }
168
233
  return result;
169
234
  }
235
+ /**
236
+ * 应答 Geyser 在 SubscribeUpdate 中下发的 ping(与 `solana-streamer` / Rust 侧一致)。
237
+ * 若不回复,公共节点或 LB 可能在超时后 RST_STREAM(HTTP/2 CANCEL)。
238
+ */
239
+ subscribePingPongRequest() {
240
+ return {
241
+ accounts: {},
242
+ slots: {},
243
+ transactions: {},
244
+ transactionsStatus: {},
245
+ entry: {},
246
+ blocks: {},
247
+ blocksMeta: {},
248
+ commitment: yellowstone_grpc_1.CommitmentLevel.CONFIRMED,
249
+ accountsDataSlice: [],
250
+ ping: { id: 1 },
251
+ };
252
+ }
253
+ static toBytes(value) {
254
+ if (!value)
255
+ return new Uint8Array();
256
+ return value instanceof Uint8Array ? value : Uint8Array.from(value);
257
+ }
258
+ static toBigInt(value) {
259
+ return typeof value === "bigint" ? value : BigInt(value);
260
+ }
261
+ static protobufNumber(value) {
262
+ if (typeof value === "bigint")
263
+ return Number(value);
264
+ if (typeof value === "number")
265
+ return value;
266
+ if (typeof value === "string")
267
+ return Number(value);
268
+ if (value && typeof value === "object") {
269
+ const longLike = value;
270
+ if (typeof longLike.toNumber === "function")
271
+ return longLike.toNumber();
272
+ if (typeof longLike.toString === "function")
273
+ return Number(longLike.toString());
274
+ }
275
+ return Number.NaN;
276
+ }
277
+ static timestampToMicros(value) {
278
+ if (!value || typeof value !== "object")
279
+ return undefined;
280
+ const ts = value;
281
+ if (ts.seconds === undefined)
282
+ return undefined;
283
+ const seconds = YellowstoneGrpc.protobufNumber(ts.seconds);
284
+ const nanos = Number(ts.nanos ?? 0);
285
+ if (!Number.isFinite(seconds) || !Number.isFinite(nanos))
286
+ return undefined;
287
+ return Math.trunc(seconds * 1_000_000 + nanos / 1_000);
288
+ }
289
+ parseAccountEvent(update, grpcRecvUs, eventTypeFilter, blockTimeUs) {
290
+ const acc = update.account;
291
+ if (!acc)
292
+ return null;
293
+ const signatureBytes = YellowstoneGrpc.toBytes(acc.txnSignature);
294
+ const account = {
295
+ pubkey: bs58_1.default.encode(YellowstoneGrpc.toBytes(acc.pubkey)),
296
+ executable: acc.executable,
297
+ lamports: YellowstoneGrpc.toBigInt(acc.lamports),
298
+ owner: bs58_1.default.encode(YellowstoneGrpc.toBytes(acc.owner)),
299
+ rent_epoch: YellowstoneGrpc.toBigInt(acc.rentEpoch),
300
+ data: YellowstoneGrpc.toBytes(acc.data),
301
+ };
302
+ const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", Number(update.slot), 0, blockTimeUs, grpcRecvUs);
303
+ return (0, mod_js_1.parseAccountUnified)(account, metadata, eventTypeFilter);
304
+ }
305
+ initialSubscribeRequest(filter) {
306
+ return {
307
+ transactions: {
308
+ client: {
309
+ accountInclude: filter.account_include,
310
+ accountExclude: filter.account_exclude,
311
+ accountRequired: filter.account_required,
312
+ vote: filter.vote,
313
+ failed: filter.failed,
314
+ signature: filter.signature,
315
+ },
316
+ },
317
+ accounts: {},
318
+ slots: {},
319
+ transactionsStatus: {},
320
+ entry: {},
321
+ blocks: {},
322
+ blocksMeta: {},
323
+ commitment: yellowstone_grpc_1.CommitmentLevel.CONFIRMED,
324
+ accountsDataSlice: [],
325
+ ping: undefined,
326
+ };
327
+ }
170
328
  /** 订阅交易 */
171
329
  async subscribeTransactions(filter, callbacks) {
172
330
  const id = `sub_${Date.now()}_${Math.random().toString(36).slice(2)}`;
173
331
  let isCancelled = false;
332
+ const streamHolder = { current: null };
174
333
  const cancel = () => {
175
334
  isCancelled = true;
335
+ streamHolder.current?.end();
336
+ streamHolder.current = null;
176
337
  };
177
338
  this.subscribers.set(id, { filter, callbacks, cancel });
178
339
  (async () => {
340
+ const autoReconnect = callbacks.autoReconnect !== false;
341
+ const maxBackoffMs = 60_000;
342
+ let backoffMs = this.config.retry_delay_ms;
179
343
  try {
180
- const stream = await this.client.subscribe();
181
- await new Promise((resolve, reject) => {
182
- stream.write({
183
- transactions: {
184
- client: {
185
- accountInclude: filter.account_include,
186
- accountExclude: filter.account_exclude,
187
- accountRequired: filter.account_required,
188
- vote: filter.vote,
189
- failed: filter.failed,
190
- signature: filter.signature,
191
- },
192
- },
193
- accounts: {},
194
- slots: {},
195
- transactionsStatus: {},
196
- entry: {},
197
- blocks: {},
198
- blocksMeta: {},
199
- commitment: yellowstone_grpc_1.CommitmentLevel.CONFIRMED,
200
- accountsDataSlice: [],
201
- ping: undefined,
202
- }, (err) => {
203
- if (err)
204
- reject(err);
205
- else
206
- resolve();
207
- });
208
- });
209
- stream.on("data", (update) => {
210
- if (isCancelled)
211
- return;
212
- if (!callbacks.onUpdate)
213
- return;
344
+ while (!isCancelled) {
214
345
  try {
215
- const converted = this.convertUpdate(update);
216
- callbacks.onUpdate(converted);
346
+ const stream = await this.client.subscribe();
347
+ streamHolder.current = stream;
348
+ await new Promise((resolve, reject) => {
349
+ stream.write(this.initialSubscribeRequest(filter), (err) => {
350
+ if (err)
351
+ reject(err);
352
+ else
353
+ resolve();
354
+ });
355
+ });
356
+ backoffMs = this.config.retry_delay_ms;
357
+ await new Promise((resolve, reject) => {
358
+ const cleanup = () => {
359
+ stream.removeAllListeners();
360
+ if (streamHolder.current === stream) {
361
+ streamHolder.current = null;
362
+ }
363
+ };
364
+ stream.on("data", (update) => {
365
+ if (isCancelled)
366
+ return;
367
+ if (update.ping) {
368
+ stream.write(this.subscribePingPongRequest(), (werr) => {
369
+ if (werr && callbacks.onError && !isCancelled) {
370
+ callbacks.onError(werr instanceof Error ? werr : new Error(String(werr)));
371
+ }
372
+ });
373
+ return;
374
+ }
375
+ if (!callbacks.onUpdate)
376
+ return;
377
+ try {
378
+ const converted = this.convertUpdate(update);
379
+ callbacks.onUpdate(converted);
380
+ }
381
+ catch (err) {
382
+ const e = err instanceof Error ? err : new Error(String(err));
383
+ callbacks.onError?.(e);
384
+ }
385
+ });
386
+ stream.on("error", (err) => {
387
+ cleanup();
388
+ reject(err);
389
+ });
390
+ stream.on("end", () => {
391
+ cleanup();
392
+ resolve();
393
+ });
394
+ });
217
395
  }
218
396
  catch (err) {
219
- // 用户 onUpdate 内抛错(如同步 IO/画图)若未捕获,会破坏 gRPC duplex 流并触发 RST_STREAM。
397
+ if (isCancelled)
398
+ break;
220
399
  const e = err instanceof Error ? err : new Error(String(err));
221
- if (callbacks.onError) {
222
- callbacks.onError(e);
223
- }
400
+ callbacks.onError?.(e);
401
+ if (!autoReconnect)
402
+ break;
403
+ await new Promise((r) => setTimeout(r, backoffMs));
404
+ backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
405
+ continue;
224
406
  }
225
- });
226
- stream.on("error", (err) => {
227
407
  if (isCancelled)
228
- return;
229
- if (callbacks.onError) {
230
- callbacks.onError(err);
408
+ break;
409
+ if (!autoReconnect) {
410
+ callbacks.onEnd?.();
411
+ break;
231
412
  }
232
- });
233
- stream.on("end", () => {
234
- if (isCancelled)
235
- return;
236
- this.subscribers.delete(id);
237
- if (callbacks.onEnd) {
238
- callbacks.onEnd();
239
- }
240
- });
241
- while (!isCancelled) {
242
- await new Promise((resolve) => setTimeout(resolve, 100));
413
+ await new Promise((r) => setTimeout(r, backoffMs));
414
+ backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
243
415
  }
244
- stream.end();
245
416
  }
246
- catch (err) {
247
- if (isCancelled)
248
- return;
417
+ finally {
418
+ streamHolder.current?.end();
419
+ streamHolder.current = null;
249
420
  this.subscribers.delete(id);
250
- if (callbacks.onError) {
251
- callbacks.onError(err);
252
- }
253
421
  }
254
422
  })();
255
423
  return {
@@ -260,6 +428,158 @@ class YellowstoneGrpc {
260
428
  },
261
429
  };
262
430
  }
431
+ /**
432
+ * 订阅并直接产出 `DexEvent`。
433
+ *
434
+ * 交易更新走统一交易解析(外层/内层指令 + 日志 + 数据填充);账户更新走 `parseAccountUnified`。
435
+ * 事件队列满时丢弃新事件,避免回调阻塞 gRPC 读循环,保持低延迟。
436
+ */
437
+ async subscribeDexEvents(transactionFilters = [], accountFilters = [], eventTypeFilter, options) {
438
+ await this.connect();
439
+ const id = `dex_${Date.now()}_${Math.random().toString(36).slice(2)}`;
440
+ const events = new AsyncEventQueue(Math.max(1, this.config.buffer_size));
441
+ const errors = new AsyncEventQueue(Math.max(1, this.config.buffer_size));
442
+ const order = new order_buffer_js_1.OrderDispatcher(this.config);
443
+ const emitEvent = (event) => events.push(event);
444
+ let flushTimer;
445
+ if (order.needsTimer) {
446
+ const intervalMs = this.config.order_mode === "MicroBatch"
447
+ ? Math.max(1, Math.ceil(this.config.micro_batch_us / 1000))
448
+ : Math.max(1, Math.floor(this.config.order_timeout_ms / 2));
449
+ flushTimer = setInterval(() => order.flushDue(emitEvent), intervalMs);
450
+ }
451
+ let isCancelled = false;
452
+ let currentTransactionFilters = transactionFilters;
453
+ let currentAccountFilters = accountFilters;
454
+ const streamHolder = { current: null };
455
+ const cancel = () => {
456
+ isCancelled = true;
457
+ streamHolder.current?.end();
458
+ streamHolder.current = null;
459
+ if (flushTimer)
460
+ clearInterval(flushTimer);
461
+ events.close();
462
+ errors.close();
463
+ };
464
+ const update = async (nextTxFilters, nextAccFilters) => {
465
+ currentTransactionFilters = nextTxFilters;
466
+ currentAccountFilters = nextAccFilters;
467
+ const stream = streamHolder.current;
468
+ if (!stream)
469
+ return;
470
+ await new Promise((resolve, reject) => {
471
+ stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
472
+ if (err)
473
+ reject(err);
474
+ else
475
+ resolve();
476
+ });
477
+ });
478
+ };
479
+ this.dexSubscribers.set(id, { cancel, update });
480
+ (async () => {
481
+ const autoReconnect = options?.autoReconnect !== false;
482
+ const maxBackoffMs = 60_000;
483
+ let backoffMs = this.config.retry_delay_ms;
484
+ try {
485
+ while (!isCancelled) {
486
+ try {
487
+ const stream = await this.client.subscribe();
488
+ streamHolder.current = stream;
489
+ await new Promise((resolve, reject) => {
490
+ stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
491
+ if (err)
492
+ reject(err);
493
+ else
494
+ resolve();
495
+ });
496
+ });
497
+ backoffMs = this.config.retry_delay_ms;
498
+ await new Promise((resolve, reject) => {
499
+ const cleanup = () => {
500
+ stream.removeAllListeners();
501
+ if (streamHolder.current === stream) {
502
+ streamHolder.current = null;
503
+ }
504
+ };
505
+ stream.on("data", (update) => {
506
+ if (isCancelled)
507
+ return;
508
+ order.flushDue(emitEvent);
509
+ if (update.ping) {
510
+ stream.write(this.subscribePingPongRequest(), (werr) => {
511
+ if (werr && !isCancelled) {
512
+ errors.push(werr instanceof Error ? werr : new Error(String(werr)));
513
+ }
514
+ });
515
+ return;
516
+ }
517
+ try {
518
+ const grpcRecvUs = Math.floor(Date.now() * 1000);
519
+ const converted = this.convertUpdate(update);
520
+ const tx = converted.transaction?.transaction;
521
+ if (tx) {
522
+ const fallbackSlot = YellowstoneGrpc.protobufNumber(converted.transaction?.slot ?? 0);
523
+ const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
524
+ const txEvents = (0, yellowstone_parse_js_1.parseDexEventsFromGrpcTransactionInfo)(tx, converted.transaction?.slot ?? 0n, { blockTimeUs: converted.createdAtUs, grpcRecvUs, eventTypeFilter });
525
+ order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
526
+ }
527
+ if (converted.account) {
528
+ const ev = this.parseAccountEvent(converted.account, grpcRecvUs, eventTypeFilter, converted.createdAtUs);
529
+ if (ev)
530
+ emitEvent(ev);
531
+ }
532
+ }
533
+ catch (err) {
534
+ errors.push(err instanceof Error ? err : new Error(String(err)));
535
+ }
536
+ });
537
+ stream.on("error", (err) => {
538
+ cleanup();
539
+ reject(err);
540
+ });
541
+ stream.on("end", () => {
542
+ cleanup();
543
+ resolve();
544
+ });
545
+ });
546
+ }
547
+ catch (err) {
548
+ if (isCancelled)
549
+ break;
550
+ errors.push(err instanceof Error ? err : new Error(String(err)));
551
+ if (!autoReconnect)
552
+ break;
553
+ await new Promise((r) => setTimeout(r, backoffMs));
554
+ backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
555
+ continue;
556
+ }
557
+ if (isCancelled || !autoReconnect)
558
+ break;
559
+ await new Promise((r) => setTimeout(r, backoffMs));
560
+ backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
561
+ }
562
+ }
563
+ finally {
564
+ streamHolder.current?.end();
565
+ streamHolder.current = null;
566
+ if (flushTimer)
567
+ clearInterval(flushTimer);
568
+ order.flushAll(emitEvent);
569
+ events.close();
570
+ errors.close();
571
+ this.dexSubscribers.delete(id);
572
+ }
573
+ })();
574
+ return Object.assign(events, {
575
+ id,
576
+ errors,
577
+ cancel: () => {
578
+ cancel();
579
+ this.dexSubscribers.delete(id);
580
+ },
581
+ });
582
+ }
263
583
  /** 取消订阅 */
264
584
  unsubscribe(subId) {
265
585
  const sub = this.subscribers.get(subId);
@@ -267,6 +587,22 @@ class YellowstoneGrpc {
267
587
  sub.cancel();
268
588
  this.subscribers.delete(subId);
269
589
  }
590
+ const dexSub = this.dexSubscribers.get(subId);
591
+ if (dexSub) {
592
+ dexSub.cancel();
593
+ this.dexSubscribers.delete(subId);
594
+ }
595
+ }
596
+ /** 动态更新当前 DEX 订阅过滤器(与 Rust update_subscription 语义对齐)。 */
597
+ async updateSubscription(transactionFilters, accountFilters) {
598
+ const active = this.dexSubscribers.values().next().value;
599
+ if (!active?.update) {
600
+ throw new Error("No active DEX subscription");
601
+ }
602
+ await active.update(transactionFilters, accountFilters);
603
+ }
604
+ async update_subscription(transactionFilters, accountFilters) {
605
+ await this.updateSubscription(transactionFilters, accountFilters);
270
606
  }
271
607
  /** 获取最新区块哈希 */
272
608
  async getLatestBlockhash(commitment) {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 与 Rust `grpc::event_parser`(`pub use crate::core as event_parser`)对应:核心解析子集再导出。
3
+ */
4
+ export { parseTransactionEvents, parseLogsOnly, parseTransactionWithListener, parseTransactionEventsStreaming, parseLogsStreaming, parseTransactionWithStreamingListener, parseLog, nowUs, type EventListener, type StreamingEventListener, } from "../core/unified_parser.js";
5
+ export type { DexEvent } from "../core/dex_event.js";
6
+ export type { EventMetadata } from "../core/metadata.js";
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nowUs = exports.parseLog = exports.parseTransactionWithStreamingListener = exports.parseLogsStreaming = exports.parseTransactionEventsStreaming = exports.parseTransactionWithListener = exports.parseLogsOnly = exports.parseTransactionEvents = void 0;
4
+ /**
5
+ * 与 Rust `grpc::event_parser`(`pub use crate::core as event_parser`)对应:核心解析子集再导出。
6
+ */
7
+ var unified_parser_js_1 = require("../core/unified_parser.js");
8
+ Object.defineProperty(exports, "parseTransactionEvents", { enumerable: true, get: function () { return unified_parser_js_1.parseTransactionEvents; } });
9
+ Object.defineProperty(exports, "parseLogsOnly", { enumerable: true, get: function () { return unified_parser_js_1.parseLogsOnly; } });
10
+ Object.defineProperty(exports, "parseTransactionWithListener", { enumerable: true, get: function () { return unified_parser_js_1.parseTransactionWithListener; } });
11
+ Object.defineProperty(exports, "parseTransactionEventsStreaming", { enumerable: true, get: function () { return unified_parser_js_1.parseTransactionEventsStreaming; } });
12
+ Object.defineProperty(exports, "parseLogsStreaming", { enumerable: true, get: function () { return unified_parser_js_1.parseLogsStreaming; } });
13
+ Object.defineProperty(exports, "parseTransactionWithStreamingListener", { enumerable: true, get: function () { return unified_parser_js_1.parseTransactionWithStreamingListener; } });
14
+ Object.defineProperty(exports, "parseLog", { enumerable: true, get: function () { return unified_parser_js_1.parseLog; } });
15
+ Object.defineProperty(exports, "nowUs", { enumerable: true, get: function () { return unified_parser_js_1.nowUs; } });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 与 Rust `sol-parser-sdk/src/grpc/geyser_connect.rs` 对齐:Yellowstone Geyser gRPC 连接选项。
3
+ *
4
+ * `@triton-one/yellowstone-grpc` 的 `Client` 通过构造函数第三参传入 `ChannelOptions`;
5
+ * `connectTimeoutMs` 无与 tonic 完全一一对应的项,仅作文档与默认值对齐(可配合环境网络调优)。
6
+ */
7
+ import type { ChannelOptions } from "@grpc/grpc-js";
8
+ import Client from "@triton-one/yellowstone-grpc";
9
+ /** 与 Rust `GeyserConnectConfig` 字段对应(毫秒 / 字节) */
10
+ export interface GeyserConnectConfig {
11
+ /** 对应 Rust `connect_timeout`(毫秒) */
12
+ connectTimeoutMs: number;
13
+ /** 对应 Rust `max_decoding_message_size` */
14
+ maxDecodingMessageSize: number;
15
+ /** 对应 Rust `x_token` */
16
+ xToken?: string;
17
+ /** gRPC 通道 HTTP/2 keepalive 间隔(毫秒),与 Rust `ClientConfig.keep_alive_interval_ms` 默认对齐 */
18
+ keepAliveIntervalMs?: number;
19
+ /** gRPC keepalive 应答超时(毫秒) */
20
+ keepAliveTimeoutMs?: number;
21
+ }
22
+ /** 与 Rust `GeyserConnectConfig::default` 一致 */
23
+ export declare function defaultGeyserConnectConfig(): GeyserConnectConfig;
24
+ /** 与 `ClientConfig` / `grpc.keepalive_*` 对齐,用于长连接抗 NAT/负载均衡 idle 断开 */
25
+ export declare function geyserGrpcChannelOptions(config?: GeyserConnectConfig): ChannelOptions;
26
+ /**
27
+ * 建立 Yellowstone Geyser 客户端(与 Rust `connect_yellowstone_geyser` 一致)。
28
+ * Rust 为 async;此处构造同步完成,返回 `Promise` 以保持 `await connectYellowstoneGeyser(...)` 写法。
29
+ */
30
+ export declare function connectYellowstoneGeyser(endpoint: string, config?: GeyserConnectConfig): Promise<Client>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defaultGeyserConnectConfig = defaultGeyserConnectConfig;
7
+ exports.geyserGrpcChannelOptions = geyserGrpcChannelOptions;
8
+ exports.connectYellowstoneGeyser = connectYellowstoneGeyser;
9
+ const yellowstone_grpc_1 = __importDefault(require("@triton-one/yellowstone-grpc"));
10
+ /** 与 Rust `GeyserConnectConfig::default` 一致 */
11
+ function defaultGeyserConnectConfig() {
12
+ return {
13
+ connectTimeoutMs: 8000,
14
+ maxDecodingMessageSize: 1024 * 1024 * 1024,
15
+ xToken: undefined,
16
+ keepAliveIntervalMs: 30_000,
17
+ keepAliveTimeoutMs: 5000,
18
+ };
19
+ }
20
+ /** 与 `ClientConfig` / `grpc.keepalive_*` 对齐,用于长连接抗 NAT/负载均衡 idle 断开 */
21
+ function geyserGrpcChannelOptions(config = defaultGeyserConnectConfig()) {
22
+ const max = config.maxDecodingMessageSize;
23
+ const interval = config.keepAliveIntervalMs ?? 30_000;
24
+ const timeout = config.keepAliveTimeoutMs ?? 5000;
25
+ return {
26
+ "grpc.max_receive_message_length": max,
27
+ "grpc.max_send_message_length": max,
28
+ "grpc.keepalive_time_ms": interval,
29
+ "grpc.keepalive_timeout_ms": timeout,
30
+ /** 无活跃 RPC 时仍发 keepalive,避免长时间仅订阅时被中间设备掐断 */
31
+ "grpc.keepalive_permit_without_calls": 1,
32
+ };
33
+ }
34
+ /**
35
+ * 建立 Yellowstone Geyser 客户端(与 Rust `connect_yellowstone_geyser` 一致)。
36
+ * Rust 为 async;此处构造同步完成,返回 `Promise` 以保持 `await connectYellowstoneGeyser(...)` 写法。
37
+ */
38
+ async function connectYellowstoneGeyser(endpoint, config = defaultGeyserConnectConfig()) {
39
+ return new yellowstone_grpc_1.default(endpoint, config.xToken, geyserGrpcChannelOptions(config));
40
+ }
@@ -0,0 +1,2 @@
1
+ import type { DexEvent } from "../core/dex_event.js";
2
+ export declare function dedupeLogInstructionEvents(logEvents: DexEvent[], instructionEvents: DexEvent[]): DexEvent[];