sol-parser-sdk 0.5.15 → 0.5.17

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 (96) hide show
  1. package/NATIVE_ALIGNMENT.md +312 -0
  2. package/NATIVE_DAMM_FACTORY_ALIGNMENT_20261004.md +16 -0
  3. package/NATIVE_DAMM_FACTORY_EVIDENCE_20261004.json +55 -0
  4. package/NATIVE_MIGRATION.md +38 -0
  5. package/NATIVE_PUMPFUN_NATIVE_DEBIT_ALIGNMENT_20261004.md +18 -0
  6. package/NATIVE_PUMPFUN_NATIVE_DEBIT_EVIDENCE_20261004.json +57 -0
  7. package/NATIVE_PUMPFUN_SETTLEMENT_ALIGNMENT_20261004.md +11 -0
  8. package/NATIVE_PUMPFUN_SETTLEMENT_EVIDENCE_20261004.json +70 -0
  9. package/README.md +24 -1
  10. package/README_CN.md +21 -1
  11. package/dist/accounts/mod.js +7 -0
  12. package/dist/core/account_dispatcher_rpc.js +50 -6
  13. package/dist/core/account_fill_pumpfun.js +7 -16
  14. package/dist/core/account_fill_raydium.js +12 -2
  15. package/dist/core/account_fill_raydium_launchlab.js +6 -0
  16. package/dist/core/clock.js +1 -1
  17. package/dist/core/dex_event.d.ts +30 -0
  18. package/dist/core/metadata.d.ts +9 -5
  19. package/dist/core/metadata.js +28 -4
  20. package/dist/core/unified_parser.d.ts +7 -7
  21. package/dist/grpc/block_meta.d.ts +9 -0
  22. package/dist/grpc/block_meta.js +26 -0
  23. package/dist/grpc/client.d.ts +24 -9
  24. package/dist/grpc/client.js +129 -67
  25. package/dist/grpc/geyser_connect.d.ts +3 -3
  26. package/dist/grpc/geyser_connect.js +13 -15
  27. package/dist/grpc/native_client.d.ts +26 -0
  28. package/dist/grpc/native_client.js +101 -0
  29. package/dist/grpc/order_buffer.d.ts +1 -1
  30. package/dist/grpc/order_buffer.js +10 -9
  31. package/dist/grpc/program_ids.js +2 -0
  32. package/dist/grpc/protocol/LICENSE +202 -0
  33. package/dist/grpc/protocol/NOTICE +4 -0
  34. package/dist/grpc/protocol/geyser.d.ts +342 -0
  35. package/dist/grpc/protocol/geyser.js +37 -0
  36. package/dist/grpc/protocol/geyser.proto +326 -0
  37. package/dist/grpc/protocol/solana-storage.d.ts +139 -0
  38. package/dist/grpc/protocol/solana-storage.js +14 -0
  39. package/dist/grpc/protocol/solana-storage.proto +151 -0
  40. package/dist/grpc/rpc_to_grpc.d.ts +1 -1
  41. package/dist/grpc/rpc_to_grpc.js +3 -2
  42. package/dist/grpc/subscribe_builder.d.ts +5 -3
  43. package/dist/grpc/subscribe_builder.js +11 -3
  44. package/dist/grpc/transaction_meta.d.ts +1 -1
  45. package/dist/grpc/types.d.ts +8 -8
  46. package/dist/grpc/types.js +7 -2
  47. package/dist/grpc/yellowstone_parse.d.ts +3 -3
  48. package/dist/grpc/yellowstone_parse.js +6 -8
  49. package/dist/grpc/yellowstone_transaction_adapter.d.ts +1 -1
  50. package/dist/grpc/yellowstone_transaction_adapter.js +1 -1
  51. package/dist/index.d.ts +11 -1
  52. package/dist/index.js +23 -5
  53. package/dist/instr/inner.d.ts +2 -2
  54. package/dist/instr/inner.js +1 -1
  55. package/dist/instr/meteora_damm_ix.d.ts +1 -1
  56. package/dist/instr/meteora_dlmm_ix.d.ts +1 -1
  57. package/dist/instr/meteora_pools_ix.d.ts +1 -1
  58. package/dist/instr/mod.d.ts +1 -1
  59. package/dist/instr/orca_whirlpool_ix.d.ts +1 -1
  60. package/dist/instr/pump_fees_ix.d.ts +1 -1
  61. package/dist/instr/pumpfun_ix.d.ts +1 -1
  62. package/dist/instr/pumpfun_ix.js +3 -3
  63. package/dist/instr/pumpswap_ix.d.ts +1 -1
  64. package/dist/instr/raydium_amm_v4_ix.d.ts +1 -1
  65. package/dist/instr/raydium_amm_v4_ix.js +9 -5
  66. package/dist/instr/raydium_clmm_ix.d.ts +1 -1
  67. package/dist/instr/raydium_cpmm_ix.d.ts +1 -1
  68. package/dist/instr/raydium_launchlab_ix.d.ts +1 -1
  69. package/dist/instr/raydium_launchlab_ix.js +62 -2
  70. package/dist/instr/utils.d.ts +2 -2
  71. package/dist/instr/utils.js +2 -7
  72. package/dist/liquidity_snapshot.d.ts +54 -0
  73. package/dist/liquidity_snapshot.js +85 -0
  74. package/dist/logs/meteora_damm.d.ts +1 -1
  75. package/dist/logs/meteora_damm.js +4 -2
  76. package/dist/logs/meteora_dlmm.d.ts +1 -1
  77. package/dist/logs/optimized_matcher.d.ts +3 -3
  78. package/dist/logs/pump.js +11 -0
  79. package/dist/logs/raydium_launchlab.js +19 -1
  80. package/dist/rpc_transaction.d.ts +4 -3
  81. package/dist/rpc_transaction.js +6 -2
  82. package/dist/shredstream/client.js +3 -7
  83. package/dist/shredstream/entries_decode.js +9 -85
  84. package/dist/shredstream/instruction_parse.d.ts +3 -3
  85. package/dist/shredstream/proto_types.d.ts +1 -1
  86. package/dist/shredstream/wire_to_shred_tx.d.ts +1 -1
  87. package/dist/shredstream/wire_to_shred_tx.js +9 -44
  88. package/dist/simulation_route.d.ts +6 -0
  89. package/dist/simulation_route.js +242 -0
  90. package/dist/stonkfun_registry.d.ts +30 -0
  91. package/dist/stonkfun_registry.js +137 -0
  92. package/dist/transaction_route.d.ts +84 -0
  93. package/dist/transaction_route.js +477 -0
  94. package/dist/wire_transaction.d.ts +35 -0
  95. package/dist/wire_transaction.js +191 -0
  96. package/package.json +7 -4
@@ -1,47 +1,17 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.YellowstoneGrpc = void 0;
7
+ const block_meta_js_1 = require("./block_meta.js");
40
8
  /**
41
- * Yellowstone gRPC 客户端实现 - 基于 @triton-one/yellowstone-grpc
9
+ * Yellowstone gRPC 客户端实现 - 基于纯 JavaScript @grpc/grpc-js
42
10
  */
43
- const yellowstone_grpc_1 = __importStar(require("@triton-one/yellowstone-grpc"));
11
+ const native_client_js_1 = __importDefault(require("./native_client.js"));
12
+ const geyser_js_1 = require("./protocol/geyser.js");
44
13
  const bs58_1 = __importDefault(require("bs58"));
14
+ const node_crypto_1 = require("node:crypto");
45
15
  const node_buffer_1 = require("node:buffer");
46
16
  const dex_event_js_1 = require("../core/dex_event.js");
47
17
  const metadata_js_1 = require("../core/metadata.js");
@@ -58,7 +28,11 @@ function cancelSubscribeStream(stream) {
58
28
  // Stream destruction can emit an error during the short setup window before
59
29
  // the normal lifecycle listeners have been installed.
60
30
  stream.once("error", () => { });
61
- stream.destroy();
31
+ const cancellable = stream;
32
+ if (typeof cancellable.cancel === "function")
33
+ cancellable.cancel();
34
+ else
35
+ stream.destroy();
62
36
  }
63
37
  function positiveIntegerOption(value, fallback, name) {
64
38
  const normalized = value ?? fallback;
@@ -74,7 +48,7 @@ function replayUnsupported(error) {
74
48
  return /from.?slot.*(?:unsupported|disabled|unavailable|invalid)/i.test(`${grpcError.message ?? ""} ${grpcError.details ?? ""}`);
75
49
  }
76
50
  function updateSlot(update) {
77
- const value = update.transaction?.slot ?? update.account?.slot;
51
+ const value = update.transaction?.slot ?? update.account?.slot ?? update.blockMeta?.slot;
78
52
  if (value === undefined || value === null)
79
53
  return undefined;
80
54
  try {
@@ -91,6 +65,8 @@ function updateIdentity(update) {
91
65
  const transaction = update.transaction?.transaction;
92
66
  if (transaction?.signature)
93
67
  return `t:${bytesKey(transaction.signature)}`;
68
+ if (update.blockMeta)
69
+ return `b:${String(update.blockMeta.slot)}:${update.blockMeta.blockhash}`;
94
70
  const account = update.account?.account;
95
71
  if (!account?.pubkey)
96
72
  return undefined;
@@ -104,10 +80,28 @@ class YellowstoneGrpc {
104
80
  connectPromise;
105
81
  subscribers = new Map();
106
82
  dexSubscribers = new Map();
83
+ dexLifecycle = Promise.resolve();
84
+ activeDex;
85
+ withDexLifecycle(work) {
86
+ const result = this.dexLifecycle.then(work);
87
+ this.dexLifecycle = result.then(() => undefined, () => undefined);
88
+ return result;
89
+ }
90
+ async stopDexUnlocked() {
91
+ const active = this.activeDex;
92
+ this.activeDex = undefined;
93
+ if (active) {
94
+ active.cancel();
95
+ await active.join();
96
+ }
97
+ }
98
+ async stop() {
99
+ await this.withDexLifecycle(() => this.stopDexUnlocked());
100
+ }
107
101
  constructor(endpoint, xToken, config = (0, types_js_1.defaultClientConfig)()) {
108
102
  this.config = config;
109
103
  const gc = (0, geyser_connect_js_1.defaultGeyserConnectConfig)();
110
- this.client = new yellowstone_grpc_1.default(endpoint, xToken || undefined, (0, geyser_connect_js_1.geyserGrpcChannelOptions)({
104
+ this.client = new native_client_js_1.default(endpoint, xToken || undefined, (0, geyser_connect_js_1.geyserGrpcChannelOptions)({
111
105
  ...gc,
112
106
  keepAliveIntervalMs: config.keep_alive_interval_ms,
113
107
  keepAliveTimeoutMs: config.keep_alive_timeout_ms,
@@ -135,6 +129,7 @@ class YellowstoneGrpc {
135
129
  }
136
130
  /** 断开连接 */
137
131
  async disconnect() {
132
+ await this.stop();
138
133
  for (const [, sub] of this.subscribers) {
139
134
  sub.cancel();
140
135
  }
@@ -143,6 +138,7 @@ class YellowstoneGrpc {
143
138
  sub.cancel();
144
139
  }
145
140
  this.dexSubscribers.clear();
141
+ this.client.close?.();
146
142
  this.connected = false;
147
143
  this.connectPromise = undefined;
148
144
  }
@@ -265,7 +261,7 @@ class YellowstoneGrpc {
265
261
  entry: {},
266
262
  blocks: {},
267
263
  blocksMeta: {},
268
- commitment: yellowstone_grpc_1.CommitmentLevel.CONFIRMED,
264
+ commitment: geyser_js_1.CommitmentLevel.CONFIRMED,
269
265
  accountsDataSlice: [],
270
266
  ping: { id: 1 },
271
267
  };
@@ -276,7 +272,7 @@ class YellowstoneGrpc {
276
272
  return value instanceof Uint8Array ? value : Uint8Array.from(value);
277
273
  }
278
274
  static toBigInt(value) {
279
- return typeof value === "bigint" ? value : BigInt(value);
275
+ return (0, metadata_js_1.exactU64)(value);
280
276
  }
281
277
  static protobufNumber(value) {
282
278
  if (typeof value === "bigint")
@@ -297,18 +293,20 @@ class YellowstoneGrpc {
297
293
  static timestampToMicros(value) {
298
294
  if (value instanceof Date) {
299
295
  const milliseconds = value.getTime();
300
- return Number.isFinite(milliseconds) ? Math.trunc(milliseconds * 1_000) : undefined;
296
+ return Number.isFinite(milliseconds) ? BigInt(milliseconds) * 1000n : undefined;
301
297
  }
302
298
  if (!value || typeof value !== "object")
303
299
  return undefined;
304
300
  const ts = value;
305
301
  if (ts.seconds === undefined)
306
302
  return undefined;
307
- const seconds = YellowstoneGrpc.protobufNumber(ts.seconds);
308
- const nanos = Number(ts.nanos ?? 0);
309
- if (!Number.isFinite(seconds) || !Number.isFinite(nanos))
310
- return undefined;
311
- return Math.trunc(seconds * 1_000_000 + nanos / 1_000);
303
+ const seconds = (0, metadata_js_1.exactInteger)(typeof ts.seconds === "object" && ts.seconds !== null ? ts.seconds.toString() : ts.seconds, "timestamp seconds");
304
+ const nanos = (0, metadata_js_1.exactInteger)((ts.nanos ?? 0), "timestamp nanos");
305
+ if (nanos < 0n || nanos >= 1000000000n)
306
+ throw new RangeError("Invalid timestamp nanos");
307
+ const micros = seconds * 1000000n + nanos / 1000n;
308
+ const lo = -(1n << 63n), hi = (1n << 63n) - 1n;
309
+ return micros < lo ? lo : micros > hi ? hi : micros;
312
310
  }
313
311
  parseAccountEvent(update, grpcRecvUs, eventTypeFilter, blockTimeUs) {
314
312
  const acc = update.account;
@@ -323,10 +321,10 @@ class YellowstoneGrpc {
323
321
  rent_epoch: YellowstoneGrpc.toBigInt(acc.rentEpoch),
324
322
  data: YellowstoneGrpc.toBytes(acc.data),
325
323
  };
326
- const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", Number(update.slot), 0, blockTimeUs, grpcRecvUs);
324
+ const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", update.slot, 0, blockTimeUs, grpcRecvUs);
327
325
  return (0, mod_js_1.parseAccountUnified)(account, metadata, eventTypeFilter);
328
326
  }
329
- parseAccountEventRaw(update, grpcRecvUs, eventTypeFilter, blockTimeUs) {
327
+ parseAccountEventRaw(update, grpcRecvUs, eventTypeFilter, blockTimeUs, rawSnapshot = false) {
330
328
  const acc = update.account;
331
329
  if (!acc)
332
330
  return null;
@@ -339,7 +337,9 @@ class YellowstoneGrpc {
339
337
  rent_epoch: YellowstoneGrpc.toBigInt(acc.rentEpoch),
340
338
  data: YellowstoneGrpc.toBytes(acc.data),
341
339
  };
342
- const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", YellowstoneGrpc.protobufNumber(update.slot), 0, blockTimeUs, grpcRecvUs);
340
+ const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", update.slot, 0, blockTimeUs, grpcRecvUs);
341
+ if (rawSnapshot)
342
+ return { RawAccountSnapshot: { metadata, account: { ...account, data: Uint8Array.from(account.data) }, write_version: YellowstoneGrpc.toBigInt(acc.writeVersion), is_startup: update.isStartup } };
343
343
  return (0, mod_js_1.parseAccountUnified)(account, metadata, eventTypeFilter);
344
344
  }
345
345
  initialSubscribeRequest(filter) {
@@ -360,7 +360,7 @@ class YellowstoneGrpc {
360
360
  entry: {},
361
361
  blocks: {},
362
362
  blocksMeta: {},
363
- commitment: yellowstone_grpc_1.CommitmentLevel.CONFIRMED,
363
+ commitment: geyser_js_1.CommitmentLevel.CONFIRMED,
364
364
  accountsDataSlice: [],
365
365
  ping: undefined,
366
366
  };
@@ -377,7 +377,7 @@ class YellowstoneGrpc {
377
377
  streamHolder.current = null;
378
378
  };
379
379
  this.subscribers.set(id, { filter, callbacks, cancel });
380
- (async () => {
380
+ const streamTask = (async () => {
381
381
  const autoReconnect = callbacks.autoReconnect !== false;
382
382
  const maxBackoffMs = 60_000;
383
383
  let backoffMs = this.config.retry_delay_ms;
@@ -482,6 +482,12 @@ class YellowstoneGrpc {
482
482
  * 事件队列满时按配置丢弃事件,避免回调阻塞 gRPC 读循环并让丢弃可观测。
483
483
  */
484
484
  async subscribeDexEvents(transactionFilters = [], accountFilters = [], eventTypeFilter, options) {
485
+ return this.withDexLifecycle(async () => {
486
+ await this.stopDexUnlocked();
487
+ return this.startDexEvents(transactionFilters, accountFilters, eventTypeFilter, options);
488
+ });
489
+ }
490
+ async startDexEvents(transactionFilters = [], accountFilters = [], eventTypeFilter, options) {
485
491
  await this.connect();
486
492
  const id = `dex_${Date.now()}_${Math.random().toString(36).slice(2)}`;
487
493
  const events = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size), options?.queueOverflowStrategy);
@@ -504,11 +510,23 @@ class YellowstoneGrpc {
504
510
  let highestRememberedSlot = 0n;
505
511
  let seenHead = 0;
506
512
  const seenUpdates = new Map();
513
+ const accountFingerprints = new Map();
507
514
  const seenOrder = [];
515
+ const states = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size));
516
+ let streamStatus = Object.freeze({ state: "connecting", continuityBroken: false, reconnects: 0, dropped: 0 });
517
+ states.push(streamStatus);
518
+ const transition = (state, broken = false) => {
519
+ const next = Object.freeze({ state, continuityBroken: streamStatus.continuityBroken || broken, reconnects: reconnectCount, dropped: eventDropped() + ingress.dropped() });
520
+ if (next.state === streamStatus.state && next.continuityBroken === streamStatus.continuityBroken && next.reconnects === streamStatus.reconnects && next.dropped === streamStatus.dropped)
521
+ return;
522
+ streamStatus = next;
523
+ states.push(next);
524
+ };
508
525
  const reportError = (error) => errors.push(error);
509
526
  const emitEvent = (event) => {
510
527
  if (events.push(event))
511
528
  return;
529
+ transition(streamStatus.state, true);
512
530
  const now = Date.now();
513
531
  if (now < nextEventDropWarningAt)
514
532
  return;
@@ -552,12 +570,14 @@ class YellowstoneGrpc {
552
570
  }
553
571
  }
554
572
  };
555
- const rememberParsedUpdate = (identity, slot) => {
573
+ const rememberParsedUpdate = (identity, slot, fingerprint) => {
556
574
  if (slot !== undefined && (lastParsedSlot === undefined || slot > lastParsedSlot)) {
557
575
  lastParsedSlot = slot;
558
576
  }
559
- if (!replayOnReconnect || identity === undefined || slot === undefined)
577
+ if (identity === undefined || slot === undefined)
560
578
  return;
579
+ if (fingerprint !== undefined)
580
+ accountFingerprints.set(identity, fingerprint);
561
581
  seenUpdates.set(identity, slot);
562
582
  seenOrder.push({ identity, slot });
563
583
  if (slot > highestRememberedSlot)
@@ -567,8 +587,10 @@ class YellowstoneGrpc {
567
587
  : 0n;
568
588
  while (seenHead < seenOrder.length && seenOrder[seenHead].slot < retainFrom) {
569
589
  const stale = seenOrder[seenHead++];
570
- if (seenUpdates.get(stale.identity) === stale.slot)
590
+ if (seenUpdates.get(stale.identity) === stale.slot) {
571
591
  seenUpdates.delete(stale.identity);
592
+ accountFingerprints.delete(stale.identity);
593
+ }
572
594
  }
573
595
  if (seenHead >= 4096 && seenHead * 2 >= seenOrder.length) {
574
596
  seenOrder.splice(0, seenHead);
@@ -578,6 +600,12 @@ class YellowstoneGrpc {
578
600
  const processUpdate = ({ update, grpcRecvUs, grpcRecvNs, blockTimeUs, sourceToGrpcLatencyUs, }) => {
579
601
  const identity = updateIdentity(update);
580
602
  const slotForReplay = updateSlot(update);
603
+ const raw = update.account?.account;
604
+ const fingerprint = raw ? (0, node_crypto_1.createHash)("sha256").update(raw.owner).update(raw.data).update(`${raw.lamports}:${raw.executable}:${raw.rentEpoch}`).digest("hex") : undefined;
605
+ if (identity && fingerprint && accountFingerprints.has(identity) && accountFingerprints.get(identity) !== fingerprint) {
606
+ transition(streamStatus.state, true);
607
+ throw Error("Conflicting account version; select and revalidate a fork explicitly");
608
+ }
581
609
  if (replayOnReconnect && identity !== undefined && seenUpdates.has(identity)) {
582
610
  replayedUpdateCount++;
583
611
  rememberParsedUpdate(undefined, slotForReplay);
@@ -590,8 +618,8 @@ class YellowstoneGrpc {
590
618
  if (tx) {
591
619
  const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
592
620
  const slot = txUpdate?.slot ?? 0n;
593
- const fallbackSlot = YellowstoneGrpc.protobufNumber(slot);
594
- const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
621
+ const fallbackSlot = YellowstoneGrpc.toBigInt(slot);
622
+ const fallbackTxIndex = YellowstoneGrpc.toBigInt(tx.index ?? 0);
595
623
  const txInfo = {
596
624
  signature: tx.signature,
597
625
  isVote: tx.isVote,
@@ -608,9 +636,23 @@ class YellowstoneGrpc {
608
636
  annotateLocalMetrics(txEvents, grpcRecvNs, parseStartedNs, parseFinishedNs, sourceToGrpcLatencyUs);
609
637
  order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
610
638
  }
639
+ if (update.blockMeta) {
640
+ const event = (0, block_meta_js_1.parseBlockMetaUpdate)(update.blockMeta, grpcRecvUs, blockTimeUs);
641
+ if (!eventTypeFilter || eventTypeFilter.shouldInclude("BlockMeta"))
642
+ emitEvent(event);
643
+ }
611
644
  if (update.account) {
645
+ const rawOnly = !!eventTypeFilter?.include_only?.length &&
646
+ eventTypeFilter.include_only.every(type => type === "AccountRawSnapshot");
647
+ if (eventTypeFilter?.include_only?.includes('AccountRawSnapshot')) {
648
+ const snapshot = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs, true);
649
+ if (snapshot)
650
+ emitEvent(snapshot);
651
+ }
612
652
  const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
613
- const ev = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
653
+ // Raw-only cache subscriptions do not need a second normalization,
654
+ // base58 conversion and protocol decode for the same account bytes.
655
+ const ev = rawOnly ? null : this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
614
656
  const parseFinishedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
615
657
  if (ev) {
616
658
  annotateLocalMetrics([ev], grpcRecvNs, parseStartedNs, parseFinishedNs, sourceToGrpcLatencyUs);
@@ -618,7 +660,7 @@ class YellowstoneGrpc {
618
660
  if (ev)
619
661
  emitEvent(ev);
620
662
  }
621
- rememberParsedUpdate(identity, slotForReplay);
663
+ rememberParsedUpdate(identity, slotForReplay, fingerprint);
622
664
  };
623
665
  const scheduleIngressDrain = () => {
624
666
  if (isCancelled || ingressDrainScheduled || ingress.len() === 0)
@@ -640,6 +682,7 @@ class YellowstoneGrpc {
640
682
  processUpdate(pending);
641
683
  }
642
684
  catch (err) {
685
+ transition(streamStatus.state, true);
643
686
  reportError(err instanceof Error ? err : new Error(String(err)));
644
687
  }
645
688
  processed++;
@@ -677,6 +720,8 @@ class YellowstoneGrpc {
677
720
  });
678
721
  const cancel = () => {
679
722
  isCancelled = true;
723
+ transition("stopped", true);
724
+ states.close();
680
725
  cancelReconnectDelay?.();
681
726
  cancelSubscribeStream(streamHolder.current);
682
727
  streamHolder.current = null;
@@ -699,7 +744,7 @@ class YellowstoneGrpc {
699
744
  if (!stream)
700
745
  return;
701
746
  await new Promise((resolve, reject) => {
702
- stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
747
+ stream.write((0, subscribe_builder_js_1.buildSubscribeRequestWithEventFilter)(currentTransactionFilters, currentAccountFilters, eventTypeFilter), (err) => {
703
748
  if (err)
704
749
  reject(err);
705
750
  else
@@ -708,7 +753,7 @@ class YellowstoneGrpc {
708
753
  });
709
754
  };
710
755
  this.dexSubscribers.set(id, { cancel, update });
711
- (async () => {
756
+ const streamTask = (async () => {
712
757
  const autoReconnect = options?.autoReconnect !== false;
713
758
  const maxBackoffMs = 60_000;
714
759
  let backoffMs = this.config.retry_delay_ms;
@@ -722,13 +767,14 @@ class YellowstoneGrpc {
722
767
  attempt++;
723
768
  if (attempt > 1)
724
769
  reconnectCount++;
770
+ transition(attempt > 1 ? "reconnecting" : "connecting");
725
771
  const stream = await this.client.subscribe();
726
772
  if (isCancelled) {
727
773
  cancelSubscribeStream(stream);
728
774
  break;
729
775
  }
730
776
  streamHolder.current = stream;
731
- const request = (0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters);
777
+ const request = (0, subscribe_builder_js_1.buildSubscribeRequestWithEventFilter)(currentTransactionFilters, currentAccountFilters, eventTypeFilter);
732
778
  if (attempt > 1 && replayAllowed && lastParsedSlot !== undefined) {
733
779
  request.fromSlot = lastParsedSlot.toString();
734
780
  attemptedReplay = true;
@@ -742,6 +788,7 @@ class YellowstoneGrpc {
742
788
  });
743
789
  });
744
790
  streamConnected = true;
791
+ transition("connected");
745
792
  await new Promise((resolve, reject) => {
746
793
  const cleanup = () => {
747
794
  streamConnected = false;
@@ -773,9 +820,10 @@ class YellowstoneGrpc {
773
820
  grpcRecvUs,
774
821
  grpcRecvNs,
775
822
  blockTimeUs: createdAtUs,
776
- sourceToGrpcLatencyUs: createdAtUs === undefined ? undefined : Math.max(0, grpcRecvUs - createdAtUs),
823
+ sourceToGrpcLatencyUs: createdAtUs === undefined ? undefined : Math.max(0, Number((0, metadata_js_1.exactI64)(grpcRecvUs) - createdAtUs)),
777
824
  });
778
825
  if (!accepted) {
826
+ transition(streamStatus.state, true);
779
827
  const now = Date.now();
780
828
  if (now >= nextIngressDropWarningAt) {
781
829
  nextIngressDropWarningAt = now + 10_000;
@@ -788,11 +836,15 @@ class YellowstoneGrpc {
788
836
  });
789
837
  stream.on("error", (err) => {
790
838
  streamDisconnectCount++;
839
+ if (!isCancelled)
840
+ transition("reconnecting", true);
791
841
  cleanup();
792
842
  reject(err);
793
843
  });
794
844
  stream.on("end", () => {
795
845
  streamDisconnectCount++;
846
+ if (!isCancelled)
847
+ transition("reconnecting", true);
796
848
  cleanup();
797
849
  if (!isCancelled)
798
850
  reportError(new Error("Yellowstone stream ended; reconnecting"));
@@ -806,6 +858,7 @@ class YellowstoneGrpc {
806
858
  cancelSubscribeStream(streamHolder.current);
807
859
  streamHolder.current = null;
808
860
  streamConnected = false;
861
+ transition("reconnecting", true);
809
862
  reportError(err instanceof Error ? err : new Error(String(err)));
810
863
  await waitForIngressIdle();
811
864
  if (!autoReconnect) {
@@ -854,12 +907,18 @@ class YellowstoneGrpc {
854
907
  ingress.close();
855
908
  events.close();
856
909
  errors.close();
910
+ transition("stopped", true);
911
+ states.close();
857
912
  this.dexSubscribers.delete(id);
858
913
  }
859
914
  })();
915
+ this.activeDex = { cancel, join: () => streamTask };
860
916
  return Object.assign(events, {
861
917
  id,
918
+ join: () => streamTask,
862
919
  errors,
920
+ states,
921
+ status: () => streamStatus,
863
922
  dropped: () => eventDropped() + ingress.dropped(),
864
923
  eventDropped,
865
924
  ingressLen: () => ingress.len(),
@@ -890,6 +949,9 @@ class YellowstoneGrpc {
890
949
  }
891
950
  /** 动态更新当前 DEX 订阅过滤器(与 Rust update_subscription 语义对齐)。 */
892
951
  async updateSubscription(transactionFilters, accountFilters) {
952
+ return this.withDexLifecycle(() => this.updateDexUnlocked(transactionFilters, accountFilters));
953
+ }
954
+ async updateDexUnlocked(transactionFilters, accountFilters) {
893
955
  const active = this.dexSubscribers.values().next().value;
894
956
  if (!active?.update) {
895
957
  throw new Error("No active DEX subscription");
@@ -904,22 +966,22 @@ class YellowstoneGrpc {
904
966
  await this.connect();
905
967
  const resp = await this.client.getLatestBlockhash(commitment);
906
968
  return {
907
- slot: Number(resp.slot),
969
+ slot: (0, metadata_js_1.exactU64)(resp.slot),
908
970
  blockhash: resp.blockhash,
909
- lastValidBlockHeight: Number(resp.lastValidBlockHeight),
971
+ lastValidBlockHeight: (0, metadata_js_1.exactU64)(resp.lastValidBlockHeight),
910
972
  };
911
973
  }
912
974
  /** 获取区块高度 */
913
975
  async getBlockHeight(commitment) {
914
976
  await this.connect();
915
977
  const resp = await this.client.getBlockHeight(commitment);
916
- return Number(resp.blockHeight);
978
+ return (0, metadata_js_1.exactU64)(resp.blockHeight);
917
979
  }
918
980
  /** 获取当前 Slot */
919
981
  async getSlot(commitment) {
920
982
  await this.connect();
921
983
  const resp = await this.client.getSlot(commitment);
922
- return Number(resp.slot);
984
+ return (0, metadata_js_1.exactU64)(resp.slot);
923
985
  }
924
986
  /** 获取服务器版本 */
925
987
  async getVersion() {
@@ -932,7 +994,7 @@ class YellowstoneGrpc {
932
994
  await this.connect();
933
995
  const resp = await this.client.isBlockhashValid(blockhash, commitment);
934
996
  return {
935
- slot: Number(resp.slot),
997
+ slot: (0, metadata_js_1.exactU64)(resp.slot),
936
998
  valid: resp.valid,
937
999
  };
938
1000
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * 与 Rust `sol-parser-sdk/src/grpc/geyser_connect.rs` 对齐:Yellowstone Geyser gRPC 连接选项。
3
3
  *
4
- * `@triton-one/yellowstone-grpc` 的 `Client` 通过构造函数第三参传入 `ChannelOptions`;
5
- * `connectTimeoutMs` 无与 tonic 完全一一对应的项,仅作文档与默认值对齐(可配合环境网络调优)。
4
+ * 纯 JavaScript `@grpc/grpc-js` 传输,不加载 Rust N-API;
5
+ * `connectTimeoutMs` 用于 waitForReady 与 unary deadline。
6
6
  */
7
- import Client, { type ChannelOptions } from "@triton-one/yellowstone-grpc";
7
+ import Client, { type NativeChannelOptions as ChannelOptions } from "./native_client.js";
8
8
  /** 与 Rust `GeyserConnectConfig` 字段对应(毫秒 / 字节) */
9
9
  export interface GeyserConnectConfig {
10
10
  /** 对应 Rust `connect_timeout`(毫秒) */
@@ -9,10 +9,10 @@ exports.connectYellowstoneGeyser = connectYellowstoneGeyser;
9
9
  /**
10
10
  * 与 Rust `sol-parser-sdk/src/grpc/geyser_connect.rs` 对齐:Yellowstone Geyser gRPC 连接选项。
11
11
  *
12
- * `@triton-one/yellowstone-grpc` 的 `Client` 通过构造函数第三参传入 `ChannelOptions`;
13
- * `connectTimeoutMs` 无与 tonic 完全一一对应的项,仅作文档与默认值对齐(可配合环境网络调优)。
12
+ * 纯 JavaScript `@grpc/grpc-js` 传输,不加载 Rust N-API;
13
+ * `connectTimeoutMs` 用于 waitForReady 与 unary deadline。
14
14
  */
15
- const yellowstone_grpc_1 = __importDefault(require("@triton-one/yellowstone-grpc"));
15
+ const native_client_js_1 = __importDefault(require("./native_client.js"));
16
16
  /** 公共字段与 Rust `GeyserConnectConfig::default` 一致,并包含 Node gRPC 通道默认值。 */
17
17
  function defaultGeyserConnectConfig() {
18
18
  return {
@@ -32,17 +32,15 @@ function geyserGrpcChannelOptions(config = defaultGeyserConnectConfig()) {
32
32
  const interval = config.keepAliveIntervalMs ?? 30_000;
33
33
  const timeout = config.keepAliveTimeoutMs ?? 5000;
34
34
  return {
35
- grpcConnectTimeout: config.connectTimeoutMs,
36
- grpcMaxDecodingMessageSize: max,
37
- grpcMaxEncodingMessageSize: max,
38
- grpcHttp2KeepAliveInterval: interval,
39
- grpcKeepAliveTimeout: timeout,
40
- grpcTcpKeepalive: interval,
41
- grpcInitialConnectionWindowSize: config.flowControlWindowBytes,
42
- grpcInitialStreamWindowSize: config.flowControlWindowBytes,
43
- grpcHttp2AdaptiveWindow: true,
44
- grpcKeepAliveWhileIdle: true,
45
- grpcTcpNodelay: true,
35
+ connectTimeoutMs: config.connectTimeoutMs,
36
+ "grpc.max_receive_message_length": max,
37
+ "grpc.max_send_message_length": max,
38
+ "grpc.keepalive_time_ms": interval,
39
+ "grpc.keepalive_timeout_ms": timeout,
40
+ "grpc.keepalive_permit_without_calls": 1,
41
+ "grpc.initial_reconnect_backoff_ms": config.initialReconnectBackoffMs ?? 1000,
42
+ "grpc.max_reconnect_backoff_ms": config.maxReconnectBackoffMs ?? 60000,
43
+ "grpc-node.flow_control_window": config.flowControlWindowBytes ?? 1024 * 1024,
46
44
  };
47
45
  }
48
46
  /**
@@ -50,7 +48,7 @@ function geyserGrpcChannelOptions(config = defaultGeyserConnectConfig()) {
50
48
  * Rust 为 async;此处构造同步完成,返回 `Promise` 以保持 `await connectYellowstoneGeyser(...)` 写法。
51
49
  */
52
50
  async function connectYellowstoneGeyser(endpoint, config = defaultGeyserConnectConfig()) {
53
- const client = new yellowstone_grpc_1.default(endpoint, config.xToken, geyserGrpcChannelOptions(config));
51
+ const client = new native_client_js_1.default(endpoint, config.xToken, geyserGrpcChannelOptions(config));
54
52
  await client.connect();
55
53
  return client;
56
54
  }
@@ -0,0 +1,26 @@
1
+ /** Pure JavaScript Yellowstone transport. Exact protobuf integers are decimal strings. */
2
+ import * as grpc from "@grpc/grpc-js";
3
+ import type { CommitmentLevel, SubscribeRequest, SubscribeUpdate } from "./protocol/geyser.js";
4
+ export interface NativeChannelOptions extends grpc.ChannelOptions {
5
+ connectTimeoutMs?: number;
6
+ }
7
+ export declare const geyserService: grpc.ServiceDefinition<grpc.UntypedServiceImplementation>;
8
+ export default class NativeYellowstoneClient {
9
+ private readonly options;
10
+ private raw?;
11
+ private readonly address;
12
+ private readonly credentials;
13
+ private readonly metadata;
14
+ constructor(endpoint: string, token?: string, options?: NativeChannelOptions);
15
+ private client;
16
+ connect(): Promise<void>;
17
+ close(): void;
18
+ subscribe(): Promise<grpc.ClientDuplexStream<SubscribeRequest, SubscribeUpdate>>;
19
+ private unary;
20
+ getLatestBlockhash(commitment?: CommitmentLevel): Promise<any>;
21
+ getBlockHeight(commitment?: CommitmentLevel): Promise<any>;
22
+ getSlot(commitment?: CommitmentLevel): Promise<any>;
23
+ getVersion(): Promise<any>;
24
+ isBlockhashValid(blockhash: string, commitment?: CommitmentLevel): Promise<any>;
25
+ ping(count: number): Promise<number>;
26
+ }