sol-parser-sdk-python 0.5.7__py3-none-any.whl → 0.5.9__py3-none-any.whl

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.
@@ -49,16 +49,19 @@ def _ix_lane(ix_name: Any) -> int:
49
49
  return 0
50
50
 
51
51
 
52
- PumpfunLaneBase = Tuple[str, str, bool, int]
52
+ OccurrenceBase = Tuple[Any, ...]
53
53
 
54
54
 
55
- def _next_occurrence(base: PumpfunLaneBase, counts: Dict[PumpfunLaneBase, int]) -> int:
55
+ def _next_occurrence(base: OccurrenceBase, counts: Dict[OccurrenceBase, int]) -> int:
56
56
  current = counts.get(base, 0)
57
57
  counts[base] = current + 1
58
58
  return current
59
59
 
60
60
 
61
- def _dedupe_key(ev: DexEvent, pumpfun_lane_counts: Dict[PumpfunLaneBase, int]) -> Optional[str]:
61
+ def _dedupe_key(
62
+ ev: DexEvent,
63
+ occurrence_counts: Dict[OccurrenceBase, int],
64
+ ) -> Optional[str]:
62
65
  data = ev.data
63
66
  if data is None:
64
67
  return None
@@ -71,7 +74,7 @@ def _dedupe_key(ev: DexEvent, pumpfun_lane_counts: Dict[PumpfunLaneBase, int]) -
71
74
  bool(getattr(data, "is_buy", False)),
72
75
  lane,
73
76
  )
74
- occ = _next_occurrence(base, pumpfun_lane_counts)
77
+ occ = _next_occurrence(("PumpFun", *base), occurrence_counts)
75
78
  return f"PumpFunTrade|{base[0]}|{base[1]}|{base[2]}|{base[3]}|{occ}"
76
79
 
77
80
  t = ev.type
@@ -110,13 +113,32 @@ def _dedupe_key(ev: DexEvent, pumpfun_lane_counts: Dict[PumpfunLaneBase, int]) -
110
113
  if t == EventType.PUMP_SWAP_LIQUIDITY_REMOVED:
111
114
  return f"PumpSwapLiquidityRemoved|{getattr(data, 'pool', '')}|{getattr(data, 'user', '')}"
112
115
  if t == EventType.RAYDIUM_CLMM_SWAP:
113
- return f"RaydiumClmmSwap|{getattr(data, 'pool_state', '')}|{bool(getattr(data, 'zero_for_one', False))}"
116
+ pool = getattr(data, "pool_state", "")
117
+ occurrence = _next_occurrence(("RaydiumClmm", pool), occurrence_counts)
118
+ return f"RaydiumClmmSwap|{pool}|{occurrence}"
119
+ if t == EventType.RAYDIUM_CPMM_SWAP:
120
+ pool = getattr(data, "pool_id", "")
121
+ occurrence = _next_occurrence(("RaydiumCpmm", pool), occurrence_counts)
122
+ return f"RaydiumCpmmSwap|{pool}|{occurrence}"
114
123
  if t == EventType.RAYDIUM_AMM_V4_SWAP:
115
- return f"RaydiumAmmV4Swap|{getattr(data, 'amm', '')}"
124
+ base_out = getattr(data, "max_amount_in", 0) != 0
125
+ amount = getattr(data, "amount_out" if base_out else "amount_in", 0)
126
+ base = (base_out, amount)
127
+ occurrence = _next_occurrence(("RaydiumAmmV4", *base), occurrence_counts)
128
+ return f"RaydiumAmmV4Swap|{base_out}|{amount}|{occurrence}"
129
+ if t == EventType.ORCA_WHIRLPOOL_SWAP:
130
+ whirlpool = getattr(data, "whirlpool", "")
131
+ occurrence = _next_occurrence(("OrcaWhirlpool", whirlpool), occurrence_counts)
132
+ return f"OrcaWhirlpoolSwap|{whirlpool}|{occurrence}"
116
133
  if t == EventType.METEORA_DLMM_SWAP:
134
+ base = (
135
+ getattr(data, "pool", ""),
136
+ getattr(data, "from_addr", ""),
137
+ bool(getattr(data, "swap_for_y", False)),
138
+ )
139
+ occurrence = _next_occurrence(("MeteoraDlmm", *base), occurrence_counts)
117
140
  return (
118
- f"MeteoraDlmmSwap|{getattr(data, 'pool', '')}|"
119
- f"{getattr(data, 'from_addr', '')}|{bool(getattr(data, 'swap_for_y', False))}"
141
+ f"MeteoraDlmmSwap|{base[0]}|{base[1]}|{base[2]}|{occurrence}"
120
142
  )
121
143
  return None
122
144
 
@@ -163,6 +185,8 @@ def _merge_pumpfun_trade(log: Any, ix: Any) -> None:
163
185
  "quote_amount",
164
186
  "virtual_quote_reserves",
165
187
  "real_quote_reserves",
188
+ "holder_rewards_bps",
189
+ "holder_rewards",
166
190
  ):
167
191
  if getattr(log, attr, 0) == 0 and getattr(ix, attr, 0) != 0:
168
192
  setattr(log, attr, getattr(ix, attr))
@@ -195,6 +219,7 @@ def _merge_pumpfun_create(log: Any, ix: Any) -> None:
195
219
  "real_token_reserves",
196
220
  "token_total_supply",
197
221
  "virtual_quote_reserves",
222
+ "creator_fee_bps",
198
223
  ):
199
224
  if getattr(log, attr, 0) == 0 and getattr(ix, attr, 0) != 0:
200
225
  setattr(log, attr, getattr(ix, attr))
@@ -204,6 +229,9 @@ def _merge_pumpfun_create(log: Any, ix: Any) -> None:
204
229
  log.is_cashback_enabled = bool(getattr(log, "is_cashback_enabled", False)) or bool(
205
230
  getattr(ix, "is_cashback_enabled", False)
206
231
  )
232
+ log.is_holder_reward = bool(getattr(log, "is_holder_reward", False)) or bool(
233
+ getattr(ix, "is_holder_reward", False)
234
+ )
207
235
 
208
236
 
209
237
  def _merge_pumpfun_create_v2(log: Any, ix: Any) -> None:
@@ -283,6 +311,20 @@ def _merge_grpc_instruction_into_log(log_ev: DexEvent, ix_ev: DexEvent) -> None:
283
311
  "coin_creator",
284
312
  ):
285
313
  _fill_attr(log, attr, ix)
314
+ if getattr(log, "creator_fee_bps", 0) == 0 and getattr(ix, "creator_fee_bps", 0) != 0:
315
+ log.creator_fee_bps = ix.creator_fee_bps
316
+ log.is_mayhem_mode = bool(getattr(log, "is_mayhem_mode", False)) or bool(
317
+ getattr(ix, "is_mayhem_mode", False)
318
+ )
319
+ log.is_cashback_coin = bool(getattr(log, "is_cashback_coin", False)) or bool(
320
+ getattr(ix, "is_cashback_coin", False)
321
+ )
322
+ log.can_edit_creator_fee = bool(getattr(log, "can_edit_creator_fee", False)) or bool(
323
+ getattr(ix, "can_edit_creator_fee", False)
324
+ )
325
+ log.is_holder_reward = bool(getattr(log, "is_holder_reward", False)) or bool(
326
+ getattr(ix, "is_holder_reward", False)
327
+ )
286
328
  elif (
287
329
  log_ev.type == EventType.PUMP_SWAP_LIQUIDITY_ADDED
288
330
  and ix_ev.type == EventType.PUMP_SWAP_LIQUIDITY_ADDED
@@ -312,6 +354,8 @@ def _merge_grpc_instruction_into_log(log_ev: DexEvent, ix_ev: DexEvent) -> None:
312
354
  "serum_vault_signer",
313
355
  "user_source_token_account",
314
356
  "user_destination_token_account",
357
+ "user_source_owner",
358
+ "amm",
315
359
  ):
316
360
  _fill_attr(log, attr, ix)
317
361
  elif log_ev.type == EventType.RAYDIUM_LAUNCHLAB_POOL_CREATE and ix_ev.type == EventType.RAYDIUM_LAUNCHLAB_POOL_CREATE:
@@ -329,17 +373,17 @@ def dedupe_log_instruction_events(
329
373
  ) -> List[DexEvent]:
330
374
  out: List[DexEvent] = []
331
375
  index_by_key: Dict[str, int] = {}
332
- log_pumpfun_counts: Dict[PumpfunLaneBase, int] = {}
333
- ix_pumpfun_counts: Dict[PumpfunLaneBase, int] = {}
376
+ log_occurrences: Dict[OccurrenceBase, int] = {}
377
+ ix_occurrences: Dict[OccurrenceBase, int] = {}
334
378
 
335
379
  for ev in log_events:
336
- key = _dedupe_key(ev, log_pumpfun_counts)
380
+ key = _dedupe_key(ev, log_occurrences)
337
381
  if key is not None:
338
382
  index_by_key[key] = len(out)
339
383
  out.append(ev)
340
384
 
341
385
  for ev in instruction_events:
342
- key = _dedupe_key(ev, ix_pumpfun_counts)
386
+ key = _dedupe_key(ev, ix_occurrences)
343
387
  if key is None:
344
388
  out.append(ev)
345
389
  continue
sol_parser/merger.py CHANGED
@@ -32,6 +32,14 @@ from .event_types import (
32
32
  MeteoraDammV2CreatePositionEvent,
33
33
  MeteoraDammV2RemoveLiquidityEvent,
34
34
  MeteoraDammV2SwapEvent,
35
+ MeteoraDlmmAddLiquidityEvent,
36
+ MeteoraDlmmClaimFeeEvent,
37
+ MeteoraDlmmClosePositionEvent,
38
+ MeteoraDlmmCreatePositionEvent,
39
+ MeteoraDlmmInitializeBinArrayEvent,
40
+ MeteoraDlmmInitializePoolEvent,
41
+ MeteoraDlmmRemoveLiquidityEvent,
42
+ MeteoraDlmmSwapEvent,
35
43
  MeteoraPoolsAddLiquidityEvent,
36
44
  MeteoraPoolsRemoveLiquidityEvent,
37
45
  MeteoraPoolsSwapEvent,
@@ -44,6 +52,40 @@ from .grpc_types import EventType
44
52
 
45
53
  ZERO = "11111111111111111111111111111111"
46
54
 
55
+ GENERIC_MERGE_TYPES = (
56
+ PumpSwapCreatePoolEvent,
57
+ PumpSwapLiquidityAddedEvent,
58
+ PumpSwapLiquidityRemovedEvent,
59
+ RaydiumClmmSwapEvent,
60
+ RaydiumClmmIncreaseLiquidityEvent,
61
+ RaydiumClmmDecreaseLiquidityEvent,
62
+ RaydiumClmmCreatePoolEvent,
63
+ RaydiumClmmCollectFeeEvent,
64
+ RaydiumCpmmSwapEvent,
65
+ RaydiumCpmmDepositEvent,
66
+ RaydiumCpmmWithdrawEvent,
67
+ RaydiumAmmV4SwapEvent,
68
+ RaydiumAmmV4DepositEvent,
69
+ RaydiumAmmV4WithdrawEvent,
70
+ OrcaWhirlpoolSwapEvent,
71
+ OrcaWhirlpoolLiquidityIncreasedEvent,
72
+ OrcaWhirlpoolLiquidityDecreasedEvent,
73
+ MeteoraPoolsSwapEvent,
74
+ MeteoraPoolsAddLiquidityEvent,
75
+ MeteoraPoolsRemoveLiquidityEvent,
76
+ MeteoraDammV2SwapEvent,
77
+ MeteoraDammV2AddLiquidityEvent,
78
+ MeteoraDammV2RemoveLiquidityEvent,
79
+ MeteoraDammV2CreatePositionEvent,
80
+ MeteoraDammV2ClosePositionEvent,
81
+ MeteoraDlmmSwapEvent,
82
+ MeteoraDlmmAddLiquidityEvent,
83
+ MeteoraDlmmRemoveLiquidityEvent,
84
+ MeteoraDlmmInitializeBinArrayEvent,
85
+ MeteoraDlmmClaimFeeEvent,
86
+ RaydiumLaunchlabTradeEvent,
87
+ )
88
+
47
89
 
48
90
  def _merge_generic(base: Any, inner: Any) -> None:
49
91
  for f in dataclasses.fields(type(base)):
@@ -134,6 +176,8 @@ def merge_pumpfun_trade(base: PumpFunTradeEvent, inner: PumpFunTradeEvent) -> No
134
176
  "quote_amount",
135
177
  "virtual_quote_reserves",
136
178
  "real_quote_reserves",
179
+ "holder_rewards_bps",
180
+ "holder_rewards",
137
181
  "amount",
138
182
  "max_sol_cost",
139
183
  "min_sol_output",
@@ -243,6 +287,8 @@ def merge_pumpfun_create(base: PumpFunCreateEvent, inner: PumpFunCreateEvent) ->
243
287
  base.is_cashback_enabled = inner.is_cashback_enabled
244
288
  base.quote_mint = inner.quote_mint
245
289
  base.virtual_quote_reserves = inner.virtual_quote_reserves
290
+ base.creator_fee_bps = inner.creator_fee_bps
291
+ base.is_holder_reward = bool(base.is_holder_reward) or bool(inner.is_holder_reward)
246
292
 
247
293
 
248
294
  def merge_pumpfun_migrate(base: PumpFunMigrateEvent, inner: PumpFunMigrateEvent) -> None:
@@ -258,6 +304,11 @@ def merge_pumpfun_migrate(base: PumpFunMigrateEvent, inner: PumpFunMigrateEvent)
258
304
 
259
305
  def merge_dex_events(base: DexEvent, inner: DexEvent) -> None:
260
306
  """将 ``inner`` 合并进 ``base``(就地修改 ``base.data``)。"""
307
+ try_merge_dex_events(base, inner)
308
+
309
+
310
+ def try_merge_dex_events(base: DexEvent, inner: DexEvent) -> bool:
311
+ """Merge compatible events and report success so unmatched events are retained."""
261
312
  bd = base.data
262
313
  ind = inner.data
263
314
 
@@ -274,108 +325,45 @@ def merge_dex_events(base: DexEvent, inner: DexEvent) -> None:
274
325
  EventType.PUMP_FUN_BUY_EXACT_SOL_IN,
275
326
  ):
276
327
  merge_pumpfun_trade(bd, ind)
277
- return
328
+ return True
329
+ return False
278
330
 
279
331
  if isinstance(bd, PumpFunCreateEvent) and isinstance(ind, PumpFunCreateEvent):
280
332
  merge_pumpfun_create(bd, ind)
281
- return
333
+ return True
282
334
 
283
335
  if isinstance(bd, PumpFunCreateV2TokenEvent) and isinstance(ind, PumpFunCreateV2TokenEvent):
284
336
  _merge_generic(bd, ind)
285
- return
337
+ return True
286
338
 
287
339
  if isinstance(bd, PumpFunMigrateEvent) and isinstance(ind, PumpFunMigrateEvent):
288
340
  merge_pumpfun_migrate(bd, ind)
289
- return
341
+ return True
290
342
 
291
343
  if isinstance(bd, PumpSwapBuyEvent) and isinstance(ind, PumpSwapBuyEvent):
292
344
  merge_pumpswap_buy(bd, ind)
293
- return
345
+ return True
294
346
  if isinstance(bd, PumpSwapSellEvent) and isinstance(ind, PumpSwapSellEvent):
295
347
  merge_pumpswap_sell(bd, ind)
296
- return
297
- if isinstance(bd, PumpSwapCreatePoolEvent) and isinstance(ind, PumpSwapCreatePoolEvent):
298
- _merge_generic(bd, ind)
299
- return
300
- if isinstance(bd, PumpSwapLiquidityAddedEvent) and isinstance(ind, PumpSwapLiquidityAddedEvent):
301
- _merge_generic(bd, ind)
302
- return
303
- if isinstance(bd, PumpSwapLiquidityRemovedEvent) and isinstance(ind, PumpSwapLiquidityRemovedEvent):
304
- _merge_generic(bd, ind)
305
- return
306
-
307
- if isinstance(bd, RaydiumClmmSwapEvent) and isinstance(ind, RaydiumClmmSwapEvent):
308
- _merge_generic(bd, ind)
309
- return
310
- if isinstance(bd, RaydiumClmmIncreaseLiquidityEvent) and isinstance(ind, RaydiumClmmIncreaseLiquidityEvent):
311
- _merge_generic(bd, ind)
312
- return
313
- if isinstance(bd, RaydiumClmmDecreaseLiquidityEvent) and isinstance(ind, RaydiumClmmDecreaseLiquidityEvent):
314
- _merge_generic(bd, ind)
315
- return
316
- if isinstance(bd, RaydiumClmmCreatePoolEvent) and isinstance(ind, RaydiumClmmCreatePoolEvent):
317
- _merge_generic(bd, ind)
318
- return
319
- if isinstance(bd, RaydiumClmmCollectFeeEvent) and isinstance(ind, RaydiumClmmCollectFeeEvent):
320
- _merge_generic(bd, ind)
321
- return
322
-
323
- if isinstance(bd, RaydiumCpmmSwapEvent) and isinstance(ind, RaydiumCpmmSwapEvent):
324
- _merge_generic(bd, ind)
325
- return
326
- if isinstance(bd, RaydiumCpmmDepositEvent) and isinstance(ind, RaydiumCpmmDepositEvent):
327
- _merge_generic(bd, ind)
328
- return
329
- if isinstance(bd, RaydiumCpmmWithdrawEvent) and isinstance(ind, RaydiumCpmmWithdrawEvent):
330
- _merge_generic(bd, ind)
331
- return
332
-
333
- if isinstance(bd, RaydiumAmmV4SwapEvent) and isinstance(ind, RaydiumAmmV4SwapEvent):
334
- _merge_generic(bd, ind)
335
- return
336
- if isinstance(bd, RaydiumAmmV4DepositEvent) and isinstance(ind, RaydiumAmmV4DepositEvent):
337
- _merge_generic(bd, ind)
338
- return
339
- if isinstance(bd, RaydiumAmmV4WithdrawEvent) and isinstance(ind, RaydiumAmmV4WithdrawEvent):
340
- _merge_generic(bd, ind)
341
- return
342
-
343
- if isinstance(bd, OrcaWhirlpoolSwapEvent) and isinstance(ind, OrcaWhirlpoolSwapEvent):
344
- _merge_generic(bd, ind)
345
- return
346
- if isinstance(bd, OrcaWhirlpoolLiquidityIncreasedEvent) and isinstance(ind, OrcaWhirlpoolLiquidityIncreasedEvent):
347
- _merge_generic(bd, ind)
348
- return
349
- if isinstance(bd, OrcaWhirlpoolLiquidityDecreasedEvent) and isinstance(ind, OrcaWhirlpoolLiquidityDecreasedEvent):
348
+ return True
349
+ if isinstance(bd, MeteoraDlmmInitializePoolEvent) and isinstance(ind, MeteoraDlmmInitializePoolEvent):
350
+ creator, active_bin_id = bd.creator, bd.active_bin_id
350
351
  _merge_generic(bd, ind)
351
- return
352
-
353
- if isinstance(bd, MeteoraPoolsSwapEvent) and isinstance(ind, MeteoraPoolsSwapEvent):
354
- _merge_generic(bd, ind)
355
- return
356
- if isinstance(bd, MeteoraPoolsAddLiquidityEvent) and isinstance(ind, MeteoraPoolsAddLiquidityEvent):
352
+ bd.creator, bd.active_bin_id = creator, active_bin_id
353
+ return True
354
+ if isinstance(bd, MeteoraDlmmCreatePositionEvent) and isinstance(ind, MeteoraDlmmCreatePositionEvent):
355
+ lower_bin_id, width = bd.lower_bin_id, bd.width
357
356
  _merge_generic(bd, ind)
358
- return
359
- if isinstance(bd, MeteoraPoolsRemoveLiquidityEvent) and isinstance(ind, MeteoraPoolsRemoveLiquidityEvent):
357
+ bd.lower_bin_id, bd.width = lower_bin_id, width
358
+ return True
359
+ if isinstance(bd, MeteoraDlmmClosePositionEvent) and isinstance(ind, MeteoraDlmmClosePositionEvent):
360
+ pool = bd.pool
360
361
  _merge_generic(bd, ind)
361
- return
362
+ bd.pool = pool
363
+ return True
362
364
 
363
- if isinstance(bd, MeteoraDammV2SwapEvent) and isinstance(ind, MeteoraDammV2SwapEvent):
364
- _merge_generic(bd, ind)
365
- return
366
- if isinstance(bd, MeteoraDammV2AddLiquidityEvent) and isinstance(ind, MeteoraDammV2AddLiquidityEvent):
367
- _merge_generic(bd, ind)
368
- return
369
- if isinstance(bd, MeteoraDammV2RemoveLiquidityEvent) and isinstance(ind, MeteoraDammV2RemoveLiquidityEvent):
365
+ if base.type == inner.type and type(bd) is type(ind) and isinstance(bd, GENERIC_MERGE_TYPES):
370
366
  _merge_generic(bd, ind)
371
- return
372
- if isinstance(bd, MeteoraDammV2CreatePositionEvent) and isinstance(ind, MeteoraDammV2CreatePositionEvent):
373
- _merge_generic(bd, ind)
374
- return
375
- if isinstance(bd, MeteoraDammV2ClosePositionEvent) and isinstance(ind, MeteoraDammV2ClosePositionEvent):
376
- _merge_generic(bd, ind)
377
- return
367
+ return True
378
368
 
379
- if isinstance(bd, RaydiumLaunchlabTradeEvent) and isinstance(ind, RaydiumLaunchlabTradeEvent):
380
- _merge_generic(bd, ind)
381
- return
369
+ return False
sol_parser/parser.py CHANGED
@@ -79,6 +79,17 @@ def parse_log_optimized(
79
79
  ) -> Optional[DexEvent]:
80
80
  """单次 base64 decode 后按 discriminator 做 early filter,再按实际事件类型二次过滤。"""
81
81
  grpc = int(time.time() * 1_000_000) if grpc_recv_us is None else grpc_recv_us
82
+ if program_id == "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" and "ray_log: " in log:
83
+ from .dex_parsers import parse_amm_ray_log_swap
84
+ from .grpc_types import EventType
85
+
86
+ if event_type_filter is not None and not event_type_filter.should_include(
87
+ EventType.RAYDIUM_AMM_V4_SWAP
88
+ ):
89
+ return None
90
+ return parse_amm_ray_log_swap(
91
+ log, _meta(signature, slot, tx_index, block_time_us, grpc, recent_blockhash)
92
+ )
82
93
  buf = decode_program_data_line(log)
83
94
  if not buf:
84
95
  return None
@@ -102,10 +102,12 @@ def enrich_create_v2_from_create_events(events: List[DexEvent]) -> None:
102
102
  "real_token_reserves",
103
103
  "token_total_supply",
104
104
  "virtual_quote_reserves",
105
+ "creator_fee_bps",
105
106
  ):
106
107
  _fill_int_if_zero(c2, attr, getattr(c, attr))
107
108
  c2.is_cashback_enabled = c2.is_cashback_enabled or c.is_cashback_enabled
108
109
  c2.is_mayhem_mode = c2.is_mayhem_mode or c.is_mayhem_mode
110
+ c2.is_holder_reward = c2.is_holder_reward or c.is_holder_reward
109
111
 
110
112
 
111
113
  def enrich_pumpfun_trades_from_create_instructions(events: List[DexEvent]) -> None:
sol_parser/rpc_parser.py CHANGED
@@ -11,7 +11,11 @@ import base58
11
11
  from .dex_parsers import DexEvent
12
12
  from .grpc_types import EventTypeFilter, EventType, event_type_filter_allows_instruction_parsing
13
13
  from .inner_instruction_parser import parse_inner_instruction
14
- from .instructions import parse_inner_compiled_instruction_if_supported, parse_instruction_unified
14
+ from .instructions import (
15
+ METEORA_DLMM_PROGRAM_ID,
16
+ parse_inner_compiled_instruction_if_supported,
17
+ parse_instruction_unified,
18
+ )
15
19
  from .log_instr_dedup import dedupe_log_instruction_events
16
20
  from .pumpfun_fee_enrich import enrich_pumpfun_same_tx_post_merge
17
21
 
@@ -30,6 +34,7 @@ class RpcCompiledInstruction:
30
34
  program_id_index: int
31
35
  accounts: Union[List[int], bytes]
32
36
  data: bytes
37
+ stack_height: Optional[int] = None
33
38
 
34
39
 
35
40
  @dataclass
@@ -215,8 +220,9 @@ def parse_rpc_transaction(
215
220
  block_tx_index = int(getattr(tx, "transaction_index", 0) or 0)
216
221
  full_account_keys = _merge_rpc_full_account_keys(msg.account_keys, meta)
217
222
 
218
- indexed_instruction_events: List[Tuple[int, Optional[int], DexEvent]] = []
223
+ indexed_instruction_events: List[Tuple[Any, ...]] = []
219
224
  from .grpc_instruction_parser import (
225
+ _is_dlmm_event_cpi,
220
226
  detect_pumpfun_create_from_logs,
221
227
  merge_instruction_events,
222
228
  )
@@ -238,7 +244,7 @@ def parse_rpc_transaction(
238
244
  is_created_buy=has_pumpfun_create_log,
239
245
  )
240
246
  if ev:
241
- indexed_instruction_events.append((outer_idx, None, ev))
247
+ indexed_instruction_events.append((outer_idx, None, 1, False, ev))
242
248
 
243
249
  # 解析内层指令
244
250
  for group in meta.inner_instructions:
@@ -256,7 +262,18 @@ def parse_rpc_transaction(
256
262
  is_created_buy=has_pumpfun_create_log,
257
263
  )
258
264
  if ev:
259
- indexed_instruction_events.append((int(group.index), inner_idx, ev))
265
+ pid = (
266
+ full_account_keys[ix.program_id_index]
267
+ if ix.program_id_index < len(full_account_keys)
268
+ else ""
269
+ )
270
+ indexed_instruction_events.append((
271
+ int(group.index),
272
+ inner_idx,
273
+ ix.stack_height,
274
+ pid == METEORA_DLMM_PROGRAM_ID and _is_dlmm_event_cpi(ix.data),
275
+ ev,
276
+ ))
260
277
 
261
278
  instruction_events = merge_instruction_events(indexed_instruction_events)
262
279
  enrich_pumpfun_same_tx_post_merge(instruction_events)
@@ -525,6 +542,13 @@ def _parse_rpc_compiled_ix(ix: dict, account_keys: List[str]) -> RpcCompiledInst
525
542
  program_id_index=pidx,
526
543
  accounts=acc_list,
527
544
  data=_ix_data_from_rpc(ix),
545
+ stack_height=(
546
+ int(ix["stackHeight"])
547
+ if ix.get("stackHeight") is not None
548
+ else int(ix["stack_height"])
549
+ if ix.get("stack_height") is not None
550
+ else None
551
+ ),
528
552
  )
529
553
 
530
554
 
@@ -717,6 +741,8 @@ def rpc_response_to_solana_storage(
717
741
  acc = ix.accounts
718
742
  ii.accounts = bytes(acc) if not isinstance(acc, bytes) else acc
719
743
  ii.data = ix.data
744
+ if ix.stack_height is not None:
745
+ ii.stack_height = ix.stack_height
720
746
  if m.loaded_addresses:
721
747
  for w in m.loaded_addresses.writable:
722
748
  meta.loaded_writable_addresses.append(base58.b58decode(w))