sol-parser-sdk-nodejs 0.4.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +16 -4
  2. package/README_CN.md +16 -4
  3. package/dist/accounts/mod.d.ts +1 -1
  4. package/dist/accounts/mod.js +43 -9
  5. package/dist/accounts/pumpfun.d.ts +10 -0
  6. package/dist/accounts/pumpfun.js +384 -3
  7. package/dist/core/account_dispatcher_rpc.js +5 -26
  8. package/dist/core/account_fill_meteora.d.ts +1 -2
  9. package/dist/core/account_fill_meteora.js +0 -2
  10. package/dist/core/account_fill_pumpfun.js +86 -24
  11. package/dist/core/account_fill_pumpswap.js +50 -0
  12. package/dist/core/account_fill_raydium_launchlab.d.ts +4 -0
  13. package/dist/core/account_fill_raydium_launchlab.js +20 -0
  14. package/dist/core/dex_event.d.ts +136 -17
  15. package/dist/core/pumpfun_fee_enrich.d.ts +1 -0
  16. package/dist/core/pumpfun_fee_enrich.js +51 -0
  17. package/dist/grpc/log_instr_dedup.js +62 -14
  18. package/dist/grpc/program_ids.d.ts +4 -3
  19. package/dist/grpc/program_ids.js +11 -5
  20. package/dist/grpc/types.d.ts +13 -6
  21. package/dist/grpc/types.js +250 -239
  22. package/dist/index.d.ts +6 -5
  23. package/dist/index.js +21 -7
  24. package/dist/instr/meteora_damm_ix.js +4 -1
  25. package/dist/instr/meteora_dlmm_ix.d.ts +2 -0
  26. package/dist/instr/meteora_dlmm_ix.js +134 -0
  27. package/dist/instr/meteora_pools_ix.d.ts +2 -0
  28. package/dist/instr/meteora_pools_ix.js +78 -0
  29. package/dist/instr/mod.d.ts +3 -1
  30. package/dist/instr/mod.js +38 -19
  31. package/dist/instr/program_ids.d.ts +1 -5
  32. package/dist/instr/program_ids.js +3 -6
  33. package/dist/instr/pumpfun_ix.js +219 -5
  34. package/dist/instr/pumpswap_ix.js +36 -2
  35. package/dist/instr/raydium_launchlab_ix.d.ts +8 -0
  36. package/dist/instr/raydium_launchlab_ix.js +125 -0
  37. package/dist/instr/rust_aliases.d.ts +2 -0
  38. package/dist/instr/rust_aliases.js +5 -1
  39. package/dist/logs/optimized_matcher.js +14 -9
  40. package/dist/logs/pump.js +76 -3
  41. package/dist/logs/pump_amm.js +1 -1
  42. package/dist/logs/raydium_launchlab.d.ts +10 -0
  43. package/dist/logs/raydium_launchlab.js +84 -0
  44. package/dist/shredstream/client.d.ts +4 -1
  45. package/dist/shredstream/client.js +18 -14
  46. package/dist/shredstream/index.d.ts +1 -1
  47. package/dist/shredstream/index.js +1 -1
  48. package/dist/shredstream/instruction_parse.d.ts +3 -3
  49. package/dist/shredstream/instruction_parse.js +36 -13
  50. package/dist/util/market.d.ts +18 -0
  51. package/dist/util/market.js +54 -0
  52. package/package.json +1 -1
package/dist/logs/pump.js CHANGED
@@ -18,12 +18,58 @@ const DISC_CREATE = disc([27, 114, 169, 77, 222, 235, 99, 118]);
18
18
  const DISC_TRADE = disc([189, 219, 127, 211, 78, 230, 97, 238]);
19
19
  const DISC_MIGRATE = disc([189, 233, 93, 185, 92, 148, 234, 148]);
20
20
  const DISC_MIGRATE_BONDING_CURVE_CREATOR = disc([155, 167, 104, 220, 213, 108, 243, 3]);
21
+ function normalizePumpfunIxName(ixName) {
22
+ if (ixName === "buy_v2")
23
+ return "buy";
24
+ if (ixName === "sell_v2")
25
+ return "sell";
26
+ if (ixName === "buy_exact_quote_in_v2")
27
+ return "buy_exact_quote_in";
28
+ return ixName;
29
+ }
21
30
  function bnU64(v) {
22
31
  return v ?? 0n;
23
32
  }
24
33
  function bnI64(v) {
25
34
  return v ?? 0n;
26
35
  }
36
+ function readOptionalU64(data, offset) {
37
+ if (offset.value + 8 > data.length)
38
+ return 0n;
39
+ const value = bnU64((0, binary_js_1.readU64LE)(data, offset.value));
40
+ offset.value += 8;
41
+ return value;
42
+ }
43
+ function readOptionalPubkey(data, offset) {
44
+ if (offset.value + 32 > data.length)
45
+ return (0, dex_event_js_1.defaultPubkey)();
46
+ const value = (0, binary_js_1.readPubkey)(data, offset.value) ?? (0, dex_event_js_1.defaultPubkey)();
47
+ offset.value += 32;
48
+ return value;
49
+ }
50
+ function readTradeShareholders(data, offset) {
51
+ if (offset.value + 4 > data.length)
52
+ return [];
53
+ const n = (0, binary_js_1.readU32LE)(data, offset.value);
54
+ if (n === null || n > 64)
55
+ return null;
56
+ offset.value += 4;
57
+ if (offset.value + n * 34 > data.length)
58
+ return null;
59
+ const out = [];
60
+ for (let i = 0; i < n; i++) {
61
+ const address = (0, binary_js_1.readPubkey)(data, offset.value);
62
+ if (!address)
63
+ return null;
64
+ offset.value += 32;
65
+ const share_bps = (0, binary_js_1.readU16LE)(data, offset.value);
66
+ if (share_bps === null)
67
+ return null;
68
+ offset.value += 2;
69
+ out.push({ address, share_bps });
70
+ }
71
+ return out;
72
+ }
27
73
  function parsePumpFunLogDecoded(programData, metadata) {
28
74
  const disc = (0, binary_js_1.readDiscriminatorU64)(programData);
29
75
  if (disc === null)
@@ -104,11 +150,23 @@ function parseTradeFromData(data, metadata, isCreatedBuy) {
104
150
  o = rs.next;
105
151
  }
106
152
  }
153
+ ix_name = normalizePumpfunIxName(ix_name);
107
154
  const mayhem_mode = (0, binary_js_1.readBool)(data, o) ?? false;
108
155
  o += 1;
109
156
  const cashback_fee_basis_points = o + 8 <= data.length ? bnU64((0, binary_js_1.readU64LE)(data, o)) : 0n;
110
157
  o += 8;
111
158
  const cashback = o + 8 <= data.length ? bnU64((0, binary_js_1.readU64LE)(data, o)) : 0n;
159
+ o += 8;
160
+ const tail = { value: o };
161
+ const buyback_fee_basis_points = readOptionalU64(data, tail);
162
+ const buyback_fee = readOptionalU64(data, tail);
163
+ const shareholders = readTradeShareholders(data, tail);
164
+ if (shareholders === null)
165
+ return null;
166
+ const quote_mint = readOptionalPubkey(data, tail);
167
+ const quote_amount = readOptionalU64(data, tail);
168
+ const virtual_quote_reserves = readOptionalU64(data, tail);
169
+ const real_quote_reserves = readOptionalU64(data, tail);
112
170
  const trade = {
113
171
  metadata,
114
172
  mint,
@@ -137,19 +195,28 @@ function parseTradeFromData(data, metadata, isCreatedBuy) {
137
195
  mayhem_mode,
138
196
  cashback_fee_basis_points,
139
197
  cashback,
198
+ buyback_fee_basis_points,
199
+ buyback_fee,
200
+ shareholders,
201
+ quote_mint,
202
+ quote_amount,
203
+ virtual_quote_reserves,
204
+ real_quote_reserves,
140
205
  is_cashback_coin: cashback_fee_basis_points > 0n,
141
206
  bonding_curve: (0, dex_event_js_1.defaultPubkey)(),
142
207
  associated_bonding_curve: (0, dex_event_js_1.defaultPubkey)(),
143
208
  token_program: (0, dex_event_js_1.defaultPubkey)(),
144
209
  creator_vault: (0, dex_event_js_1.defaultPubkey)(),
145
210
  };
146
- if (ix_name === "buy" || ix_name === "buy_v2")
211
+ if (ix_name === "buy")
147
212
  return { PumpFunBuy: trade };
148
- if (ix_name === "sell" || ix_name === "sell_v2")
213
+ if (ix_name === "sell")
149
214
  return { PumpFunSell: trade };
150
- if (ix_name === "buy_exact_sol_in" || ix_name === "buy_exact_quote_in_v2") {
215
+ if (ix_name === "buy_exact_sol_in") {
151
216
  return { PumpFunBuyExactSolIn: trade };
152
217
  }
218
+ if (ix_name === "buy_exact_quote_in")
219
+ return { PumpFunBuy: trade };
153
220
  return { PumpFunTrade: trade };
154
221
  }
155
222
  function parseCreateFromData(data, metadata) {
@@ -199,6 +266,10 @@ function parseCreateFromData(data, metadata) {
199
266
  const is_mayhem_mode = (0, binary_js_1.readBool)(data, o) ?? false;
200
267
  o += 1;
201
268
  const is_cashback_enabled = (0, binary_js_1.readBool)(data, o) ?? false;
269
+ o += 1;
270
+ const quote_mint = o + 32 <= data.length ? (0, binary_js_1.readPubkey)(data, o) : (0, dex_event_js_1.defaultPubkey)();
271
+ o += 32;
272
+ const virtual_quote_reserves = o + 8 <= data.length ? bnU64((0, binary_js_1.readU64LE)(data, o)) : 0n;
202
273
  const ev = {
203
274
  metadata,
204
275
  name: n1.s,
@@ -216,6 +287,8 @@ function parseCreateFromData(data, metadata) {
216
287
  token_program,
217
288
  is_mayhem_mode,
218
289
  is_cashback_enabled,
290
+ quote_mint,
291
+ virtual_quote_reserves,
219
292
  };
220
293
  return { PumpFunCreate: ev };
221
294
  }
@@ -18,7 +18,7 @@ function bnI64(v) {
18
18
  }
19
19
  const ZP = (0, dex_event_js_1.defaultPubkey)();
20
20
  function parseBuyFromData(data, metadata) {
21
- const MIN = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
21
+ const MIN = 16 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
22
22
  if (data.length < MIN)
23
23
  return null;
24
24
  let o = 0;
@@ -0,0 +1,10 @@
1
+ /** Raydium LaunchLab 日志解析 */
2
+ import type { EventMetadata } from "../core/metadata.js";
3
+ import type { DexEvent } from "../core/dex_event.js";
4
+ export declare const RAYDIUM_LAUNCHLAB_DISC: {
5
+ TRADE: bigint;
6
+ POOL_CREATE: bigint;
7
+ };
8
+ export declare function parseRaydiumLaunchlabTradeFromData(data: Uint8Array, metadata: EventMetadata): DexEvent | null;
9
+ export declare function parseRaydiumLaunchlabPoolCreateFromData(data: Uint8Array, metadata: EventMetadata): DexEvent | null;
10
+ export declare function parseRaydiumLaunchlabFromDiscriminator(discriminator: bigint, data: Uint8Array, metadata: EventMetadata): DexEvent | null;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RAYDIUM_LAUNCHLAB_DISC = void 0;
4
+ exports.parseRaydiumLaunchlabTradeFromData = parseRaydiumLaunchlabTradeFromData;
5
+ exports.parseRaydiumLaunchlabPoolCreateFromData = parseRaydiumLaunchlabPoolCreateFromData;
6
+ exports.parseRaydiumLaunchlabFromDiscriminator = parseRaydiumLaunchlabFromDiscriminator;
7
+ const binary_js_1 = require("../util/binary.js");
8
+ function disc(bytes) {
9
+ const u8 = new Uint8Array(8);
10
+ for (let i = 0; i < 8; i++)
11
+ u8[i] = bytes[i];
12
+ return new DataView(u8.buffer).getBigUint64(0, true);
13
+ }
14
+ exports.RAYDIUM_LAUNCHLAB_DISC = {
15
+ TRADE: disc([189, 219, 127, 211, 78, 230, 97, 238]),
16
+ POOL_CREATE: disc([151, 215, 226, 9, 118, 161, 115, 174]),
17
+ };
18
+ function bn64(v) {
19
+ return v ?? 0n;
20
+ }
21
+ function parseRaydiumLaunchlabTradeFromData(data, metadata) {
22
+ const pool_state = (0, binary_js_1.readPubkey)(data, 0);
23
+ const amount_in = (0, binary_js_1.readU64LE)(data, 88);
24
+ const amount_out = (0, binary_js_1.readU64LE)(data, 96);
25
+ const trade_direction = (0, binary_js_1.readU8)(data, 136);
26
+ const exact_in = (0, binary_js_1.readBool)(data, 138);
27
+ if (!pool_state || amount_in === null || amount_out === null || trade_direction === null || exact_in === null) {
28
+ return null;
29
+ }
30
+ const is_buy = trade_direction === 0;
31
+ const ev = {
32
+ metadata,
33
+ pool_state,
34
+ user: "11111111111111111111111111111111",
35
+ amount_in: bn64(amount_in),
36
+ amount_out: bn64(amount_out),
37
+ is_buy,
38
+ trade_direction: is_buy ? "Buy" : "Sell",
39
+ exact_in,
40
+ };
41
+ return { RaydiumLaunchlabTrade: ev };
42
+ }
43
+ function parseRaydiumLaunchlabPoolCreateFromData(data, metadata) {
44
+ if (data.length < 97)
45
+ return null;
46
+ let o = 0;
47
+ const pool_state = (0, binary_js_1.readPubkey)(data, o);
48
+ o += 32;
49
+ const creator = (0, binary_js_1.readPubkey)(data, o);
50
+ o += 32;
51
+ const config = (0, binary_js_1.readPubkey)(data, o);
52
+ if (!pool_state || !creator || !config)
53
+ return null;
54
+ o += 32;
55
+ const decimals = (0, binary_js_1.readU8)(data, o);
56
+ if (decimals === null)
57
+ return null;
58
+ o += 1;
59
+ const name = (0, binary_js_1.readBorshString)(data, o);
60
+ if (!name)
61
+ return null;
62
+ o = name.next;
63
+ const symbol = (0, binary_js_1.readBorshString)(data, o);
64
+ if (!symbol)
65
+ return null;
66
+ o = symbol.next;
67
+ const uri = (0, binary_js_1.readBorshString)(data, o);
68
+ if (!uri)
69
+ return null;
70
+ const ev = {
71
+ metadata,
72
+ base_mint_param: { symbol: symbol.s, name: name.s, uri: uri.s, decimals },
73
+ pool_state,
74
+ creator,
75
+ };
76
+ return { RaydiumLaunchlabPoolCreate: ev };
77
+ }
78
+ function parseRaydiumLaunchlabFromDiscriminator(discriminator, data, metadata) {
79
+ if (discriminator === exports.RAYDIUM_LAUNCHLAB_DISC.TRADE)
80
+ return parseRaydiumLaunchlabTradeFromData(data, metadata);
81
+ if (discriminator === exports.RAYDIUM_LAUNCHLAB_DISC.POOL_CREATE)
82
+ return parseRaydiumLaunchlabPoolCreateFromData(data, metadata);
83
+ return null;
84
+ }
@@ -1,4 +1,5 @@
1
1
  import { type DexEvent } from "../core/dex_event.js";
2
+ import type { EventTypeFilter } from "../grpc/types.js";
2
3
  import { type ShredStreamConfig } from "./config.js";
3
4
  /** 订阅周期内累计(用于排查「无 gRPC 包 / 解码失败 / 无 DEX 事件」) */
4
5
  export type ShredStreamReceiveStats = {
@@ -48,7 +49,9 @@ export declare class ShredStreamClient {
48
49
  * 订阅 DEX 事件(自动重连);返回队列供轮询消费(与 Rust `subscribe` 一致)。
49
50
  * 重连循环使用订阅时刻的配置快照(与 Rust 在 `tokio::spawn` 前 `config.clone()` 一致)。
50
51
  */
51
- subscribe(): Promise<ShredEventQueue>;
52
+ subscribe(eventTypeFilter?: EventTypeFilter): Promise<ShredEventQueue>;
53
+ /** 与 Rust `subscribe_with_filter` 对齐:在解析热路径按事件类型预过滤。 */
54
+ subscribeWithFilter(eventTypeFilter?: EventTypeFilter): Promise<ShredEventQueue>;
52
55
  /** 停止订阅并中止当前流 */
53
56
  stop(): Promise<void>;
54
57
  private runReconnectLoop;
@@ -193,7 +193,7 @@ class ShredStreamClient {
193
193
  * 订阅 DEX 事件(自动重连);返回队列供轮询消费(与 Rust `subscribe` 一致)。
194
194
  * 重连循环使用订阅时刻的配置快照(与 Rust 在 `tokio::spawn` 前 `config.clone()` 一致)。
195
195
  */
196
- async subscribe() {
196
+ async subscribe(eventTypeFilter) {
197
197
  await this.stop();
198
198
  this.receiveStats = {
199
199
  entryMessagesReceived: 0,
@@ -205,9 +205,13 @@ class ShredStreamClient {
205
205
  const ac = new AbortController();
206
206
  this.loopAbort = ac;
207
207
  const configSnapshot = { ...this.config };
208
- this.loopPromise = this.runReconnectLoop(queue, ac.signal, configSnapshot);
208
+ this.loopPromise = this.runReconnectLoop(queue, ac.signal, configSnapshot, eventTypeFilter);
209
209
  return queue;
210
210
  }
211
+ /** 与 Rust `subscribe_with_filter` 对齐:在解析热路径按事件类型预过滤。 */
212
+ async subscribeWithFilter(eventTypeFilter) {
213
+ return this.subscribe(eventTypeFilter);
214
+ }
211
215
  /** 停止订阅并中止当前流 */
212
216
  async stop() {
213
217
  this.loopAbort?.abort();
@@ -219,7 +223,7 @@ class ShredStreamClient {
219
223
  this.loopPromise = null;
220
224
  }
221
225
  }
222
- async runReconnectLoop(queue, signal, config) {
226
+ async runReconnectLoop(queue, signal, config, eventTypeFilter) {
223
227
  let delay = config.reconnect_delay_ms;
224
228
  let attempts = 0;
225
229
  while (!signal.aborted) {
@@ -230,7 +234,7 @@ class ShredStreamClient {
230
234
  }
231
235
  attempts += 1;
232
236
  try {
233
- await this.streamOnce(queue, signal, config);
237
+ await this.streamOnce(queue, signal, config, eventTypeFilter);
234
238
  delay = config.reconnect_delay_ms;
235
239
  attempts = 0;
236
240
  }
@@ -247,7 +251,7 @@ class ShredStreamClient {
247
251
  }
248
252
  }
249
253
  /** 与 Rust `stream_events` + `connect_with_config`(tonic Endpoint / Grpc 消息上限)一致 */
250
- streamOnce(queue, signal, config) {
254
+ streamOnce(queue, signal, config, eventTypeFilter) {
251
255
  if (signal.aborted)
252
256
  return Promise.resolve();
253
257
  const target = grpcTarget(this.endpoint);
@@ -272,7 +276,7 @@ class ShredStreamClient {
272
276
  if (signal.aborted)
273
277
  return;
274
278
  try {
275
- void this.processEntryMessage(entry, queue);
279
+ void this.processEntryMessage(entry, queue, eventTypeFilter);
276
280
  }
277
281
  catch (err) {
278
282
  console.error("processEntryMessage:", err);
@@ -293,7 +297,7 @@ class ShredStreamClient {
293
297
  });
294
298
  });
295
299
  }
296
- async processEntryMessage(entry, queue) {
300
+ async processEntryMessage(entry, queue, eventTypeFilter) {
297
301
  this.receiveStats.entryMessagesReceived += 1;
298
302
  const recvUs = (0, unified_parser_js_1.nowUs)();
299
303
  const slotNum = toSlotNumber(entry.slot);
@@ -312,18 +316,18 @@ class ShredStreamClient {
312
316
  const conn = this.config.connection;
313
317
  if (conn) {
314
318
  try {
315
- await this.processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, bytes.length, conn);
319
+ await this.processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, bytes.length, conn, eventTypeFilter);
316
320
  }
317
321
  catch (e) {
318
322
  console.warn(`[shredstream] ALT/RPC 解析失败 slot=${slotNum}:`, e);
319
- this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length);
323
+ this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length, eventTypeFilter);
320
324
  }
321
325
  return;
322
326
  }
323
- this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length);
327
+ this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length, eventTypeFilter);
324
328
  }
325
329
  /** 无 RPC:仅用静态账户表 */
326
- processEntryMessageSync(decoded, slotNum, recvUs, queue, entriesBytesLen) {
330
+ processEntryMessageSync(decoded, slotNum, recvUs, queue, entriesBytesLen, eventTypeFilter) {
327
331
  let txTotal = 0;
328
332
  let evTotal = 0;
329
333
  /** 与 golang `shredstream_entries` 一致:单条 gRPC 消息内跨所有 Solana Entry 的连续下标 */
@@ -336,7 +340,7 @@ class ShredStreamClient {
336
340
  const txIndex = globalTxIndex++;
337
341
  if (!tx?.signature)
338
342
  continue;
339
- const events = (0, instruction_parse_js_1.dexEventsFromShredWasmTx)(tx, slotNum, txIndex, recvUs, undefined);
343
+ const events = (0, instruction_parse_js_1.dexEventsFromShredWasmTx)(tx, slotNum, txIndex, recvUs, eventTypeFilter);
340
344
  evTotal += events.length;
341
345
  for (const ev of events) {
342
346
  setGrpcRecvUsMut(ev, recvUs);
@@ -351,7 +355,7 @@ class ShredStreamClient {
351
355
  }
352
356
  }
353
357
  /** 拉取 ALT 后完整账户表解析 */
354
- async processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, entriesBytesLen, conn) {
358
+ async processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, entriesBytesLen, conn, eventTypeFilter) {
355
359
  const altKeys = new Set();
356
360
  for (const outer of decoded) {
357
361
  for (const tx of outer) {
@@ -373,7 +377,7 @@ class ShredStreamClient {
373
377
  if (!tx?.signature)
374
378
  continue;
375
379
  const fullKeys = (0, alt_lookup_js_1.fullAccountKeyStringsFromShredTx)(tx, altMap);
376
- const events = (0, instruction_parse_js_1.dexEventsFromShredWasmTxWithFullKeys)(tx, fullKeys, slotNum, txIndex, recvUs, undefined);
380
+ const events = (0, instruction_parse_js_1.dexEventsFromShredWasmTxWithFullKeys)(tx, fullKeys, slotNum, txIndex, recvUs, eventTypeFilter);
377
381
  evTotal += events.length;
378
382
  for (const ev of events) {
379
383
  setGrpcRecvUsMut(ev, recvUs);
@@ -5,7 +5,7 @@
5
5
  * - 仅静态账户键;使用 ALT 的交易账户列表不完整
6
6
  * - 无 inner instructions,无法 CPI 解析
7
7
  * - 无 block_time
8
- * - 无交易日志(program logs);客户端对每条交易走 **外层 `parseInstructionUnified`**(与 gRPC 指令解析同源),可产出 `DexEvent`(V0+ALT 若账户下标超出静态表则跳过该指令)
8
+ * - 无交易日志(program logs);客户端对每条交易走 **外层 `parseInstructionUnified`**(与 gRPC 指令解析同源),可产出 `DexEvent`(V0+ALT 若账户下标超出静态表则用默认 pubkey best-effort)
9
9
  * - `metadata.tx_index` 为**单条 gRPC `Entry` 消息内**跨所有 Solana `Entry` 分组的连续下标(与 golang `shredstream_entries` 扁平 `ti` 对齐),非 slot 级全局序号
10
10
  */
11
11
  export { type ShredStreamConfig, defaultShredStreamConfig, lowLatencyShredStreamConfig, highThroughputShredStreamConfig, } from "./config.js";
@@ -8,7 +8,7 @@ exports.wireBytesToShredWasmTx = exports.decodeShredstreamEntriesBincode = expor
8
8
  * - 仅静态账户键;使用 ALT 的交易账户列表不完整
9
9
  * - 无 inner instructions,无法 CPI 解析
10
10
  * - 无 block_time
11
- * - 无交易日志(program logs);客户端对每条交易走 **外层 `parseInstructionUnified`**(与 gRPC 指令解析同源),可产出 `DexEvent`(V0+ALT 若账户下标超出静态表则跳过该指令)
11
+ * - 无交易日志(program logs);客户端对每条交易走 **外层 `parseInstructionUnified`**(与 gRPC 指令解析同源),可产出 `DexEvent`(V0+ALT 若账户下标超出静态表则用默认 pubkey best-effort)
12
12
  * - `metadata.tx_index` 为**单条 gRPC `Entry` 消息内**跨所有 Solana `Entry` 分组的连续下标(与 golang `shredstream_entries` 扁平 `ti` 对齐),非 slot 级全局序号
13
13
  */
14
14
  var config_js_1 = require("./config.js");
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * ShredStream:外层编译指令 → `parseInstructionUnified`(与 gRPC 指令解析同源)。
3
3
  * `ShredWasmTx` 来自线格式交易经 `@solana/web3.js` 反序列化(见 `wire_to_shred_tx.ts`),非 WASM。
4
- * 完整账户列表需含 V0 ALT 展开,见 `alt_lookup.ts` + `ShredStreamConfig.connection`。
4
+ * `ShredStreamConfig.connection` 时可展开 V0 ALT;无 RPC 时用静态账户表 + 默认 pubkey best-effort。
5
5
  */
6
6
  import type { MessageHeader } from "@solana/web3.js";
7
- import type { DexEvent } from "../core/dex_event.js";
7
+ import { type DexEvent } from "../core/dex_event.js";
8
8
  import type { EventTypeFilter } from "../grpc/types.js";
9
9
  export type ShredWasmCompiledIx = {
10
10
  programIdIndex: number;
@@ -30,5 +30,5 @@ export type ShredWasmTx = {
30
30
  * 使用已解析的完整账户表(静态+ALT)解析外层指令。
31
31
  */
32
32
  export declare function dexEventsFromShredWasmTxWithFullKeys(tx: ShredWasmTx, fullAccountKeys: string[], slot: number, txIndex: number, grpcRecvUs: number, eventTypeFilter?: EventTypeFilter): DexEvent[];
33
- /** 仅静态账户表(无 RPC 时;V0+ALT 交易多数指令无法解析) */
33
+ /** 仅静态账户表(无 RPC 时;V0+ALT 缺失账户以默认 pubkey 占位) */
34
34
  export declare function dexEventsFromShredWasmTx(tx: ShredWasmTx, slot: number, txIndex: number, grpcRecvUs: number, eventTypeFilter?: EventTypeFilter): DexEvent[];
@@ -2,13 +2,37 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dexEventsFromShredWasmTxWithFullKeys = dexEventsFromShredWasmTxWithFullKeys;
4
4
  exports.dexEventsFromShredWasmTx = dexEventsFromShredWasmTx;
5
+ const dex_event_js_1 = require("../core/dex_event.js");
5
6
  const pumpfun_fee_enrich_js_1 = require("../core/pumpfun_fee_enrich.js");
6
7
  const mod_js_1 = require("../instr/mod.js");
8
+ const program_ids_js_1 = require("../instr/program_ids.js");
7
9
  function asU8(b) {
8
10
  if (b instanceof Uint8Array)
9
11
  return b;
10
12
  return Uint8Array.from(b);
11
13
  }
14
+ const UNKNOWN_PROGRAM_CANDIDATES = [
15
+ program_ids_js_1.PUMPFUN_PROGRAM_ID,
16
+ program_ids_js_1.PUMPSWAP_PROGRAM_ID,
17
+ program_ids_js_1.PUMP_FEES_PROGRAM_ID,
18
+ program_ids_js_1.RAYDIUM_LAUNCHLAB_PROGRAM_ID,
19
+ program_ids_js_1.RAYDIUM_CPMM_PROGRAM_ID,
20
+ program_ids_js_1.RAYDIUM_CLMM_PROGRAM_ID,
21
+ program_ids_js_1.RAYDIUM_AMM_V4_PROGRAM_ID,
22
+ program_ids_js_1.ORCA_WHIRLPOOL_PROGRAM_ID,
23
+ program_ids_js_1.METEORA_POOLS_PROGRAM_ID,
24
+ program_ids_js_1.METEORA_DAMM_V2_PROGRAM_ID,
25
+ program_ids_js_1.METEORA_DLMM_PROGRAM_ID,
26
+ ];
27
+ const SHRED_DEFAULT_PUBKEY = (0, dex_event_js_1.defaultPubkey)();
28
+ function ixAccountStrings(fullAccountKeys, accBytes) {
29
+ const accountStrs = [];
30
+ for (let i = 0; i < accBytes.length; i++) {
31
+ const ai = accBytes[i];
32
+ accountStrs.push(ai < fullAccountKeys.length ? fullAccountKeys[ai] : SHRED_DEFAULT_PUBKEY);
33
+ }
34
+ return accountStrs;
35
+ }
12
36
  /**
13
37
  * 使用已解析的完整账户表(静态+ALT)解析外层指令。
14
38
  */
@@ -19,23 +43,22 @@ function dexEventsFromShredWasmTxWithFullKeys(tx, fullAccountKeys, slot, txIndex
19
43
  const out = [];
20
44
  for (const rawIx of ixs) {
21
45
  const pidIdx = rawIx.programIdIndex;
22
- if (!Number.isFinite(pidIdx) || pidIdx < 0 || pidIdx >= fullAccountKeys.length)
46
+ if (!Number.isFinite(pidIdx) || pidIdx < 0)
23
47
  continue;
24
- const programId = fullAccountKeys[pidIdx];
25
48
  const accBytes = asU8(rawIx.accounts);
26
49
  const data = asU8(rawIx.data);
27
- const accountStrs = [];
28
- let oob = false;
29
- for (let i = 0; i < accBytes.length; i++) {
30
- const ai = accBytes[i];
31
- if (ai >= fullAccountKeys.length) {
32
- oob = true;
33
- break;
50
+ const accountStrs = ixAccountStrings(fullAccountKeys, accBytes);
51
+ if (pidIdx >= fullAccountKeys.length) {
52
+ for (const programId of UNKNOWN_PROGRAM_CANDIDATES) {
53
+ const ev = (0, mod_js_1.parseInstructionUnified)(data, accountStrs, tx.signature, slot, txIndex, undefined, grpcRecvUs, eventTypeFilter, programId);
54
+ if (ev) {
55
+ out.push(ev);
56
+ break;
57
+ }
34
58
  }
35
- accountStrs.push(fullAccountKeys[ai]);
36
- }
37
- if (oob)
38
59
  continue;
60
+ }
61
+ const programId = fullAccountKeys[pidIdx];
39
62
  const ev = (0, mod_js_1.parseInstructionUnified)(data, accountStrs, tx.signature, slot, txIndex, undefined, grpcRecvUs, eventTypeFilter, programId);
40
63
  if (ev)
41
64
  out.push(ev);
@@ -43,7 +66,7 @@ function dexEventsFromShredWasmTxWithFullKeys(tx, fullAccountKeys, slot, txIndex
43
66
  (0, pumpfun_fee_enrich_js_1.enrichPumpfunSameTxPostMerge)(out);
44
67
  return out;
45
68
  }
46
- /** 仅静态账户表(无 RPC 时;V0+ALT 交易多数指令无法解析) */
69
+ /** 仅静态账户表(无 RPC 时;V0+ALT 缺失账户以默认 pubkey 占位) */
47
70
  function dexEventsFromShredWasmTx(tx, slot, txIndex, grpcRecvUs, eventTypeFilter) {
48
71
  return dexEventsFromShredWasmTxWithFullKeys(tx, tx.accounts, slot, txIndex, grpcRecvUs, eventTypeFilter);
49
72
  }
@@ -0,0 +1,18 @@
1
+ export type NumericAmount = bigint | number | string;
2
+ export type NormalizedTradeSide = "Buy" | "Sell";
3
+ /**
4
+ * Convert a Q64.64 sqrt price into quote-token units per one base token.
5
+ */
6
+ export declare function sqrtPriceX64ToPrice(sqrtPriceX64: NumericAmount, baseDecimals: number, quoteDecimals: number): number;
7
+ /**
8
+ * Compute quote-token price per one base token from raw vault balances.
9
+ */
10
+ export declare function vaultPriceFromBalances(baseRaw: NumericAmount, quoteRaw: NumericAmount, baseDecimals: number, quoteDecimals: number): number | undefined;
11
+ /**
12
+ * Positive watched-token delta means Buy; negative means Sell.
13
+ */
14
+ export declare function normalizeBuySellFromTokenDelta(tokenDelta: NumericAmount): NormalizedTradeSide | undefined;
15
+ /**
16
+ * If input is quote, the user buys base. If input is base, the user sells base.
17
+ */
18
+ export declare function normalizeBuySellFromInputMint(inputMint: string, baseMint: string, quoteMint: string): NormalizedTradeSide | undefined;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sqrtPriceX64ToPrice = sqrtPriceX64ToPrice;
4
+ exports.vaultPriceFromBalances = vaultPriceFromBalances;
5
+ exports.normalizeBuySellFromTokenDelta = normalizeBuySellFromTokenDelta;
6
+ exports.normalizeBuySellFromInputMint = normalizeBuySellFromInputMint;
7
+ function amountToNumber(value) {
8
+ if (typeof value === "bigint")
9
+ return Number(value);
10
+ if (typeof value === "number")
11
+ return value;
12
+ if (value.trim() === "")
13
+ throw new Error("amount must not be empty");
14
+ return Number(value);
15
+ }
16
+ /**
17
+ * Convert a Q64.64 sqrt price into quote-token units per one base token.
18
+ */
19
+ function sqrtPriceX64ToPrice(sqrtPriceX64, baseDecimals, quoteDecimals) {
20
+ const sqrt = amountToNumber(sqrtPriceX64) / 2 ** 64;
21
+ return sqrt * sqrt * 10 ** (baseDecimals - quoteDecimals);
22
+ }
23
+ /**
24
+ * Compute quote-token price per one base token from raw vault balances.
25
+ */
26
+ function vaultPriceFromBalances(baseRaw, quoteRaw, baseDecimals, quoteDecimals) {
27
+ const base = amountToNumber(baseRaw);
28
+ if (base === 0)
29
+ return undefined;
30
+ return (amountToNumber(quoteRaw) / base) * 10 ** (baseDecimals - quoteDecimals);
31
+ }
32
+ /**
33
+ * Positive watched-token delta means Buy; negative means Sell.
34
+ */
35
+ function normalizeBuySellFromTokenDelta(tokenDelta) {
36
+ const delta = amountToNumber(tokenDelta);
37
+ if (delta > 0)
38
+ return "Buy";
39
+ if (delta < 0)
40
+ return "Sell";
41
+ return undefined;
42
+ }
43
+ /**
44
+ * If input is quote, the user buys base. If input is base, the user sells base.
45
+ */
46
+ function normalizeBuySellFromInputMint(inputMint, baseMint, quoteMint) {
47
+ if (baseMint === quoteMint)
48
+ return undefined;
49
+ if (inputMint === quoteMint)
50
+ return "Buy";
51
+ if (inputMint === baseMint)
52
+ return "Sell";
53
+ return undefined;
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol-parser-sdk-nodejs",
3
- "version": "0.4.4",
3
+ "version": "0.5.5",
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",