sol-parser-sdk 0.5.11 → 0.5.12

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.12
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,51 @@ 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
+ For precise local-only measurements, set `config.enable_metrics = true`. Event metadata then includes
180
+ `local_queue_latency_us`, `parse_duration_us`, and `local_processing_latency_us`. These values use
181
+ Node's monotonic high-resolution clock. They measure only local queueing and parsing; the hot path
182
+ does not call `getSlot`, `getTransaction`, JSON-RPC, or any unary RPC method.
183
+
184
+ `local_processing_latency_us` is the end-to-end local metric: it starts at entry to Yellowstone's
185
+ gRPC `data` callback and ends immediately after complete event parsing and field filling.
186
+
187
+ Full-volume PumpFun and PumpSwap can use the same Rust-aligned program filter. Yellowstone applies
188
+ `account_include` as ANY, so this receives every transaction containing either supported program:
189
+
190
+ ```typescript
191
+ const allPumpTransactions = transactionFilterForProtocols(["PumpFun", "PumpSwap"]);
192
+ const sub = await client.subscribeDexEvents([allPumpTransactions], [], eventTypeFilter);
193
+ ```
194
+
195
+ `eventTypeFilter` avoids unnecessary event decoding, but it does not reduce traffic sent by
196
+ Yellowstone. When an application intentionally needs only selected mints, pools, or users, the
197
+ optional scoped filter requires the protocol program and any one tracked account:
198
+
199
+ ```typescript
200
+ const pumpSwap = transactionFilterForProtocolAccounts("PumpSwap", knownMintsAndPools);
201
+ const sub = await client.subscribeDexEvents(
202
+ [transactionFilterForProtocols(["PumpFun"]), pumpSwap],
203
+ [],
204
+ eventTypeFilter
205
+ );
206
+ ```
207
+
208
+ The combined example uses full-volume PumpSwap by default. Set `PUMPSWAP_FILTER_MODE=scoped` only when
209
+ scoped monitoring is desired; in that mode `updateSubscription()` refreshes the active account
210
+ filters. `PumpSwapCreatePool` maps a known base or quote mint to its pool, and
211
+ `PumpSwapLiquidityAdded` can then be compared with the developer associated with that mint.
212
+
213
+ Two `YellowstoneGrpc` instances in one Node process still share one JavaScript event loop. The direct
214
+ protobuf path supports full PumpFun + PumpSwap in one subscription; use a separate OS process or
215
+ Worker Thread only when application callbacks, database work, or additional protocols saturate that
216
+ event loop. A separate gRPC connection alone does not provide CPU isolation. Send database work to a
217
+ bounded handoff queue and batch asynchronous SQLite WAL writes; do not await one write per event in
218
+ the subscription loop.
175
219
 
176
220
  No bounded in-memory stream can guarantee both zero loss and bounded latency when the consumer's
177
221
  average processing rate is below the filtered input rate. Keep the loop body CPU-light, avoid
@@ -205,6 +249,7 @@ From the **package root** after `npm install`. Examples use `npx tsx` and load `
205
249
  | 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
250
  | **PumpFun** | | |
207
251
  | 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) |
252
+ | 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
253
  | 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
254
  | 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
255
  | 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 +257,7 @@ From the **package root** after `npm install`. Examples use `npx tsx` and load `
212
257
  | **PumpSwap** | | |
213
258
  | 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
259
  | 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) |
260
+ | 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
261
  | **Meteora DAMM** | | |
217
262
  | 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
263
  | **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.12
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,47 @@ 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
+ 如需精确测量纯本地延迟,请设置 `config.enable_metrics = true`。事件 metadata 将包含
178
+ `local_queue_latency_us`、`parse_duration_us` 和 `local_processing_latency_us`,这些字段使用
179
+ Node 单调高精度时钟,只测量本地排队与解析。热路径不会调用 `getSlot`、`getTransaction`、
180
+ JSON-RPC 或任何 unary RPC 方法。
181
+
182
+ `local_processing_latency_us` 是本地端到端指标:从进入 Yellowstone gRPC `data` 回调开始,
183
+ 到完整事件解析和字段填充结束为止。
184
+
185
+ PumpFun 与 PumpSwap 全量交易可以直接使用和 Rust 对齐的 Program ID 过滤器。Yellowstone 的
186
+ `account_include` 是 ANY 语义,因此下面会接收包含任一协议程序的全部交易:
187
+
188
+ ```typescript
189
+ const allPumpTransactions = transactionFilterForProtocols(["PumpFun", "PumpSwap"]);
190
+ const sub = await client.subscribeDexEvents([allPumpTransactions], [], eventTypeFilter);
191
+ ```
192
+
193
+ `eventTypeFilter` 可以减少无关事件解码,但不会减少 Yellowstone 发来的交易。只有应用明确只想
194
+ 监控部分 mint、pool 或 user 时,才需要下面的可选限定过滤器;它要求交易同时包含协议程序,
195
+ 并包含任意一个已跟踪账户:
196
+
197
+ ```typescript
198
+ const pumpSwap = transactionFilterForProtocolAccounts("PumpSwap", knownMintsAndPools);
199
+ const sub = await client.subscribeDexEvents(
200
+ [transactionFilterForProtocols(["PumpFun"]), pumpSwap],
201
+ [],
202
+ eventTypeFilter
203
+ );
204
+ ```
205
+
206
+ 组合示例默认订阅全量 PumpSwap。只有设置 `PUMPSWAP_FILTER_MODE=scoped` 时才按账户限定;
207
+ 该模式会通过 `updateSubscription()` 刷新活跃账户过滤器。`PumpSwapCreatePool` 可把已知 base
208
+ mint 或 quote mint 映射到 pool,再将 `PumpSwapLiquidityAdded.user` 与该 mint 的开发者比较。
209
+
210
+ 同一 Node 进程里的两个 `YellowstoneGrpc` 实例仍共享一个 JavaScript 事件循环。直接 protobuf
211
+ 路径支持在一条订阅中接收全量 PumpFun + PumpSwap;只有业务回调、数据库写入或更多协议使事件
212
+ 循环饱和时,才需要独立进程或 Worker Thread。仅拆成两条 gRPC 连接不能隔离 CPU。数据库写入
213
+ 应进入有界交接队列并异步批量写 SQLite WAL,不要在订阅循环中逐事件等待写入。
174
214
 
175
215
  当消费者平均处理速度低于过滤后的入流速度时,任何有界内存流都无法同时保证零丢失和
176
216
  有界延迟。消费循环应保持轻量,避免逐事件同步日志,在服务端尽量缩小过滤范围,并把
@@ -203,6 +243,7 @@ npx tsx examples/shredstream_example.ts -- --url=http://127.0.0.1:10800
203
243
  | 调试:打印 `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
244
  | **PumpFun** | | |
205
245
  | 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) |
246
+ | 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
247
  | 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
248
  | 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
249
  | 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 +251,7 @@ npx tsx examples/shredstream_example.ts -- --url=http://127.0.0.1:10800
210
251
  | **PumpSwap** | | |
211
252
  | 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
253
  | 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) |
254
+ | 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
255
  | **Meteora DAMM** | | |
215
256
  | 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
257
  | **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,23 @@ 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;
17
21
  }
18
22
  export interface SubscribeDexEventsOptions {
19
23
  autoReconnect?: boolean;
20
24
  /**
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.
25
+ * Queue behavior when parsing or the async-iterator consumer cannot keep up.
26
+ * The default preserves existing behavior; `drop-oldest` favors chain-tip latency.
23
27
  */
24
28
  queueOverflowStrategy?: EventQueueOverflowStrategy;
29
+ /** Raw Yellowstone updates retained before CPU-heavy parsing. Defaults to min(buffer_size, 1024). */
30
+ ingressBufferSize?: number;
31
+ /** Maximum raw updates parsed before yielding back to Node's event loop. Defaults to 8. */
32
+ parserBatchSize?: number;
25
33
  }
26
34
  /** Yellowstone gRPC 客户端包装器 */
27
35
  export declare class YellowstoneGrpc {
@@ -42,6 +42,7 @@ 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 dex_event_js_1 = require("../core/dex_event.js");
45
46
  const metadata_js_1 = require("../core/metadata.js");
46
47
  const mod_js_1 = require("../accounts/mod.js");
47
48
  const geyser_connect_js_1 = require("./geyser_connect.js");
@@ -58,6 +59,13 @@ function cancelSubscribeStream(stream) {
58
59
  stream.once("error", () => { });
59
60
  stream.cancel();
60
61
  }
62
+ function positiveIntegerOption(value, fallback, name) {
63
+ const normalized = value ?? fallback;
64
+ if (!Number.isInteger(normalized) || normalized < 1) {
65
+ throw new RangeError(`${name} must be a positive integer`);
66
+ }
67
+ return normalized;
68
+ }
61
69
  /** Yellowstone gRPC 客户端包装器 */
62
70
  class YellowstoneGrpc {
63
71
  client;
@@ -430,18 +438,23 @@ class YellowstoneGrpc {
430
438
  await this.connect();
431
439
  const id = `dex_${Date.now()}_${Math.random().toString(36).slice(2)}`;
432
440
  const events = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size), options?.queueOverflowStrategy);
441
+ const ingressBufferSize = positiveIntegerOption(options?.ingressBufferSize, Math.min(this.config.buffer_size, 1024), "ingressBufferSize");
442
+ const parserBatchSize = positiveIntegerOption(options?.parserBatchSize, 8, "parserBatchSize");
443
+ const ingress = new async_event_queue_js_1.AsyncEventQueue(ingressBufferSize, options?.queueOverflowStrategy);
433
444
  const errors = new async_event_queue_js_1.AsyncEventQueue(Math.max(1, this.config.buffer_size));
434
445
  const order = new order_buffer_js_1.OrderDispatcher(this.config);
435
- let nextDropWarningAt = 0;
446
+ const eventDropped = events.dropped.bind(events);
447
+ let nextEventDropWarningAt = 0;
448
+ let nextIngressDropWarningAt = 0;
436
449
  const reportError = (error) => errors.push(error);
437
450
  const emitEvent = (event) => {
438
451
  if (events.push(event))
439
452
  return;
440
453
  const now = Date.now();
441
- if (now < nextDropWarningAt)
454
+ if (now < nextEventDropWarningAt)
442
455
  return;
443
- nextDropWarningAt = now + 10_000;
444
- reportError(new Error(`Dex event queue full; dropped=${events.dropped()} buffer_size=${this.config.buffer_size} ` +
456
+ nextEventDropWarningAt = now + 10_000;
457
+ reportError(new Error(`Dex event queue full; dropped=${eventDropped()} buffer_size=${this.config.buffer_size} ` +
445
458
  `strategy=${options?.queueOverflowStrategy ?? "drop-newest"}`));
446
459
  };
447
460
  let flushTimer;
@@ -452,6 +465,102 @@ class YellowstoneGrpc {
452
465
  flushTimer = setInterval(() => order.flushDue(emitEvent), intervalMs);
453
466
  }
454
467
  let isCancelled = false;
468
+ let ingressDrainScheduled = false;
469
+ let ingressDrainHandle;
470
+ const ingressIdleWaiters = [];
471
+ const resolveIngressIdle = () => {
472
+ if (ingress.len() > 0 || ingressDrainScheduled)
473
+ return;
474
+ for (const resolve of ingressIdleWaiters.splice(0))
475
+ resolve();
476
+ };
477
+ const annotateLocalMetrics = (events, grpcRecvNs, parseStartedNs, parseFinishedNs) => {
478
+ if (grpcRecvNs === undefined || parseStartedNs === undefined || parseFinishedNs === undefined) {
479
+ return;
480
+ }
481
+ const queueUs = Number(parseStartedNs - grpcRecvNs) / 1_000;
482
+ const parseUs = Number(parseFinishedNs - parseStartedNs) / 1_000;
483
+ const processingUs = Number(parseFinishedNs - grpcRecvNs) / 1_000;
484
+ for (const event of events) {
485
+ const metadata = (0, dex_event_js_1.metadataForDexEvent)(event);
486
+ if (!metadata)
487
+ continue;
488
+ metadata.local_queue_latency_us = queueUs;
489
+ metadata.parse_duration_us = parseUs;
490
+ metadata.local_processing_latency_us = processingUs;
491
+ }
492
+ };
493
+ const processUpdate = ({ update, grpcRecvUs, grpcRecvNs, blockTimeUs }) => {
494
+ if (order.needsTimer)
495
+ order.flushDue(emitEvent);
496
+ const txUpdate = update.transaction;
497
+ const tx = txUpdate?.transaction;
498
+ if (tx) {
499
+ const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
500
+ const slot = txUpdate?.slot ?? 0n;
501
+ const fallbackSlot = YellowstoneGrpc.protobufNumber(slot);
502
+ const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
503
+ const txInfo = {
504
+ signature: tx.signature,
505
+ isVote: tx.isVote,
506
+ transactionRaw: tx.transaction,
507
+ metaRaw: tx.meta,
508
+ index: (tx.index ?? 0n),
509
+ };
510
+ const txEvents = (0, yellowstone_parse_js_1.parseDexEventsFromGrpcTransactionInfo)(txInfo, slot, {
511
+ blockTimeUs,
512
+ grpcRecvUs,
513
+ eventTypeFilter,
514
+ });
515
+ const parseFinishedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
516
+ annotateLocalMetrics(txEvents, grpcRecvNs, parseStartedNs, parseFinishedNs);
517
+ order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
518
+ }
519
+ if (update.account) {
520
+ const parseStartedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
521
+ const ev = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
522
+ const parseFinishedNs = grpcRecvNs === undefined ? undefined : process.hrtime.bigint();
523
+ if (ev)
524
+ annotateLocalMetrics([ev], grpcRecvNs, parseStartedNs, parseFinishedNs);
525
+ if (ev)
526
+ emitEvent(ev);
527
+ }
528
+ };
529
+ const scheduleIngressDrain = () => {
530
+ if (isCancelled || ingressDrainScheduled || ingress.len() === 0)
531
+ return;
532
+ ingressDrainScheduled = true;
533
+ ingressDrainHandle = setImmediate(() => {
534
+ ingressDrainHandle = undefined;
535
+ ingressDrainScheduled = false;
536
+ if (isCancelled) {
537
+ resolveIngressIdle();
538
+ return;
539
+ }
540
+ let processed = 0;
541
+ while (processed < parserBatchSize) {
542
+ const pending = ingress.shift();
543
+ if (!pending)
544
+ break;
545
+ try {
546
+ processUpdate(pending);
547
+ }
548
+ catch (err) {
549
+ reportError(err instanceof Error ? err : new Error(String(err)));
550
+ }
551
+ processed++;
552
+ }
553
+ if (ingress.len() > 0)
554
+ scheduleIngressDrain();
555
+ else
556
+ resolveIngressIdle();
557
+ });
558
+ };
559
+ const waitForIngressIdle = () => {
560
+ if (ingress.len() === 0 && !ingressDrainScheduled)
561
+ return Promise.resolve();
562
+ return new Promise((resolve) => ingressIdleWaiters.push(resolve));
563
+ };
455
564
  let currentTransactionFilters = transactionFilters;
456
565
  let currentAccountFilters = accountFilters;
457
566
  const streamHolder = { current: null };
@@ -477,6 +586,13 @@ class YellowstoneGrpc {
477
586
  cancelReconnectDelay?.();
478
587
  cancelSubscribeStream(streamHolder.current);
479
588
  streamHolder.current = null;
589
+ if (ingressDrainHandle)
590
+ clearImmediate(ingressDrainHandle);
591
+ ingressDrainHandle = undefined;
592
+ ingressDrainScheduled = false;
593
+ ingress.clear();
594
+ ingress.close();
595
+ resolveIngressIdle();
480
596
  if (flushTimer)
481
597
  clearInterval(flushTimer);
482
598
  events.close();
@@ -529,9 +645,11 @@ class YellowstoneGrpc {
529
645
  stream.on("data", (update) => {
530
646
  if (isCancelled)
531
647
  return;
648
+ const grpcRecvNs = this.config.enable_metrics
649
+ ? process.hrtime.bigint()
650
+ : undefined;
651
+ const grpcRecvUs = Math.floor(Date.now() * 1000);
532
652
  backoffMs = this.config.retry_delay_ms;
533
- if (order.needsTimer)
534
- order.flushDue(emitEvent);
535
653
  if (update.ping) {
536
654
  stream.write(this.subscribePingPongRequest(), (werr) => {
537
655
  if (werr && !isCancelled) {
@@ -540,34 +658,22 @@ class YellowstoneGrpc {
540
658
  });
541
659
  return;
542
660
  }
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);
661
+ const accepted = ingress.push({
662
+ update,
663
+ grpcRecvUs,
664
+ grpcRecvNs,
665
+ blockTimeUs: YellowstoneGrpc.timestampToMicros(update.createdAt ?? update.created_at),
666
+ });
667
+ if (!accepted) {
668
+ const now = Date.now();
669
+ if (now >= nextIngressDropWarningAt) {
670
+ nextIngressDropWarningAt = now + 10_000;
671
+ reportError(new Error(`Yellowstone ingress queue full; dropped=${ingress.dropped()} ` +
672
+ `buffer_size=${ingressBufferSize} ` +
673
+ `strategy=${options?.queueOverflowStrategy ?? "drop-newest"}`));
561
674
  }
562
- if (update.account) {
563
- const ev = this.parseAccountEventRaw(update.account, grpcRecvUs, eventTypeFilter, blockTimeUs);
564
- if (ev)
565
- emitEvent(ev);
566
- }
567
- }
568
- catch (err) {
569
- reportError(err instanceof Error ? err : new Error(String(err)));
570
675
  }
676
+ scheduleIngressDrain();
571
677
  });
572
678
  stream.on("error", (err) => {
573
679
  cleanup();
@@ -602,8 +708,11 @@ class YellowstoneGrpc {
602
708
  streamHolder.current = null;
603
709
  if (flushTimer)
604
710
  clearInterval(flushTimer);
605
- if (!isCancelled)
711
+ if (!isCancelled) {
712
+ await waitForIngressIdle();
606
713
  order.flushAll(emitEvent);
714
+ }
715
+ ingress.close();
607
716
  events.close();
608
717
  errors.close();
609
718
  this.dexSubscribers.delete(id);
@@ -612,6 +721,10 @@ class YellowstoneGrpc {
612
721
  return Object.assign(events, {
613
722
  id,
614
723
  errors,
724
+ dropped: () => eventDropped() + ingress.dropped(),
725
+ eventDropped,
726
+ ingressLen: () => ingress.len(),
727
+ ingressDropped: () => ingress.dropped(),
615
728
  cancel: () => {
616
729
  cancel();
617
730
  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.12",
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",