sol-parser-sdk-python 0.4.4__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.
Files changed (54) hide show
  1. sol_parser/__init__.py +400 -0
  2. sol_parser/account_dispatcher.py +209 -0
  3. sol_parser/account_fillers/__init__.py +5 -0
  4. sol_parser/account_fillers/bonk.py +30 -0
  5. sol_parser/account_fillers/meteora.py +51 -0
  6. sol_parser/account_fillers/orca.py +40 -0
  7. sol_parser/account_fillers/pumpfun.py +97 -0
  8. sol_parser/account_fillers/pumpswap.py +93 -0
  9. sol_parser/account_fillers/raydium.py +119 -0
  10. sol_parser/accounts/__init__.py +461 -0
  11. sol_parser/accounts/rpc_wallet.py +64 -0
  12. sol_parser/accounts/utils.py +71 -0
  13. sol_parser/check_migration.py +18 -0
  14. sol_parser/clock.py +10 -0
  15. sol_parser/common/__init__.py +27 -0
  16. sol_parser/dex_parsers.py +2576 -0
  17. sol_parser/entries_decode.py +186 -0
  18. sol_parser/env_config.py +215 -0
  19. sol_parser/event_types.py +1750 -0
  20. sol_parser/geyser_pb2.py +148 -0
  21. sol_parser/geyser_pb2_grpc.py +398 -0
  22. sol_parser/grpc/__init__.py +61 -0
  23. sol_parser/grpc/geyser_connect.py +42 -0
  24. sol_parser/grpc/subscribe_builder.py +133 -0
  25. sol_parser/grpc/transaction_meta.py +183 -0
  26. sol_parser/grpc_client.py +870 -0
  27. sol_parser/grpc_instruction_parser.py +318 -0
  28. sol_parser/grpc_types.py +919 -0
  29. sol_parser/inner_instruction_parser.py +281 -0
  30. sol_parser/instr/__init__.py +15 -0
  31. sol_parser/instr_account_utils.py +58 -0
  32. sol_parser/instructions.py +1026 -0
  33. sol_parser/json_util.py +41 -0
  34. sol_parser/log_instr_dedup.py +284 -0
  35. sol_parser/logs/__init__.py +15 -0
  36. sol_parser/merger.py +233 -0
  37. sol_parser/order_buffer.py +171 -0
  38. sol_parser/parser.py +300 -0
  39. sol_parser/pumpfun_fee_enrich.py +75 -0
  40. sol_parser/rpc_parser.py +655 -0
  41. sol_parser/rust_api_inventory.py +42 -0
  42. sol_parser/rust_event_json.py +50 -0
  43. sol_parser/shredstream_client.py +191 -0
  44. sol_parser/shredstream_pb2.py +40 -0
  45. sol_parser/shredstream_pb2_grpc.py +81 -0
  46. sol_parser/shredstream_pumpfun.py +296 -0
  47. sol_parser/solana_storage_pb2.py +75 -0
  48. sol_parser/solana_storage_pb2_grpc.py +24 -0
  49. sol_parser/u128_parity.py +115 -0
  50. sol_parser/verify_discriminators.py +85 -0
  51. sol_parser_sdk_python-0.4.4.dist-info/METADATA +14 -0
  52. sol_parser_sdk_python-0.4.4.dist-info/RECORD +54 -0
  53. sol_parser_sdk_python-0.4.4.dist-info/WHEEL +4 -0
  54. sol_parser_sdk_python-0.4.4.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,171 @@
1
+ """OrderMode buffers for low-latency DEX subscriptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from dataclasses import dataclass
7
+ from typing import Callable, Dict, List, Tuple
8
+
9
+ from .event_types import DexEvent
10
+ from .grpc_types import ClientConfig, OrderMode
11
+
12
+
13
+ @dataclass
14
+ class _Batch:
15
+ slot: int
16
+ tx_index: int
17
+ seq: int
18
+ events: List[DexEvent]
19
+
20
+
21
+ def _event_slot_tx(events: List[DexEvent], fallback_slot: int, fallback_tx_index: int) -> Tuple[int, int]:
22
+ if not events:
23
+ return fallback_slot, fallback_tx_index
24
+ meta = getattr(events[0].data, "metadata", None)
25
+ slot = int(getattr(meta, "slot", fallback_slot) or fallback_slot)
26
+ tx_index = int(getattr(meta, "tx_index", fallback_tx_index) or fallback_tx_index)
27
+ return slot, tx_index
28
+
29
+
30
+ class OrderDispatcher:
31
+ def __init__(self, config: ClientConfig):
32
+ self.mode = config.order_mode
33
+ self.timeout_s = max(0.001, float(config.order_timeout_ms or 100) / 1000.0)
34
+ self.micro_batch_s = max(0.000001, float(config.micro_batch_us or 100) / 1_000_000.0)
35
+ self.slots: Dict[int, List[_Batch]] = {}
36
+ self.watermarks: Dict[int, int] = {}
37
+ self.micro_batch: List[_Batch] = []
38
+ self.micro_start = 0.0
39
+ self.last_flush = time.monotonic()
40
+ self.current_slot = 0
41
+ self.seq = 0
42
+
43
+ @property
44
+ def needs_timer(self) -> bool:
45
+ return self.mode != OrderMode.UNORDERED
46
+
47
+ @property
48
+ def interval_s(self) -> float:
49
+ if self.mode == OrderMode.MICRO_BATCH:
50
+ return self.micro_batch_s
51
+ return max(0.001, self.timeout_s / 2.0)
52
+
53
+ def push_transaction_events(
54
+ self,
55
+ events: List[DexEvent],
56
+ fallback_slot: int,
57
+ fallback_tx_index: int,
58
+ emit: Callable[[DexEvent], None],
59
+ ) -> None:
60
+ if not events:
61
+ return
62
+ slot, tx_index = _event_slot_tx(events, fallback_slot, fallback_tx_index)
63
+ batch = _Batch(slot=slot, tx_index=tx_index, seq=self.seq, events=events)
64
+ self.seq += 1
65
+
66
+ if self.mode == OrderMode.UNORDERED:
67
+ self._emit_batch(batch, emit)
68
+ elif self.mode == OrderMode.ORDERED:
69
+ self._push_ordered(batch, emit)
70
+ elif self.mode == OrderMode.STREAMING_ORDERED:
71
+ self._push_streaming(batch, emit)
72
+ elif self.mode == OrderMode.MICRO_BATCH:
73
+ self._push_micro_batch(batch, emit)
74
+ else:
75
+ self._emit_batch(batch, emit)
76
+
77
+ def flush_due(self, emit: Callable[[DexEvent], None]) -> None:
78
+ now = time.monotonic()
79
+ if self.mode in (OrderMode.ORDERED, OrderMode.STREAMING_ORDERED):
80
+ if self.slots and now - self.last_flush > self.timeout_s:
81
+ self._flush_all_slots(emit)
82
+ if self.mode == OrderMode.MICRO_BATCH:
83
+ if self.micro_batch and now - self.micro_start >= self.micro_batch_s:
84
+ self._flush_micro_batch(emit)
85
+
86
+ def flush_all(self, emit: Callable[[DexEvent], None]) -> None:
87
+ self._flush_all_slots(emit)
88
+ self._flush_micro_batch(emit)
89
+
90
+ def _push_ordered(self, batch: _Batch, emit: Callable[[DexEvent], None]) -> None:
91
+ if batch.slot > self.current_slot and self.current_slot > 0:
92
+ self._flush_before(batch.slot, emit)
93
+ if batch.slot > self.current_slot:
94
+ self.current_slot = batch.slot
95
+ self.slots.setdefault(batch.slot, []).append(batch)
96
+
97
+ def _push_streaming(self, batch: _Batch, emit: Callable[[DexEvent], None]) -> None:
98
+ if batch.slot > self.current_slot and self.current_slot > 0:
99
+ self._flush_before(batch.slot, emit)
100
+ for slot in list(self.watermarks):
101
+ if slot < batch.slot:
102
+ self.watermarks.pop(slot, None)
103
+ if batch.slot > self.current_slot:
104
+ self.current_slot = batch.slot
105
+
106
+ expected = self.watermarks.get(batch.slot, 0)
107
+ if batch.tx_index == expected:
108
+ self._emit_batch(batch, emit)
109
+ watermark = expected + 1
110
+ buffered = self.slots.get(batch.slot, [])
111
+ buffered.sort(key=_batch_key)
112
+ while True:
113
+ pos = next((i for i, item in enumerate(buffered) if item.tx_index == watermark), -1)
114
+ if pos < 0:
115
+ break
116
+ self._emit_batch(buffered.pop(pos), emit)
117
+ watermark += 1
118
+ if buffered:
119
+ self.slots[batch.slot] = buffered
120
+ else:
121
+ self.slots.pop(batch.slot, None)
122
+ self.watermarks[batch.slot] = watermark
123
+ self.last_flush = time.monotonic()
124
+ elif batch.tx_index > expected:
125
+ self.slots.setdefault(batch.slot, []).append(batch)
126
+
127
+ def _push_micro_batch(self, batch: _Batch, emit: Callable[[DexEvent], None]) -> None:
128
+ now = time.monotonic()
129
+ if not self.micro_batch:
130
+ self.micro_start = now
131
+ self.micro_batch.append(batch)
132
+ if now - self.micro_start >= self.micro_batch_s:
133
+ self._flush_micro_batch(emit)
134
+
135
+ def _flush_before(self, slot: int, emit: Callable[[DexEvent], None]) -> None:
136
+ for s in sorted(k for k in self.slots if k < slot):
137
+ batches = self.slots.pop(s)
138
+ batches.sort(key=_batch_key)
139
+ for batch in batches:
140
+ self._emit_batch(batch, emit)
141
+ self.watermarks.pop(s, None)
142
+ self.last_flush = time.monotonic()
143
+
144
+ def _flush_all_slots(self, emit: Callable[[DexEvent], None]) -> None:
145
+ for s in sorted(self.slots):
146
+ batches = self.slots[s]
147
+ batches.sort(key=_batch_key)
148
+ for batch in batches:
149
+ self._emit_batch(batch, emit)
150
+ self.slots.clear()
151
+ self.watermarks.clear()
152
+ self.last_flush = time.monotonic()
153
+
154
+ def _flush_micro_batch(self, emit: Callable[[DexEvent], None]) -> None:
155
+ if not self.micro_batch:
156
+ return
157
+ self.micro_batch.sort(key=_batch_key)
158
+ for batch in self.micro_batch:
159
+ self._emit_batch(batch, emit)
160
+ self.micro_batch = []
161
+ self.micro_start = 0.0
162
+ self.last_flush = time.monotonic()
163
+
164
+ @staticmethod
165
+ def _emit_batch(batch: _Batch, emit: Callable[[DexEvent], None]) -> None:
166
+ for event in batch.events:
167
+ emit(event)
168
+
169
+
170
+ def _batch_key(batch: _Batch) -> Tuple[int, int, int]:
171
+ return (batch.slot, batch.tx_index, batch.seq)
sol_parser/parser.py ADDED
@@ -0,0 +1,300 @@
1
+ from __future__ import annotations
2
+
3
+ import base58
4
+ import base64
5
+ import struct
6
+ import time
7
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
8
+
9
+ from .dex_parsers import (
10
+ DexEvent,
11
+ apply_event_type_filter,
12
+ dispatch_program_data,
13
+ event_type_for_discriminator,
14
+ filter_allows_unknown_log_event,
15
+ parse_trade_from_data,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from .grpc_types import SubscribeUpdateTransactionInfo
20
+ from .pumpfun_fee_enrich import enrich_pumpfun_same_tx_post_merge
21
+
22
+
23
+ def _disc8(bs: bytes) -> int:
24
+ return struct.unpack("<Q", bs)[0]
25
+
26
+
27
+ def decode_program_data_line(log: str) -> Optional[bytes]:
28
+ p = "Program data: "
29
+ i = log.find(p)
30
+ if i < 0:
31
+ return None
32
+ s = log[i + len(p) :].strip()
33
+ if len(s) > 2700:
34
+ return None
35
+ try:
36
+ raw = base64.standard_b64decode(s)
37
+ except Exception:
38
+ return None
39
+ if len(raw) < 8 or len(raw) > 2048:
40
+ return None
41
+ return raw
42
+
43
+
44
+ def _meta(
45
+ sig: str,
46
+ slot: int,
47
+ tx_idx: int,
48
+ block_us: Optional[int],
49
+ grpc_us: int,
50
+ recent_blockhash: str = "",
51
+ ) -> dict:
52
+ m: dict = {
53
+ "signature": sig,
54
+ "slot": slot,
55
+ "tx_index": tx_idx,
56
+ "block_time_us": 0 if block_us is None else block_us,
57
+ "grpc_recv_us": grpc_us,
58
+ }
59
+ if recent_blockhash:
60
+ m["recent_blockhash"] = recent_blockhash
61
+ return m
62
+
63
+
64
+ def parse_log_optimized(
65
+ log: str,
66
+ signature: str,
67
+ slot: int,
68
+ tx_index: int = 0,
69
+ block_time_us: Optional[int] = None,
70
+ grpc_recv_us: Optional[int] = None,
71
+ event_type_filter: Any = None,
72
+ is_created_buy: bool = False,
73
+ recent_blockhash: str = "",
74
+ ) -> Optional[DexEvent]:
75
+ """单次 base64 decode 后按 discriminator 做 early filter,再按实际事件类型二次过滤。"""
76
+ grpc = int(time.time() * 1_000_000) if grpc_recv_us is None else grpc_recv_us
77
+ buf = decode_program_data_line(log)
78
+ if not buf:
79
+ return None
80
+ disc = _disc8(buf[:8])
81
+ if event_type_filter is not None:
82
+ event_type = event_type_for_discriminator(disc)
83
+ if event_type is not None:
84
+ if not event_type_filter.should_include(event_type):
85
+ return None
86
+ elif not filter_allows_unknown_log_event(event_type_filter):
87
+ return None
88
+ data = buf[8:]
89
+ meta = _meta(signature, slot, tx_index, block_time_us, grpc, recent_blockhash)
90
+ return apply_event_type_filter(
91
+ dispatch_program_data(disc, data, buf, meta, is_created_buy),
92
+ event_type_filter,
93
+ )
94
+
95
+
96
+ def parse_log_unified(
97
+ log: str,
98
+ signature: str,
99
+ slot: int,
100
+ block_time_us: Optional[int] = None,
101
+ *,
102
+ tx_index: int = 0,
103
+ ) -> Optional[DexEvent]:
104
+ grpc = int(time.time() * 1_000_000)
105
+ return parse_log_optimized(
106
+ log,
107
+ signature,
108
+ slot,
109
+ tx_index,
110
+ block_time_us,
111
+ grpc,
112
+ None,
113
+ False,
114
+ "",
115
+ )
116
+
117
+
118
+ def parse_transaction_events(
119
+ logs: List[str],
120
+ signature: str,
121
+ slot: int,
122
+ block_time_us: Optional[int] = None,
123
+ *,
124
+ subscribe_tx_info: Optional["SubscribeUpdateTransactionInfo"] = None,
125
+ tx_index: Optional[int] = None,
126
+ ) -> List[DexEvent]:
127
+ """对齐 Rust `parse_transaction_events` - 解析完整交易并返回所有 DEX 事件"""
128
+ return parse_logs_only(
129
+ logs,
130
+ signature,
131
+ slot,
132
+ block_time_us,
133
+ subscribe_tx_info=subscribe_tx_info,
134
+ tx_index=tx_index,
135
+ )
136
+
137
+
138
+ def parse_logs_only(
139
+ logs: List[str],
140
+ signature: str,
141
+ slot: int,
142
+ block_time_us: Optional[int] = None,
143
+ *,
144
+ subscribe_tx_info: Optional["SubscribeUpdateTransactionInfo"] = None,
145
+ tx_index: Optional[int] = None,
146
+ ) -> List[DexEvent]:
147
+ """解析日志中的 Program data 事件。
148
+
149
+ 若传入 ``subscribe_tx_info`` 且含 ``transaction_raw`` / ``meta_raw``(Yellowstone 订阅),
150
+ 会在解析后调用 :func:`grpc_instruction_parser.enrich_dex_events_with_subscribe_tx_info`
151
+ 从指令账户补全 bonding_curve、creator_vault 等字段(与 Rust gRPC 路径一致)。
152
+
153
+ ``tx_index`` 为区块内交易序号(与 gRPC ``SubscribeUpdateTransactionInfo.index`` 一致)。
154
+ 未显式传入且提供了 ``subscribe_tx_info`` 时,使用 ``subscribe_tx_info.index``。
155
+ """
156
+ resolved_tx_index = 0
157
+ if tx_index is not None:
158
+ resolved_tx_index = int(tx_index)
159
+ elif subscribe_tx_info is not None:
160
+ resolved_tx_index = int(getattr(subscribe_tx_info, "index", 0) or 0)
161
+ out: List[DexEvent] = []
162
+ for log in logs:
163
+ ev = parse_log_unified(log, signature, slot, block_time_us, tx_index=resolved_tx_index)
164
+ if ev:
165
+ out.append(ev)
166
+ enrich_pumpfun_same_tx_post_merge(out)
167
+ if subscribe_tx_info is not None:
168
+ from .grpc_instruction_parser import enrich_dex_events_with_subscribe_tx_info
169
+
170
+ enrich_dex_events_with_subscribe_tx_info(out, subscribe_tx_info)
171
+ return out
172
+
173
+
174
+ def parse_transaction_events_streaming(
175
+ logs: List[str],
176
+ signature: str,
177
+ slot: int,
178
+ block_time_us: Optional[int],
179
+ callback: Callable[[DexEvent], None],
180
+ *,
181
+ tx_index: int = 0,
182
+ ) -> None:
183
+ """对齐 Rust `parse_transaction_events_streaming`"""
184
+ parse_logs_streaming(logs, signature, slot, block_time_us, callback, tx_index=tx_index)
185
+
186
+
187
+ def parse_logs_streaming(
188
+ logs: List[str],
189
+ signature: str,
190
+ slot: int,
191
+ block_time_us: Optional[int],
192
+ callback: Callable[[DexEvent], None],
193
+ *,
194
+ tx_index: int = 0,
195
+ ) -> None:
196
+ """对齐 Rust `parse_logs_streaming` - 流式解析,每解析出一个事件立即回调"""
197
+ for log in logs:
198
+ ev = parse_log_unified(log, signature, slot, block_time_us, tx_index=tx_index)
199
+ if ev:
200
+ callback(ev)
201
+
202
+
203
+ class EventListener:
204
+ """对齐 Rust `EventListener` trait"""
205
+
206
+ def on_dex_event(self, event: DexEvent) -> None:
207
+ raise NotImplementedError
208
+
209
+
210
+ def parse_transaction_with_listener(
211
+ logs: List[str],
212
+ signature: str,
213
+ slot: int,
214
+ block_time_us: Optional[int],
215
+ listener: EventListener,
216
+ *,
217
+ subscribe_tx_info: Optional["SubscribeUpdateTransactionInfo"] = None,
218
+ tx_index: Optional[int] = None,
219
+ ) -> None:
220
+ """对齐 Rust `parse_transaction_with_listener`"""
221
+ events = parse_logs_only(
222
+ logs,
223
+ signature,
224
+ slot,
225
+ block_time_us,
226
+ subscribe_tx_info=subscribe_tx_info,
227
+ tx_index=tx_index,
228
+ )
229
+ for ev in events:
230
+ listener.on_dex_event(ev)
231
+
232
+
233
+ class StreamingEventListener:
234
+ """对齐 Rust `StreamingEventListener` trait"""
235
+
236
+ def on_dex_event_streaming(self, event: DexEvent) -> None:
237
+ raise NotImplementedError
238
+
239
+
240
+ def parse_transaction_with_streaming_listener(
241
+ logs: List[str],
242
+ signature: str,
243
+ slot: int,
244
+ block_time_us: Optional[int],
245
+ listener: StreamingEventListener,
246
+ ) -> None:
247
+ """对齐 Rust `parse_transaction_with_streaming_listener`"""
248
+
249
+ def callback(ev: DexEvent) -> None:
250
+ listener.on_dex_event_streaming(ev)
251
+
252
+ parse_logs_streaming(logs, signature, slot, block_time_us, callback)
253
+
254
+
255
+ def parse_log(
256
+ log: str,
257
+ signature: str,
258
+ slot: int,
259
+ tx_index: int,
260
+ block_time_us: Optional[int],
261
+ grpc_recv_us: int,
262
+ is_created_buy: bool,
263
+ recent_blockhash: str = "",
264
+ ) -> Optional[DexEvent]:
265
+ """对齐 Rust `parse_log` - 带完整 gRPC 元数据字段的日志解析"""
266
+ return parse_log_optimized(
267
+ log,
268
+ signature,
269
+ slot,
270
+ tx_index,
271
+ block_time_us,
272
+ grpc_recv_us,
273
+ None,
274
+ is_created_buy,
275
+ recent_blockhash,
276
+ )
277
+
278
+
279
+ def warmup_parser() -> None:
280
+ decode_program_data_line("Program data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
281
+
282
+
283
+ __all__ = [
284
+ "DexEvent",
285
+ "decode_program_data_line",
286
+ "dispatch_program_data",
287
+ "parse_log",
288
+ "parse_log_unified",
289
+ "parse_log_optimized",
290
+ "parse_logs_only",
291
+ "parse_logs_streaming",
292
+ "parse_transaction_events",
293
+ "parse_transaction_events_streaming",
294
+ "parse_transaction_with_listener",
295
+ "parse_transaction_with_streaming_listener",
296
+ "EventListener",
297
+ "StreamingEventListener",
298
+ "parse_trade_from_data",
299
+ "warmup_parser",
300
+ ]
@@ -0,0 +1,75 @@
1
+ """同笔交易 PumpFun 后处理(对齐 Rust ``pumpfun_fee_enrich``)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict, List, Optional, Tuple
6
+
7
+ from .dex_parsers import Z
8
+ from .event_types import DexEvent, PumpFunCreateEvent, PumpFunCreateV2TokenEvent, PumpFunTradeEvent
9
+ from .grpc_types import EventType
10
+
11
+
12
+ def _buy_like_mint_fee(ev: DexEvent) -> Optional[Tuple[str, str]]:
13
+ if not isinstance(ev.data, PumpFunTradeEvent):
14
+ return None
15
+ t = ev.data
16
+ if t.mint == Z or not t.mint:
17
+ return None
18
+ if ev.type == EventType.PUMP_FUN_TRADE:
19
+ if t.is_buy:
20
+ return (t.mint, t.fee_recipient)
21
+ return None
22
+ if ev.type in (EventType.PUMP_FUN_BUY, EventType.PUMP_FUN_BUY_EXACT_SOL_IN):
23
+ return (t.mint, t.fee_recipient)
24
+ return None
25
+
26
+
27
+ def enrich_create_v2_observed_fee_recipient(events: List[DexEvent]) -> None:
28
+ mint_to_fee: Dict[str, str] = {}
29
+ for e in events:
30
+ p = _buy_like_mint_fee(e)
31
+ if not p:
32
+ continue
33
+ mint, fee = p
34
+ if fee and fee != Z:
35
+ mint_to_fee.setdefault(mint, fee)
36
+ if not mint_to_fee:
37
+ return
38
+ for e in events:
39
+ if e.type != EventType.PUMP_FUN_CREATE_V2:
40
+ continue
41
+ if not isinstance(e.data, PumpFunCreateV2TokenEvent):
42
+ continue
43
+ c = e.data
44
+ if not c.observed_fee_recipient and c.mint in mint_to_fee:
45
+ c.observed_fee_recipient = mint_to_fee[c.mint]
46
+
47
+
48
+ def enrich_pumpfun_trades_from_create_instructions(events: List[DexEvent]) -> None:
49
+ flags: Dict[str, Tuple[bool, bool]] = {}
50
+ for e in events:
51
+ if e.type not in (EventType.PUMP_FUN_CREATE, EventType.PUMP_FUN_CREATE_V2):
52
+ continue
53
+ if not isinstance(e.data, (PumpFunCreateEvent, PumpFunCreateV2TokenEvent)):
54
+ continue
55
+ c = e.data
56
+ if c.mint and c.mint != Z:
57
+ flags.setdefault(c.mint, (c.is_cashback_enabled, c.is_mayhem_mode))
58
+ if not flags:
59
+ return
60
+ for e in events:
61
+ if not isinstance(e.data, PumpFunTradeEvent):
62
+ continue
63
+ t = e.data
64
+ if not t.mint or t.mint == Z or t.mint not in flags:
65
+ continue
66
+ cashback_enabled, mayhem_mode = flags[t.mint]
67
+ t.is_cashback_coin = t.is_cashback_coin or cashback_enabled
68
+ t.mayhem_mode = t.mayhem_mode or mayhem_mode
69
+ if cashback_enabled:
70
+ t.track_volume = True
71
+
72
+
73
+ def enrich_pumpfun_same_tx_post_merge(events: List[DexEvent]) -> None:
74
+ enrich_create_v2_observed_fee_recipient(events)
75
+ enrich_pumpfun_trades_from_create_instructions(events)