sol-parser-sdk-nodejs 0.4.0 → 0.4.4
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 +6 -0
- package/README_CN.md +6 -0
- package/dist/accounts/mod.d.ts +1 -0
- package/dist/accounts/mod.js +14 -1
- package/dist/accounts/pumpfun.d.ts +6 -0
- package/dist/accounts/pumpfun.js +157 -0
- package/dist/accounts/rust_aliases.d.ts +1 -0
- package/dist/accounts/rust_aliases.js +3 -1
- package/dist/core/account_fill_pumpfun.js +17 -0
- package/dist/core/dex_event.d.ts +147 -0
- package/dist/core/pumpfun_fee_enrich.d.ts +4 -0
- package/dist/core/pumpfun_fee_enrich.js +80 -0
- package/dist/core/unified_parser.js +2 -0
- package/dist/grpc/client.d.ts +26 -1
- package/dist/grpc/client.js +279 -0
- package/dist/grpc/log_instr_dedup.d.ts +2 -0
- package/dist/grpc/log_instr_dedup.js +330 -0
- package/dist/grpc/order_buffer.d.ts +27 -0
- package/dist/grpc/order_buffer.js +166 -0
- package/dist/grpc/program_ids.d.ts +1 -0
- package/dist/grpc/program_ids.js +2 -1
- package/dist/grpc/types.d.ts +5 -1
- package/dist/grpc/types.js +101 -0
- package/dist/grpc/yellowstone_parse.d.ts +3 -1
- package/dist/grpc/yellowstone_parse.js +13 -10
- package/dist/index.d.ts +8 -7
- package/dist/index.js +16 -4
- package/dist/instr/mod.d.ts +1 -0
- package/dist/instr/mod.js +9 -1
- package/dist/instr/program_ids.d.ts +1 -1
- package/dist/instr/program_ids.js +2 -1
- package/dist/instr/pump_fees_ix.d.ts +2 -0
- package/dist/instr/pump_fees_ix.js +166 -0
- package/dist/instr/pumpfun_ix.js +57 -0
- package/dist/instr/raydium_clmm_ix.js +73 -52
- package/dist/instr/rust_aliases.d.ts +1 -0
- package/dist/instr/rust_aliases.js +3 -1
- package/dist/logs/discriminator_lut.d.ts +1 -1
- package/dist/logs/discriminator_lut.js +2 -0
- package/dist/logs/optimized_matcher.js +120 -20
- package/dist/logs/program_log_discriminators.d.ts +10 -0
- package/dist/logs/program_log_discriminators.js +10 -0
- package/dist/logs/pump.d.ts +2 -0
- package/dist/logs/pump.js +51 -4
- package/dist/logs/pump_fees.d.ts +23 -0
- package/dist/logs/pump_fees.js +364 -0
- package/dist/rpc_transaction.d.ts +2 -0
- package/dist/rpc_transaction.js +14 -6
- package/dist/shredstream/instruction_parse.js +2 -0
- package/package.json +1 -1
package/dist/grpc/client.js
CHANGED
|
@@ -32,20 +32,72 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.YellowstoneGrpc = void 0;
|
|
37
40
|
/**
|
|
38
41
|
* Yellowstone gRPC 客户端实现 - 基于 @triton-one/yellowstone-grpc
|
|
39
42
|
*/
|
|
40
43
|
const yellowstone_grpc_1 = __importStar(require("@triton-one/yellowstone-grpc"));
|
|
44
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
45
|
+
const metadata_js_1 = require("../core/metadata.js");
|
|
46
|
+
const mod_js_1 = require("../accounts/mod.js");
|
|
41
47
|
const geyser_connect_js_1 = require("./geyser_connect.js");
|
|
48
|
+
const order_buffer_js_1 = require("./order_buffer.js");
|
|
49
|
+
const subscribe_builder_js_1 = require("./subscribe_builder.js");
|
|
50
|
+
const yellowstone_parse_js_1 = require("./yellowstone_parse.js");
|
|
42
51
|
const types_js_1 = require("./types.js");
|
|
52
|
+
class AsyncEventQueue {
|
|
53
|
+
maxSize;
|
|
54
|
+
items = [];
|
|
55
|
+
waiters = [];
|
|
56
|
+
closed = false;
|
|
57
|
+
constructor(maxSize) {
|
|
58
|
+
this.maxSize = maxSize;
|
|
59
|
+
}
|
|
60
|
+
push(item) {
|
|
61
|
+
if (this.closed)
|
|
62
|
+
return;
|
|
63
|
+
const waiter = this.waiters.shift();
|
|
64
|
+
if (waiter) {
|
|
65
|
+
waiter({ value: item, done: false });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this.items.length < this.maxSize) {
|
|
69
|
+
this.items.push(item);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
close() {
|
|
73
|
+
if (this.closed)
|
|
74
|
+
return;
|
|
75
|
+
this.closed = true;
|
|
76
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
77
|
+
waiter({ value: undefined, done: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
next() {
|
|
81
|
+
const item = this.items.shift();
|
|
82
|
+
if (item !== undefined) {
|
|
83
|
+
return Promise.resolve({ value: item, done: false });
|
|
84
|
+
}
|
|
85
|
+
if (this.closed) {
|
|
86
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
87
|
+
}
|
|
88
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
89
|
+
}
|
|
90
|
+
[Symbol.asyncIterator]() {
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
43
94
|
/** Yellowstone gRPC 客户端包装器 */
|
|
44
95
|
class YellowstoneGrpc {
|
|
45
96
|
client;
|
|
46
97
|
config;
|
|
47
98
|
connected = false;
|
|
48
99
|
subscribers = new Map();
|
|
100
|
+
dexSubscribers = new Map();
|
|
49
101
|
constructor(endpoint, xToken, config = (0, types_js_1.defaultClientConfig)()) {
|
|
50
102
|
this.config = config;
|
|
51
103
|
const gc = (0, geyser_connect_js_1.defaultGeyserConnectConfig)();
|
|
@@ -68,6 +120,10 @@ class YellowstoneGrpc {
|
|
|
68
120
|
sub.cancel();
|
|
69
121
|
}
|
|
70
122
|
this.subscribers.clear();
|
|
123
|
+
for (const [, sub] of this.dexSubscribers) {
|
|
124
|
+
sub.cancel();
|
|
125
|
+
}
|
|
126
|
+
this.dexSubscribers.clear();
|
|
71
127
|
this.connected = false;
|
|
72
128
|
}
|
|
73
129
|
/** 检查是否已连接 */
|
|
@@ -96,6 +152,9 @@ class YellowstoneGrpc {
|
|
|
96
152
|
const result = {
|
|
97
153
|
filters: update.filters,
|
|
98
154
|
};
|
|
155
|
+
const createdAtUs = YellowstoneGrpc.timestampToMicros(update.createdAt ?? update.created_at);
|
|
156
|
+
if (createdAtUs !== undefined)
|
|
157
|
+
result.createdAtUs = createdAtUs;
|
|
99
158
|
if (update.account) {
|
|
100
159
|
const acc = update.account;
|
|
101
160
|
result.account = {
|
|
@@ -191,6 +250,58 @@ class YellowstoneGrpc {
|
|
|
191
250
|
ping: { id: 1 },
|
|
192
251
|
};
|
|
193
252
|
}
|
|
253
|
+
static toBytes(value) {
|
|
254
|
+
if (!value)
|
|
255
|
+
return new Uint8Array();
|
|
256
|
+
return value instanceof Uint8Array ? value : Uint8Array.from(value);
|
|
257
|
+
}
|
|
258
|
+
static toBigInt(value) {
|
|
259
|
+
return typeof value === "bigint" ? value : BigInt(value);
|
|
260
|
+
}
|
|
261
|
+
static protobufNumber(value) {
|
|
262
|
+
if (typeof value === "bigint")
|
|
263
|
+
return Number(value);
|
|
264
|
+
if (typeof value === "number")
|
|
265
|
+
return value;
|
|
266
|
+
if (typeof value === "string")
|
|
267
|
+
return Number(value);
|
|
268
|
+
if (value && typeof value === "object") {
|
|
269
|
+
const longLike = value;
|
|
270
|
+
if (typeof longLike.toNumber === "function")
|
|
271
|
+
return longLike.toNumber();
|
|
272
|
+
if (typeof longLike.toString === "function")
|
|
273
|
+
return Number(longLike.toString());
|
|
274
|
+
}
|
|
275
|
+
return Number.NaN;
|
|
276
|
+
}
|
|
277
|
+
static timestampToMicros(value) {
|
|
278
|
+
if (!value || typeof value !== "object")
|
|
279
|
+
return undefined;
|
|
280
|
+
const ts = value;
|
|
281
|
+
if (ts.seconds === undefined)
|
|
282
|
+
return undefined;
|
|
283
|
+
const seconds = YellowstoneGrpc.protobufNumber(ts.seconds);
|
|
284
|
+
const nanos = Number(ts.nanos ?? 0);
|
|
285
|
+
if (!Number.isFinite(seconds) || !Number.isFinite(nanos))
|
|
286
|
+
return undefined;
|
|
287
|
+
return Math.trunc(seconds * 1_000_000 + nanos / 1_000);
|
|
288
|
+
}
|
|
289
|
+
parseAccountEvent(update, grpcRecvUs, eventTypeFilter, blockTimeUs) {
|
|
290
|
+
const acc = update.account;
|
|
291
|
+
if (!acc)
|
|
292
|
+
return null;
|
|
293
|
+
const signatureBytes = YellowstoneGrpc.toBytes(acc.txnSignature);
|
|
294
|
+
const account = {
|
|
295
|
+
pubkey: bs58_1.default.encode(YellowstoneGrpc.toBytes(acc.pubkey)),
|
|
296
|
+
executable: acc.executable,
|
|
297
|
+
lamports: YellowstoneGrpc.toBigInt(acc.lamports),
|
|
298
|
+
owner: bs58_1.default.encode(YellowstoneGrpc.toBytes(acc.owner)),
|
|
299
|
+
rent_epoch: YellowstoneGrpc.toBigInt(acc.rentEpoch),
|
|
300
|
+
data: YellowstoneGrpc.toBytes(acc.data),
|
|
301
|
+
};
|
|
302
|
+
const metadata = (0, metadata_js_1.makeMetadata)(signatureBytes.length > 0 ? bs58_1.default.encode(signatureBytes) : "", Number(update.slot), 0, blockTimeUs, grpcRecvUs);
|
|
303
|
+
return (0, mod_js_1.parseAccountUnified)(account, metadata, eventTypeFilter);
|
|
304
|
+
}
|
|
194
305
|
initialSubscribeRequest(filter) {
|
|
195
306
|
return {
|
|
196
307
|
transactions: {
|
|
@@ -317,6 +428,158 @@ class YellowstoneGrpc {
|
|
|
317
428
|
},
|
|
318
429
|
};
|
|
319
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* 订阅并直接产出 `DexEvent`。
|
|
433
|
+
*
|
|
434
|
+
* 交易更新走统一交易解析(外层/内层指令 + 日志 + 数据填充);账户更新走 `parseAccountUnified`。
|
|
435
|
+
* 事件队列满时丢弃新事件,避免回调阻塞 gRPC 读循环,保持低延迟。
|
|
436
|
+
*/
|
|
437
|
+
async subscribeDexEvents(transactionFilters = [], accountFilters = [], eventTypeFilter, options) {
|
|
438
|
+
await this.connect();
|
|
439
|
+
const id = `dex_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
440
|
+
const events = new AsyncEventQueue(Math.max(1, this.config.buffer_size));
|
|
441
|
+
const errors = new AsyncEventQueue(Math.max(1, this.config.buffer_size));
|
|
442
|
+
const order = new order_buffer_js_1.OrderDispatcher(this.config);
|
|
443
|
+
const emitEvent = (event) => events.push(event);
|
|
444
|
+
let flushTimer;
|
|
445
|
+
if (order.needsTimer) {
|
|
446
|
+
const intervalMs = this.config.order_mode === "MicroBatch"
|
|
447
|
+
? Math.max(1, Math.ceil(this.config.micro_batch_us / 1000))
|
|
448
|
+
: Math.max(1, Math.floor(this.config.order_timeout_ms / 2));
|
|
449
|
+
flushTimer = setInterval(() => order.flushDue(emitEvent), intervalMs);
|
|
450
|
+
}
|
|
451
|
+
let isCancelled = false;
|
|
452
|
+
let currentTransactionFilters = transactionFilters;
|
|
453
|
+
let currentAccountFilters = accountFilters;
|
|
454
|
+
const streamHolder = { current: null };
|
|
455
|
+
const cancel = () => {
|
|
456
|
+
isCancelled = true;
|
|
457
|
+
streamHolder.current?.end();
|
|
458
|
+
streamHolder.current = null;
|
|
459
|
+
if (flushTimer)
|
|
460
|
+
clearInterval(flushTimer);
|
|
461
|
+
events.close();
|
|
462
|
+
errors.close();
|
|
463
|
+
};
|
|
464
|
+
const update = async (nextTxFilters, nextAccFilters) => {
|
|
465
|
+
currentTransactionFilters = nextTxFilters;
|
|
466
|
+
currentAccountFilters = nextAccFilters;
|
|
467
|
+
const stream = streamHolder.current;
|
|
468
|
+
if (!stream)
|
|
469
|
+
return;
|
|
470
|
+
await new Promise((resolve, reject) => {
|
|
471
|
+
stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
|
|
472
|
+
if (err)
|
|
473
|
+
reject(err);
|
|
474
|
+
else
|
|
475
|
+
resolve();
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
this.dexSubscribers.set(id, { cancel, update });
|
|
480
|
+
(async () => {
|
|
481
|
+
const autoReconnect = options?.autoReconnect !== false;
|
|
482
|
+
const maxBackoffMs = 60_000;
|
|
483
|
+
let backoffMs = this.config.retry_delay_ms;
|
|
484
|
+
try {
|
|
485
|
+
while (!isCancelled) {
|
|
486
|
+
try {
|
|
487
|
+
const stream = await this.client.subscribe();
|
|
488
|
+
streamHolder.current = stream;
|
|
489
|
+
await new Promise((resolve, reject) => {
|
|
490
|
+
stream.write((0, subscribe_builder_js_1.buildSubscribeRequest)(currentTransactionFilters, currentAccountFilters), (err) => {
|
|
491
|
+
if (err)
|
|
492
|
+
reject(err);
|
|
493
|
+
else
|
|
494
|
+
resolve();
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
backoffMs = this.config.retry_delay_ms;
|
|
498
|
+
await new Promise((resolve, reject) => {
|
|
499
|
+
const cleanup = () => {
|
|
500
|
+
stream.removeAllListeners();
|
|
501
|
+
if (streamHolder.current === stream) {
|
|
502
|
+
streamHolder.current = null;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
stream.on("data", (update) => {
|
|
506
|
+
if (isCancelled)
|
|
507
|
+
return;
|
|
508
|
+
order.flushDue(emitEvent);
|
|
509
|
+
if (update.ping) {
|
|
510
|
+
stream.write(this.subscribePingPongRequest(), (werr) => {
|
|
511
|
+
if (werr && !isCancelled) {
|
|
512
|
+
errors.push(werr instanceof Error ? werr : new Error(String(werr)));
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
const grpcRecvUs = Math.floor(Date.now() * 1000);
|
|
519
|
+
const converted = this.convertUpdate(update);
|
|
520
|
+
const tx = converted.transaction?.transaction;
|
|
521
|
+
if (tx) {
|
|
522
|
+
const fallbackSlot = YellowstoneGrpc.protobufNumber(converted.transaction?.slot ?? 0);
|
|
523
|
+
const fallbackTxIndex = YellowstoneGrpc.protobufNumber(tx.index ?? 0);
|
|
524
|
+
const txEvents = (0, yellowstone_parse_js_1.parseDexEventsFromGrpcTransactionInfo)(tx, converted.transaction?.slot ?? 0n, { blockTimeUs: converted.createdAtUs, grpcRecvUs, eventTypeFilter });
|
|
525
|
+
order.pushTransactionEvents(txEvents, fallbackSlot, fallbackTxIndex, emitEvent);
|
|
526
|
+
}
|
|
527
|
+
if (converted.account) {
|
|
528
|
+
const ev = this.parseAccountEvent(converted.account, grpcRecvUs, eventTypeFilter, converted.createdAtUs);
|
|
529
|
+
if (ev)
|
|
530
|
+
emitEvent(ev);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
stream.on("error", (err) => {
|
|
538
|
+
cleanup();
|
|
539
|
+
reject(err);
|
|
540
|
+
});
|
|
541
|
+
stream.on("end", () => {
|
|
542
|
+
cleanup();
|
|
543
|
+
resolve();
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
catch (err) {
|
|
548
|
+
if (isCancelled)
|
|
549
|
+
break;
|
|
550
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
551
|
+
if (!autoReconnect)
|
|
552
|
+
break;
|
|
553
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
554
|
+
backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (isCancelled || !autoReconnect)
|
|
558
|
+
break;
|
|
559
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
560
|
+
backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
finally {
|
|
564
|
+
streamHolder.current?.end();
|
|
565
|
+
streamHolder.current = null;
|
|
566
|
+
if (flushTimer)
|
|
567
|
+
clearInterval(flushTimer);
|
|
568
|
+
order.flushAll(emitEvent);
|
|
569
|
+
events.close();
|
|
570
|
+
errors.close();
|
|
571
|
+
this.dexSubscribers.delete(id);
|
|
572
|
+
}
|
|
573
|
+
})();
|
|
574
|
+
return Object.assign(events, {
|
|
575
|
+
id,
|
|
576
|
+
errors,
|
|
577
|
+
cancel: () => {
|
|
578
|
+
cancel();
|
|
579
|
+
this.dexSubscribers.delete(id);
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
}
|
|
320
583
|
/** 取消订阅 */
|
|
321
584
|
unsubscribe(subId) {
|
|
322
585
|
const sub = this.subscribers.get(subId);
|
|
@@ -324,6 +587,22 @@ class YellowstoneGrpc {
|
|
|
324
587
|
sub.cancel();
|
|
325
588
|
this.subscribers.delete(subId);
|
|
326
589
|
}
|
|
590
|
+
const dexSub = this.dexSubscribers.get(subId);
|
|
591
|
+
if (dexSub) {
|
|
592
|
+
dexSub.cancel();
|
|
593
|
+
this.dexSubscribers.delete(subId);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/** 动态更新当前 DEX 订阅过滤器(与 Rust update_subscription 语义对齐)。 */
|
|
597
|
+
async updateSubscription(transactionFilters, accountFilters) {
|
|
598
|
+
const active = this.dexSubscribers.values().next().value;
|
|
599
|
+
if (!active?.update) {
|
|
600
|
+
throw new Error("No active DEX subscription");
|
|
601
|
+
}
|
|
602
|
+
await active.update(transactionFilters, accountFilters);
|
|
603
|
+
}
|
|
604
|
+
async update_subscription(transactionFilters, accountFilters) {
|
|
605
|
+
await this.updateSubscription(transactionFilters, accountFilters);
|
|
327
606
|
}
|
|
328
607
|
/** 获取最新区块哈希 */
|
|
329
608
|
async getLatestBlockhash(commitment) {
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dedupeLogInstructionEvents = dedupeLogInstructionEvents;
|
|
4
|
+
const dex_event_js_1 = require("../core/dex_event.js");
|
|
5
|
+
const ZERO = (0, dex_event_js_1.defaultPubkey)();
|
|
6
|
+
const PUMPFUN_TRADE_NAMES = new Set([
|
|
7
|
+
"PumpFunTrade",
|
|
8
|
+
"PumpFunBuy",
|
|
9
|
+
"PumpFunSell",
|
|
10
|
+
"PumpFunBuyExactSolIn",
|
|
11
|
+
]);
|
|
12
|
+
function eventName(ev) {
|
|
13
|
+
return Object.keys(ev)[0] ?? "";
|
|
14
|
+
}
|
|
15
|
+
function payload(ev) {
|
|
16
|
+
const key = eventName(ev);
|
|
17
|
+
return key ? ev[key] : null;
|
|
18
|
+
}
|
|
19
|
+
function isDefaultPubkey(v) {
|
|
20
|
+
return v === "" || v === ZERO || v == null;
|
|
21
|
+
}
|
|
22
|
+
function fillString(to, key, from) {
|
|
23
|
+
const value = from[key];
|
|
24
|
+
if (isDefaultPubkey(to[key]) && !isDefaultPubkey(value)) {
|
|
25
|
+
to[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function fillNestedString(to, parent, key, from) {
|
|
29
|
+
const srcParent = from[parent];
|
|
30
|
+
if (!srcParent || typeof srcParent !== "object")
|
|
31
|
+
return;
|
|
32
|
+
if (!to[parent] || typeof to[parent] !== "object")
|
|
33
|
+
to[parent] = {};
|
|
34
|
+
fillString(to[parent], key, srcParent);
|
|
35
|
+
}
|
|
36
|
+
function fillIxName(to, from) {
|
|
37
|
+
if ((to.ix_name ?? "") === "" && (from.ix_name ?? "") !== "") {
|
|
38
|
+
to.ix_name = from.ix_name;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function ixLane(ixName) {
|
|
42
|
+
switch (ixName) {
|
|
43
|
+
case "sell":
|
|
44
|
+
return 1;
|
|
45
|
+
case "buy_exact_sol_in":
|
|
46
|
+
return 2;
|
|
47
|
+
default:
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function nextOccurrence(base, counts) {
|
|
52
|
+
const current = counts.get(base) ?? 0;
|
|
53
|
+
counts.set(base, current + 1);
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
56
|
+
function dedupeKey(ev, pumpfunLaneCounts) {
|
|
57
|
+
const name = eventName(ev);
|
|
58
|
+
const data = payload(ev);
|
|
59
|
+
if (!data)
|
|
60
|
+
return null;
|
|
61
|
+
if (PUMPFUN_TRADE_NAMES.has(name)) {
|
|
62
|
+
const lane = ixLane(data.ix_name);
|
|
63
|
+
const base = `${data.mint}|${data.user}|${Boolean(data.is_buy)}|${lane}`;
|
|
64
|
+
const occurrence = nextOccurrence(base, pumpfunLaneCounts);
|
|
65
|
+
return `PumpFunTrade|${base}|${occurrence}`;
|
|
66
|
+
}
|
|
67
|
+
switch (name) {
|
|
68
|
+
case "PumpFunCreate":
|
|
69
|
+
return `PumpFunCreate|${data.mint}`;
|
|
70
|
+
case "PumpFunCreateV2":
|
|
71
|
+
return `PumpFunCreateV2|${data.mint}`;
|
|
72
|
+
case "PumpFunMigrate":
|
|
73
|
+
return `PumpFunMigrate|${data.mint}|${data.pool}|${data.user}`;
|
|
74
|
+
case "BonkTrade":
|
|
75
|
+
return `BonkTrade|${data.pool_state}|${data.user}|${Boolean(data.is_buy)}`;
|
|
76
|
+
case "BonkPoolCreate":
|
|
77
|
+
return `BonkPoolCreate|${data.pool_state}`;
|
|
78
|
+
case "BonkMigrateAmm":
|
|
79
|
+
return `BonkMigrateAmm|${data.old_pool}|${data.new_pool}|${data.user}`;
|
|
80
|
+
case "PumpSwapTrade":
|
|
81
|
+
return `PumpSwapTrade|${data.mint}|${data.user}|${Boolean(data.is_buy)}|${ixLane(data.ix_name)}`;
|
|
82
|
+
case "PumpSwapBuy":
|
|
83
|
+
return `PumpSwapBuy|${data.pool}|${data.user}`;
|
|
84
|
+
case "PumpSwapSell":
|
|
85
|
+
return `PumpSwapSell|${data.pool}|${data.user}`;
|
|
86
|
+
case "PumpSwapCreatePool":
|
|
87
|
+
return `PumpSwapCreatePool|${data.pool}|${data.base_mint}|${data.quote_mint}`;
|
|
88
|
+
case "PumpSwapLiquidityAdded":
|
|
89
|
+
return `PumpSwapLiquidityAdded|${data.pool}|${data.user}`;
|
|
90
|
+
case "PumpSwapLiquidityRemoved":
|
|
91
|
+
return `PumpSwapLiquidityRemoved|${data.pool}|${data.user}`;
|
|
92
|
+
case "RaydiumClmmSwap":
|
|
93
|
+
return `RaydiumClmmSwap|${data.pool_state}|${Boolean(data.zero_for_one)}`;
|
|
94
|
+
case "RaydiumAmmV4Swap":
|
|
95
|
+
return `RaydiumAmmV4Swap|${data.amm}`;
|
|
96
|
+
case "MeteoraDlmmSwap":
|
|
97
|
+
return `MeteoraDlmmSwap|${data.pool}|${data.from}|${Boolean(data.swap_for_y)}`;
|
|
98
|
+
default:
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function mergePumpfunTrade(log, ix) {
|
|
103
|
+
for (const key of [
|
|
104
|
+
"bonding_curve",
|
|
105
|
+
"associated_bonding_curve",
|
|
106
|
+
"token_program",
|
|
107
|
+
"creator_vault",
|
|
108
|
+
"fee_recipient",
|
|
109
|
+
"creator",
|
|
110
|
+
]) {
|
|
111
|
+
fillString(log, key, ix);
|
|
112
|
+
}
|
|
113
|
+
if (log.account == null && ix.account != null)
|
|
114
|
+
log.account = ix.account;
|
|
115
|
+
fillIxName(log, ix);
|
|
116
|
+
log.is_created_buy = Boolean(log.is_created_buy) || Boolean(ix.is_created_buy);
|
|
117
|
+
}
|
|
118
|
+
function mergePumpfunCreate(log, ix) {
|
|
119
|
+
for (const key of [
|
|
120
|
+
"name",
|
|
121
|
+
"symbol",
|
|
122
|
+
"uri",
|
|
123
|
+
"bonding_curve",
|
|
124
|
+
"user",
|
|
125
|
+
"creator",
|
|
126
|
+
"token_program",
|
|
127
|
+
]) {
|
|
128
|
+
fillString(log, key, ix);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function mergePumpfunCreateV2(log, ix) {
|
|
132
|
+
mergePumpfunCreate(log, ix);
|
|
133
|
+
for (const key of [
|
|
134
|
+
"mint_authority",
|
|
135
|
+
"associated_bonding_curve",
|
|
136
|
+
"global",
|
|
137
|
+
"system_program",
|
|
138
|
+
"associated_token_program",
|
|
139
|
+
"mayhem_program_id",
|
|
140
|
+
"global_params",
|
|
141
|
+
"sol_vault",
|
|
142
|
+
"mayhem_state",
|
|
143
|
+
"mayhem_token_vault",
|
|
144
|
+
"event_authority",
|
|
145
|
+
"program",
|
|
146
|
+
"observed_fee_recipient",
|
|
147
|
+
]) {
|
|
148
|
+
fillString(log, key, ix);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function mergePumpfunMigrate(log, ix) {
|
|
152
|
+
for (const key of ["bonding_curve", "pool", "user"]) {
|
|
153
|
+
fillString(log, key, ix);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function mergePumpSwapBuySell(log, ix, includeIxName) {
|
|
157
|
+
for (const key of [
|
|
158
|
+
"user_base_token_account",
|
|
159
|
+
"user_quote_token_account",
|
|
160
|
+
"protocol_fee_recipient",
|
|
161
|
+
"protocol_fee_recipient_token_account",
|
|
162
|
+
"coin_creator",
|
|
163
|
+
"base_mint",
|
|
164
|
+
"quote_mint",
|
|
165
|
+
"pool_base_token_account",
|
|
166
|
+
"pool_quote_token_account",
|
|
167
|
+
"coin_creator_vault_ata",
|
|
168
|
+
"coin_creator_vault_authority",
|
|
169
|
+
"base_token_program",
|
|
170
|
+
"quote_token_program",
|
|
171
|
+
]) {
|
|
172
|
+
fillString(log, key, ix);
|
|
173
|
+
}
|
|
174
|
+
if (includeIxName)
|
|
175
|
+
fillIxName(log, ix);
|
|
176
|
+
}
|
|
177
|
+
function mergePumpSwapCreatePool(log, ix) {
|
|
178
|
+
for (const key of [
|
|
179
|
+
"creator",
|
|
180
|
+
"pool",
|
|
181
|
+
"lp_mint",
|
|
182
|
+
"user_base_token_account",
|
|
183
|
+
"user_quote_token_account",
|
|
184
|
+
"coin_creator",
|
|
185
|
+
]) {
|
|
186
|
+
fillString(log, key, ix);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function mergePumpSwapLiquidity(log, ix) {
|
|
190
|
+
for (const key of [
|
|
191
|
+
"user_base_token_account",
|
|
192
|
+
"user_quote_token_account",
|
|
193
|
+
"user_pool_token_account",
|
|
194
|
+
]) {
|
|
195
|
+
fillString(log, key, ix);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function mergeRaydiumClmmSwap(log, ix) {
|
|
199
|
+
for (const key of ["token_account_0", "token_account_1", "sender"]) {
|
|
200
|
+
fillString(log, key, ix);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function mergeRaydiumAmmV4Swap(log, ix) {
|
|
204
|
+
for (const key of [
|
|
205
|
+
"token_program",
|
|
206
|
+
"amm_authority",
|
|
207
|
+
"amm_open_orders",
|
|
208
|
+
"amm_target_orders",
|
|
209
|
+
"pool_coin_token_account",
|
|
210
|
+
"pool_pc_token_account",
|
|
211
|
+
"serum_program",
|
|
212
|
+
"serum_market",
|
|
213
|
+
"serum_bids",
|
|
214
|
+
"serum_asks",
|
|
215
|
+
"serum_event_queue",
|
|
216
|
+
"serum_coin_vault_account",
|
|
217
|
+
"serum_pc_vault_account",
|
|
218
|
+
"serum_vault_signer",
|
|
219
|
+
"user_source_token_account",
|
|
220
|
+
"user_destination_token_account",
|
|
221
|
+
]) {
|
|
222
|
+
fillString(log, key, ix);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function mergeBonkPoolCreate(log, ix) {
|
|
226
|
+
fillString(log, "creator", ix);
|
|
227
|
+
for (const key of ["name", "symbol", "uri"]) {
|
|
228
|
+
fillNestedString(log, "base_mint_param", key, ix);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function mergeBonkMigrateAmm(log, ix) {
|
|
232
|
+
for (const key of ["old_pool", "new_pool", "user"]) {
|
|
233
|
+
fillString(log, key, ix);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function mergeGrpcInstructionIntoLog(logEvent, ixEvent) {
|
|
237
|
+
const logName = eventName(logEvent);
|
|
238
|
+
const ixName = eventName(ixEvent);
|
|
239
|
+
const log = payload(logEvent);
|
|
240
|
+
const ix = payload(ixEvent);
|
|
241
|
+
if (!log || !ix)
|
|
242
|
+
return;
|
|
243
|
+
if (PUMPFUN_TRADE_NAMES.has(logName) && PUMPFUN_TRADE_NAMES.has(ixName)) {
|
|
244
|
+
mergePumpfunTrade(log, ix);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
switch (logName) {
|
|
248
|
+
case "PumpFunCreate":
|
|
249
|
+
if (ixName === "PumpFunCreate")
|
|
250
|
+
mergePumpfunCreate(log, ix);
|
|
251
|
+
break;
|
|
252
|
+
case "PumpFunCreateV2":
|
|
253
|
+
if (ixName === "PumpFunCreateV2")
|
|
254
|
+
mergePumpfunCreateV2(log, ix);
|
|
255
|
+
break;
|
|
256
|
+
case "PumpFunMigrate":
|
|
257
|
+
if (ixName === "PumpFunMigrate")
|
|
258
|
+
mergePumpfunMigrate(log, ix);
|
|
259
|
+
break;
|
|
260
|
+
case "PumpSwapTrade":
|
|
261
|
+
if (ixName === "PumpSwapTrade")
|
|
262
|
+
fillIxName(log, ix);
|
|
263
|
+
break;
|
|
264
|
+
case "PumpSwapBuy":
|
|
265
|
+
if (ixName === "PumpSwapBuy")
|
|
266
|
+
mergePumpSwapBuySell(log, ix, true);
|
|
267
|
+
break;
|
|
268
|
+
case "PumpSwapSell":
|
|
269
|
+
if (ixName === "PumpSwapSell")
|
|
270
|
+
mergePumpSwapBuySell(log, ix, false);
|
|
271
|
+
break;
|
|
272
|
+
case "PumpSwapCreatePool":
|
|
273
|
+
if (ixName === "PumpSwapCreatePool")
|
|
274
|
+
mergePumpSwapCreatePool(log, ix);
|
|
275
|
+
break;
|
|
276
|
+
case "PumpSwapLiquidityAdded":
|
|
277
|
+
if (ixName === "PumpSwapLiquidityAdded")
|
|
278
|
+
mergePumpSwapLiquidity(log, ix);
|
|
279
|
+
break;
|
|
280
|
+
case "PumpSwapLiquidityRemoved":
|
|
281
|
+
if (ixName === "PumpSwapLiquidityRemoved")
|
|
282
|
+
mergePumpSwapLiquidity(log, ix);
|
|
283
|
+
break;
|
|
284
|
+
case "RaydiumClmmSwap":
|
|
285
|
+
if (ixName === "RaydiumClmmSwap")
|
|
286
|
+
mergeRaydiumClmmSwap(log, ix);
|
|
287
|
+
break;
|
|
288
|
+
case "RaydiumAmmV4Swap":
|
|
289
|
+
if (ixName === "RaydiumAmmV4Swap")
|
|
290
|
+
mergeRaydiumAmmV4Swap(log, ix);
|
|
291
|
+
break;
|
|
292
|
+
case "BonkPoolCreate":
|
|
293
|
+
if (ixName === "BonkPoolCreate")
|
|
294
|
+
mergeBonkPoolCreate(log, ix);
|
|
295
|
+
break;
|
|
296
|
+
case "BonkMigrateAmm":
|
|
297
|
+
if (ixName === "BonkMigrateAmm")
|
|
298
|
+
mergeBonkMigrateAmm(log, ix);
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function dedupeLogInstructionEvents(logEvents, instructionEvents) {
|
|
305
|
+
const out = [];
|
|
306
|
+
const indexByKey = new Map();
|
|
307
|
+
const logPumpfunLaneCounts = new Map();
|
|
308
|
+
const ixPumpfunLaneCounts = new Map();
|
|
309
|
+
for (const ev of logEvents) {
|
|
310
|
+
const key = dedupeKey(ev, logPumpfunLaneCounts);
|
|
311
|
+
if (key)
|
|
312
|
+
indexByKey.set(key, out.length);
|
|
313
|
+
out.push(ev);
|
|
314
|
+
}
|
|
315
|
+
for (const ev of instructionEvents) {
|
|
316
|
+
const key = dedupeKey(ev, ixPumpfunLaneCounts);
|
|
317
|
+
if (!key) {
|
|
318
|
+
out.push(ev);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const existing = indexByKey.get(key);
|
|
322
|
+
if (existing === undefined) {
|
|
323
|
+
indexByKey.set(key, out.length);
|
|
324
|
+
out.push(ev);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
mergeGrpcInstructionIntoLog(out[existing], ev);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DexEvent } from "../core/dex_event.js";
|
|
2
|
+
import type { ClientConfig } from "./types.js";
|
|
3
|
+
export declare class OrderDispatcher {
|
|
4
|
+
private readonly mode;
|
|
5
|
+
private readonly timeoutMs;
|
|
6
|
+
private readonly microBatchUs;
|
|
7
|
+
private readonly slots;
|
|
8
|
+
private readonly streamingWatermarks;
|
|
9
|
+
private microBatch;
|
|
10
|
+
private microBatchStartUs;
|
|
11
|
+
private lastFlushMs;
|
|
12
|
+
private currentSlot;
|
|
13
|
+
private seq;
|
|
14
|
+
constructor(config: ClientConfig);
|
|
15
|
+
get needsTimer(): boolean;
|
|
16
|
+
pushTransactionEvents(events: DexEvent[], fallbackSlot: number, fallbackTxIndex: number, emit: (event: DexEvent) => void): void;
|
|
17
|
+
flushDue(emit: (event: DexEvent) => void): void;
|
|
18
|
+
flushAll(emit: (event: DexEvent) => void): void;
|
|
19
|
+
private pushOrdered;
|
|
20
|
+
private pushStreaming;
|
|
21
|
+
private pushMicroBatch;
|
|
22
|
+
private pushSlotBatch;
|
|
23
|
+
private flushBefore;
|
|
24
|
+
private flushAllSlots;
|
|
25
|
+
private flushMicroBatch;
|
|
26
|
+
private emitBatch;
|
|
27
|
+
}
|