sol-parser-sdk-nodejs 0.3.0 → 0.4.0
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/.env.example +24 -0
- package/README.md +94 -352
- package/README_CN.md +101 -253
- package/dist/accounts/mod.d.ts +2 -0
- package/dist/accounts/mod.js +5 -1
- package/dist/accounts/rpc_wallet.d.ts +5 -0
- package/dist/accounts/rpc_wallet.js +18 -0
- package/dist/accounts/rust_aliases.d.ts +9 -0
- package/dist/accounts/rust_aliases.js +19 -0
- package/dist/accounts/wallet_resolve.d.ts +1 -0
- package/dist/accounts/wallet_resolve.js +28 -0
- package/dist/common/constants.d.ts +10 -0
- package/dist/common/constants.js +13 -0
- package/dist/core/account_dispatcher_rpc.js +26 -5
- package/dist/core/account_fill_meteora.d.ts +4 -2
- package/dist/core/account_fill_meteora.js +5 -2
- package/dist/core/account_pubkey_cache.d.ts +12 -0
- package/dist/core/account_pubkey_cache.js +26 -0
- package/dist/core/clock.d.ts +6 -0
- package/dist/core/clock.js +13 -0
- package/dist/core/dex_event.d.ts +25 -44
- package/dist/core/metadata.d.ts +1 -0
- package/dist/core/unified_parser.d.ts +2 -2
- package/dist/core/unified_parser.js +6 -6
- package/dist/grpc/client.d.ts +6 -0
- package/dist/grpc/client.js +121 -64
- package/dist/grpc/event_parser.d.ts +6 -0
- package/dist/grpc/event_parser.js +15 -0
- package/dist/grpc/geyser_connect.d.ts +30 -0
- package/dist/grpc/geyser_connect.js +40 -0
- package/dist/grpc/program_ids.d.ts +25 -0
- package/dist/grpc/program_ids.js +54 -0
- package/dist/grpc/rpc_to_grpc.d.ts +18 -0
- package/dist/grpc/rpc_to_grpc.js +127 -0
- package/dist/grpc/subscribe_builder.d.ts +13 -0
- package/dist/grpc/subscribe_builder.js +66 -0
- package/dist/grpc/transaction_meta.d.ts +29 -0
- package/dist/grpc/transaction_meta.js +208 -0
- package/dist/grpc/types.d.ts +53 -2
- package/dist/grpc/types.js +98 -7
- package/dist/grpc/yellowstone_parse.d.ts +5 -0
- package/dist/grpc/yellowstone_parse.js +15 -2
- package/dist/index.d.ts +36 -6
- package/dist/index.js +172 -2
- package/dist/instr/bonk_ix.d.ts +4 -1
- package/dist/instr/bonk_ix.js +106 -27
- package/dist/instr/meteora_damm_ix.d.ts +4 -2
- package/dist/instr/meteora_damm_ix.js +248 -13
- package/dist/instr/mod.js +7 -2
- package/dist/instr/orca_whirlpool_ix.d.ts +4 -1
- package/dist/instr/orca_whirlpool_ix.js +45 -16
- package/dist/instr/program_ids.d.ts +7 -13
- package/dist/instr/program_ids.js +19 -15
- package/dist/instr/pumpswap_ix.d.ts +1 -1
- package/dist/instr/pumpswap_ix.js +78 -57
- package/dist/instr/raydium_amm_v4_ix.d.ts +1 -1
- package/dist/instr/raydium_amm_v4_ix.js +94 -28
- package/dist/instr/raydium_clmm_ix.d.ts +1 -1
- package/dist/instr/raydium_clmm_ix.js +59 -26
- package/dist/instr/raydium_cpmm_ix.d.ts +1 -1
- package/dist/instr/raydium_cpmm_ix.js +46 -12
- package/dist/instr/rust_aliases.d.ts +7 -0
- package/dist/instr/rust_aliases.js +14 -0
- package/dist/instr/utils.d.ts +1 -1
- package/dist/instr/utils.js +2 -1
- package/dist/logs/discriminator_lut.d.ts +19 -0
- package/dist/logs/discriminator_lut.js +60 -0
- package/dist/logs/meteora_damm.d.ts +3 -4
- package/dist/logs/meteora_damm.js +3 -369
- package/dist/logs/optimized_matcher.d.ts +2 -2
- package/dist/logs/optimized_matcher.js +3 -3
- package/dist/logs/rust_aliases.d.ts +6 -0
- package/dist/logs/rust_aliases.js +13 -0
- package/dist/rpc_parser.d.ts +1 -0
- package/dist/rpc_parser.js +4 -1
- package/dist/shredstream/alt_lookup.d.ts +9 -0
- package/dist/shredstream/alt_lookup.js +70 -0
- package/dist/shredstream/client.d.ts +62 -0
- package/dist/shredstream/client.js +399 -0
- package/dist/shredstream/config.d.ts +30 -0
- package/dist/shredstream/config.js +34 -0
- package/dist/shredstream/entries_decode.d.ts +28 -0
- package/dist/shredstream/entries_decode.js +251 -0
- package/dist/shredstream/index.d.ts +17 -0
- package/dist/shredstream/index.js +33 -0
- package/dist/shredstream/instruction_parse.d.ts +34 -0
- package/dist/shredstream/instruction_parse.js +47 -0
- package/dist/shredstream/proto_types.d.ts +9 -0
- package/dist/shredstream/proto_types.js +2 -0
- package/dist/shredstream/shredstream.proto +15 -0
- package/dist/shredstream/wire_to_shred_tx.d.ts +2 -0
- package/dist/shredstream/wire_to_shred_tx.js +59 -0
- package/package.json +28 -11
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type DexEvent } from "../core/dex_event.js";
|
|
2
|
+
import { type ShredStreamConfig } from "./config.js";
|
|
3
|
+
/** 订阅周期内累计(用于排查「无 gRPC 包 / 解码失败 / 无 DEX 事件」) */
|
|
4
|
+
export type ShredStreamReceiveStats = {
|
|
5
|
+
entryMessagesReceived: number;
|
|
6
|
+
entryDecodeFailures: number;
|
|
7
|
+
transactionsDecoded: number;
|
|
8
|
+
dexEventsQueued: number;
|
|
9
|
+
};
|
|
10
|
+
/** 有界 FIFO 队列(容量与 Rust `ArrayQueue::new(100_000)` 一致;`clone()` 共享底层缓冲,同 `Arc<ArrayQueue>`) */
|
|
11
|
+
export declare class ShredEventQueue {
|
|
12
|
+
private static readonly CAP;
|
|
13
|
+
private readonly buf;
|
|
14
|
+
constructor(sharedBuf?: DexEvent[]);
|
|
15
|
+
/** 当前元素个数(与 Rust `ArrayQueue::len` 一致) */
|
|
16
|
+
len(): number;
|
|
17
|
+
/** 最大容量 */
|
|
18
|
+
capacity(): number;
|
|
19
|
+
push(e: DexEvent): boolean;
|
|
20
|
+
pop(): DexEvent | undefined;
|
|
21
|
+
clone(): ShredEventQueue;
|
|
22
|
+
}
|
|
23
|
+
export declare class ShredStreamClient {
|
|
24
|
+
private readonly endpoint;
|
|
25
|
+
private readonly config;
|
|
26
|
+
private readonly ServiceClient;
|
|
27
|
+
private loopAbort;
|
|
28
|
+
private loopPromise;
|
|
29
|
+
private activeCall;
|
|
30
|
+
private receiveStats;
|
|
31
|
+
private constructor();
|
|
32
|
+
static connect(endpoint: string): Promise<ShredStreamClient>;
|
|
33
|
+
/** 与 Rust `ShredStreamClient::new` 一致 */
|
|
34
|
+
static new(endpoint: string): Promise<ShredStreamClient>;
|
|
35
|
+
static connectWithConfig(endpoint: string, config: ShredStreamConfig): Promise<ShredStreamClient>;
|
|
36
|
+
/** 与 Rust `ShredStreamClient::new_with_config` 一致 */
|
|
37
|
+
static newWithConfig(endpoint: string, config: ShredStreamConfig): Promise<ShredStreamClient>;
|
|
38
|
+
/** 与 Rust `#[derive(Clone)]` 一致:多句柄共享同一连接状态(TS 中为同一对象引用) */
|
|
39
|
+
clone(): ShredStreamClient;
|
|
40
|
+
/** 当前订阅周期内 gRPC Entry 条数、解码失败次数、解码出的交易数、入队 DexEvent 数 */
|
|
41
|
+
getReceiveStats(): Readonly<ShredStreamReceiveStats>;
|
|
42
|
+
/**
|
|
43
|
+
* 与 Rust `new_with_config` 中 `ShredstreamProxyClient::connect_with_config(endpoint, &config)` 一致:
|
|
44
|
+
* `connection_timeout_ms` / `request_timeout_ms` / `max_decoding_message_size` 见 `shredstream/proto/mod.rs`。
|
|
45
|
+
*/
|
|
46
|
+
private pingConnection;
|
|
47
|
+
/**
|
|
48
|
+
* 订阅 DEX 事件(自动重连);返回队列供轮询消费(与 Rust `subscribe` 一致)。
|
|
49
|
+
* 重连循环使用订阅时刻的配置快照(与 Rust 在 `tokio::spawn` 前 `config.clone()` 一致)。
|
|
50
|
+
*/
|
|
51
|
+
subscribe(): Promise<ShredEventQueue>;
|
|
52
|
+
/** 停止订阅并中止当前流 */
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
private runReconnectLoop;
|
|
55
|
+
/** 与 Rust `stream_events` + `connect_with_config`(tonic Endpoint / Grpc 消息上限)一致 */
|
|
56
|
+
private streamOnce;
|
|
57
|
+
private processEntryMessage;
|
|
58
|
+
/** 无 RPC:仅用静态账户表 */
|
|
59
|
+
private processEntryMessageSync;
|
|
60
|
+
/** 拉取 ALT 后完整账户表解析 */
|
|
61
|
+
private processEntryMessageWithAlt;
|
|
62
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ShredStreamClient = exports.ShredEventQueue = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* ShredStream 客户端:与 Rust `shredstream/client.rs` 对齐(gRPC + bincode 解码 + `parseTransactionEvents`)。
|
|
39
|
+
*/
|
|
40
|
+
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
41
|
+
const protoLoader = __importStar(require("@grpc/proto-loader"));
|
|
42
|
+
const node_path_1 = require("node:path");
|
|
43
|
+
const unified_parser_js_1 = require("../core/unified_parser.js");
|
|
44
|
+
const instruction_parse_js_1 = require("./instruction_parse.js");
|
|
45
|
+
const alt_lookup_js_1 = require("./alt_lookup.js");
|
|
46
|
+
const config_js_1 = require("./config.js");
|
|
47
|
+
const entries_decode_js_1 = require("./entries_decode.js");
|
|
48
|
+
const PROTO_PATH = (0, node_path_1.join)(__dirname, "shredstream.proto");
|
|
49
|
+
function loadProtoDefinition() {
|
|
50
|
+
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
|
51
|
+
keepCase: true,
|
|
52
|
+
longs: String,
|
|
53
|
+
enums: String,
|
|
54
|
+
defaults: true,
|
|
55
|
+
oneofs: true,
|
|
56
|
+
});
|
|
57
|
+
return grpc.loadPackageDefinition(packageDefinition);
|
|
58
|
+
}
|
|
59
|
+
function grpcTarget(endpoint) {
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(endpoint.includes("://") ? endpoint : `http://${endpoint}`);
|
|
62
|
+
return u.host;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return endpoint;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** 与 tonic 对 `https://` 端点使用 TLS 的行为对齐 */
|
|
69
|
+
function grpcCredentialsForEndpoint(endpoint) {
|
|
70
|
+
try {
|
|
71
|
+
const u = new URL(endpoint.includes("://") ? endpoint : `http://${endpoint}`);
|
|
72
|
+
if (u.protocol === "https:") {
|
|
73
|
+
return grpc.credentials.createSsl();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* use insecure */
|
|
78
|
+
}
|
|
79
|
+
return grpc.credentials.createInsecure();
|
|
80
|
+
}
|
|
81
|
+
function sleepMs(ms) {
|
|
82
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
83
|
+
}
|
|
84
|
+
function shredDebugEnabled() {
|
|
85
|
+
const v = process.env.SHREDSTREAM_DEBUG;
|
|
86
|
+
return v === "1" || v === "true";
|
|
87
|
+
}
|
|
88
|
+
/** 有界 FIFO 队列(容量与 Rust `ArrayQueue::new(100_000)` 一致;`clone()` 共享底层缓冲,同 `Arc<ArrayQueue>`) */
|
|
89
|
+
class ShredEventQueue {
|
|
90
|
+
static CAP = 100_000;
|
|
91
|
+
buf;
|
|
92
|
+
constructor(sharedBuf) {
|
|
93
|
+
this.buf = sharedBuf ?? [];
|
|
94
|
+
}
|
|
95
|
+
/** 当前元素个数(与 Rust `ArrayQueue::len` 一致) */
|
|
96
|
+
len() {
|
|
97
|
+
return this.buf.length;
|
|
98
|
+
}
|
|
99
|
+
/** 最大容量 */
|
|
100
|
+
capacity() {
|
|
101
|
+
return ShredEventQueue.CAP;
|
|
102
|
+
}
|
|
103
|
+
push(e) {
|
|
104
|
+
if (this.buf.length >= ShredEventQueue.CAP)
|
|
105
|
+
return false;
|
|
106
|
+
this.buf.push(e);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
pop() {
|
|
110
|
+
return this.buf.shift();
|
|
111
|
+
}
|
|
112
|
+
clone() {
|
|
113
|
+
return new ShredEventQueue(this.buf);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.ShredEventQueue = ShredEventQueue;
|
|
117
|
+
function setGrpcRecvUsMut(event, grpcRecvUs) {
|
|
118
|
+
const key = Object.keys(event)[0];
|
|
119
|
+
if (key === "Error")
|
|
120
|
+
return;
|
|
121
|
+
const inner = event[key];
|
|
122
|
+
if (inner?.metadata)
|
|
123
|
+
inner.metadata.grpc_recv_us = grpcRecvUs;
|
|
124
|
+
}
|
|
125
|
+
class ShredStreamClient {
|
|
126
|
+
endpoint;
|
|
127
|
+
config;
|
|
128
|
+
ServiceClient;
|
|
129
|
+
loopAbort = null;
|
|
130
|
+
loopPromise = null;
|
|
131
|
+
activeCall = null;
|
|
132
|
+
receiveStats = {
|
|
133
|
+
entryMessagesReceived: 0,
|
|
134
|
+
entryDecodeFailures: 0,
|
|
135
|
+
transactionsDecoded: 0,
|
|
136
|
+
dexEventsQueued: 0,
|
|
137
|
+
};
|
|
138
|
+
constructor(endpoint, config) {
|
|
139
|
+
this.endpoint = endpoint;
|
|
140
|
+
this.config = config;
|
|
141
|
+
const root = loadProtoDefinition();
|
|
142
|
+
const shred = root.shredstream;
|
|
143
|
+
this.ServiceClient = shred.ShredstreamProxy;
|
|
144
|
+
}
|
|
145
|
+
static async connect(endpoint) {
|
|
146
|
+
return ShredStreamClient.connectWithConfig(endpoint, (0, config_js_1.defaultShredStreamConfig)());
|
|
147
|
+
}
|
|
148
|
+
/** 与 Rust `ShredStreamClient::new` 一致 */
|
|
149
|
+
static new(endpoint) {
|
|
150
|
+
return ShredStreamClient.connect(endpoint);
|
|
151
|
+
}
|
|
152
|
+
static async connectWithConfig(endpoint, config) {
|
|
153
|
+
const client = new ShredStreamClient(endpoint, config);
|
|
154
|
+
await client.pingConnection();
|
|
155
|
+
return client;
|
|
156
|
+
}
|
|
157
|
+
/** 与 Rust `ShredStreamClient::new_with_config` 一致 */
|
|
158
|
+
static newWithConfig(endpoint, config) {
|
|
159
|
+
return ShredStreamClient.connectWithConfig(endpoint, config);
|
|
160
|
+
}
|
|
161
|
+
/** 与 Rust `#[derive(Clone)]` 一致:多句柄共享同一连接状态(TS 中为同一对象引用) */
|
|
162
|
+
clone() {
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
/** 当前订阅周期内 gRPC Entry 条数、解码失败次数、解码出的交易数、入队 DexEvent 数 */
|
|
166
|
+
getReceiveStats() {
|
|
167
|
+
return { ...this.receiveStats };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 与 Rust `new_with_config` 中 `ShredstreamProxyClient::connect_with_config(endpoint, &config)` 一致:
|
|
171
|
+
* `connection_timeout_ms` / `request_timeout_ms` / `max_decoding_message_size` 见 `shredstream/proto/mod.rs`。
|
|
172
|
+
*/
|
|
173
|
+
pingConnection() {
|
|
174
|
+
const target = grpcTarget(this.endpoint);
|
|
175
|
+
const creds = grpcCredentialsForEndpoint(this.endpoint);
|
|
176
|
+
const channelOpts = {
|
|
177
|
+
"grpc.max_receive_message_length": this.config.max_decoding_message_size,
|
|
178
|
+
"grpc.max_send_message_length": this.config.max_decoding_message_size,
|
|
179
|
+
};
|
|
180
|
+
const c = new this.ServiceClient(target, creds, channelOpts);
|
|
181
|
+
const deadline = Date.now() + this.config.connection_timeout_ms;
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
c.waitForReady(deadline, (err) => {
|
|
184
|
+
c.close();
|
|
185
|
+
if (err)
|
|
186
|
+
reject(err);
|
|
187
|
+
else
|
|
188
|
+
resolve();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 订阅 DEX 事件(自动重连);返回队列供轮询消费(与 Rust `subscribe` 一致)。
|
|
194
|
+
* 重连循环使用订阅时刻的配置快照(与 Rust 在 `tokio::spawn` 前 `config.clone()` 一致)。
|
|
195
|
+
*/
|
|
196
|
+
async subscribe() {
|
|
197
|
+
await this.stop();
|
|
198
|
+
this.receiveStats = {
|
|
199
|
+
entryMessagesReceived: 0,
|
|
200
|
+
entryDecodeFailures: 0,
|
|
201
|
+
transactionsDecoded: 0,
|
|
202
|
+
dexEventsQueued: 0,
|
|
203
|
+
};
|
|
204
|
+
const queue = new ShredEventQueue();
|
|
205
|
+
const ac = new AbortController();
|
|
206
|
+
this.loopAbort = ac;
|
|
207
|
+
const configSnapshot = { ...this.config };
|
|
208
|
+
this.loopPromise = this.runReconnectLoop(queue, ac.signal, configSnapshot);
|
|
209
|
+
return queue;
|
|
210
|
+
}
|
|
211
|
+
/** 停止订阅并中止当前流 */
|
|
212
|
+
async stop() {
|
|
213
|
+
this.loopAbort?.abort();
|
|
214
|
+
this.loopAbort = null;
|
|
215
|
+
this.activeCall?.cancel();
|
|
216
|
+
this.activeCall = null;
|
|
217
|
+
if (this.loopPromise) {
|
|
218
|
+
await this.loopPromise.catch(() => undefined);
|
|
219
|
+
this.loopPromise = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async runReconnectLoop(queue, signal, config) {
|
|
223
|
+
let delay = config.reconnect_delay_ms;
|
|
224
|
+
let attempts = 0;
|
|
225
|
+
while (!signal.aborted) {
|
|
226
|
+
const max = config.max_reconnect_attempts;
|
|
227
|
+
if (max > 0 && attempts >= max) {
|
|
228
|
+
console.error("Max reconnection attempts reached, giving up");
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
attempts += 1;
|
|
232
|
+
try {
|
|
233
|
+
await this.streamOnce(queue, signal, config);
|
|
234
|
+
delay = config.reconnect_delay_ms;
|
|
235
|
+
attempts = 0;
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
if (signal.aborted)
|
|
239
|
+
break;
|
|
240
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
241
|
+
console.error(`ShredStream error: ${msg} - retry in ${delay}ms`);
|
|
242
|
+
await sleepMs(delay);
|
|
243
|
+
if (signal.aborted)
|
|
244
|
+
break;
|
|
245
|
+
delay = Math.min(delay * 2, 60_000);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** 与 Rust `stream_events` + `connect_with_config`(tonic Endpoint / Grpc 消息上限)一致 */
|
|
250
|
+
streamOnce(queue, signal, config) {
|
|
251
|
+
if (signal.aborted)
|
|
252
|
+
return Promise.resolve();
|
|
253
|
+
const target = grpcTarget(this.endpoint);
|
|
254
|
+
const creds = grpcCredentialsForEndpoint(this.endpoint);
|
|
255
|
+
const channelOpts = {
|
|
256
|
+
"grpc.max_receive_message_length": config.max_decoding_message_size,
|
|
257
|
+
"grpc.max_send_message_length": config.max_decoding_message_size,
|
|
258
|
+
};
|
|
259
|
+
const client = new this.ServiceClient(target, creds, channelOpts);
|
|
260
|
+
return new Promise((resolve, reject) => {
|
|
261
|
+
let settled = false;
|
|
262
|
+
const finish = (fn) => {
|
|
263
|
+
if (settled)
|
|
264
|
+
return;
|
|
265
|
+
settled = true;
|
|
266
|
+
fn();
|
|
267
|
+
};
|
|
268
|
+
const call = client.subscribeEntries({}, new grpc.Metadata());
|
|
269
|
+
this.activeCall = call;
|
|
270
|
+
console.info("ShredStream connected, receiving entries...");
|
|
271
|
+
call.on("data", (entry) => {
|
|
272
|
+
if (signal.aborted)
|
|
273
|
+
return;
|
|
274
|
+
try {
|
|
275
|
+
void this.processEntryMessage(entry, queue);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
console.error("processEntryMessage:", err);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
call.on("error", (err) => {
|
|
282
|
+
client.close();
|
|
283
|
+
if (signal.aborted || err.code === grpc.status.CANCELLED) {
|
|
284
|
+
finish(() => resolve());
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.error("Stream error:", err);
|
|
288
|
+
finish(() => reject(err));
|
|
289
|
+
});
|
|
290
|
+
call.on("end", () => {
|
|
291
|
+
client.close();
|
|
292
|
+
finish(() => resolve());
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async processEntryMessage(entry, queue) {
|
|
297
|
+
this.receiveStats.entryMessagesReceived += 1;
|
|
298
|
+
const recvUs = (0, unified_parser_js_1.nowUs)();
|
|
299
|
+
const slotNum = toSlotNumber(entry.slot);
|
|
300
|
+
const raw = entry.entries;
|
|
301
|
+
const bytes = raw instanceof Buffer ? new Uint8Array(raw) : new Uint8Array(raw);
|
|
302
|
+
let decoded;
|
|
303
|
+
try {
|
|
304
|
+
decoded = (0, entries_decode_js_1.decodeShredstreamEntriesBincode)(bytes);
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
this.receiveStats.entryDecodeFailures += 1;
|
|
308
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
309
|
+
console.warn(`[shredstream] bincode 解码失败 slot=${slotNum} bytes=${bytes.length}: ${msg}`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const conn = this.config.connection;
|
|
313
|
+
if (conn) {
|
|
314
|
+
try {
|
|
315
|
+
await this.processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, bytes.length, conn);
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
console.warn(`[shredstream] ALT/RPC 解析失败 slot=${slotNum}:`, e);
|
|
319
|
+
this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length);
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.processEntryMessageSync(decoded, slotNum, recvUs, queue, bytes.length);
|
|
324
|
+
}
|
|
325
|
+
/** 无 RPC:仅用静态账户表 */
|
|
326
|
+
processEntryMessageSync(decoded, slotNum, recvUs, queue, entriesBytesLen) {
|
|
327
|
+
let txTotal = 0;
|
|
328
|
+
let evTotal = 0;
|
|
329
|
+
/** 与 golang `shredstream_entries` 一致:单条 gRPC 消息内跨所有 Solana Entry 的连续下标 */
|
|
330
|
+
let globalTxIndex = 0;
|
|
331
|
+
for (const outer of decoded) {
|
|
332
|
+
const txs = outer;
|
|
333
|
+
txTotal += txs.length;
|
|
334
|
+
for (let i = 0; i < txs.length; i++) {
|
|
335
|
+
const tx = txs[i];
|
|
336
|
+
const txIndex = globalTxIndex++;
|
|
337
|
+
if (!tx?.signature)
|
|
338
|
+
continue;
|
|
339
|
+
const events = (0, instruction_parse_js_1.dexEventsFromShredWasmTx)(tx, slotNum, txIndex, recvUs, undefined);
|
|
340
|
+
evTotal += events.length;
|
|
341
|
+
for (const ev of events) {
|
|
342
|
+
setGrpcRecvUsMut(ev, recvUs);
|
|
343
|
+
queue.push(ev);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
this.receiveStats.transactionsDecoded += txTotal;
|
|
348
|
+
this.receiveStats.dexEventsQueued += evTotal;
|
|
349
|
+
if (shredDebugEnabled()) {
|
|
350
|
+
console.info(`[shredstream] slot=${slotNum} entries_bytes=${entriesBytesLen} solana_entries=${decoded.length} txs=${txTotal} dex_events=${evTotal} (static_accounts_only)`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** 拉取 ALT 后完整账户表解析 */
|
|
354
|
+
async processEntryMessageWithAlt(decoded, slotNum, recvUs, queue, entriesBytesLen, conn) {
|
|
355
|
+
const altKeys = new Set();
|
|
356
|
+
for (const outer of decoded) {
|
|
357
|
+
for (const tx of outer) {
|
|
358
|
+
for (const l of tx.addressTableLookups ?? []) {
|
|
359
|
+
altKeys.add(l.accountKey);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const altMap = await (0, alt_lookup_js_1.loadAddressLookupTableAccounts)(conn, [...altKeys]);
|
|
364
|
+
let txTotal = 0;
|
|
365
|
+
let evTotal = 0;
|
|
366
|
+
let globalTxIndex = 0;
|
|
367
|
+
for (const outer of decoded) {
|
|
368
|
+
const txs = outer;
|
|
369
|
+
txTotal += txs.length;
|
|
370
|
+
for (let i = 0; i < txs.length; i++) {
|
|
371
|
+
const tx = txs[i];
|
|
372
|
+
const txIndex = globalTxIndex++;
|
|
373
|
+
if (!tx?.signature)
|
|
374
|
+
continue;
|
|
375
|
+
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);
|
|
377
|
+
evTotal += events.length;
|
|
378
|
+
for (const ev of events) {
|
|
379
|
+
setGrpcRecvUsMut(ev, recvUs);
|
|
380
|
+
queue.push(ev);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
this.receiveStats.transactionsDecoded += txTotal;
|
|
385
|
+
this.receiveStats.dexEventsQueued += evTotal;
|
|
386
|
+
if (shredDebugEnabled()) {
|
|
387
|
+
console.info(`[shredstream] slot=${slotNum} entries_bytes=${entriesBytesLen} solana_entries=${decoded.length} txs=${txTotal} dex_events=${evTotal} alt_tables=${altKeys.size}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
exports.ShredStreamClient = ShredStreamClient;
|
|
392
|
+
function toSlotNumber(slot) {
|
|
393
|
+
if (typeof slot === "number")
|
|
394
|
+
return slot;
|
|
395
|
+
if (typeof slot === "bigint")
|
|
396
|
+
return Number(slot);
|
|
397
|
+
const n = Number(slot);
|
|
398
|
+
return Number.isFinite(n) ? Math.floor(n) : 0;
|
|
399
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Connection } from "@solana/web3.js";
|
|
2
|
+
/**
|
|
3
|
+
* 与 Rust `sol-parser-sdk/src/shredstream/config.rs` 字段一一对应。
|
|
4
|
+
*
|
|
5
|
+
* Rust:`connect_with_config` 使用 `connection_timeout_ms` → tonic `Endpoint::connect_timeout`,
|
|
6
|
+
* `max_decoding_message_size` → `Grpc::max_*_message_size`;**未**对长流设置 `Endpoint::timeout`
|
|
7
|
+
* (`request_timeout_ms` 保留与 Rust `config.rs` 一致,流式路径不填 deadline)。
|
|
8
|
+
* `reconnect_delay_ms` / `max_reconnect_attempts` 由 `client.rs` 重连循环使用。
|
|
9
|
+
*
|
|
10
|
+
* **TS 扩展**:`connection` 用于拉取链上 ALT,补全 V0 交易账户表以解析外层指令(主网 Pump 等必需)。
|
|
11
|
+
*/
|
|
12
|
+
export interface ShredStreamConfig {
|
|
13
|
+
/** 连接超时(毫秒)→ tonic `Endpoint::connect_timeout` / gRPC `waitForReady` */
|
|
14
|
+
connection_timeout_ms: number;
|
|
15
|
+
/** 与 Rust 字段一致;长流 `SubscribeEntries` 不设单 RPC deadline(避免断流) */
|
|
16
|
+
request_timeout_ms: number;
|
|
17
|
+
/** 最大编解码消息大小(字节)→ tonic `Grpc` / `@grpc/grpc-js` channel 选项 */
|
|
18
|
+
max_decoding_message_size: number;
|
|
19
|
+
/** 自动重连延迟(毫秒) */
|
|
20
|
+
reconnect_delay_ms: number;
|
|
21
|
+
/** 最大重连次数(0 表示无限重连) */
|
|
22
|
+
max_reconnect_attempts: number;
|
|
23
|
+
/** 可选:主网 RPC,用于解析 V0 地址查找表(ALT) */
|
|
24
|
+
connection?: Connection;
|
|
25
|
+
}
|
|
26
|
+
export declare function defaultShredStreamConfig(): ShredStreamConfig;
|
|
27
|
+
/** 低延迟:更短超时与重连间隔 */
|
|
28
|
+
export declare function lowLatencyShredStreamConfig(): ShredStreamConfig;
|
|
29
|
+
/** 高吞吐:更大消息与更多重试 */
|
|
30
|
+
export declare function highThroughputShredStreamConfig(): ShredStreamConfig;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultShredStreamConfig = defaultShredStreamConfig;
|
|
4
|
+
exports.lowLatencyShredStreamConfig = lowLatencyShredStreamConfig;
|
|
5
|
+
exports.highThroughputShredStreamConfig = highThroughputShredStreamConfig;
|
|
6
|
+
function defaultShredStreamConfig() {
|
|
7
|
+
return {
|
|
8
|
+
connection_timeout_ms: 8000,
|
|
9
|
+
request_timeout_ms: 15_000,
|
|
10
|
+
max_decoding_message_size: 1024 * 1024 * 100,
|
|
11
|
+
reconnect_delay_ms: 1000,
|
|
12
|
+
max_reconnect_attempts: 3,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** 低延迟:更短超时与重连间隔 */
|
|
16
|
+
function lowLatencyShredStreamConfig() {
|
|
17
|
+
return {
|
|
18
|
+
connection_timeout_ms: 5000,
|
|
19
|
+
request_timeout_ms: 10_000,
|
|
20
|
+
max_decoding_message_size: 1024 * 1024 * 50,
|
|
21
|
+
reconnect_delay_ms: 100,
|
|
22
|
+
max_reconnect_attempts: 1,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** 高吞吐:更大消息与更多重试 */
|
|
26
|
+
function highThroughputShredStreamConfig() {
|
|
27
|
+
return {
|
|
28
|
+
connection_timeout_ms: 10_000,
|
|
29
|
+
request_timeout_ms: 30_000,
|
|
30
|
+
max_decoding_message_size: 1024 * 1024 * 200,
|
|
31
|
+
reconnect_delay_ms: 2000,
|
|
32
|
+
max_reconnect_attempts: 5,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gRPC `Entry.entries` 负载解码:对齐 sol-parser-sdk-golang `shredstream/entries_decode.go`
|
|
3
|
+
*(bincode Vec<Entry> 前缀 + 每笔线格式交易定长扫描)。
|
|
4
|
+
*/
|
|
5
|
+
import type { ShredWasmTx } from "./instruction_parse.js";
|
|
6
|
+
export type DecodedWireTransaction = {
|
|
7
|
+
raw: Uint8Array;
|
|
8
|
+
/** 全部签名(每笔交易至少一枚;展示用首签) */
|
|
9
|
+
signatures: Uint8Array[];
|
|
10
|
+
};
|
|
11
|
+
/** 与 Go `BincodeVecEntryCount` 一致 */
|
|
12
|
+
export declare function bincodeVecEntryCount(entriesBytes: Uint8Array): bigint;
|
|
13
|
+
/** compact-u16,与 Go `decodeCompactU16` 一致 */
|
|
14
|
+
export declare function decodeCompactU16(buf: Uint8Array, pos: number): {
|
|
15
|
+
value: number;
|
|
16
|
+
bytes: number;
|
|
17
|
+
} | null;
|
|
18
|
+
/** 返回 (txWireLength, signatures);失败返回 null */
|
|
19
|
+
export declare function parseTransaction(buf: Uint8Array, pos: number): {
|
|
20
|
+
txLen: number;
|
|
21
|
+
sigs: Uint8Array[];
|
|
22
|
+
} | null;
|
|
23
|
+
export declare function decodeEntriesBincodeFlat(entriesBytes: Uint8Array): DecodedWireTransaction[];
|
|
24
|
+
export declare function decodeEntriesBincodeNested(entriesBytes: Uint8Array): DecodedWireTransaction[][];
|
|
25
|
+
/**
|
|
26
|
+
* 解码 gRPC `entries` 字节并展开为与旧 WASM 相同的嵌套 `ShredWasmTx`(跳过无法反序列化的单笔交易)。
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeShredstreamEntriesBincode(bytes: Uint8Array): ShredWasmTx[][];
|