sol-parser-sdk 0.5.17 → 0.5.18
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 +24 -15
- package/README_CN.md +23 -14
- package/dist/accounts/pumpfun.js +15 -0
- package/dist/accounts/pumpswap.js +2 -0
- package/dist/core/account_dispatcher_rpc.d.ts +2 -1
- package/dist/core/account_dispatcher_rpc.js +94 -9
- package/dist/core/account_fill_pumpfun.js +20 -0
- package/dist/core/account_fill_pumpswap.d.ts +2 -0
- package/dist/core/account_fill_pumpswap.js +55 -6
- package/dist/core/account_fill_raydium.js +4 -0
- package/dist/core/dex_event.d.ts +149 -0
- package/dist/grpc/client.js +2 -0
- package/dist/grpc/log_instr_dedup.js +75 -7
- package/dist/grpc/order_buffer.d.ts +5 -0
- package/dist/grpc/order_buffer.js +53 -17
- package/dist/grpc/types.d.ts +4 -1
- package/dist/grpc/types.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/instr/inner.js +28 -1
- package/dist/instr/meteora_damm_ix.js +26 -0
- package/dist/instr/meteora_pools_ix.js +17 -9
- package/dist/instr/orca_whirlpool_ix.js +13 -9
- package/dist/instr/pumpfun_ix.js +84 -0
- package/dist/instr/pumpswap_ix.js +85 -10
- package/dist/instr/raydium_clmm_ix.js +5 -3
- package/dist/instr/raydium_cpmm_ix.js +11 -8
- package/dist/logs/meteora_damm.d.ts +7 -0
- package/dist/logs/meteora_damm.js +211 -0
- package/dist/logs/meteora_dbc.d.ts +3 -0
- package/dist/logs/meteora_dbc.js +23 -0
- package/dist/logs/meteora_dlmm.js +16 -4
- package/dist/logs/optimized_matcher.js +93 -10
- package/dist/logs/program_log_discriminators.d.ts +11 -0
- package/dist/logs/program_log_discriminators.js +11 -0
- package/dist/logs/pump.js +6 -3
- package/dist/logs/pump_amm.js +8 -2
- package/dist/logs/pump_upgrade.d.ts +68 -0
- package/dist/logs/pump_upgrade.js +128 -0
- package/dist/logs/raydium_cpmm.js +7 -3
- package/dist/rpc_transaction.js +84 -7
- package/dist/shredstream/alt_cache.d.ts +26 -0
- package/dist/shredstream/alt_cache.js +138 -0
- package/dist/shredstream/client.d.ts +11 -3
- package/dist/shredstream/client.js +76 -72
- package/dist/shredstream/config.d.ts +7 -3
- package/dist/shredstream/index.d.ts +3 -2
- package/dist/shredstream/index.js +5 -3
- package/dist/shredstream/instruction_parse.d.ts +1 -1
- package/dist/shredstream/instruction_parse.js +1 -0
- package/dist/simulation_route.d.ts +1 -1
- package/dist/simulation_route.js +17 -4
- package/dist/transaction_route.js +20 -1
- package/dist/wire_transaction.js +9 -10
- package/docs/USAGE.md +161 -0
- package/package.json +2 -5
- package/NATIVE_ALIGNMENT.md +0 -312
- package/NATIVE_DAMM_FACTORY_ALIGNMENT_20261004.md +0 -16
- package/NATIVE_DAMM_FACTORY_EVIDENCE_20261004.json +0 -55
- package/NATIVE_MIGRATION.md +0 -38
- package/NATIVE_PUMPFUN_NATIVE_DEBIT_ALIGNMENT_20261004.md +0 -18
- package/NATIVE_PUMPFUN_NATIVE_DEBIT_EVIDENCE_20261004.json +0 -57
- package/NATIVE_PUMPFUN_SETTLEMENT_ALIGNMENT_20261004.md +0 -11
- package/NATIVE_PUMPFUN_SETTLEMENT_EVIDENCE_20261004.json +0 -70
package/dist/logs/pump_amm.js
CHANGED
|
@@ -19,6 +19,7 @@ function bnI64(v) {
|
|
|
19
19
|
const ZP = (0, dex_event_js_1.defaultPubkey)();
|
|
20
20
|
function emptyTradeTail() {
|
|
21
21
|
return {
|
|
22
|
+
creator_fee_unclaimed: 0n,
|
|
22
23
|
cashback_fee_basis_points: 0n,
|
|
23
24
|
cashback: 0n,
|
|
24
25
|
buyback_fee_basis_points: 0n,
|
|
@@ -63,6 +64,9 @@ function parseTradeTail(data) {
|
|
|
63
64
|
tail.holder_rewards_bps = bn64((0, binary_js_1.readU64LE)(data, 57));
|
|
64
65
|
tail.holder_rewards = bn64((0, binary_js_1.readU64LE)(data, 65));
|
|
65
66
|
}
|
|
67
|
+
if (data.length > 73 && data.length < 81)
|
|
68
|
+
return null;
|
|
69
|
+
tail.creator_fee_unclaimed = bn64((0, binary_js_1.readU64LE)(data, 73));
|
|
66
70
|
return tail;
|
|
67
71
|
}
|
|
68
72
|
function readStrictBorshString(data, offset) {
|
|
@@ -198,6 +202,7 @@ function parseBuyFromData(data, metadata) {
|
|
|
198
202
|
base_supply: tail.base_supply,
|
|
199
203
|
holder_rewards_bps: tail.holder_rewards_bps,
|
|
200
204
|
holder_rewards: tail.holder_rewards,
|
|
205
|
+
creator_fee_unclaimed: tail.creator_fee_unclaimed,
|
|
201
206
|
is_pump_pool: false,
|
|
202
207
|
base_mint: ZP,
|
|
203
208
|
quote_mint: ZP,
|
|
@@ -298,6 +303,7 @@ function parseSellFromData(data, metadata) {
|
|
|
298
303
|
base_supply: tail.base_supply,
|
|
299
304
|
holder_rewards_bps: tail.holder_rewards_bps,
|
|
300
305
|
holder_rewards: tail.holder_rewards,
|
|
306
|
+
creator_fee_unclaimed: tail.creator_fee_unclaimed,
|
|
301
307
|
is_pump_pool: false,
|
|
302
308
|
base_mint: ZP,
|
|
303
309
|
quote_mint: ZP,
|
|
@@ -392,7 +398,7 @@ function parseCreatePoolFromData(data, metadata) {
|
|
|
392
398
|
return { PumpSwapCreatePool: ev };
|
|
393
399
|
}
|
|
394
400
|
function parseAddLiquidityFromData(data, metadata) {
|
|
395
|
-
const REQUIRED =
|
|
401
|
+
const REQUIRED = 11 * 8 + 5 * 32;
|
|
396
402
|
if (data.length < REQUIRED)
|
|
397
403
|
return null;
|
|
398
404
|
let o = 0;
|
|
@@ -449,7 +455,7 @@ function parseAddLiquidityFromData(data, metadata) {
|
|
|
449
455
|
return { PumpSwapLiquidityAdded: ev };
|
|
450
456
|
}
|
|
451
457
|
function parseRemoveLiquidityFromData(data, metadata) {
|
|
452
|
-
const REQUIRED =
|
|
458
|
+
const REQUIRED = 11 * 8 + 5 * 32;
|
|
453
459
|
if (data.length < REQUIRED)
|
|
454
460
|
return null;
|
|
455
461
|
let o = 0;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { DexEvent } from "../core/dex_event.js";
|
|
2
|
+
import type { EventMetadata } from "../core/metadata.js";
|
|
3
|
+
export interface PumpFunPostCompleteBuyEvent {
|
|
4
|
+
metadata: EventMetadata;
|
|
5
|
+
user: string;
|
|
6
|
+
mint: string;
|
|
7
|
+
bonding_curve: string;
|
|
8
|
+
quote_mint: string;
|
|
9
|
+
timestamp: bigint;
|
|
10
|
+
base_out: bigint;
|
|
11
|
+
quote_in: bigint;
|
|
12
|
+
fee_basis_points: bigint;
|
|
13
|
+
fee: bigint;
|
|
14
|
+
creator_fee_basis_points: bigint;
|
|
15
|
+
creator_fee: bigint;
|
|
16
|
+
buyback_fee: bigint;
|
|
17
|
+
pool_base_reserves_before: bigint;
|
|
18
|
+
pool_quote_reserves_before: bigint;
|
|
19
|
+
pool_base_reserves_after: bigint;
|
|
20
|
+
pool_quote_reserves_after: bigint;
|
|
21
|
+
}
|
|
22
|
+
export interface PumpFunSweepBondingCurveFeeEvent {
|
|
23
|
+
metadata: EventMetadata;
|
|
24
|
+
timestamp: bigint;
|
|
25
|
+
mint: string;
|
|
26
|
+
bonding_curve: string;
|
|
27
|
+
quote_mint: string;
|
|
28
|
+
recipient: string;
|
|
29
|
+
amount: bigint;
|
|
30
|
+
bucket: number;
|
|
31
|
+
}
|
|
32
|
+
export interface PumpFunCompleteEvent {
|
|
33
|
+
metadata: EventMetadata;
|
|
34
|
+
user: string;
|
|
35
|
+
mint: string;
|
|
36
|
+
bonding_curve: string;
|
|
37
|
+
timestamp: bigint;
|
|
38
|
+
quote_mint: string;
|
|
39
|
+
}
|
|
40
|
+
export interface PumpSwapSweepPoolFeeEvent {
|
|
41
|
+
metadata: EventMetadata;
|
|
42
|
+
timestamp: bigint;
|
|
43
|
+
pool: string;
|
|
44
|
+
base_mint: string;
|
|
45
|
+
quote_mint: string;
|
|
46
|
+
recipient: string;
|
|
47
|
+
payer: string;
|
|
48
|
+
amount: bigint;
|
|
49
|
+
bucket: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function pumpUpgradeEventType(disc: bigint, programId?: string): "PumpFunPostCompleteBuy" | "PumpFunSweepBondingCurveFee" | "PumpFunComplete" | "PumpSwapSweepPoolFee" | null;
|
|
52
|
+
export declare function parsePumpUpgradeEvent(disc: bigint, data: Uint8Array, metadata: EventMetadata, programId?: string): DexEvent | null;
|
|
53
|
+
export interface PumpMultiHopIntent {
|
|
54
|
+
user: string;
|
|
55
|
+
inputAccount: string;
|
|
56
|
+
outputAccount: string;
|
|
57
|
+
amountIn: bigint;
|
|
58
|
+
minAmountOut: bigint;
|
|
59
|
+
hops: {
|
|
60
|
+
baseMint: string;
|
|
61
|
+
quoteMint: string;
|
|
62
|
+
venue: string;
|
|
63
|
+
baseVault: string;
|
|
64
|
+
quoteVault: string;
|
|
65
|
+
}[];
|
|
66
|
+
}
|
|
67
|
+
/** Intent amounts are limits, not execution. Each hop emits its own actual trade. */
|
|
68
|
+
export declare function decodePumpMultiHopIntent(program: string, data: Uint8Array, accounts: readonly string[]): PumpMultiHopIntent | null;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pumpUpgradeEventType = pumpUpgradeEventType;
|
|
4
|
+
exports.parsePumpUpgradeEvent = parsePumpUpgradeEvent;
|
|
5
|
+
exports.decodePumpMultiHopIntent = decodePumpMultiHopIntent;
|
|
6
|
+
const binary_js_1 = require("../util/binary.js");
|
|
7
|
+
function pumpUpgradeEventType(disc, programId) {
|
|
8
|
+
if (disc === 18146529233607700591n &&
|
|
9
|
+
(!programId || programId === "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"))
|
|
10
|
+
return "PumpFunPostCompleteBuy";
|
|
11
|
+
if (disc === 3118876958563052404n &&
|
|
12
|
+
(!programId || programId === "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"))
|
|
13
|
+
return "PumpFunSweepBondingCurveFee";
|
|
14
|
+
if (disc === 619296439455019615n &&
|
|
15
|
+
(!programId || programId === "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"))
|
|
16
|
+
return "PumpFunComplete";
|
|
17
|
+
if (disc === 11927646055507993730n &&
|
|
18
|
+
(!programId || programId === "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"))
|
|
19
|
+
return "PumpSwapSweepPoolFee";
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function parsePumpUpgradeEvent(disc, data, metadata, programId) {
|
|
23
|
+
const type = pumpUpgradeEventType(disc, programId);
|
|
24
|
+
if (type === "PumpFunPostCompleteBuy") {
|
|
25
|
+
if (data.length != 224)
|
|
26
|
+
return null;
|
|
27
|
+
return {
|
|
28
|
+
PumpFunPostCompleteBuy: {
|
|
29
|
+
metadata,
|
|
30
|
+
user: (0, binary_js_1.readPubkey)(data, 0),
|
|
31
|
+
mint: (0, binary_js_1.readPubkey)(data, 32),
|
|
32
|
+
bonding_curve: (0, binary_js_1.readPubkey)(data, 64),
|
|
33
|
+
quote_mint: (0, binary_js_1.readPubkey)(data, 96),
|
|
34
|
+
timestamp: (0, binary_js_1.readI64LE)(data, 128),
|
|
35
|
+
base_out: (0, binary_js_1.readU64LE)(data, 136),
|
|
36
|
+
quote_in: (0, binary_js_1.readU64LE)(data, 144),
|
|
37
|
+
fee_basis_points: (0, binary_js_1.readU64LE)(data, 152),
|
|
38
|
+
fee: (0, binary_js_1.readU64LE)(data, 160),
|
|
39
|
+
creator_fee_basis_points: (0, binary_js_1.readU64LE)(data, 168),
|
|
40
|
+
creator_fee: (0, binary_js_1.readU64LE)(data, 176),
|
|
41
|
+
buyback_fee: (0, binary_js_1.readU64LE)(data, 184),
|
|
42
|
+
pool_base_reserves_before: (0, binary_js_1.readU64LE)(data, 192),
|
|
43
|
+
pool_quote_reserves_before: (0, binary_js_1.readU64LE)(data, 200),
|
|
44
|
+
pool_base_reserves_after: (0, binary_js_1.readU64LE)(data, 208),
|
|
45
|
+
pool_quote_reserves_after: (0, binary_js_1.readU64LE)(data, 216),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (type === "PumpFunSweepBondingCurveFee") {
|
|
50
|
+
if (data.length != 145)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
PumpFunSweepBondingCurveFee: {
|
|
54
|
+
metadata,
|
|
55
|
+
timestamp: (0, binary_js_1.readI64LE)(data, 0),
|
|
56
|
+
mint: (0, binary_js_1.readPubkey)(data, 8),
|
|
57
|
+
bonding_curve: (0, binary_js_1.readPubkey)(data, 40),
|
|
58
|
+
quote_mint: (0, binary_js_1.readPubkey)(data, 72),
|
|
59
|
+
recipient: (0, binary_js_1.readPubkey)(data, 104),
|
|
60
|
+
amount: (0, binary_js_1.readU64LE)(data, 136),
|
|
61
|
+
bucket: data[144],
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (type === "PumpFunComplete") {
|
|
66
|
+
if (data.length != 136 && data.length != 104)
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
PumpFunComplete: {
|
|
70
|
+
metadata,
|
|
71
|
+
user: (0, binary_js_1.readPubkey)(data, 0),
|
|
72
|
+
mint: (0, binary_js_1.readPubkey)(data, 32),
|
|
73
|
+
bonding_curve: (0, binary_js_1.readPubkey)(data, 64),
|
|
74
|
+
timestamp: (0, binary_js_1.readI64LE)(data, 96),
|
|
75
|
+
quote_mint: data.length === 104
|
|
76
|
+
? "So11111111111111111111111111111111111111112"
|
|
77
|
+
: (0, binary_js_1.readPubkey)(data, 104),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (type === "PumpSwapSweepPoolFee") {
|
|
82
|
+
if (data.length != 177)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
PumpSwapSweepPoolFee: {
|
|
86
|
+
metadata,
|
|
87
|
+
timestamp: (0, binary_js_1.readI64LE)(data, 0),
|
|
88
|
+
pool: (0, binary_js_1.readPubkey)(data, 8),
|
|
89
|
+
base_mint: (0, binary_js_1.readPubkey)(data, 40),
|
|
90
|
+
quote_mint: (0, binary_js_1.readPubkey)(data, 72),
|
|
91
|
+
recipient: (0, binary_js_1.readPubkey)(data, 104),
|
|
92
|
+
payer: (0, binary_js_1.readPubkey)(data, 136),
|
|
93
|
+
amount: (0, binary_js_1.readU64LE)(data, 168),
|
|
94
|
+
bucket: data[176],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
/** Intent amounts are limits, not execution. Each hop emits its own actual trade. */
|
|
101
|
+
function decodePumpMultiHopIntent(program, data, accounts) {
|
|
102
|
+
if (program !== "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA" ||
|
|
103
|
+
data.length !== 24 ||
|
|
104
|
+
(0, binary_js_1.readU64LE)(data, 0) !== 10696039120939607083n ||
|
|
105
|
+
accounts.length < 21 ||
|
|
106
|
+
(accounts.length - 16) % 5 !== 0)
|
|
107
|
+
return null;
|
|
108
|
+
const amountIn = (0, binary_js_1.readU64LE)(data, 8), minAmountOut = (0, binary_js_1.readU64LE)(data, 16);
|
|
109
|
+
if (!amountIn || !minAmountOut)
|
|
110
|
+
return null;
|
|
111
|
+
const hops = [];
|
|
112
|
+
for (let i = 16; i < accounts.length; i += 5)
|
|
113
|
+
hops.push({
|
|
114
|
+
baseMint: accounts[i],
|
|
115
|
+
quoteMint: accounts[i + 1],
|
|
116
|
+
venue: accounts[i + 2],
|
|
117
|
+
baseVault: accounts[i + 3],
|
|
118
|
+
quoteVault: accounts[i + 4],
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
user: accounts[0],
|
|
122
|
+
inputAccount: accounts[1],
|
|
123
|
+
outputAccount: accounts[2],
|
|
124
|
+
amountIn,
|
|
125
|
+
minAmountOut,
|
|
126
|
+
hops,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -12,9 +12,8 @@ function bn64(v) {
|
|
|
12
12
|
}
|
|
13
13
|
/** Current Anchor `SwapEvent` payload (the 8-byte event discriminator is removed by the caller). */
|
|
14
14
|
function parseSwapEventFromData(data, metadata) {
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
if (data.length < 32 + (6 * 8) + 1)
|
|
15
|
+
// Accept the complete legacy prefix or the complete current mint/fee suffix.
|
|
16
|
+
if (data.length < 81 || (data.length > 81 && data.length < 162) || data[80] > 1 || (data.length >= 162 && data[161] > 1))
|
|
18
17
|
return null;
|
|
19
18
|
let o = 0;
|
|
20
19
|
const pool_id = (0, binary_js_1.readPubkey)(data, o);
|
|
@@ -40,6 +39,11 @@ function parseSwapEventFromData(data, metadata) {
|
|
|
40
39
|
base_input == null)
|
|
41
40
|
return null;
|
|
42
41
|
const ev = {
|
|
42
|
+
input_mint: data.length >= 162 ? (0, binary_js_1.readPubkey)(data, 81) : undefined,
|
|
43
|
+
output_mint: data.length >= 162 ? (0, binary_js_1.readPubkey)(data, 113) : undefined,
|
|
44
|
+
trade_fee: data.length >= 162 ? (0, binary_js_1.readU64LE)(data, 145) : undefined,
|
|
45
|
+
creator_fee: data.length >= 162 ? (0, binary_js_1.readU64LE)(data, 153) : undefined,
|
|
46
|
+
creator_fee_on_input: data.length >= 162 ? data[161] !== 0 : undefined,
|
|
43
47
|
metadata,
|
|
44
48
|
pool_id,
|
|
45
49
|
input_vault_before,
|
package/dist/rpc_transaction.js
CHANGED
|
@@ -41,6 +41,7 @@ const SUPPORTED_INSTRUCTION_PROGRAM_IDS = new Set([
|
|
|
41
41
|
program_ids_js_1.RAYDIUM_AMM_V4_PROGRAM_ID,
|
|
42
42
|
program_ids_js_1.ORCA_WHIRLPOOL_PROGRAM_ID,
|
|
43
43
|
program_ids_js_1.METEORA_POOLS_PROGRAM_ID,
|
|
44
|
+
program_ids_js_1.METEORA_DBC_PROGRAM_ID,
|
|
44
45
|
program_ids_js_1.METEORA_DAMM_V2_PROGRAM_ID,
|
|
45
46
|
program_ids_js_1.METEORA_DLMM_PROGRAM_ID,
|
|
46
47
|
]);
|
|
@@ -51,8 +52,8 @@ function bytesEqualAt(data, expected, offset) {
|
|
|
51
52
|
}
|
|
52
53
|
return true;
|
|
53
54
|
}
|
|
54
|
-
function
|
|
55
|
-
return
|
|
55
|
+
function isEventCpi(programId, data) {
|
|
56
|
+
return [program_ids_js_1.METEORA_DLMM_PROGRAM_ID, program_ids_js_1.PUMPFUN_PROGRAM_ID, program_ids_js_1.PUMPSWAP_PROGRAM_ID].includes(programId) && data.length >= 16 && (bytesEqualAt(data, EVENT_CPI_PREFIX, 0) ||
|
|
56
57
|
bytesEqualAt(data, LEGACY_EVENT_CPI_SUFFIX, 8));
|
|
57
58
|
}
|
|
58
59
|
function isDlmmEvent(event) {
|
|
@@ -273,6 +274,9 @@ function mergeDlmmInstruction(baseName, base, inner) {
|
|
|
273
274
|
if (context)
|
|
274
275
|
Object.assign(base, context);
|
|
275
276
|
}
|
|
277
|
+
function pumpIdentityKeyConflicts(a, b) {
|
|
278
|
+
return !isDefaultValue(a) && !isDefaultValue(b) && a !== b;
|
|
279
|
+
}
|
|
276
280
|
function mergeInstructionEvent(base, inner) {
|
|
277
281
|
const baseName = eventName(base);
|
|
278
282
|
const innerName = eventName(inner);
|
|
@@ -281,6 +285,8 @@ function mergeInstructionEvent(base, inner) {
|
|
|
281
285
|
if (!baseData || !innerData)
|
|
282
286
|
return false;
|
|
283
287
|
if (PUMPFUN_TRADE_EVENT_NAMES.has(baseName) && PUMPFUN_TRADE_EVENT_NAMES.has(innerName)) {
|
|
288
|
+
if (baseData.is_buy !== innerData.is_buy || pumpIdentityKeyConflicts(baseData.mint, innerData.mint) || pumpIdentityKeyConflicts(baseData.user, innerData.user))
|
|
289
|
+
return false;
|
|
284
290
|
mergePumpfunTradeInstruction(baseData, innerData);
|
|
285
291
|
return true;
|
|
286
292
|
}
|
|
@@ -290,6 +296,8 @@ function mergeInstructionEvent(base, inner) {
|
|
|
290
296
|
}
|
|
291
297
|
if ((baseName === "PumpSwapBuy" && innerName === "PumpSwapBuy") ||
|
|
292
298
|
(baseName === "PumpSwapSell" && innerName === "PumpSwapSell")) {
|
|
299
|
+
if (pumpIdentityKeyConflicts(baseData.pool, innerData.pool) || pumpIdentityKeyConflicts(baseData.user, innerData.user))
|
|
300
|
+
return false;
|
|
293
301
|
mergePumpSwapBuySellInstruction(baseData, innerData);
|
|
294
302
|
return true;
|
|
295
303
|
}
|
|
@@ -304,6 +312,10 @@ function mergeInstructionEvent(base, inner) {
|
|
|
304
312
|
}
|
|
305
313
|
return false;
|
|
306
314
|
}
|
|
315
|
+
function isPumpTrade(event) {
|
|
316
|
+
const name = eventName(event);
|
|
317
|
+
return PUMPFUN_TRADE_EVENT_NAMES.has(name) || name === "PumpSwapBuy" || name === "PumpSwapSell";
|
|
318
|
+
}
|
|
307
319
|
function mergeInstructionEvents(events) {
|
|
308
320
|
if (events.length === 0)
|
|
309
321
|
return [];
|
|
@@ -317,11 +329,15 @@ function mergeInstructionEvents(events) {
|
|
|
317
329
|
const out = [];
|
|
318
330
|
let outerTarget = null;
|
|
319
331
|
const dlmmTargets = [];
|
|
332
|
+
const pumpTargets = [];
|
|
320
333
|
for (const item of events) {
|
|
321
334
|
if (item.innerIdx === null) {
|
|
322
335
|
out.push(item.event);
|
|
323
336
|
const resultIdx = out.length - 1;
|
|
324
337
|
outerTarget = { outerIdx: item.outerIdx, resultIdx };
|
|
338
|
+
pumpTargets.length = 0;
|
|
339
|
+
if (isPumpTrade(item.event))
|
|
340
|
+
pumpTargets.push({ outerIdx: item.outerIdx, stackHeight: item.stackHeight, resultIdx, consumed: false });
|
|
325
341
|
dlmmTargets.length = 0;
|
|
326
342
|
if (isDlmmEvent(item.event)) {
|
|
327
343
|
dlmmTargets.push({
|
|
@@ -332,7 +348,40 @@ function mergeInstructionEvents(events) {
|
|
|
332
348
|
}
|
|
333
349
|
continue;
|
|
334
350
|
}
|
|
335
|
-
if (item.
|
|
351
|
+
if (isPumpTrade(item.event) && (item.isEventCpi || !PUMPFUN_TRADE_EVENT_NAMES.has(eventName(item.event)) || eventPayload(item.event)?.ix_name)) {
|
|
352
|
+
if (item.isEventCpi) {
|
|
353
|
+
let target;
|
|
354
|
+
for (let i = pumpTargets.length - 1; i >= 0; i--) {
|
|
355
|
+
const candidate = pumpTargets[i];
|
|
356
|
+
if (candidate.outerIdx === item.outerIdx && (candidate.stackHeight === undefined || item.stackHeight === undefined || item.stackHeight === candidate.stackHeight + 1)) {
|
|
357
|
+
target = candidate;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (target && !target.consumed) {
|
|
362
|
+
target.consumed = true;
|
|
363
|
+
if (mergeInstructionEvent(out[target.resultIdx], item.event))
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
out.push(item.event);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
if (item.stackHeight === undefined)
|
|
370
|
+
pumpTargets.length = 0;
|
|
371
|
+
else
|
|
372
|
+
while (pumpTargets.length) {
|
|
373
|
+
const last = pumpTargets[pumpTargets.length - 1];
|
|
374
|
+
if (last.outerIdx !== item.outerIdx || (last.stackHeight !== undefined && last.stackHeight >= item.stackHeight))
|
|
375
|
+
pumpTargets.pop();
|
|
376
|
+
else
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
out.push(item.event);
|
|
380
|
+
pumpTargets.push({ outerIdx: item.outerIdx, stackHeight: item.stackHeight, resultIdx: out.length - 1, consumed: false });
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (item.isEventCpi && isDlmmEvent(item.event)) {
|
|
336
385
|
let merged = false;
|
|
337
386
|
for (let i = dlmmTargets.length - 1; i >= 0; i--) {
|
|
338
387
|
const target = dlmmTargets[i];
|
|
@@ -403,7 +452,7 @@ function parseOuterAndInnerInstructions(message, meta, signature, slot, txIndex,
|
|
|
403
452
|
outerIdx,
|
|
404
453
|
innerIdx: null,
|
|
405
454
|
stackHeight: 1,
|
|
406
|
-
|
|
455
|
+
isEventCpi: false,
|
|
407
456
|
event: ev,
|
|
408
457
|
});
|
|
409
458
|
}
|
|
@@ -426,7 +475,7 @@ function parseOuterAndInnerInstructions(message, meta, signature, slot, txIndex,
|
|
|
426
475
|
outerIdx: group.index,
|
|
427
476
|
innerIdx,
|
|
428
477
|
stackHeight: ix.stackHeight,
|
|
429
|
-
|
|
478
|
+
isEventCpi: isEventCpi(programId, data),
|
|
430
479
|
event: ev,
|
|
431
480
|
});
|
|
432
481
|
}
|
|
@@ -441,8 +490,36 @@ function recentBlockhashBytes(recentBlockhash) {
|
|
|
441
490
|
return undefined;
|
|
442
491
|
}
|
|
443
492
|
}
|
|
444
|
-
function detectPumpfunCreateInLogs(logMessages,
|
|
445
|
-
|
|
493
|
+
function detectPumpfunCreateInLogs(logMessages, signature, slot, txIndex, blockTimeUs, grpcRecvUs, recentBlockhash) {
|
|
494
|
+
const stack = [];
|
|
495
|
+
for (const log of logMessages) {
|
|
496
|
+
const invoke = (0, optimized_matcher_js_1.parseInvokeInfo)(log);
|
|
497
|
+
if (invoke && log === `Program ${invoke.programId} invoke [${invoke.depth}]`) {
|
|
498
|
+
stack.length = Math.min(stack.length, invoke.depth - 1);
|
|
499
|
+
stack.push(invoke.programId);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
// The last base64 character of the discriminator also contains payload bits.
|
|
503
|
+
if (stack[stack.length - 1] === program_ids_js_1.PUMPFUN_PROGRAM_ID &&
|
|
504
|
+
log.startsWith("Program data: G3KpTd7rY3")) {
|
|
505
|
+
const encoded = log.slice("Program data: ".length);
|
|
506
|
+
if (Buffer.from(encoded, "base64").toString("base64") === encoded) {
|
|
507
|
+
// Ignore the caller's output filter: a real create still classifies buys
|
|
508
|
+
// when Create events are excluded. Decode only rare create candidates.
|
|
509
|
+
const event = (0, optimized_matcher_js_1.parseLogOptimizedWithProgramId)(log, signature, slot, txIndex, blockTimeUs, grpcRecvUs, undefined, false, recentBlockhash, program_ids_js_1.PUMPFUN_PROGRAM_ID);
|
|
510
|
+
if (event && ("PumpFunCreate" in event || "PumpFunCreateV2" in event))
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const completed = (0, optimized_matcher_js_1.parseProgramCompleteInfo)(log);
|
|
515
|
+
if (completed && (log === `Program ${completed} success` ||
|
|
516
|
+
log.startsWith(`Program ${completed} failed: `))) {
|
|
517
|
+
const index = stack.lastIndexOf(completed);
|
|
518
|
+
if (index >= 0)
|
|
519
|
+
stack.length = index;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return false;
|
|
446
523
|
}
|
|
447
524
|
function applyRpcFills(events, msg, meta) {
|
|
448
525
|
const resolver = (0, rpc_invoke_map_js_1.getAccountKeyResolver)(msg, meta);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AddressLookupTableAccount, Connection } from "@solana/web3.js";
|
|
2
|
+
import type { ShredWasmTx } from "./instruction_parse.js";
|
|
3
|
+
/** Bounded ALT cache. resolve() only reads memory; its misses are refreshed by a timer. */
|
|
4
|
+
export declare class AddressLookupTableCache {
|
|
5
|
+
private readonly connection?;
|
|
6
|
+
private readonly refreshIntervalMs;
|
|
7
|
+
private static readonly CAPACITY;
|
|
8
|
+
private readonly tables;
|
|
9
|
+
private readonly pending;
|
|
10
|
+
private timer?;
|
|
11
|
+
private refreshing;
|
|
12
|
+
private generation;
|
|
13
|
+
private refreshOffset;
|
|
14
|
+
private failures;
|
|
15
|
+
constructor(connection?: Connection | undefined, initial?: readonly AddressLookupTableAccount[], refreshIntervalMs?: number);
|
|
16
|
+
get refreshFailures(): number;
|
|
17
|
+
private store;
|
|
18
|
+
private schedule;
|
|
19
|
+
/** null means this transaction is unresolved; callers must skip it, never emit guessed keys. */
|
|
20
|
+
resolve(tx: ShredWasmTx): string[] | null;
|
|
21
|
+
/** Explicit pre-subscription network operation, batched to the RPC's 100-account limit. */
|
|
22
|
+
preload(addresses: readonly string[]): Promise<void>;
|
|
23
|
+
start(): void;
|
|
24
|
+
stop(): void;
|
|
25
|
+
private refresh;
|
|
26
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddressLookupTableCache = void 0;
|
|
4
|
+
const alt_lookup_js_1 = require("./alt_lookup.js");
|
|
5
|
+
/** Bounded ALT cache. resolve() only reads memory; its misses are refreshed by a timer. */
|
|
6
|
+
class AddressLookupTableCache {
|
|
7
|
+
connection;
|
|
8
|
+
refreshIntervalMs;
|
|
9
|
+
static CAPACITY = 2048;
|
|
10
|
+
tables = new Map();
|
|
11
|
+
pending = new Set();
|
|
12
|
+
timer;
|
|
13
|
+
refreshing = false;
|
|
14
|
+
generation = 0;
|
|
15
|
+
refreshOffset = 0;
|
|
16
|
+
failures = 0;
|
|
17
|
+
constructor(connection, initial = [], refreshIntervalMs = 1000) {
|
|
18
|
+
this.connection = connection;
|
|
19
|
+
this.refreshIntervalMs = refreshIntervalMs;
|
|
20
|
+
if (!Number.isFinite(refreshIntervalMs) || refreshIntervalMs < 1) {
|
|
21
|
+
throw new RangeError("alt_refresh_interval_ms must be positive");
|
|
22
|
+
}
|
|
23
|
+
this.store(initial);
|
|
24
|
+
}
|
|
25
|
+
get refreshFailures() { return this.failures; }
|
|
26
|
+
store(tables) {
|
|
27
|
+
for (const table of tables) {
|
|
28
|
+
const key = table.key.toBase58();
|
|
29
|
+
// Encode each loaded pubkey once during refresh, never per transaction.
|
|
30
|
+
this.tables.set(key, table.state.addresses.map(address => address.toBase58()));
|
|
31
|
+
this.pending.delete(key);
|
|
32
|
+
if (this.tables.size > AddressLookupTableCache.CAPACITY) {
|
|
33
|
+
this.tables.delete(this.tables.keys().next().value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
schedule(key) {
|
|
38
|
+
if (this.pending.has(key))
|
|
39
|
+
return;
|
|
40
|
+
// Keep new misses eligible even when old tables remain unavailable forever.
|
|
41
|
+
if (this.pending.size >= AddressLookupTableCache.CAPACITY) {
|
|
42
|
+
this.pending.delete(this.pending.keys().next().value);
|
|
43
|
+
}
|
|
44
|
+
this.pending.add(key);
|
|
45
|
+
}
|
|
46
|
+
/** null means this transaction is unresolved; callers must skip it, never emit guessed keys. */
|
|
47
|
+
resolve(tx) {
|
|
48
|
+
if (tx.messageVersion !== "v0" || !tx.addressTableLookups?.length)
|
|
49
|
+
return tx.accounts;
|
|
50
|
+
let missing = false;
|
|
51
|
+
for (const lookup of tx.addressTableLookups) {
|
|
52
|
+
if (!this.tables.has(lookup.accountKey)) {
|
|
53
|
+
missing = true;
|
|
54
|
+
this.schedule(lookup.accountKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (missing)
|
|
58
|
+
return null;
|
|
59
|
+
const keys = tx.accounts.slice();
|
|
60
|
+
// Runtime layout is all writable keys across tables, then all readonly keys.
|
|
61
|
+
for (const kind of ["writableIndexes", "readonlyIndexes"]) {
|
|
62
|
+
for (const lookup of tx.addressTableLookups) {
|
|
63
|
+
const table = this.tables.get(lookup.accountKey);
|
|
64
|
+
for (const index of lookup[kind]) {
|
|
65
|
+
const key = table[index];
|
|
66
|
+
if (key === undefined) {
|
|
67
|
+
// A snapshot can predate an ALT extension. Refresh outside parsing.
|
|
68
|
+
this.schedule(lookup.accountKey);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
keys.push(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return keys;
|
|
76
|
+
}
|
|
77
|
+
/** Explicit pre-subscription network operation, batched to the RPC's 100-account limit. */
|
|
78
|
+
async preload(addresses) {
|
|
79
|
+
if (!this.connection)
|
|
80
|
+
throw new Error("ALT preloading requires config.connection");
|
|
81
|
+
const unique = [...new Set(addresses)];
|
|
82
|
+
for (let start = 0; start < unique.length; start += 100) {
|
|
83
|
+
this.store((await (0, alt_lookup_js_1.loadAddressLookupTableAccounts)(this.connection, unique.slice(start, start + 100))).values());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
start() {
|
|
87
|
+
if (!this.connection || this.timer)
|
|
88
|
+
return;
|
|
89
|
+
const generation = ++this.generation;
|
|
90
|
+
this.timer = setInterval(() => { void this.refresh(generation); }, this.refreshIntervalMs);
|
|
91
|
+
this.timer.unref?.();
|
|
92
|
+
}
|
|
93
|
+
stop() {
|
|
94
|
+
if (this.timer)
|
|
95
|
+
clearInterval(this.timer);
|
|
96
|
+
this.timer = undefined;
|
|
97
|
+
this.generation++;
|
|
98
|
+
}
|
|
99
|
+
async refresh(generation) {
|
|
100
|
+
if (!this.connection || this.refreshing)
|
|
101
|
+
return;
|
|
102
|
+
// Reserve a share of each batch for existing snapshots under a full miss queue.
|
|
103
|
+
const pendingLimit = 100 - Math.min(25, this.tables.size);
|
|
104
|
+
const keys = [...this.pending].slice(0, pendingLimit);
|
|
105
|
+
// Rotate unresolved misses too; absent tables must not starve the rest of the queue.
|
|
106
|
+
for (const key of keys) {
|
|
107
|
+
this.pending.delete(key);
|
|
108
|
+
this.pending.add(key);
|
|
109
|
+
}
|
|
110
|
+
// Rotate cached snapshots as well, so existing tables can acquire appended addresses.
|
|
111
|
+
const cached = [...this.tables.keys()];
|
|
112
|
+
if (cached.length) {
|
|
113
|
+
let visited = 0;
|
|
114
|
+
while (visited < cached.length && keys.length < 100) {
|
|
115
|
+
const key = cached[(this.refreshOffset + visited) % cached.length];
|
|
116
|
+
visited++;
|
|
117
|
+
if (!keys.includes(key))
|
|
118
|
+
keys.push(key);
|
|
119
|
+
}
|
|
120
|
+
this.refreshOffset = (this.refreshOffset + visited) % cached.length;
|
|
121
|
+
}
|
|
122
|
+
if (!keys.length)
|
|
123
|
+
return;
|
|
124
|
+
this.refreshing = true;
|
|
125
|
+
try {
|
|
126
|
+
const tables = await (0, alt_lookup_js_1.loadAddressLookupTableAccounts)(this.connection, keys);
|
|
127
|
+
if (generation === this.generation)
|
|
128
|
+
this.store(tables.values());
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
this.failures++;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
this.refreshing = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.AddressLookupTableCache = AddressLookupTableCache;
|
|
@@ -7,6 +7,8 @@ export type ShredStreamReceiveStats = {
|
|
|
7
7
|
entryDecodeFailures: number;
|
|
8
8
|
transactionsDecoded: number;
|
|
9
9
|
dexEventsQueued: number;
|
|
10
|
+
/** V0 transactions skipped because a complete cached ALT snapshot was unavailable. */
|
|
11
|
+
altCacheMissTransactions: number;
|
|
10
12
|
};
|
|
11
13
|
/** 有界 FIFO 队列(容量与 Rust `ArrayQueue::new(100_000)` 一致;`clone()` 共享底层缓冲,同 `Arc<ArrayQueue>`) */
|
|
12
14
|
export declare class ShredEventQueue {
|
|
@@ -25,9 +27,11 @@ export declare class ShredStreamClient {
|
|
|
25
27
|
private readonly endpoint;
|
|
26
28
|
private readonly config;
|
|
27
29
|
private readonly ServiceClient;
|
|
30
|
+
private lifecycleTail;
|
|
28
31
|
private loopAbort;
|
|
29
32
|
private loopPromise;
|
|
30
33
|
private activeCall;
|
|
34
|
+
private readonly altCache;
|
|
31
35
|
private receiveStats;
|
|
32
36
|
private constructor();
|
|
33
37
|
static connect(endpoint: string): Promise<ShredStreamClient>;
|
|
@@ -40,6 +44,10 @@ export declare class ShredStreamClient {
|
|
|
40
44
|
clone(): ShredStreamClient;
|
|
41
45
|
/** 当前订阅周期内 gRPC Entry 条数、解码失败次数、解码出的交易数、入队 DexEvent 数 */
|
|
42
46
|
getReceiveStats(): Readonly<ShredStreamReceiveStats>;
|
|
47
|
+
/** Prewarm required ALTs before subscribe(); this explicit call may perform RPC. */
|
|
48
|
+
preloadAddressLookupTables(addresses: readonly string[]): Promise<void>;
|
|
49
|
+
/** Background refresh failures; cache misses are reported separately in receive stats. */
|
|
50
|
+
getAltRefreshFailures(): number;
|
|
43
51
|
/**
|
|
44
52
|
* 与 Rust `new_with_config` 中 `ShredstreamProxyClient::connect_with_config(endpoint, &config)` 一致:
|
|
45
53
|
* `connection_timeout_ms` / `request_timeout_ms` / `max_decoding_message_size` 见 `shredstream/proto/mod.rs`。
|
|
@@ -54,12 +62,12 @@ export declare class ShredStreamClient {
|
|
|
54
62
|
subscribeWithFilter(eventTypeFilter?: EventTypeFilter): Promise<ShredEventQueue>;
|
|
55
63
|
/** 停止订阅并中止当前流 */
|
|
56
64
|
stop(): Promise<void>;
|
|
65
|
+
private serializeLifecycle;
|
|
66
|
+
private stopUnlocked;
|
|
57
67
|
private runReconnectLoop;
|
|
58
68
|
/** 与 Rust `stream_events` + `connect_with_config`(tonic Endpoint / Grpc 消息上限)一致 */
|
|
59
69
|
private streamOnce;
|
|
60
70
|
private processEntryMessage;
|
|
61
|
-
/**
|
|
71
|
+
/** Synchronous parsing: static keys or complete prewarmed ALT snapshots only. */
|
|
62
72
|
private processEntryMessageSync;
|
|
63
|
-
/** 拉取 ALT 后完整账户表解析 */
|
|
64
|
-
private processEntryMessageWithAlt;
|
|
65
73
|
}
|