sol-parser-sdk 0.5.16 → 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.
Files changed (66) hide show
  1. package/README.md +31 -15
  2. package/README_CN.md +30 -14
  3. package/dist/accounts/pumpfun.js +15 -0
  4. package/dist/accounts/pumpswap.js +2 -0
  5. package/dist/core/account_dispatcher_rpc.d.ts +2 -1
  6. package/dist/core/account_dispatcher_rpc.js +120 -11
  7. package/dist/core/account_fill_pumpfun.js +27 -16
  8. package/dist/core/account_fill_pumpswap.d.ts +2 -0
  9. package/dist/core/account_fill_pumpswap.js +55 -6
  10. package/dist/core/account_fill_raydium.js +4 -0
  11. package/dist/core/dex_event.d.ts +149 -0
  12. package/dist/grpc/client.js +2 -0
  13. package/dist/grpc/log_instr_dedup.js +75 -7
  14. package/dist/grpc/order_buffer.d.ts +5 -0
  15. package/dist/grpc/order_buffer.js +53 -17
  16. package/dist/grpc/rpc_to_grpc.js +3 -2
  17. package/dist/grpc/types.d.ts +4 -1
  18. package/dist/grpc/types.js +23 -0
  19. package/dist/grpc/yellowstone_transaction_adapter.js +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1 -0
  22. package/dist/instr/inner.js +28 -1
  23. package/dist/instr/meteora_damm_ix.js +26 -0
  24. package/dist/instr/meteora_pools_ix.js +17 -9
  25. package/dist/instr/orca_whirlpool_ix.js +13 -9
  26. package/dist/instr/pumpfun_ix.js +87 -3
  27. package/dist/instr/pumpswap_ix.js +85 -10
  28. package/dist/instr/raydium_clmm_ix.js +5 -3
  29. package/dist/instr/raydium_cpmm_ix.js +11 -8
  30. package/dist/logs/meteora_damm.d.ts +7 -0
  31. package/dist/logs/meteora_damm.js +211 -0
  32. package/dist/logs/meteora_dbc.d.ts +3 -0
  33. package/dist/logs/meteora_dbc.js +23 -0
  34. package/dist/logs/meteora_dlmm.js +16 -4
  35. package/dist/logs/optimized_matcher.js +93 -10
  36. package/dist/logs/program_log_discriminators.d.ts +11 -0
  37. package/dist/logs/program_log_discriminators.js +11 -0
  38. package/dist/logs/pump.js +17 -3
  39. package/dist/logs/pump_amm.js +8 -2
  40. package/dist/logs/pump_upgrade.d.ts +68 -0
  41. package/dist/logs/pump_upgrade.js +128 -0
  42. package/dist/logs/raydium_cpmm.js +7 -3
  43. package/dist/rpc_transaction.js +87 -7
  44. package/dist/shredstream/alt_cache.d.ts +26 -0
  45. package/dist/shredstream/alt_cache.js +138 -0
  46. package/dist/shredstream/client.d.ts +11 -3
  47. package/dist/shredstream/client.js +76 -72
  48. package/dist/shredstream/config.d.ts +7 -3
  49. package/dist/shredstream/index.d.ts +3 -2
  50. package/dist/shredstream/index.js +5 -3
  51. package/dist/shredstream/instruction_parse.d.ts +1 -1
  52. package/dist/shredstream/instruction_parse.js +1 -0
  53. package/dist/simulation_route.d.ts +1 -1
  54. package/dist/simulation_route.js +17 -4
  55. package/dist/transaction_route.js +20 -1
  56. package/dist/wire_transaction.js +9 -10
  57. package/docs/USAGE.md +161 -0
  58. package/package.json +2 -5
  59. package/NATIVE_ALIGNMENT.md +0 -312
  60. package/NATIVE_DAMM_FACTORY_ALIGNMENT_20261004.md +0 -16
  61. package/NATIVE_DAMM_FACTORY_EVIDENCE_20261004.json +0 -55
  62. package/NATIVE_MIGRATION.md +0 -38
  63. package/NATIVE_PUMPFUN_NATIVE_DEBIT_ALIGNMENT_20261004.md +0 -18
  64. package/NATIVE_PUMPFUN_NATIVE_DEBIT_EVIDENCE_20261004.json +0 -57
  65. package/NATIVE_PUMPFUN_SETTLEMENT_ALIGNMENT_20261004.md +0 -11
  66. package/NATIVE_PUMPFUN_SETTLEMENT_EVIDENCE_20261004.json +0 -70
package/dist/logs/pump.js CHANGED
@@ -173,7 +173,9 @@ function parseTradeFromData(data, metadata, isCreatedBuy) {
173
173
  const real_quote_reserves = readOptionalU64(data, tail);
174
174
  const holder_rewards_bps = readOptionalU64(data, tail);
175
175
  const holder_rewards = readOptionalU64(data, tail);
176
+ const creator_fee_unclaimed = readOptionalU64(data, tail);
176
177
  const trade = {
178
+ creator_fee_unclaimed,
177
179
  metadata,
178
180
  mint,
179
181
  sol_amount,
@@ -216,14 +218,14 @@ function parseTradeFromData(data, metadata, isCreatedBuy) {
216
218
  token_program: (0, dex_event_js_1.defaultPubkey)(),
217
219
  creator_vault: (0, dex_event_js_1.defaultPubkey)(),
218
220
  };
219
- if (ix_name === "buy")
221
+ if (ix_name === "buy" || ix_name === "buy_v3")
220
222
  return { PumpFunBuy: trade };
221
- if (ix_name === "sell")
223
+ if (ix_name === "sell" || ix_name === "sell_v3")
222
224
  return { PumpFunSell: trade };
223
225
  if (ix_name === "buy_exact_sol_in") {
224
226
  return { PumpFunBuyExactSolIn: trade };
225
227
  }
226
- if (ix_name === "buy_exact_quote_in")
228
+ if (ix_name === "buy_exact_quote_in" || ix_name === "buy_exact_quote_in_v3")
227
229
  return { PumpFunBuy: trade };
228
230
  return { PumpFunTrade: trade };
229
231
  }
@@ -241,6 +243,17 @@ function parseCreateFromData(data, metadata) {
241
243
  if (!n3)
242
244
  return null;
243
245
  o = n3.next;
246
+ if (data.length - o === 96) {
247
+ return { PumpFunCreate: {
248
+ metadata, name: n1.s, symbol: n2.s, uri: n3.s,
249
+ mint: (0, binary_js_1.readPubkey)(data, o), bonding_curve: (0, binary_js_1.readPubkey)(data, o + 32), user: (0, binary_js_1.readPubkey)(data, o + 64),
250
+ creator: (0, dex_event_js_1.defaultPubkey)(), timestamp: 0n, virtual_token_reserves: 0n, virtual_sol_reserves: 0n,
251
+ real_token_reserves: 0n, token_total_supply: 0n, token_program: (0, dex_event_js_1.defaultPubkey)(),
252
+ is_mayhem_mode: false, is_cashback_enabled: false, quote_mint: normalizePumpfunQuoteMint((0, dex_event_js_1.defaultPubkey)()),
253
+ quote_vault: (0, dex_event_js_1.defaultPubkey)(), quote_token_program: (0, dex_event_js_1.defaultPubkey)(), virtual_quote_reserves: 0n,
254
+ creator_fee_bps: 0n, is_holder_reward: false, ix_name: "create",
255
+ } };
256
+ }
244
257
  if (data.length < o + 32 * 4 + 8 * 5 + 32 + 1)
245
258
  return null;
246
259
  const mint = (0, binary_js_1.readPubkey)(data, o);
@@ -305,6 +318,7 @@ function parseCreateFromData(data, metadata) {
305
318
  virtual_quote_reserves,
306
319
  creator_fee_bps,
307
320
  is_holder_reward,
321
+ depth: data[o + 1] ?? 0,
308
322
  ix_name: "create",
309
323
  };
310
324
  return { PumpFunCreate: ev };
@@ -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 = 10 * 8 + 5 * 32;
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 = 10 * 8 + 5 * 32;
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
- // pool_id + six u64 fields + base_input. Newer IDLs append mint/fee fields,
16
- // which remain wire-compatible with this stable prefix.
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,
@@ -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 isDlmmEventCpi(programId, data) {
55
- return programId === program_ids_js_1.METEORA_DLMM_PROGRAM_ID && data.length >= 16 && (bytesEqualAt(data, EVENT_CPI_PREFIX, 0) ||
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.isDlmmEventCpi) {
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
- isDlmmEventCpi: false,
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
- isDlmmEventCpi: isDlmmEventCpi(programId, data),
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, _signature, _slot, _txIndex, _blockTimeUs, _grpcRecvUs, _recentBlockhash) {
445
- return logMessages.some((log) => log.includes("Program data: G3KpTd7rY3Y"));
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);
@@ -473,6 +550,9 @@ function applyAccountFillsToLogEvents(events, msg, meta) {
473
550
  * 解析已获取的 `VersionedTransactionResponse`(顺序:指令 → 日志 → 账户/数据填充)。
474
551
  */
475
552
  function parseRpcTransaction(tx, signature, filter, options) {
553
+ // Failed transactions roll back every instruction, including earlier create logs.
554
+ if (tx.meta?.err != null)
555
+ return { ok: true, events: [] };
476
556
  const msg = tx.transaction?.message;
477
557
  if (!msg || !(0, rpc_invoke_map_js_1.isCompiledVersionedMessage)(msg)) {
478
558
  return {
@@ -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;