sol-parser-sdk 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,7 +75,7 @@
75
75
  **From npm**
76
76
 
77
77
  ```bash
78
- npm install sol-parser-sdk@0.5.11
78
+ npm install sol-parser-sdk@0.5.13
79
79
  ```
80
80
 
81
81
  **From source** (folder may be named `sol-parser-sdk-ts` in a monorepo)
@@ -150,9 +150,10 @@ console.log("subscribed", sub.id);
150
150
  ### Long-running low-latency DEX subscriptions
151
151
 
152
152
  The public API remains queue-based, matching the Rust SDK's `subscribe_dex_events` shape. The Node
153
- subscription is an async iterable instead of Rust's `ArrayQueue`, while ring-buffer dequeue,
154
- Yellowstone update parsing, HTTP/2 receive-window tuning, and reconnect behavior are handled inside
155
- the SDK:
153
+ subscription is an async iterable instead of Rust's `ArrayQueue`. Raw Yellowstone protobuf objects
154
+ are mapped directly into the parser without the former protobuf/WASM/JSON/Base58 transaction
155
+ round-trip. Ring-buffer dequeue, bounded pre-parser scheduling, HTTP/2 receive-window tuning, and
156
+ reconnect behavior are handled inside the SDK:
156
157
 
157
158
  ```typescript
158
159
  import { YellowstoneGrpc, lowLatencyClientConfig } from "sol-parser-sdk";
@@ -170,8 +171,59 @@ for await (const event of sub) {
170
171
  ```
171
172
 
172
173
  If using `for await`, the default overflow behavior remains `drop-newest` for compatibility. A
173
- tip-oriented consumer can choose `queueOverflowStrategy: "drop-oldest"` and monitor `sub.len()` and
174
- `sub.dropped()`. Every overflow is counted and periodically reported through `sub.errors`.
174
+ tip-oriented consumer can choose `queueOverflowStrategy: "drop-oldest"`. Monitor the public event
175
+ queue with `sub.len()` / `sub.eventDropped()` and the pre-parser queue with
176
+ `sub.ingressLen()` / `sub.ingressDropped()`. `sub.dropped()` is the combined count. Every overflow
177
+ is counted and periodically reported through `sub.errors`.
178
+
179
+ Automatic reconnect resumes from the last successfully parsed slot by default and deduplicates
180
+ replayed transaction/account updates. Monitor transport continuity with `sub.isStreamConnected()`,
181
+ `sub.streamDisconnects()`, `sub.reconnects()`, `sub.replayedUpdates()`, and
182
+ `sub.continuityBreaks()`. A zero local drop count does not describe updates lost before the gRPC
183
+ `data` callback. If a provider rejects Yellowstone `fromSlot`, the SDK reports the failure through
184
+ `sub.errors`, increments `continuityBreaks()`, and reconnects live. Set `replayOnReconnect: false`
185
+ only when duplicate-free live delivery is more important than recovering a disconnect window.
186
+
187
+ For precise local-only measurements, set `config.enable_metrics = true`. Event metadata then includes
188
+ `local_queue_latency_us`, `parse_duration_us`, and `local_processing_latency_us`. These values use
189
+ Node's monotonic high-resolution clock. They measure only local queueing and parsing; the hot path
190
+ does not call `getSlot`, `getTransaction`, JSON-RPC, or any unary RPC method.
191
+
192
+ `local_processing_latency_us` is the end-to-end local metric: it starts at entry to Yellowstone's
193
+ gRPC `data` callback and ends immediately after complete event parsing and field filling.
194
+
195
+ Full-volume PumpFun and PumpSwap can use the same Rust-aligned program filter. Yellowstone applies
196
+ `account_include` as ANY, so this receives every transaction containing either supported program:
197
+
198
+ ```typescript
199
+ const allPumpTransactions = transactionFilterForProtocols(["PumpFun", "PumpSwap"]);
200
+ const sub = await client.subscribeDexEvents([allPumpTransactions], [], eventTypeFilter);
201
+ ```
202
+
203
+ `eventTypeFilter` avoids unnecessary event decoding, but it does not reduce traffic sent by
204
+ Yellowstone. When an application intentionally needs only selected mints, pools, or users, the
205
+ optional scoped filter requires the protocol program and any one tracked account:
206
+
207
+ ```typescript
208
+ const pumpSwap = transactionFilterForProtocolAccounts("PumpSwap", knownMintsAndPools);
209
+ const sub = await client.subscribeDexEvents(
210
+ [transactionFilterForProtocols(["PumpFun"]), pumpSwap],
211
+ [],
212
+ eventTypeFilter
213
+ );
214
+ ```
215
+
216
+ The combined example uses full-volume PumpSwap by default. Set `PUMPSWAP_FILTER_MODE=scoped` only when
217
+ scoped monitoring is desired; in that mode `updateSubscription()` refreshes the active account
218
+ filters. `PumpSwapCreatePool` maps a known base or quote mint to its pool, and
219
+ `PumpSwapLiquidityAdded` can then be compared with the developer associated with that mint.
220
+
221
+ Two `YellowstoneGrpc` instances in one Node process still share one JavaScript event loop. The direct
222
+ protobuf path supports full PumpFun + PumpSwap in one subscription; use a separate OS process or
223
+ Worker Thread only when application callbacks, database work, or additional protocols saturate that
224
+ event loop. A separate gRPC connection alone does not provide CPU isolation. Send database work to a
225
+ bounded handoff queue and batch asynchronous SQLite WAL writes; do not await one write per event in
226
+ the subscription loop.
175
227
 
176
228
  No bounded in-memory stream can guarantee both zero loss and bounded latency when the consumer's
177
229
  average processing rate is below the filtered input rate. Keep the loop body CPU-light, avoid
@@ -205,6 +257,7 @@ From the **package root** after `npm install`. Examples use `npx tsx` and load `
205
257
  | Debug: print `metaRaw` / log structure | `npx tsx scripts/debug-grpc-ts.ts` | [debug-grpc-ts.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/scripts/debug-grpc-ts.ts) |
206
258
  | **PumpFun** | | |
207
259
  | CREATE + dev BUY/SELL, long-running low latency | `npx tsx examples/devtrades_low_latency.ts` | [devtrades_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/devtrades_low_latency.ts) |
260
+ | PumpFun + full-volume PumpSwap dev trades/liquidity | `npm run example:grpc:devtrades:pumpswap` | [devtrades_pumpfun_pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/devtrades_pumpfun_pumpswap_low_latency.ts) |
208
261
  | Pretty-print full JSON `DexEvent` over gRPC | `npx tsx examples/pumpfun_grpc_json.ts` | [pumpfun_grpc_json.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_grpc_json.ts) |
209
262
  | PumpFun events + metrics | `npx tsx examples/pumpfun_with_metrics.ts` | [pumpfun_with_metrics.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_with_metrics.ts) |
210
263
  | PumpFun trade filter | `npx tsx examples/pumpfun_trade_filter.ts` | [pumpfun_trade_filter.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_trade_filter.ts) |
@@ -212,7 +265,7 @@ From the **package root** after `npm install`. Examples use `npx tsx` and load `
212
265
  | **PumpSwap** | | |
213
266
  | Pretty-print full JSON `DexEvent` over gRPC | `npx tsx examples/pumpswap_grpc_json.ts` | [pumpswap_grpc_json.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_grpc_json.ts) |
214
267
  | PumpSwap events + metrics | `npx tsx examples/pumpswap_with_metrics.ts` | [pumpswap_with_metrics.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_with_metrics.ts) |
215
- | PumpSwap ultra-low latency | `npx tsx examples/pumpswap_low_latency.ts` | [pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_low_latency.ts) |
268
+ | PumpSwap swaps, pool creation, and liquidity events (ultra-low latency) | `npm run example:grpc:pumpswap` | [pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_low_latency.ts) |
216
269
  | **Meteora DAMM** | | |
217
270
  | Meteora DAMM V2 events | `npx tsx examples/meteora_damm_grpc.ts` | [meteora_damm_grpc.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/meteora_damm_grpc.ts) |
218
271
  | **ShredStream** (HTTP, not Yellowstone gRPC; see **step 5** above) | | |
package/README_CN.md CHANGED
@@ -75,7 +75,7 @@
75
75
  **npm**
76
76
 
77
77
  ```bash
78
- npm install sol-parser-sdk@0.5.11
78
+ npm install sol-parser-sdk@0.5.13
79
79
  ```
80
80
 
81
81
  **源码**(monorepo 里目录可能是 `sol-parser-sdk-ts`)
@@ -150,7 +150,8 @@ console.log("subscribed", sub.id);
150
150
  ### 长时间运行的低延迟 DEX 订阅
151
151
 
152
152
  公开 API 保持队列式调用,与 Rust SDK 的 `subscribe_dex_events` 形态一致。Node 使用异步
153
- 迭代器代替 Rust 的 `ArrayQueue`;环形队列出队、Yellowstone update 解析、HTTP/2 接收窗口
153
+ 迭代器代替 Rust 的 `ArrayQueue`。Yellowstone 原始 protobuf 对象会直接映射给解析器,不再经过
154
+ 旧的 protobuf/WASM/JSON/Base58 整笔交易往返;环形队列出队、有界解析前调度、HTTP/2 接收窗口
154
155
  和重连优化全部由 SDK 内部完成:
155
156
 
156
157
  ```typescript
@@ -169,8 +170,54 @@ for await (const event of sub) {
169
170
  ```
170
171
 
171
172
  若使用 `for await`,默认溢出策略仍是 `drop-newest`,以保持兼容。更重视贴近链尖的消费者
172
- 可以设置 `queueOverflowStrategy: "drop-oldest"`,并监控 `sub.len()` 和 `sub.dropped()`。
173
- 每次溢出都会计数,并定期通过 `sub.errors` 报告。
173
+ 可以设置 `queueOverflowStrategy: "drop-oldest"`。通过 `sub.len()` / `sub.eventDropped()` 监控
174
+ 公开事件队列,通过 `sub.ingressLen()` / `sub.ingressDropped()` 监控解析前入口队列;
175
+ `sub.dropped()` 返回两者之和。每次溢出都会计数,并定期通过 `sub.errors` 报告。
176
+
177
+ 自动重连默认从最后一个成功解析的 slot 继续订阅,并对回放的 transaction/account update
178
+ 去重。可通过 `sub.isStreamConnected()`、`sub.streamDisconnects()`、`sub.reconnects()`、
179
+ `sub.replayedUpdates()` 和 `sub.continuityBreaks()` 监控传输连续性。本地 drop 为零并不能说明
180
+ 进入 gRPC `data` 回调之前没有丢数据。如果 provider 拒绝 Yellowstone `fromSlot`,SDK 会通过
181
+ `sub.errors` 明确报告,增加 `continuityBreaks()`,再降级为 live 重连。只有在“绝不接收回放重复”
182
+ 比“补偿断线窗口”更重要时,才应设置 `replayOnReconnect: false`。
183
+
184
+ 如需精确测量纯本地延迟,请设置 `config.enable_metrics = true`。事件 metadata 将包含
185
+ `local_queue_latency_us`、`parse_duration_us` 和 `local_processing_latency_us`,这些字段使用
186
+ Node 单调高精度时钟,只测量本地排队与解析。热路径不会调用 `getSlot`、`getTransaction`、
187
+ JSON-RPC 或任何 unary RPC 方法。
188
+
189
+ `local_processing_latency_us` 是本地端到端指标:从进入 Yellowstone gRPC `data` 回调开始,
190
+ 到完整事件解析和字段填充结束为止。
191
+
192
+ PumpFun 与 PumpSwap 全量交易可以直接使用和 Rust 对齐的 Program ID 过滤器。Yellowstone 的
193
+ `account_include` 是 ANY 语义,因此下面会接收包含任一协议程序的全部交易:
194
+
195
+ ```typescript
196
+ const allPumpTransactions = transactionFilterForProtocols(["PumpFun", "PumpSwap"]);
197
+ const sub = await client.subscribeDexEvents([allPumpTransactions], [], eventTypeFilter);
198
+ ```
199
+
200
+ `eventTypeFilter` 可以减少无关事件解码,但不会减少 Yellowstone 发来的交易。只有应用明确只想
201
+ 监控部分 mint、pool 或 user 时,才需要下面的可选限定过滤器;它要求交易同时包含协议程序,
202
+ 并包含任意一个已跟踪账户:
203
+
204
+ ```typescript
205
+ const pumpSwap = transactionFilterForProtocolAccounts("PumpSwap", knownMintsAndPools);
206
+ const sub = await client.subscribeDexEvents(
207
+ [transactionFilterForProtocols(["PumpFun"]), pumpSwap],
208
+ [],
209
+ eventTypeFilter
210
+ );
211
+ ```
212
+
213
+ 组合示例默认订阅全量 PumpSwap。只有设置 `PUMPSWAP_FILTER_MODE=scoped` 时才按账户限定;
214
+ 该模式会通过 `updateSubscription()` 刷新活跃账户过滤器。`PumpSwapCreatePool` 可把已知 base
215
+ mint 或 quote mint 映射到 pool,再将 `PumpSwapLiquidityAdded.user` 与该 mint 的开发者比较。
216
+
217
+ 同一 Node 进程里的两个 `YellowstoneGrpc` 实例仍共享一个 JavaScript 事件循环。直接 protobuf
218
+ 路径支持在一条订阅中接收全量 PumpFun + PumpSwap;只有业务回调、数据库写入或更多协议使事件
219
+ 循环饱和时,才需要独立进程或 Worker Thread。仅拆成两条 gRPC 连接不能隔离 CPU。数据库写入
220
+ 应进入有界交接队列并异步批量写 SQLite WAL,不要在订阅循环中逐事件等待写入。
174
221
 
175
222
  当消费者平均处理速度低于过滤后的入流速度时,任何有界内存流都无法同时保证零丢失和
176
223
  有界延迟。消费循环应保持轻量,避免逐事件同步日志,在服务端尽量缩小过滤范围,并把
@@ -203,6 +250,7 @@ npx tsx examples/shredstream_example.ts -- --url=http://127.0.0.1:10800
203
250
  | 调试:打印 `metaRaw` / 日志结构 | `npx tsx scripts/debug-grpc-ts.ts` | [debug-grpc-ts.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/scripts/debug-grpc-ts.ts) |
204
251
  | **PumpFun** | | |
205
252
  | CREATE + dev BUY/SELL 长时间低延迟订阅 | `npx tsx examples/devtrades_low_latency.ts` | [devtrades_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/devtrades_low_latency.ts) |
253
+ | PumpFun + 全量 PumpSwap 开发者交易/加池 | `npm run example:grpc:devtrades:pumpswap` | [devtrades_pumpfun_pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/devtrades_pumpfun_pumpswap_low_latency.ts) |
206
254
  | gRPC 输出完整 JSON `DexEvent` | `npx tsx examples/pumpfun_grpc_json.ts` | [pumpfun_grpc_json.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_grpc_json.ts) |
207
255
  | PumpFun 事件 + 性能指标 | `npx tsx examples/pumpfun_with_metrics.ts` | [pumpfun_with_metrics.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_with_metrics.ts) |
208
256
  | PumpFun 交易类型过滤 | `npx tsx examples/pumpfun_trade_filter.ts` | [pumpfun_trade_filter.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpfun_trade_filter.ts) |
@@ -210,7 +258,7 @@ npx tsx examples/shredstream_example.ts -- --url=http://127.0.0.1:10800
210
258
  | **PumpSwap** | | |
211
259
  | gRPC 输出完整 JSON `DexEvent` | `npx tsx examples/pumpswap_grpc_json.ts` | [pumpswap_grpc_json.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_grpc_json.ts) |
212
260
  | PumpSwap 事件 + 性能指标 | `npx tsx examples/pumpswap_with_metrics.ts` | [pumpswap_with_metrics.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_with_metrics.ts) |
213
- | PumpSwap 超低延迟 | `npx tsx examples/pumpswap_low_latency.ts` | [pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_low_latency.ts) |
261
+ | PumpSwap 交易、创建池及流动性事件(超低延迟) | `npm run example:grpc:pumpswap` | [pumpswap_low_latency.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/pumpswap_low_latency.ts) |
214
262
  | **Meteora DAMM** | | |
215
263
  | Meteora DAMM V2 事件 | `npx tsx examples/meteora_damm_grpc.ts` | [meteora_damm_grpc.ts](https://github.com/0xfnzero/sol-parser-sdk-nodejs/blob/main/examples/meteora_damm_grpc.ts) |
216
264
  | **ShredStream**(HTTP,**非** Yellowstone gRPC;端点见上文步骤 5) | | |
@@ -6,6 +6,12 @@ export interface EventMetadata {
6
6
  tx_index: number;
7
7
  block_time_us: number;
8
8
  grpc_recv_us: number;
9
+ /** Time waiting in the local pre-parser queue, measured with a monotonic clock. */
10
+ local_queue_latency_us?: number;
11
+ /** Pure local adapter + parser duration. No network or RPC calls are included. */
12
+ parse_duration_us?: number;
13
+ /** Start of the local gRPC data callback through parser completion. No RPC calls are included. */
14
+ local_processing_latency_us?: number;
9
15
  recent_blockhash?: string;
10
16
  }
11
17
  export declare function makeMetadata(signature: string, slot: number, txIndex: number, blockTimeUs: number | undefined, grpcRecvUs: number, recentBlockhash?: string): EventMetadata;
@@ -19,6 +19,7 @@ exports.getInstructionDataBytes = getInstructionDataBytes;
19
19
  */
20
20
  const bs58_1 = __importDefault(require("bs58"));
21
21
  const web3_js_1 = require("@solana/web3.js");
22
+ const accountKeyBase58Cache = new WeakMap();
22
23
  /**
23
24
  * 将账户表项转为 Base58(与 `PublicKey.toBase58()` 一致)。
24
25
  * 兼容:已是地址字符串、标准 `PublicKey`、以及多份 `@solana/web3.js` 副本导致 `instanceof` 失效的情况。
@@ -37,7 +38,13 @@ function accountKeyToBase58(account) {
37
38
  const maybeFn = account.toBase58;
38
39
  if (typeof maybeFn === "function") {
39
40
  try {
40
- return maybeFn.call(account);
41
+ const object = account;
42
+ const cached = accountKeyBase58Cache.get(object);
43
+ if (cached !== undefined)
44
+ return cached;
45
+ const encoded = maybeFn.call(account);
46
+ accountKeyBase58Cache.set(object, encoded);
47
+ return encoded;
41
48
  }
42
49
  catch {
43
50
  /* fall through */
@@ -13,6 +13,9 @@ export declare class AsyncEventQueue<T> implements AsyncIterable<T>, AsyncIterat
13
13
  push(item: T): boolean;
14
14
  len(): number;
15
15
  dropped(): number;
16
+ /** Synchronous O(1) dequeue for internal scheduled consumers. */
17
+ shift(): T | undefined;
18
+ clear(): void;
16
19
  close(): void;
17
20
  next(): Promise<IteratorResult<T>>;
18
21
  [Symbol.asyncIterator](): AsyncIterator<T>;
@@ -45,6 +45,20 @@ class AsyncEventQueue {
45
45
  dropped() {
46
46
  return this.droppedCount;
47
47
  }
48
+ /** Synchronous O(1) dequeue for internal scheduled consumers. */
49
+ shift() {
50
+ if (this.size === 0)
51
+ return undefined;
52
+ const item = this.items[this.head];
53
+ this.items[this.head] = undefined;
54
+ this.head = (this.head + 1) % this.maxSize;
55
+ this.size--;
56
+ return item;
57
+ }
58
+ clear() {
59
+ while (this.size > 0)
60
+ this.shift();
61
+ }
48
62
  close() {
49
63
  if (this.closed)
50
64
  return;
@@ -55,11 +69,7 @@ class AsyncEventQueue {
55
69
  }
56
70
  next() {
57
71
  if (this.size > 0) {
58
- const item = this.items[this.head];
59
- this.items[this.head] = undefined;
60
- this.head = (this.head + 1) % this.maxSize;
61
- this.size--;
62
- return Promise.resolve({ value: item, done: false });
72
+ return Promise.resolve({ value: this.shift(), done: false });
63
73
  }
64
74
  if (this.closed) {
65
75
  return Promise.resolve({ value: undefined, done: true });
@@ -2,7 +2,7 @@
2
2
  * Yellowstone gRPC 客户端实现 - 基于 @triton-one/yellowstone-grpc
3
3
  */
4
4
  import { CommitmentLevel } from "@triton-one/yellowstone-grpc";
5
- import type { DexEvent } from "../core/dex_event.js";
5
+ import { type DexEvent } from "../core/dex_event.js";
6
6
  import { type EventQueueOverflowStrategy } from "./async_event_queue.js";
7
7
  import { type AccountFilter, type ClientConfig, type EventTypeFilter, type TransactionFilter, type SubscribeCallbacks } from "./types.js";
8
8
  export { SubscribeCallbacks };
@@ -13,15 +13,33 @@ export interface DexEventSubscription extends AsyncIterable<DexEvent> {
13
13
  cancel(): void;
14
14
  next(): Promise<IteratorResult<DexEvent>>;
15
15
  len(): number;
16
+ /** Total drops across ingress and the public event queue. */
16
17
  dropped(): number;
18
+ eventDropped(): number;
19
+ ingressLen(): number;
20
+ ingressDropped(): number;
21
+ isStreamConnected(): boolean;
22
+ streamDisconnects(): number;
23
+ reconnects(): number;
24
+ replayedUpdates(): number;
25
+ /** Reconnects that could not resume from the last parsed slot. */
26
+ continuityBreaks(): number;
17
27
  }
18
28
  export interface SubscribeDexEventsOptions {
19
29
  autoReconnect?: boolean;
20
30
  /**
21
- * Queue behavior when an async-iterator consumer cannot keep up. The default
22
- * preserves the existing behavior; `drop-oldest` favors staying near chain tip.
31
+ * Queue behavior when parsing or the async-iterator consumer cannot keep up.
32
+ * The default preserves existing behavior; `drop-oldest` favors chain-tip latency.
23
33
  */
24
34
  queueOverflowStrategy?: EventQueueOverflowStrategy;
35
+ /** Raw Yellowstone updates retained before CPU-heavy parsing. Defaults to min(buffer_size, 1024). */
36
+ ingressBufferSize?: number;
37
+ /** Maximum raw updates parsed before yielding back to Node's event loop. Defaults to 8. */
38
+ parserBatchSize?: number;
39
+ /** Resume from the last parsed slot after reconnect. Defaults to true. */
40
+ replayOnReconnect?: boolean;
41
+ /** Parsed slots retained for replay deduplication. Defaults to 128. */
42
+ replayDedupeSlots?: number;
25
43
  }
26
44
  /** Yellowstone gRPC 客户端包装器 */
27
45
  export declare class YellowstoneGrpc {
@@ -42,6 +42,8 @@ exports.YellowstoneGrpc = void 0;
42
42
  */
43
43
  const yellowstone_grpc_1 = __importStar(require("@triton-one/yellowstone-grpc"));
44
44
  const bs58_1 = __importDefault(require("bs58"));
45
+ const node_buffer_1 = require("node:buffer");
46
+ const dex_event_js_1 = require("../core/dex_event.js");
45
47
  const metadata_js_1 = require("../core/metadata.js");
46
48
  const mod_js_1 = require("../accounts/mod.js");
47
49
  const geyser_connect_js_1 = require("./geyser_connect.js");
@@ -58,6 +60,42 @@ function cancelSubscribeStream(stream) {
58
60
  stream.once("error", () => { });
59
61
  stream.cancel();
60
62
  }
63
+ function positiveIntegerOption(value, fallback, name) {
64
+ const normalized = value ?? fallback;
65
+ if (!Number.isInteger(normalized) || normalized < 1) {
66
+ throw new RangeError(`${name} must be a positive integer`);
67
+ }
68
+ return normalized;
69
+ }
70
+ function replayUnsupported(error) {
71
+ const grpcError = error;
72
+ if (grpcError.code === 3 || grpcError.code === 11 || grpcError.code === 12)
73
+ return true;
74
+ return /from.?slot.*(?:unsupported|disabled|unavailable|invalid)/i.test(`${grpcError.message ?? ""} ${grpcError.details ?? ""}`);
75
+ }
76
+ function updateSlot(update) {
77
+ const value = update.transaction?.slot ?? update.account?.slot;
78
+ if (value === undefined || value === null)
79
+ return undefined;
80
+ try {
81
+ return BigInt(value);
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ function bytesKey(value) {
88
+ return node_buffer_1.Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("base64");
89
+ }
90
+ function updateIdentity(update) {
91
+ const transaction = update.transaction?.transaction;
92
+ if (transaction?.signature)
93
+ return `t:${bytesKey(transaction.signature)}`;
94
+ const account = update.account?.account;
95
+ if (!account?.pubkey)
96
+ return undefined;
97
+ return `a:${bytesKey(account.pubkey)}:${String(update.account?.slot ?? 0)}:${String(account.writeVersion ?? 0)}`;
98
+ }
61
99
  /** Yellowstone gRPC 客户端包装器 */
62
100
  class YellowstoneGrpc {
63
101
  client;
@@ -430,18 +468,35 @@ class YellowstoneGrpc {
430
468
  await this.connect();
431
469
  const id = `dex_${Date.now()}_${Math.random().toString(36).slice(2)}`;
432
470
  const events = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size), options?.queueOverflowStrategy);
471
+ const ingressBufferSize = positiveIntegerOption(options?.ingressBufferSize, Math.min(this.config.buffer_size, 1024), "ingressBufferSize");
472
+ const parserBatchSize = positiveIntegerOption(options?.parserBatchSize, 8, "parserBatchSize");
473
+ const replayDedupeSlots = positiveIntegerOption(options?.replayDedupeSlots, 128, "replayDedupeSlots");
474
+ const replayOnReconnect = options?.replayOnReconnect !== false;
475
+ const ingress = new async_event_queue_js_1.AsyncEventQueue(ingressBufferSize, options?.queueOverflowStrategy);
433
476
  const errors = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size));
434
477
  const order = new order_buffer_js_1.OrderDispatcher(this.config);
435
- let nextDropWarningAt = 0;
478
+ const eventDropped = events.dropped.bind(events);
479
+ let nextEventDropWarningAt = 0;
480
+ let nextIngressDropWarningAt = 0;
481
+ let streamConnected = false;
482
+ let streamDisconnectCount = 0;
483
+ let reconnectCount = 0;
484
+ let replayedUpdateCount = 0;
485
+ let continuityBreakCount = 0;
486
+ let lastParsedSlot;
487
+ let highestRememberedSlot = 0n;
488
+ let seenHead = 0;
489
+ const seenUpdates = new Map();
490
+ const seenOrder = [];
436
491
  const reportError = (error) => errors.push(error);
437
492
  const emitEvent = (event) => {
438
493
  if (events.push(event))
439
494
  return;
440
495
  const now = Date.now();
441
- if (now < nextDropWarningAt)
496
+ if (now < nextEventDropWarningAt)
442
497
  return;
443
- nextDropWarningAt = now + 10_000;
444
- reportError(new Error(`Dex event queue full; dropped=${events.dropped()} buffer_size=${this.config.buffer_size} ` +
498
+ nextEventDropWarningAt = now + 10_000;
499
+ reportError(new Error(`Dex event queue full; dropped=${eventDropped()} buffer_size=${this.config.buffer_size} ` +
445
500
  `strategy=${options?.queueOverflowStrategy ?? "drop-newest"}`));
446
501
  };
447
502
  let flushTimer;
@@ -452,6 +507,133 @@ class YellowstoneGrpc {
452
507
  flushTimer = setInterval(() => order.flushDue(emitEvent), intervalMs);
453
508
  }
454
509
  let isCancelled = false;
510
+ let ingressDrainScheduled = false;
511
+ let ingressDrainHandle;
512
+ const ingressIdleWaiters = [];
513
+ const resolveIngressIdle = () => {
514
+ if (ingress.len() > 0 || ingressDrainScheduled)
515
+ return;
516
+ for (const resolve of ingressIdleWaiters.splice(0))
517
+ resolve();
518
+ };
519
+ const annotateLocalMetrics = (events, grpcRecvNs, parseStartedNs, parseFinishedNs) => {
520
+ if (grpcRecvNs === undefined || parseStartedNs === undefined || parseFinishedNs === undefined) {
521
+ return;
522
+ }
523
+ const queueUs = Number(parseStartedNs - grpcRecvNs) / 1_000;
524
+ const parseUs = Number(parseFinishedNs - parseStartedNs) / 1_000;
525
+ const processingUs = Number(parseFinishedNs - grpcRecvNs) / 1_000;
526
+ for (const event of events) {
527
+ const metadata = (0, dex_event_js_1.metadataForDexEvent)(event);
528
+ if (!metadata)
529
+ continue;
530
+ metadata.local_queue_latency_us = queueUs;
531
+ metadata.parse_duration_us = parseUs;
532
+ metadata.local_processing_latency_us = processingUs;
533
+ }
534
+ };
535
+ const rememberParsedUpdate = (identity, slot) => {
536
+ if (slot !== undefined && (lastParsedSlot === undefined || slot > lastParsedSlot)) {
537
+ lastParsedSlot = slot;
538
+ }
539
+ if (!replayOnReconnect || identity === undefined || slot === undefined)
540
+ return;
541
+ seenUpdates.set(identity, slot);
542
+ seenOrder.push({ identity, slot });
543
+ if (slot > highestRememberedSlot)
544
+ highestRememberedSlot = slot;
545
+ const retainFrom = highestRememberedSlot > BigInt(replayDedupeSlots)
546
+ ? highestRememberedSlot - BigInt(replayDedupeSlots)
547
+ : 0n;
548
+ while (seenHead < seenOrder.length && seenOrder[seenHead].slot < retainFrom) {
549
+ const stale = seenOrder[seenHead++];
550
+ if (seenUpdates.get(stale.identity) === stale.slot)
551
+ seenUpdates.delete(stale.identity);
552
+ }
553
+ if (seenHead >= 4096 && seenHead * 2 >= seenOrder.length) {
554
+ seenOrder.splice(0, seenHead);
555
+ seenHead = 0;
556
+ }
557
+ };
558
+ const processUpdate = ({ update, grpcRecvUs, grpcRecvNs, blockTimeUs }) => {
559
+ const identity = updateIdentity(update);
560
+ const slotForReplay = updateSlot(update);
561
+ if (replayOnReconnect && identity !== undefined && seenUpdates.has(identity)) {
562
+ replayedUpdateCount++;
563
+ rememberParsedUpdate(undefined, slotForReplay);
564
+ return;
565
+ }
566
+ if (order.needsTimer)
567
+ order.flushDue(emitEvent);
568
+ const txUpdate = update.transaction;
569
+ const tx = txUpdate?.transaction;
570
+ if (tx) {
571
+ const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
572
+ const slot = txUpdate?.slot ?? 0n;
573
+ const fallbackSlot = YellowstoneGrpc.protobufNumber(slot);
574
+ const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
575
+ const txInfo = {
576
+ signature: tx.signature,
577
+ isVote: tx.isVote,
578
+ transactionRaw: tx.transaction,
579
+ metaRaw: tx.meta,
580
+ index: (tx.index ?? 0n),
581
+ };
582
+ const txEvents = (0, yellowstone_parse_js_1.parseDexEventsFromGrpcTransactionInfo)(txInfo, slot, {
583
+ blockTimeUs,
584
+ grpcRecvUs,
585
+ eventTypeFilter,
586
+ });
587
+ const parseFinishedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
588
+ annotateLocalMetrics(txEvents, grpcRecvNs, parseStartedNs, parseFinishedNs);
589
+ order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
590
+ }
591
+ if (update.account) {
592
+ const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
593
+ const ev = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
594
+ const parseFinishedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
595
+ if (ev)
596
+ annotateLocalMetrics([ev], grpcRecvNs, parseStartedNs, parseFinishedNs);
597
+ if (ev)
598
+ emitEvent(ev);
599
+ }
600
+ rememberParsedUpdate(identity, slotForReplay);
601
+ };
602
+ const scheduleIngressDrain = () => {
603
+ if (isCancelled || ingressDrainScheduled || ingress.len() === 0)
604
+ return;
605
+ ingressDrainScheduled = true;
606
+ ingressDrainHandle = setImmediate(() => {
607
+ ingressDrainHandle = undefined;
608
+ ingressDrainScheduled = false;
609
+ if (isCancelled) {
610
+ resolveIngressIdle();
611
+ return;
612
+ }
613
+ let processed = 0;
614
+ while (processed < parserBatchSize) {
615
+ const pending = ingress.shift();
616
+ if (!pending)
617
+ break;
618
+ try {
619
+ processUpdate(pending);
620
+ }
621
+ catch (err) {
622
+ reportError(err instanceof Error ? err : new Error(String(err)));
623
+ }
624
+ processed++;
625
+ }
626
+ if (ingress.len() > 0)
627
+ scheduleIngressDrain();
628
+ else
629
+ resolveIngressIdle();
630
+ });
631
+ };
632
+ const waitForIngressIdle = () => {
633
+ if (ingress.len() === 0 && !ingressDrainScheduled)
634
+ return Promise.resolve();
635
+ return new Promise((resolve) => ingressIdleWaiters.push(resolve));
636
+ };
455
637
  let currentTransactionFilters = transactionFilters;
456
638
  let currentAccountFilters = accountFilters;
457
639
  const streamHolder = { current: null };
@@ -477,6 +659,13 @@ class YellowstoneGrpc {
477
659
  cancelReconnectDelay?.();
478
660
  cancelSubscribeStream(streamHolder.current);
479
661
  streamHolder.current = null;
662
+ if (ingressDrainHandle)
663
+ clearImmediate(ingressDrainHandle);
664
+ ingressDrainHandle = undefined;
665
+ ingressDrainScheduled = false;
666
+ ingress.clear();
667
+ ingress.close();
668
+ resolveIngressIdle();
480
669
  if (flushTimer)
481
670
  clearInterval(flushTimer);
482
671
  events.close();
@@ -502,25 +691,39 @@ class YellowstoneGrpc {
502
691
  const autoReconnect = options?.autoReconnect !== false;
503
692
  const maxBackoffMs = 60_000;
504
693
  let backoffMs = this.config.retry_delay_ms;
694
+ let replayAllowed = replayOnReconnect;
695
+ let attempt = 0;
505
696
  try {
506
697
  while (!isCancelled) {
698
+ let attemptedReplay = false;
699
+ let receivedData = false;
507
700
  try {
701
+ attempt++;
702
+ if (attempt > 1)
703
+ reconnectCount++;
508
704
  const stream = await this.client.subscribe();
509
705
  if (isCancelled) {
510
706
  cancelSubscribeStream(stream);
511
707
  break;
512
708
  }
513
709
  streamHolder.current = stream;
710
+ const request = (0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters);
711
+ if (attempt > 1 && replayAllowed && lastParsedSlot !== undefined) {
712
+ request.fromSlot = lastParsedSlot.toString();
713
+ attemptedReplay = true;
714
+ }
514
715
  await new Promise((resolve, reject) => {
515
- stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
716
+ stream.write(request, (err) => {
516
717
  if (err)
517
718
  reject(err);
518
719
  else
519
720
  resolve();
520
721
  });
521
722
  });
723
+ streamConnected = true;
522
724
  await new Promise((resolve, reject) => {
523
725
  const cleanup = () => {
726
+ streamConnected = false;
524
727
  stream.removeAllListeners();
525
728
  if (streamHolder.current === stream) {
526
729
  streamHolder.current = null;
@@ -529,9 +732,12 @@ class YellowstoneGrpc {
529
732
  stream.on("data", (update) => {
530
733
  if (isCancelled)
531
734
  return;
735
+ receivedData = true;
736
+ const grpcRecvNs = this.config.enable_metrics
737
+ ? process.hrtime.bigint()
738
+ : undefined;
739
+ const grpcRecvUs = Math.floor(Date.now() * 1000);
532
740
  backoffMs = this.config.retry_delay_ms;
533
- if (order.needsTimer)
534
- order.flushDue(emitEvent);
535
741
  if (update.ping) {
536
742
  stream.write(this.subscribePingPongRequest(), (werr) => {
537
743
  if (werr && !isCancelled) {
@@ -540,41 +746,33 @@ class YellowstoneGrpc {
540
746
  });
541
747
  return;
542
748
  }
543
- try {
544
- const grpcRecvUs = Math.floor(Date.now() * 1000);
545
- const blockTimeUs = YellowstoneGrpc.timestampToMicros(update.createdAt ?? update.created_at);
546
- const txUpdate = update.transaction;
547
- const tx = txUpdate?.transaction;
548
- if (tx) {
549
- const slot = txUpdate?.slot ?? 0n;
550
- const fallbackSlot = YellowstoneGrpc.protobufNumber(slot);
551
- const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
552
- const txInfo = {
553
- signature: tx.signature,
554
- isVote: tx.isVote,
555
- transactionRaw: tx.transaction,
556
- metaRaw: tx.meta,
557
- index: (tx.index ?? 0n),
558
- };
559
- const txEvents = (0, yellowstone_parse_js_1.parseDexEventsFromGrpcTransactionInfo)(txInfo, slot, { blockTimeUs, grpcRecvUs, eventTypeFilter });
560
- order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
561
- }
562
- if (update.account) {
563
- const ev = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
564
- if (ev)
565
- emitEvent(ev);
749
+ const accepted = ingress.push({
750
+ update,
751
+ grpcRecvUs,
752
+ grpcRecvNs,
753
+ blockTimeUs: YellowstoneGrpc.timestampToMicros(update.createdAt ?? update.created_at),
754
+ });
755
+ if (!accepted) {
756
+ const now = Date.now();
757
+ if (now >= nextIngressDropWarningAt) {
758
+ nextIngressDropWarningAt = now + 10_000;
759
+ reportError(new Error(`Yellowstone ingress queue full; dropped=${ingress.dropped()} ` +
760
+ `buffer_size=${ingressBufferSize} ` +
761
+ `strategy=${options?.queueOverflowStrategy ?? "drop-newest"}`));
566
762
  }
567
763
  }
568
- catch (err) {
569
- reportError(err instanceof Error ? err : new Error(String(err)));
570
- }
764
+ scheduleIngressDrain();
571
765
  });
572
766
  stream.on("error", (err) => {
767
+ streamDisconnectCount++;
573
768
  cleanup();
574
769
  reject(err);
575
770
  });
576
771
  stream.on("end", () => {
772
+ streamDisconnectCount++;
577
773
  cleanup();
774
+ if (!isCancelled)
775
+ reportError(new Error("Yellowstone stream ended; reconnecting"));
578
776
  resolve();
579
777
  });
580
778
  });
@@ -584,15 +782,38 @@ class YellowstoneGrpc {
584
782
  break;
585
783
  cancelSubscribeStream(streamHolder.current);
586
784
  streamHolder.current = null;
785
+ streamConnected = false;
587
786
  reportError(err instanceof Error ? err : new Error(String(err)));
588
- if (!autoReconnect)
787
+ await waitForIngressIdle();
788
+ if (!autoReconnect) {
789
+ if (lastParsedSlot !== undefined)
790
+ continuityBreakCount++;
589
791
  break;
792
+ }
793
+ let continuityBreakRecorded = false;
794
+ if (attemptedReplay && !receivedData && replayUnsupported(err)) {
795
+ replayAllowed = false;
796
+ continuityBreakCount++;
797
+ continuityBreakRecorded = true;
798
+ reportError(new Error("Yellowstone provider rejected fromSlot replay; reconnecting live and continuity is not guaranteed"));
799
+ }
800
+ if (!replayAllowed && lastParsedSlot !== undefined && !continuityBreakRecorded) {
801
+ continuityBreakCount++;
802
+ }
590
803
  await waitBeforeReconnect(backoffMs);
591
804
  backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
592
805
  continue;
593
806
  }
594
- if (isCancelled || !autoReconnect)
807
+ await waitForIngressIdle();
808
+ if (isCancelled)
809
+ break;
810
+ if (!autoReconnect) {
811
+ if (lastParsedSlot !== undefined)
812
+ continuityBreakCount++;
595
813
  break;
814
+ }
815
+ if (!replayAllowed && lastParsedSlot !== undefined)
816
+ continuityBreakCount++;
596
817
  await waitBeforeReconnect(backoffMs);
597
818
  backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
598
819
  }
@@ -600,10 +821,14 @@ class YellowstoneGrpc {
600
821
  finally {
601
822
  cancelSubscribeStream(streamHolder.current);
602
823
  streamHolder.current = null;
824
+ streamConnected = false;
603
825
  if (flushTimer)
604
826
  clearInterval(flushTimer);
605
- if (!isCancelled)
827
+ if (!isCancelled) {
828
+ await waitForIngressIdle();
606
829
  order.flushAll(emitEvent);
830
+ }
831
+ ingress.close();
607
832
  events.close();
608
833
  errors.close();
609
834
  this.dexSubscribers.delete(id);
@@ -612,6 +837,15 @@ class YellowstoneGrpc {
612
837
  return Object.assign(events, {
613
838
  id,
614
839
  errors,
840
+ dropped: () => eventDropped() + ingress.dropped(),
841
+ eventDropped,
842
+ ingressLen: () => ingress.len(),
843
+ ingressDropped: () => ingress.dropped(),
844
+ isStreamConnected: () => streamConnected,
845
+ streamDisconnects: () => streamDisconnectCount,
846
+ reconnects: () => reconnectCount,
847
+ replayedUpdates: () => replayedUpdateCount,
848
+ continuityBreaks: () => continuityBreakCount,
615
849
  cancel: () => {
616
850
  cancel();
617
851
  this.dexSubscribers.delete(id);
@@ -23,5 +23,13 @@ export declare const PROTOCOL_PROGRAM_IDS: Record<Protocol, readonly string[]>;
23
23
  export declare function getProgramIdsForProtocols(protocols: readonly Protocol[]): string[];
24
24
  /** 与 Rust `TransactionFilter::for_protocols` 一致 */
25
25
  export declare function transactionFilterForProtocols(protocols: readonly Protocol[]): TransactionFilter;
26
+ /**
27
+ * Server-side transaction filter for one protocol restricted to known accounts.
28
+ *
29
+ * Yellowstone applies `account_include` as ANY and `account_required` as ALL,
30
+ * so matching transactions must contain the protocol program and at least one
31
+ * tracked mint, pool, or other account.
32
+ */
33
+ export declare function transactionFilterForProtocolAccounts(protocol: Protocol, accounts: readonly string[]): TransactionFilter;
26
34
  /** 与 Rust `AccountFilter::for_protocols` 一致 */
27
35
  export declare function accountFilterForProtocols(protocols: readonly Protocol[]): AccountFilter;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROTOCOL_PROGRAM_IDS = exports.METEORA_DBC_PROGRAM_ID = exports.METEORA_DLMM_PROGRAM_ID = exports.METEORA_DAMM_V2_PROGRAM_ID = exports.METEORA_POOLS_PROGRAM_ID = exports.ORCA_WHIRLPOOL_PROGRAM_ID = exports.RAYDIUM_AMM_V4_PROGRAM_ID = exports.RAYDIUM_CLMM_PROGRAM_ID = exports.RAYDIUM_CPMM_PROGRAM_ID = exports.RAYDIUM_LAUNCHLAB_PROGRAM_ID = exports.PUMP_FEES_PROGRAM_ID = exports.PUMPSWAP_FEES_PROGRAM_ID = exports.PUMPSWAP_PROGRAM_ID = exports.PUMPFUN_PROGRAM_ID = void 0;
4
4
  exports.getProgramIdsForProtocols = getProgramIdsForProtocols;
5
5
  exports.transactionFilterForProtocols = transactionFilterForProtocols;
6
+ exports.transactionFilterForProtocolAccounts = transactionFilterForProtocolAccounts;
6
7
  exports.accountFilterForProtocols = accountFilterForProtocols;
7
8
  exports.PUMPFUN_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
8
9
  exports.PUMPSWAP_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
@@ -51,6 +52,26 @@ function transactionFilterForProtocols(protocols) {
51
52
  account_required: [],
52
53
  };
53
54
  }
55
+ /**
56
+ * Server-side transaction filter for one protocol restricted to known accounts.
57
+ *
58
+ * Yellowstone applies `account_include` as ANY and `account_required` as ALL,
59
+ * so matching transactions must contain the protocol program and at least one
60
+ * tracked mint, pool, or other account.
61
+ */
62
+ function transactionFilterForProtocolAccounts(protocol, accounts) {
63
+ const trackedAccounts = [
64
+ ...new Set(accounts.map((account) => account.trim()).filter((account) => account.length > 0)),
65
+ ].sort();
66
+ if (trackedAccounts.length === 0) {
67
+ throw new RangeError("At least one tracked account is required");
68
+ }
69
+ return {
70
+ account_include: trackedAccounts,
71
+ account_exclude: [],
72
+ account_required: [...exports.PROTOCOL_PROGRAM_IDS[protocol]],
73
+ };
74
+ }
54
75
  /** 与 Rust `AccountFilter::for_protocols` 一致 */
55
76
  function accountFilterForProtocols(protocols) {
56
77
  return {
@@ -5,15 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.grpcTxIndexFromInfo = grpcTxIndexFromInfo;
7
7
  exports.parseDexEventsFromGrpcTransactionInfo = parseDexEventsFromGrpcTransactionInfo;
8
- /**
9
- * Yellowstone gRPC 单笔交易:统一交易解析(指令 + 日志 + 与 Rust 相同的账户/数据填充)。
10
- * 依赖 `@triton-one/yellowstone-grpc` 的 `txEncode`(Binary)得到与 web3 兼容的 meta + 反序列化交易。
11
- */
12
- const yellowstone_grpc_1 = require("@triton-one/yellowstone-grpc");
13
- const yellowstone_grpc_solana_encoding_wasm_js_1 = require("@triton-one/yellowstone-grpc/dist/encoding/yellowstone_grpc_solana_encoding_wasm.js");
14
8
  const bs58_1 = __importDefault(require("bs58"));
15
- const web3_js_1 = require("@solana/web3.js");
16
9
  const rpc_transaction_js_1 = require("../rpc_transaction.js");
10
+ const yellowstone_transaction_adapter_js_1 = require("./yellowstone_transaction_adapter.js");
17
11
  /**
18
12
  * Yellowstone `SubscribeUpdateTransactionInfo.index` → `EventMetadata.tx_index`。
19
13
  * 与 Rust `sol-parser-sdk` gRPC 路径中 `let idx = info.index` 传入 `parse_logs(..., idx, ...)` 一致。
@@ -41,19 +35,14 @@ function parseDexEventsFromGrpcTransactionInfo(info, slot, options) {
41
35
  meta: mr,
42
36
  index: String(info.index),
43
37
  };
44
- const enc = yellowstone_grpc_1.txEncode.encode(y, yellowstone_grpc_solana_encoding_wasm_js_1.WasmUiTransactionEncoding.Binary, 0, false);
45
- const vt = web3_js_1.VersionedTransaction.deserialize(bs58_1.default.decode(enc.transaction));
46
- const meta = enc.meta;
47
38
  const slotNum = typeof slot === "bigint" ? Number(slot) : Number(slot);
48
39
  const signatureBase58 = bs58_1.default.encode(Uint8Array.from(info.signature));
49
40
  const txIndex = grpcTxIndexFromInfo(info);
50
41
  const blockTime = options?.blockTimeUs == null ? null : Math.floor(options.blockTimeUs / 1_000_000);
51
- const parsed = (0, rpc_transaction_js_1.parseRpcTransaction)({
52
- slot: slotNum,
53
- blockTime,
54
- meta,
55
- transaction: vt,
56
- }, signatureBase58, options?.eventTypeFilter, {
42
+ const response = (0, yellowstone_transaction_adapter_js_1.yellowstoneTransactionToWeb3)(y.transaction, y.meta, slotNum, blockTime);
43
+ // This reuses the parser for the web3-compatible in-memory shape. It does not
44
+ // perform an RPC request or any other network I/O.
45
+ const parsed = (0, rpc_transaction_js_1.parseRpcTransaction)(response, signatureBase58, options?.eventTypeFilter, {
57
46
  blockTimeUs: options?.blockTimeUs,
58
47
  grpcRecvUs: options?.grpcRecvUs,
59
48
  txIndex,
@@ -0,0 +1,4 @@
1
+ import { type VersionedTransactionResponse } from "@solana/web3.js";
2
+ import type { Transaction as YellowstoneTransaction, TransactionStatusMeta as YellowstoneMeta } from "@triton-one/yellowstone-grpc/dist/grpc/solana-storage.js";
3
+ /** Convert Yellowstone protobuf objects directly, without a WASM/Base58 serialization round trip. */
4
+ export declare function yellowstoneTransactionToWeb3(transaction: YellowstoneTransaction, meta: YellowstoneMeta, slot: number, blockTime: number | null): VersionedTransactionResponse;
@@ -0,0 +1,101 @@
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.yellowstoneTransactionToWeb3 = yellowstoneTransactionToWeb3;
7
+ const bs58_1 = __importDefault(require("bs58"));
8
+ const web3_js_1 = require("@solana/web3.js");
9
+ function publicKey(bytes) {
10
+ return new web3_js_1.PublicKey(bytes);
11
+ }
12
+ function messageHeader(message) {
13
+ const header = message.header;
14
+ if (!header)
15
+ throw new TypeError("Yellowstone transaction message is missing its header");
16
+ return {
17
+ numRequiredSignatures: header.numRequiredSignatures,
18
+ numReadonlySignedAccounts: header.numReadonlySignedAccounts,
19
+ numReadonlyUnsignedAccounts: header.numReadonlyUnsignedAccounts,
20
+ };
21
+ }
22
+ function legacyMessage(message) {
23
+ const instructions = message.instructions.map((instruction) => ({
24
+ programIdIndex: instruction.programIdIndex,
25
+ accounts: [...instruction.accounts],
26
+ data: bs58_1.default.encode(instruction.data),
27
+ }));
28
+ return new web3_js_1.Message({
29
+ header: messageHeader(message),
30
+ accountKeys: message.accountKeys.map(publicKey),
31
+ recentBlockhash: bs58_1.default.encode(message.recentBlockhash),
32
+ instructions,
33
+ });
34
+ }
35
+ function versionedMessage(message) {
36
+ const compiledInstructions = message.instructions.map((instruction) => ({
37
+ programIdIndex: instruction.programIdIndex,
38
+ accountKeyIndexes: [...instruction.accounts],
39
+ data: instruction.data,
40
+ }));
41
+ return new web3_js_1.MessageV0({
42
+ header: messageHeader(message),
43
+ staticAccountKeys: message.accountKeys.map(publicKey),
44
+ recentBlockhash: bs58_1.default.encode(message.recentBlockhash),
45
+ compiledInstructions,
46
+ addressTableLookups: message.addressTableLookups.map((lookup) => ({
47
+ accountKey: publicKey(lookup.accountKey),
48
+ writableIndexes: [...lookup.writableIndexes],
49
+ readonlyIndexes: [...lookup.readonlyIndexes],
50
+ })),
51
+ });
52
+ }
53
+ function transactionMeta(meta) {
54
+ return {
55
+ err: null,
56
+ fee: Number(meta.fee),
57
+ preBalances: meta.preBalances.map(Number),
58
+ postBalances: meta.postBalances.map(Number),
59
+ innerInstructions: meta.innerInstructionsNone
60
+ ? null
61
+ : meta.innerInstructions.map((group) => ({
62
+ index: group.index,
63
+ instructions: group.instructions.map((instruction) => ({
64
+ programIdIndex: instruction.programIdIndex,
65
+ accounts: [...instruction.accounts],
66
+ // The internal parser accepts bytes directly; keep Yellowstone's representation
67
+ // instead of paying for a Base58 encode followed by an immediate decode.
68
+ data: instruction.data,
69
+ ...(instruction.stackHeight !== undefined
70
+ ? { stackHeight: instruction.stackHeight }
71
+ : {}),
72
+ })),
73
+ })),
74
+ logMessages: meta.logMessagesNone ? null : meta.logMessages,
75
+ preTokenBalances: [],
76
+ postTokenBalances: [],
77
+ loadedAddresses: {
78
+ writable: meta.loadedWritableAddresses.map(publicKey),
79
+ readonly: meta.loadedReadonlyAddresses.map(publicKey),
80
+ },
81
+ ...(meta.computeUnitsConsumed !== undefined
82
+ ? { computeUnitsConsumed: Number(meta.computeUnitsConsumed) }
83
+ : {}),
84
+ };
85
+ }
86
+ /** Convert Yellowstone protobuf objects directly, without a WASM/Base58 serialization round trip. */
87
+ function yellowstoneTransactionToWeb3(transaction, meta, slot, blockTime) {
88
+ const message = transaction.message;
89
+ if (!message)
90
+ throw new TypeError("Yellowstone transaction is missing its message");
91
+ return {
92
+ slot,
93
+ blockTime,
94
+ meta: transactionMeta(meta),
95
+ transaction: {
96
+ message: message.versioned ? versionedMessage(message) : legacyMessage(message),
97
+ signatures: transaction.signatures.map((signature) => bs58_1.default.encode(signature)),
98
+ },
99
+ version: message.versioned ? 0 : "legacy",
100
+ };
101
+ }
package/dist/index.d.ts CHANGED
@@ -19,8 +19,8 @@ export { pubkeyBytesToBs58 as pubkey_bytes_to_bs58, collectAccountKeysBs58 as co
19
19
  export { type OrderMode, type Protocol, type ClientConfig, type StreamingConfig, type TransactionFilter, type AccountFilter, type AccountFilterData, type AccountFilterMemcmp, type SlotFilter, type EventType, type StreamingEventType, type EventTypeFilter, defaultClientConfig, lowLatencyClientConfig, highThroughputClientConfig, newTransactionFilter, transactionFilterFromProgramIds, newAccountFilter, newSlotFilter, slotFilterMinSlot, slotFilterMaxSlot, accountFilterFromProgramOwners, accountFilterMemcmp, eventTypeFilterIncludeOnly, eventTypeFilterExclude, eventTypeFilterIncludesPumpfun, eventTypeFilterIncludesPumpswap, eventTypeFilterIncludesMeteoraDammV2, eventTypeFilterIncludesMeteoraDbc, eventTypeFilterIncludesMeteoraPools, eventTypeFilterIncludesMeteoraDlmm, eventTypeFilterIncludesRaydiumClmm, eventTypeFilterIncludesRaydiumCpmm, eventTypeFilterIncludesRaydiumAmmV4, eventTypeFilterIncludesOrcaWhirlpool, eventTypeFilterIncludesRaydiumLaunchlab, eventTypeFilterIncludesPumpFees, eventTypeFilterAllowsInstructionParsing, ALL_EVENT_TYPES, } from "./grpc/types.js";
20
20
  export { ALL_EVENT_TYPES as all_event_types } from "./grpc/types.js";
21
21
  export type { StreamingConfig as streaming_config } from "./grpc/types.js";
22
- export { PROTOCOL_PROGRAM_IDS, PUMPFUN_PROGRAM_ID, PUMPSWAP_PROGRAM_ID, PUMPSWAP_FEES_PROGRAM_ID, PUMP_FEES_PROGRAM_ID, RAYDIUM_LAUNCHLAB_PROGRAM_ID, RAYDIUM_CPMM_PROGRAM_ID, RAYDIUM_CLMM_PROGRAM_ID, RAYDIUM_AMM_V4_PROGRAM_ID, ORCA_WHIRLPOOL_PROGRAM_ID, METEORA_POOLS_PROGRAM_ID, METEORA_DAMM_V2_PROGRAM_ID, METEORA_DLMM_PROGRAM_ID, METEORA_DBC_PROGRAM_ID, getProgramIdsForProtocols, transactionFilterForProtocols, accountFilterForProtocols, } from "./grpc/program_ids.js";
23
- export { PROTOCOL_PROGRAM_IDS as protocol_program_ids, getProgramIdsForProtocols as get_program_ids_for_protocols, transactionFilterForProtocols as transaction_filter_for_protocols, accountFilterForProtocols as account_filter_for_protocols, } from "./grpc/program_ids.js";
22
+ export { PROTOCOL_PROGRAM_IDS, PUMPFUN_PROGRAM_ID, PUMPSWAP_PROGRAM_ID, PUMPSWAP_FEES_PROGRAM_ID, PUMP_FEES_PROGRAM_ID, RAYDIUM_LAUNCHLAB_PROGRAM_ID, RAYDIUM_CPMM_PROGRAM_ID, RAYDIUM_CLMM_PROGRAM_ID, RAYDIUM_AMM_V4_PROGRAM_ID, ORCA_WHIRLPOOL_PROGRAM_ID, METEORA_POOLS_PROGRAM_ID, METEORA_DAMM_V2_PROGRAM_ID, METEORA_DLMM_PROGRAM_ID, METEORA_DBC_PROGRAM_ID, getProgramIdsForProtocols, transactionFilterForProtocols, transactionFilterForProtocolAccounts, accountFilterForProtocols, } from "./grpc/program_ids.js";
23
+ export { PROTOCOL_PROGRAM_IDS as protocol_program_ids, getProgramIdsForProtocols as get_program_ids_for_protocols, transactionFilterForProtocols as transaction_filter_for_protocols, transactionFilterForProtocolAccounts as transaction_filter_for_protocol_accounts, accountFilterForProtocols as account_filter_for_protocols, } from "./grpc/program_ids.js";
24
24
  /** Rust `grpc::types` 蛇形命名别名 */
25
25
  export { defaultClientConfig as default_client_config, lowLatencyClientConfig as low_latency_client_config, highThroughputClientConfig as high_throughput_client_config, newTransactionFilter as new_transaction_filter, transactionFilterFromProgramIds as transaction_filter_from_program_ids, newAccountFilter as new_account_filter, newSlotFilter as new_slot_filter, slotFilterMinSlot as slot_filter_min_slot, slotFilterMaxSlot as slot_filter_max_slot, accountFilterFromProgramOwners as account_filter_from_program_owners, accountFilterMemcmp as account_filter_memcmp, eventTypeFilterIncludeOnly as event_type_filter_include_only, eventTypeFilterExclude as event_type_filter_exclude, eventTypeFilterIncludeOnly as include_only, eventTypeFilterExclude as exclude_types, } from "./grpc/types.js";
26
26
  export { buildSubscribeRequest, buildSubscribeRequestWithCommitment, buildSubscribeTransactionFiltersNamed, CommitmentLevel, } from "./grpc/subscribe_builder.js";
package/dist/index.js CHANGED
@@ -35,9 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.heuristicSolCounterpartiesForWatchedKeys = exports.lamportBalanceDeltas = exports.collectAccountKeysBs58 = exports.pubkeyBytesToBs58 = exports.grpcTxIndexFromInfo = exports.parseDexEventsFromGrpcTransactionInfo = exports.convert_rpc_to_grpc = exports.convertRpcToGrpc = exports.applyAccountFillsToLogEvents = exports.fillDataRpc = exports.fillAccountsFromTransactionDataRpc = exports.parseRpcTransaction = exports.parseTransactionFromRpc = exports.isWarmedUp = exports.warmupParser = exports.build_account_pubkeys_with_cache = exports.buildAccountPubkeysWithCache = exports.AccountPubkeyCache = exports.normalizeBuySellFromInputMint = exports.normalizeBuySellFromTokenDelta = exports.vaultPriceFromBalances = exports.sqrtPriceX64ToPrice = exports.elapsedMicrosSince = exports.nowNanos = exports.nowMicros = exports.nowUs = exports.parse_log = exports.parseLog = exports.parseTransactionWithStreamingListener = exports.parseLogsStreaming = exports.parseTransactionEventsStreaming = exports.parseTransactionWithListener = exports.parseLogsOnly = exports.parseTransactionEvents = exports.enrichPumpfunSameTxPostMerge = exports.enrichPumpfunTradesFromCreateInstructions = exports.enrichCreateV2ObservedFeeRecipient = exports.enrichCreateV2FromCreateEvents = exports.formatParseError = exports.dexEventToJsonString = exports.bigintToJsonReplacer = exports.defaultPubkey = exports.metadataForDexEvent = exports.SLOW_PROCESSING_THRESHOLD_US = exports.DEFAULT_REQUEST_TIMEOUT = exports.DEFAULT_METRICS_WINDOW_SECONDS = exports.DEFAULT_METRICS_PRINT_INTERVAL_SECONDS = exports.DEFAULT_MAX_DECODING_MESSAGE_SIZE = exports.DEFAULT_CONNECT_TIMEOUT = exports.DEFAULT_CHANNEL_SIZE = void 0;
37
37
  exports.ORCA_WHIRLPOOL_PROGRAM_ID = exports.RAYDIUM_AMM_V4_PROGRAM_ID = exports.RAYDIUM_CLMM_PROGRAM_ID = exports.RAYDIUM_CPMM_PROGRAM_ID = exports.RAYDIUM_LAUNCHLAB_PROGRAM_ID = exports.PUMP_FEES_PROGRAM_ID = exports.PUMPSWAP_FEES_PROGRAM_ID = exports.PUMPSWAP_PROGRAM_ID = exports.PUMPFUN_PROGRAM_ID = exports.PROTOCOL_PROGRAM_IDS = exports.all_event_types = exports.ALL_EVENT_TYPES = exports.eventTypeFilterAllowsInstructionParsing = exports.eventTypeFilterIncludesPumpFees = exports.eventTypeFilterIncludesRaydiumLaunchlab = exports.eventTypeFilterIncludesOrcaWhirlpool = exports.eventTypeFilterIncludesRaydiumAmmV4 = exports.eventTypeFilterIncludesRaydiumCpmm = exports.eventTypeFilterIncludesRaydiumClmm = exports.eventTypeFilterIncludesMeteoraDlmm = exports.eventTypeFilterIncludesMeteoraPools = exports.eventTypeFilterIncludesMeteoraDbc = exports.eventTypeFilterIncludesMeteoraDammV2 = exports.eventTypeFilterIncludesPumpswap = exports.eventTypeFilterIncludesPumpfun = exports.eventTypeFilterExclude = exports.eventTypeFilterIncludeOnly = exports.accountFilterMemcmp = exports.accountFilterFromProgramOwners = exports.slotFilterMaxSlot = exports.slotFilterMinSlot = exports.newSlotFilter = exports.newAccountFilter = exports.transactionFilterFromProgramIds = exports.newTransactionFilter = exports.highThroughputClientConfig = exports.lowLatencyClientConfig = exports.defaultClientConfig = exports.try_yellowstone_signature = exports.collect_watch_transfer_counterparty_pairs = exports.spl_token_counterparty_by_owner = exports.token_balance_raw_amount = exports.heuristic_sol_counterparties_for_watched_keys = exports.lamport_balance_deltas = exports.collect_account_keys_bs58 = exports.pubkey_bytes_to_bs58 = exports.tryYellowstoneSignature = exports.collectWatchTransferCounterpartyPairs = exports.splTokenCounterpartyByOwner = exports.tokenBalanceRawAmount = void 0;
38
- exports.PROGRAM_LOG_DISC = exports.parseMeteoraDammLog = exports.lookup_discriminator = exports.lookupDiscriminator = exports.discriminator_to_protocol = exports.discriminatorToProtocol = exports.discriminator_to_name = exports.discriminatorToName = exports.parse_log_unified = exports.parse_log_optimized = exports.parse_meteora_dlmm_log = exports.parse_meteora_damm_log = exports.parseProgramCompleteInfo = exports.parseLogUnified = exports.parseLogOptimizedWithProgramId = exports.parseLogOptimized = exports.parseInvokeInfo = exports.build_subscribe_transaction_filters_named = exports.build_subscribe_request_with_commitment = exports.build_subscribe_request = exports.CommitmentLevel = exports.buildSubscribeTransactionFiltersNamed = exports.buildSubscribeRequestWithCommitment = exports.buildSubscribeRequest = exports.exclude_types = exports.include_only = exports.event_type_filter_exclude = exports.event_type_filter_include_only = exports.account_filter_memcmp = exports.account_filter_from_program_owners = exports.slot_filter_max_slot = exports.slot_filter_min_slot = exports.new_slot_filter = exports.new_account_filter = exports.transaction_filter_from_program_ids = exports.new_transaction_filter = exports.high_throughput_client_config = exports.low_latency_client_config = exports.default_client_config = exports.account_filter_for_protocols = exports.transaction_filter_for_protocols = exports.get_program_ids_for_protocols = exports.protocol_program_ids = exports.accountFilterForProtocols = exports.transactionFilterForProtocols = exports.getProgramIdsForProtocols = exports.METEORA_DBC_PROGRAM_ID = exports.METEORA_DLMM_PROGRAM_ID = exports.METEORA_DAMM_V2_PROGRAM_ID = exports.METEORA_POOLS_PROGRAM_ID = void 0;
39
- exports.ShredEventQueue = exports.ShredStreamClient = exports.geyser_grpc_channel_options = exports.default_geyser_connect_config = exports.connect_yellowstone_geyser = exports.event_parser = exports.geyserGrpcChannelOptions = exports.defaultGeyserConnectConfig = exports.connectYellowstoneGeyser = exports.YellowstoneGrpc = exports.programIds = exports.parse_pump_fees_instruction = exports.parse_meteora_pools_instruction = exports.parse_meteora_dlmm_instruction = exports.parse_meteora_damm_instruction = exports.parse_pumpswap_instruction = exports.parse_pumpfun_instruction = exports.parse_instruction_unified = exports.parsePumpFeesInstruction = exports.parseMeteoraPoolsInstruction = exports.parseMeteoraDlmmInstruction = exports.parseMeteoraDammInstruction = exports.parsePumpswapInstruction = exports.parsePumpfunInstruction = exports.parseInstructionUnified = exports.user_wallet_pubkey_for_onchain_account = exports.rpc_resolve_user_wallet_pubkey = exports.parse_pumpswap_pool = exports.parse_pumpswap_global_config = exports.parse_pumpfun_global = exports.parse_token_account = exports.parse_nonce_account = exports.parse_account_unified = exports.rpcResolveUserWalletPubkey = exports.userWalletPubkeyForOnchainAccount = exports.hasDiscriminator = exports.isPoolAccount = exports.isGlobalConfigAccount = exports.parsePumpswapAccount = exports.parsePumpswapPool = exports.parsePumpswapGlobalConfig = exports.isPumpfunGlobalAccount = exports.parsePumpfunAccount = exports.parsePumpfunGlobal = exports.parseTokenAccount = exports.isNonceAccount = exports.parseNonceAccount = exports.parseAccountUnified = exports.u64leDiscriminator = exports.PUMPSWAP_DISC = void 0;
40
- exports.high_throughput_shred_stream_config = exports.low_latency_shred_stream_config = exports.default_shred_stream_config = exports.highThroughputShredStreamConfig = exports.lowLatencyShredStreamConfig = exports.defaultShredStreamConfig = exports.wireBytesToShredWasmTx = exports.decodeShredstreamEntriesBincode = exports.decodeEntriesBincodeNested = exports.decodeEntriesBincodeFlat = exports.bincodeVecEntryCount = exports.loadAddressLookupTableAccounts = exports.fullAccountKeyStringsFromShredTx = exports.dexEventsFromShredWasmTxWithFullKeys = exports.dexEventsFromShredWasmTx = void 0;
38
+ exports.lookup_discriminator = exports.lookupDiscriminator = exports.discriminator_to_protocol = exports.discriminatorToProtocol = exports.discriminator_to_name = exports.discriminatorToName = exports.parse_log_unified = exports.parse_log_optimized = exports.parse_meteora_dlmm_log = exports.parse_meteora_damm_log = exports.parseProgramCompleteInfo = exports.parseLogUnified = exports.parseLogOptimizedWithProgramId = exports.parseLogOptimized = exports.parseInvokeInfo = exports.build_subscribe_transaction_filters_named = exports.build_subscribe_request_with_commitment = exports.build_subscribe_request = exports.CommitmentLevel = exports.buildSubscribeTransactionFiltersNamed = exports.buildSubscribeRequestWithCommitment = exports.buildSubscribeRequest = exports.exclude_types = exports.include_only = exports.event_type_filter_exclude = exports.event_type_filter_include_only = exports.account_filter_memcmp = exports.account_filter_from_program_owners = exports.slot_filter_max_slot = exports.slot_filter_min_slot = exports.new_slot_filter = exports.new_account_filter = exports.transaction_filter_from_program_ids = exports.new_transaction_filter = exports.high_throughput_client_config = exports.low_latency_client_config = exports.default_client_config = exports.account_filter_for_protocols = exports.transaction_filter_for_protocol_accounts = exports.transaction_filter_for_protocols = exports.get_program_ids_for_protocols = exports.protocol_program_ids = exports.accountFilterForProtocols = exports.transactionFilterForProtocolAccounts = exports.transactionFilterForProtocols = exports.getProgramIdsForProtocols = exports.METEORA_DBC_PROGRAM_ID = exports.METEORA_DLMM_PROGRAM_ID = exports.METEORA_DAMM_V2_PROGRAM_ID = exports.METEORA_POOLS_PROGRAM_ID = void 0;
39
+ exports.geyser_grpc_channel_options = exports.default_geyser_connect_config = exports.connect_yellowstone_geyser = exports.event_parser = exports.geyserGrpcChannelOptions = exports.defaultGeyserConnectConfig = exports.connectYellowstoneGeyser = exports.YellowstoneGrpc = exports.programIds = exports.parse_pump_fees_instruction = exports.parse_meteora_pools_instruction = exports.parse_meteora_dlmm_instruction = exports.parse_meteora_damm_instruction = exports.parse_pumpswap_instruction = exports.parse_pumpfun_instruction = exports.parse_instruction_unified = exports.parsePumpFeesInstruction = exports.parseMeteoraPoolsInstruction = exports.parseMeteoraDlmmInstruction = exports.parseMeteoraDammInstruction = exports.parsePumpswapInstruction = exports.parsePumpfunInstruction = exports.parseInstructionUnified = exports.user_wallet_pubkey_for_onchain_account = exports.rpc_resolve_user_wallet_pubkey = exports.parse_pumpswap_pool = exports.parse_pumpswap_global_config = exports.parse_pumpfun_global = exports.parse_token_account = exports.parse_nonce_account = exports.parse_account_unified = exports.rpcResolveUserWalletPubkey = exports.userWalletPubkeyForOnchainAccount = exports.hasDiscriminator = exports.isPoolAccount = exports.isGlobalConfigAccount = exports.parsePumpswapAccount = exports.parsePumpswapPool = exports.parsePumpswapGlobalConfig = exports.isPumpfunGlobalAccount = exports.parsePumpfunAccount = exports.parsePumpfunGlobal = exports.parseTokenAccount = exports.isNonceAccount = exports.parseNonceAccount = exports.parseAccountUnified = exports.u64leDiscriminator = exports.PUMPSWAP_DISC = exports.PROGRAM_LOG_DISC = exports.parseMeteoraDammLog = void 0;
40
+ exports.high_throughput_shred_stream_config = exports.low_latency_shred_stream_config = exports.default_shred_stream_config = exports.highThroughputShredStreamConfig = exports.lowLatencyShredStreamConfig = exports.defaultShredStreamConfig = exports.wireBytesToShredWasmTx = exports.decodeShredstreamEntriesBincode = exports.decodeEntriesBincodeNested = exports.decodeEntriesBincodeFlat = exports.bincodeVecEntryCount = exports.loadAddressLookupTableAccounts = exports.fullAccountKeyStringsFromShredTx = exports.dexEventsFromShredWasmTxWithFullKeys = exports.dexEventsFromShredWasmTx = exports.ShredEventQueue = exports.ShredStreamClient = void 0;
41
41
  var constants_js_1 = require("./common/constants.js");
42
42
  Object.defineProperty(exports, "DEFAULT_CHANNEL_SIZE", { enumerable: true, get: function () { return constants_js_1.DEFAULT_CHANNEL_SIZE; } });
43
43
  Object.defineProperty(exports, "DEFAULT_CONNECT_TIMEOUT", { enumerable: true, get: function () { return constants_js_1.DEFAULT_CONNECT_TIMEOUT; } });
@@ -162,11 +162,13 @@ Object.defineProperty(exports, "METEORA_DLMM_PROGRAM_ID", { enumerable: true, ge
162
162
  Object.defineProperty(exports, "METEORA_DBC_PROGRAM_ID", { enumerable: true, get: function () { return program_ids_js_1.METEORA_DBC_PROGRAM_ID; } });
163
163
  Object.defineProperty(exports, "getProgramIdsForProtocols", { enumerable: true, get: function () { return program_ids_js_1.getProgramIdsForProtocols; } });
164
164
  Object.defineProperty(exports, "transactionFilterForProtocols", { enumerable: true, get: function () { return program_ids_js_1.transactionFilterForProtocols; } });
165
+ Object.defineProperty(exports, "transactionFilterForProtocolAccounts", { enumerable: true, get: function () { return program_ids_js_1.transactionFilterForProtocolAccounts; } });
165
166
  Object.defineProperty(exports, "accountFilterForProtocols", { enumerable: true, get: function () { return program_ids_js_1.accountFilterForProtocols; } });
166
167
  var program_ids_js_2 = require("./grpc/program_ids.js");
167
168
  Object.defineProperty(exports, "protocol_program_ids", { enumerable: true, get: function () { return program_ids_js_2.PROTOCOL_PROGRAM_IDS; } });
168
169
  Object.defineProperty(exports, "get_program_ids_for_protocols", { enumerable: true, get: function () { return program_ids_js_2.getProgramIdsForProtocols; } });
169
170
  Object.defineProperty(exports, "transaction_filter_for_protocols", { enumerable: true, get: function () { return program_ids_js_2.transactionFilterForProtocols; } });
171
+ Object.defineProperty(exports, "transaction_filter_for_protocol_accounts", { enumerable: true, get: function () { return program_ids_js_2.transactionFilterForProtocolAccounts; } });
170
172
  Object.defineProperty(exports, "account_filter_for_protocols", { enumerable: true, get: function () { return program_ids_js_2.accountFilterForProtocols; } });
171
173
  /** Rust `grpc::types` 蛇形命名别名 */
172
174
  var types_js_3 = require("./grpc/types.js");
@@ -30,6 +30,19 @@ const PUMPFUN_TRADE_EVENT_NAMES = new Set([
30
30
  ]);
31
31
  const EVENT_CPI_PREFIX = Uint8Array.from([228, 69, 165, 46, 81, 203, 154, 29]);
32
32
  const LEGACY_EVENT_CPI_SUFFIX = Uint8Array.from([155, 167, 108, 32, 122, 76, 173, 64]);
33
+ const SUPPORTED_INSTRUCTION_PROGRAM_IDS = new Set([
34
+ program_ids_js_1.PUMPFUN_PROGRAM_ID,
35
+ program_ids_js_1.PUMPSWAP_PROGRAM_ID,
36
+ program_ids_js_1.PUMP_FEES_PROGRAM_ID,
37
+ program_ids_js_1.RAYDIUM_LAUNCHLAB_PROGRAM_ID,
38
+ program_ids_js_1.RAYDIUM_CPMM_PROGRAM_ID,
39
+ program_ids_js_1.RAYDIUM_CLMM_PROGRAM_ID,
40
+ program_ids_js_1.RAYDIUM_AMM_V4_PROGRAM_ID,
41
+ program_ids_js_1.ORCA_WHIRLPOOL_PROGRAM_ID,
42
+ program_ids_js_1.METEORA_POOLS_PROGRAM_ID,
43
+ program_ids_js_1.METEORA_DAMM_V2_PROGRAM_ID,
44
+ program_ids_js_1.METEORA_DLMM_PROGRAM_ID,
45
+ ]);
33
46
  function bytesEqualAt(data, expected, offset) {
34
47
  for (let i = 0; i < expected.length; i++) {
35
48
  if (data[offset + i] !== expected[i])
@@ -379,6 +392,8 @@ function parseOuterAndInnerInstructions(message, meta, signature, slot, txIndex,
379
392
  const programId = (0, rpc_invoke_map_js_1.accountKeyToBase58)(resolver.get(ix.programIdIndex));
380
393
  if (!programId)
381
394
  continue;
395
+ if (!SUPPORTED_INSTRUCTION_PROGRAM_IDS.has(programId))
396
+ continue;
382
397
  const data = (0, rpc_invoke_map_js_1.decodeIxData)(ix.data);
383
398
  const accounts = resolveAccounts(resolver, ix.accountKeyIndexes);
384
399
  const ev = (0, mod_js_1.parseInstructionUnified)(data, accounts, signature, slot, txIndex, blockTimeUs, grpcRecvUs, filter, programId);
@@ -399,6 +414,8 @@ function parseOuterAndInnerInstructions(message, meta, signature, slot, txIndex,
399
414
  const programId = (0, rpc_invoke_map_js_1.accountKeyToBase58)(resolver.get(ix.programIdIndex));
400
415
  if (!programId)
401
416
  continue;
417
+ if (!SUPPORTED_INSTRUCTION_PROGRAM_IDS.has(programId))
418
+ continue;
402
419
  const data = (0, rpc_invoke_map_js_1.decodeIxData)(ix.data);
403
420
  const accounts = resolveAccounts(resolver, ix.accounts);
404
421
  const ev = (0, inner_js_1.parseInnerCompiledInstructionIfSupported)(data, accounts, signature, slot, txIndex, blockTimeUs, grpcRecvUs, filter, programId) ??
@@ -493,8 +510,7 @@ function parseRpcTransaction(tx, signature, filter, options) {
493
510
  activeProgramStack.length = idx;
494
511
  }
495
512
  }
496
- applyRpcFills(instructionEvents, msg, meta);
497
- applyRpcFills(logEvents, msg, meta);
513
+ applyRpcFills([...instructionEvents, ...logEvents], msg, meta);
498
514
  const events = (0, log_instr_dedup_js_1.dedupeLogInstructionEvents)(logEvents, instructionEvents);
499
515
  (0, pumpfun_fee_enrich_js_1.enrichPumpfunSameTxPostMerge)(events);
500
516
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol-parser-sdk",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "High-performance Solana DEX event parser for Node.js/TypeScript (Yellowstone gRPC, log parsing)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,6 +32,8 @@
32
32
  "test": "vitest run",
33
33
  "check:migration": "npm run check:dex-events && npm run verify:discriminators && npm run check:discriminator-lut && npm run check:json-utils && npm run build",
34
34
  "example:grpc:devtrades": "tsx examples/devtrades_low_latency.ts",
35
+ "example:grpc:devtrades:pumpswap": "tsx examples/devtrades_pumpfun_pumpswap_low_latency.ts",
36
+ "example:grpc:pumpswap": "tsx examples/pumpswap_low_latency.ts",
35
37
  "example:shredstream:subscribe": "tsx examples/shredstream_example.ts",
36
38
  "example:shredstream:pumpfun-json": "tsx examples/shredstream_pumpfun_json.ts",
37
39
  "example:shredstream": "tsx examples/shredstream_example.ts",