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.
- package/README.md +16 -4
- package/README_CN.md +16 -4
- package/dist/accounts/mod.d.ts +1 -1
- package/dist/accounts/mod.js +43 -9
- package/dist/accounts/pumpfun.d.ts +10 -0
- package/dist/accounts/pumpfun.js +384 -3
- package/dist/core/account_dispatcher_rpc.js +5 -26
- package/dist/core/account_fill_meteora.d.ts +1 -2
- package/dist/core/account_fill_meteora.js +0 -2
- package/dist/core/account_fill_pumpfun.js +86 -24
- package/dist/core/account_fill_pumpswap.js +50 -0
- package/dist/core/account_fill_raydium_launchlab.d.ts +4 -0
- package/dist/core/account_fill_raydium_launchlab.js +20 -0
- package/dist/core/dex_event.d.ts +136 -17
- package/dist/core/pumpfun_fee_enrich.d.ts +1 -0
- package/dist/core/pumpfun_fee_enrich.js +51 -0
- package/dist/grpc/log_instr_dedup.js +62 -14
- package/dist/grpc/program_ids.d.ts +4 -3
- package/dist/grpc/program_ids.js +11 -5
- package/dist/grpc/types.d.ts +13 -6
- package/dist/grpc/types.js +250 -239
- package/dist/index.d.ts +6 -5
- package/dist/index.js +21 -7
- package/dist/instr/meteora_damm_ix.js +4 -1
- package/dist/instr/meteora_dlmm_ix.d.ts +2 -0
- package/dist/instr/meteora_dlmm_ix.js +134 -0
- package/dist/instr/meteora_pools_ix.d.ts +2 -0
- package/dist/instr/meteora_pools_ix.js +78 -0
- package/dist/instr/mod.d.ts +3 -1
- package/dist/instr/mod.js +38 -19
- package/dist/instr/program_ids.d.ts +1 -5
- package/dist/instr/program_ids.js +3 -6
- package/dist/instr/pumpfun_ix.js +219 -5
- package/dist/instr/pumpswap_ix.js +36 -2
- package/dist/instr/raydium_launchlab_ix.d.ts +8 -0
- package/dist/instr/raydium_launchlab_ix.js +125 -0
- package/dist/instr/rust_aliases.d.ts +2 -0
- package/dist/instr/rust_aliases.js +5 -1
- package/dist/logs/optimized_matcher.js +14 -9
- package/dist/logs/pump.js +76 -3
- package/dist/logs/pump_amm.js +1 -1
- package/dist/logs/raydium_launchlab.d.ts +10 -0
- package/dist/logs/raydium_launchlab.js +84 -0
- package/dist/shredstream/client.d.ts +4 -1
- package/dist/shredstream/client.js +18 -14
- package/dist/shredstream/index.d.ts +1 -1
- package/dist/shredstream/index.js +1 -1
- package/dist/shredstream/instruction_parse.d.ts +3 -3
- package/dist/shredstream/instruction_parse.js +36 -13
- package/dist/util/market.d.ts +18 -0
- package/dist/util/market.js +54 -0
- 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"
|
|
211
|
+
if (ix_name === "buy")
|
|
147
212
|
return { PumpFunBuy: trade };
|
|
148
|
-
if (ix_name === "sell"
|
|
213
|
+
if (ix_name === "sell")
|
|
149
214
|
return { PumpFunSell: trade };
|
|
150
|
-
if (ix_name === "buy_exact_sol_in"
|
|
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
|
}
|
package/dist/logs/pump_amm.js
CHANGED
|
@@ -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 =
|
|
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,
|
|
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,
|
|
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
|
-
*
|
|
4
|
+
* 有 `ShredStreamConfig.connection` 时可展开 V0 ALT;无 RPC 时用静态账户表 + 默认 pubkey best-effort。
|
|
5
5
|
*/
|
|
6
6
|
import type { MessageHeader } from "@solana/web3.js";
|
|
7
|
-
import type
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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