sol-parser-sdk-python 0.5.5__py3-none-any.whl → 0.5.7__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.
sol_parser/merger.py CHANGED
@@ -60,48 +60,123 @@ def _fill_attr_if_empty(base: Any, attr: str, source: Any) -> None:
60
60
  setattr(base, attr, value)
61
61
 
62
62
 
63
+ def _put_attr_if_set(base: Any, attr: str, source: Any) -> None:
64
+ value = getattr(source, attr, None)
65
+ if not _empty(value):
66
+ setattr(base, attr, value)
67
+
68
+
69
+ def _put_num_if_nonzero(base: Any, attr: str, source: Any) -> None:
70
+ value = getattr(source, attr, 0)
71
+ if value:
72
+ setattr(base, attr, value)
73
+
74
+
63
75
  def merge_pumpfun_trade(base: PumpFunTradeEvent, inner: PumpFunTradeEvent) -> None:
64
- base.mint = inner.mint
65
- base.sol_amount = inner.sol_amount
66
- base.token_amount = inner.token_amount
67
- base.is_buy = inner.is_buy
68
- base.user = inner.user
69
- base.timestamp = inner.timestamp
70
- base.virtual_sol_reserves = inner.virtual_sol_reserves
71
- base.virtual_token_reserves = inner.virtual_token_reserves
72
- base.real_sol_reserves = inner.real_sol_reserves
73
- base.real_token_reserves = inner.real_token_reserves
74
- base.fee_recipient = inner.fee_recipient
75
- base.fee_basis_points = inner.fee_basis_points
76
- base.fee = inner.fee
77
- base.creator = inner.creator
78
- base.creator_fee_basis_points = inner.creator_fee_basis_points
79
- base.creator_fee = inner.creator_fee
80
- base.track_volume = inner.track_volume
81
- base.total_unclaimed_tokens = inner.total_unclaimed_tokens
82
- base.total_claimed_tokens = inner.total_claimed_tokens
83
- base.current_sol_volume = inner.current_sol_volume
84
- base.last_update_timestamp = inner.last_update_timestamp
85
- base.ix_name = inner.ix_name
86
- base.is_created_buy = inner.is_created_buy
87
- base.mayhem_mode = inner.mayhem_mode
88
- base.cashback_fee_basis_points = inner.cashback_fee_basis_points
89
- base.cashback = inner.cashback
90
- if inner.buyback_fee_basis_points != 0:
91
- base.buyback_fee_basis_points = inner.buyback_fee_basis_points
92
- if inner.buyback_fee != 0:
93
- base.buyback_fee = inner.buyback_fee
94
- if inner.shareholders:
76
+ leg = inner.sol_amount != 0 or inner.token_amount != 0
77
+
78
+ for attr in ("mint", "user", "fee_recipient", "creator"):
79
+ _put_attr_if_set(base, attr, inner)
80
+
81
+ if leg:
82
+ for attr in (
83
+ "sol_amount",
84
+ "token_amount",
85
+ "timestamp",
86
+ "virtual_sol_reserves",
87
+ "virtual_token_reserves",
88
+ "real_sol_reserves",
89
+ "real_token_reserves",
90
+ "fee_basis_points",
91
+ "fee",
92
+ "creator_fee_basis_points",
93
+ "creator_fee",
94
+ "total_unclaimed_tokens",
95
+ "total_claimed_tokens",
96
+ "current_sol_volume",
97
+ "last_update_timestamp",
98
+ ):
99
+ setattr(base, attr, getattr(inner, attr))
100
+ base.is_buy = inner.is_buy
101
+ base.track_volume = bool(base.track_volume) or bool(inner.track_volume)
102
+ base.mayhem_mode = bool(base.mayhem_mode) or bool(inner.mayhem_mode)
103
+ if inner.ix_name:
104
+ base.ix_name = inner.ix_name
105
+ base.is_cashback_coin = bool(base.is_cashback_coin) or bool(inner.is_cashback_coin)
106
+ else:
107
+ for attr in (
108
+ "fee",
109
+ "creator_fee",
110
+ "fee_basis_points",
111
+ "creator_fee_basis_points",
112
+ "virtual_sol_reserves",
113
+ "virtual_token_reserves",
114
+ "real_sol_reserves",
115
+ "real_token_reserves",
116
+ "total_unclaimed_tokens",
117
+ "total_claimed_tokens",
118
+ "current_sol_volume",
119
+ "timestamp",
120
+ "last_update_timestamp",
121
+ ):
122
+ _put_num_if_nonzero(base, attr, inner)
123
+ base.track_volume = bool(base.track_volume) or bool(inner.track_volume)
124
+ base.mayhem_mode = bool(base.mayhem_mode) or bool(inner.mayhem_mode)
125
+ if inner.ix_name:
126
+ base.ix_name = inner.ix_name
127
+ base.is_cashback_coin = bool(base.is_cashback_coin) or bool(inner.is_cashback_coin)
128
+
129
+ for attr in (
130
+ "cashback_fee_basis_points",
131
+ "cashback",
132
+ "buyback_fee_basis_points",
133
+ "buyback_fee",
134
+ "quote_amount",
135
+ "virtual_quote_reserves",
136
+ "real_quote_reserves",
137
+ "amount",
138
+ "max_sol_cost",
139
+ "min_sol_output",
140
+ "spendable_sol_in",
141
+ "spendable_quote_in",
142
+ "min_tokens_out",
143
+ ):
144
+ _put_num_if_nonzero(base, attr, inner)
145
+
146
+ if inner.shareholders and not base.shareholders:
95
147
  base.shareholders = inner.shareholders
96
- if not _empty(inner.quote_mint):
97
- base.quote_mint = inner.quote_mint
98
- if inner.quote_amount != 0:
99
- base.quote_amount = inner.quote_amount
100
- if inner.virtual_quote_reserves != 0:
101
- base.virtual_quote_reserves = inner.virtual_quote_reserves
102
- if inner.real_quote_reserves != 0:
103
- base.real_quote_reserves = inner.real_quote_reserves
104
- base.is_cashback_coin = inner.is_cashback_coin
148
+
149
+ for attr in (
150
+ "quote_mint",
151
+ "global_account",
152
+ "bonding_curve",
153
+ "bonding_curve_v2",
154
+ "associated_bonding_curve",
155
+ "associated_user",
156
+ "system_program",
157
+ "token_program",
158
+ "quote_token_program",
159
+ "associated_token_program",
160
+ "creator_vault",
161
+ "associated_quote_fee_recipient",
162
+ "buyback_fee_recipient",
163
+ "associated_quote_buyback_fee_recipient",
164
+ "associated_quote_bonding_curve",
165
+ "associated_quote_user",
166
+ "associated_creator_vault",
167
+ "sharing_config",
168
+ "event_authority",
169
+ "program",
170
+ "global_volume_accumulator",
171
+ "user_volume_accumulator",
172
+ "associated_user_volume_accumulator",
173
+ "fee_config",
174
+ "fee_program",
175
+ "extra_instruction_account",
176
+ ):
177
+ _put_attr_if_set(base, attr, inner)
178
+
179
+ base.is_created_buy = bool(base.is_created_buy) or bool(inner.is_created_buy)
105
180
 
106
181
 
107
182
  def merge_pumpswap_buy(base: PumpSwapBuyEvent, inner: PumpSwapBuyEvent) -> None:
@@ -199,7 +274,6 @@ def merge_dex_events(base: DexEvent, inner: DexEvent) -> None:
199
274
  EventType.PUMP_FUN_BUY_EXACT_SOL_IN,
200
275
  ):
201
276
  merge_pumpfun_trade(bd, ind)
202
- base.type = inner.type
203
277
  return
204
278
 
205
279
  if isinstance(bd, PumpFunCreateEvent) and isinstance(ind, PumpFunCreateEvent):
sol_parser/parser.py CHANGED
@@ -8,10 +8,16 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
8
8
 
9
9
  from .dex_parsers import (
10
10
  DexEvent,
11
+ PUMPFUN_PROGRAM_ID,
12
+ PUMP_TRADE,
11
13
  apply_event_type_filter,
12
14
  dispatch_program_data,
13
15
  event_type_for_discriminator,
16
+ event_type_for_program_discriminator,
14
17
  filter_allows_unknown_log_event,
18
+ filter_allows_unscoped_discriminator,
19
+ filter_includes_program,
20
+ filter_wants_pumpfun_trade,
15
21
  parse_trade_from_data,
16
22
  )
17
23
 
@@ -30,13 +36,11 @@ def decode_program_data_line(log: str) -> Optional[bytes]:
30
36
  if i < 0:
31
37
  return None
32
38
  s = log[i + len(p) :].strip()
33
- if len(s) > 2700:
34
- return None
35
39
  try:
36
40
  raw = base64.standard_b64decode(s)
37
41
  except Exception:
38
42
  return None
39
- if len(raw) < 8 or len(raw) > 2048:
43
+ if len(raw) < 8:
40
44
  return None
41
45
  return raw
42
46
 
@@ -71,6 +75,7 @@ def parse_log_optimized(
71
75
  event_type_filter: Any = None,
72
76
  is_created_buy: bool = False,
73
77
  recent_blockhash: str = "",
78
+ program_id: Optional[str] = None,
74
79
  ) -> Optional[DexEvent]:
75
80
  """单次 base64 decode 后按 discriminator 做 early filter,再按实际事件类型二次过滤。"""
76
81
  grpc = int(time.time() * 1_000_000) if grpc_recv_us is None else grpc_recv_us
@@ -79,20 +84,103 @@ def parse_log_optimized(
79
84
  return None
80
85
  disc = _disc8(buf[:8])
81
86
  if event_type_filter is not None:
82
- event_type = event_type_for_discriminator(disc)
83
- if event_type is not None:
87
+ is_unscoped_shared_discriminator = program_id is None and disc in (
88
+ _disc8(bytes([189, 219, 127, 211, 78, 230, 97, 238])),
89
+ _disc8(bytes([143, 190, 90, 218, 196, 30, 51, 222])),
90
+ )
91
+ event_type = event_type_for_program_discriminator(program_id, disc)
92
+ if is_unscoped_shared_discriminator:
93
+ if not filter_allows_unscoped_discriminator(event_type_filter, disc):
94
+ return None
95
+ elif program_id == PUMPFUN_PROGRAM_ID and disc == PUMP_TRADE:
96
+ if not filter_wants_pumpfun_trade(event_type_filter):
97
+ return None
98
+ elif event_type is not None:
84
99
  if not event_type_filter.should_include(event_type):
85
100
  return None
86
- elif not filter_allows_unknown_log_event(event_type_filter):
101
+ elif program_id:
102
+ if not filter_includes_program(event_type_filter, program_id):
103
+ return None
104
+ elif not filter_allows_unscoped_discriminator(event_type_filter, disc):
87
105
  return None
88
106
  data = buf[8:]
89
107
  meta = _meta(signature, slot, tx_index, block_time_us, grpc, recent_blockhash)
90
108
  return apply_event_type_filter(
91
- dispatch_program_data(disc, data, buf, meta, is_created_buy),
109
+ dispatch_program_data(
110
+ disc,
111
+ data,
112
+ buf,
113
+ meta,
114
+ is_created_buy,
115
+ program_id,
116
+ event_type_filter,
117
+ ),
118
+ event_type_filter,
119
+ )
120
+
121
+
122
+ def parse_log_optimized_with_program_id(
123
+ log: str,
124
+ signature: str,
125
+ slot: int,
126
+ tx_index: int = 0,
127
+ block_time_us: Optional[int] = None,
128
+ grpc_recv_us: Optional[int] = None,
129
+ event_type_filter: Any = None,
130
+ is_created_buy: bool = False,
131
+ recent_blockhash: str = "",
132
+ program_id: Optional[str] = None,
133
+ ) -> Optional[DexEvent]:
134
+ return parse_log_optimized(
135
+ log,
136
+ signature,
137
+ slot,
138
+ tx_index,
139
+ block_time_us,
140
+ grpc_recv_us,
92
141
  event_type_filter,
142
+ is_created_buy,
143
+ recent_blockhash,
144
+ program_id,
93
145
  )
94
146
 
95
147
 
148
+ def parse_invoke_info(log: str) -> Optional[tuple[str, int]]:
149
+ prefix = "Program "
150
+ start = log.find(prefix)
151
+ if start < 0:
152
+ return None
153
+ marker = " invoke ["
154
+ mid = log.find(marker, start + len(prefix))
155
+ if mid < 0:
156
+ return None
157
+ end = log.find("]", mid + len(marker))
158
+ if end < 0:
159
+ return None
160
+ program_id = log[start + len(prefix):mid]
161
+ try:
162
+ depth = int(log[mid + len(marker):end])
163
+ except ValueError:
164
+ return None
165
+ if not program_id or depth <= 0:
166
+ return None
167
+ return program_id, depth
168
+
169
+
170
+ def parse_program_complete_info(log: str) -> Optional[str]:
171
+ prefix = "Program "
172
+ start = log.find(prefix)
173
+ if start < 0:
174
+ return None
175
+ success = log.find(" success", start + len(prefix))
176
+ if success >= 0:
177
+ return log[start + len(prefix):success]
178
+ failed = log.find(" failed:", start + len(prefix))
179
+ if failed >= 0:
180
+ return log[start + len(prefix):failed]
181
+ return None
182
+
183
+
96
184
  def parse_log_unified(
97
185
  log: str,
98
186
  signature: str,
@@ -82,7 +82,18 @@ def enrich_create_v2_from_create_events(events: List[DexEvent]) -> None:
82
82
  if c is None:
83
83
  continue
84
84
 
85
- for attr in ("name", "symbol", "uri", "bonding_curve", "user", "creator", "token_program", "quote_mint"):
85
+ for attr in (
86
+ "name",
87
+ "symbol",
88
+ "uri",
89
+ "bonding_curve",
90
+ "user",
91
+ "creator",
92
+ "token_program",
93
+ "quote_mint",
94
+ "quote_vault",
95
+ "quote_token_program",
96
+ ):
86
97
  _fill_str_if_empty(c2, attr, getattr(c, attr))
87
98
  for attr in (
88
99
  "timestamp",
sol_parser/rpc_parser.py CHANGED
@@ -8,9 +8,10 @@ from dataclasses import dataclass
8
8
 
9
9
  import base58
10
10
 
11
- from .dex_parsers import DexEvent, dispatch_program_data, parse_trade_from_data
12
- from .grpc_types import EventTypeFilter, EventType, IncludeOnlyFilter
13
- from .instructions import parse_instruction_unified
11
+ from .dex_parsers import DexEvent
12
+ from .grpc_types import EventTypeFilter, EventType, event_type_filter_allows_instruction_parsing
13
+ from .inner_instruction_parser import parse_inner_instruction
14
+ from .instructions import parse_inner_compiled_instruction_if_supported, parse_instruction_unified
14
15
  from .log_instr_dedup import dedupe_log_instruction_events
15
16
  from .pumpfun_fee_enrich import enrich_pumpfun_same_tx_post_merge
16
17
 
@@ -112,6 +113,31 @@ class RpcTransactionResponse:
112
113
  transaction_index: int = 0
113
114
 
114
115
 
116
+ def _merge_rpc_full_account_keys(
117
+ account_keys: List[str],
118
+ meta: Optional[RpcTransactionMeta],
119
+ ) -> List[str]:
120
+ """Merge static keys with ALT loaded writable + readonly keys in Solana runtime order."""
121
+ if meta is None or meta.loaded_addresses is None:
122
+ return list(account_keys)
123
+ return [
124
+ *account_keys,
125
+ *meta.loaded_addresses.writable,
126
+ *meta.loaded_addresses.readonly,
127
+ ]
128
+
129
+
130
+ def _should_parse_rpc_instructions(filter: Optional[EventTypeFilter]) -> bool:
131
+ if filter is None:
132
+ return True
133
+ include_only = getattr(filter, "include_only", None)
134
+ if include_only is None:
135
+ return True
136
+ if not include_only:
137
+ return False
138
+ return event_type_filter_allows_instruction_parsing(list(include_only))
139
+
140
+
115
141
  class RpcClient:
116
142
  """RPC 客户端接口"""
117
143
  def get_transaction(
@@ -185,51 +211,75 @@ def parse_rpc_transaction(
185
211
  )
186
212
 
187
213
  slot = tx.slot
188
- block_time_us = tx.block_time * 1_000_000 if tx.block_time else None
214
+ block_time_us = tx.block_time * 1_000_000 if tx.block_time is not None else None
189
215
  block_tx_index = int(getattr(tx, "transaction_index", 0) or 0)
216
+ full_account_keys = _merge_rpc_full_account_keys(msg.account_keys, meta)
190
217
 
191
- instruction_events: List[DexEvent] = []
192
-
193
- # 解析外层指令
194
- for i, ix in enumerate(msg.instructions):
195
- ev = _parse_rpc_instruction(
196
- ix,
197
- msg.account_keys,
198
- signature,
199
- slot,
200
- block_tx_index,
201
- block_time_us,
202
- grpc_recv_us,
203
- filter,
204
- )
205
- if ev:
206
- instruction_events.append(ev)
218
+ indexed_instruction_events: List[Tuple[int, Optional[int], DexEvent]] = []
219
+ from .grpc_instruction_parser import (
220
+ detect_pumpfun_create_from_logs,
221
+ merge_instruction_events,
222
+ )
223
+ has_pumpfun_create_log = detect_pumpfun_create_from_logs(list(meta.log_messages))
207
224
 
208
- # 解析内层指令
209
- for group in meta.inner_instructions:
210
- for ix in group.instructions:
225
+ if _should_parse_rpc_instructions(filter):
226
+ # 解析外层指令
227
+ for outer_idx, ix in enumerate(msg.instructions):
211
228
  ev = _parse_rpc_instruction(
212
229
  ix,
213
- msg.account_keys,
230
+ full_account_keys,
214
231
  signature,
215
232
  slot,
216
233
  block_tx_index,
217
234
  block_time_us,
218
235
  grpc_recv_us,
219
236
  filter,
237
+ inner=False,
238
+ is_created_buy=has_pumpfun_create_log,
220
239
  )
221
240
  if ev:
222
- instruction_events.append(ev)
241
+ indexed_instruction_events.append((outer_idx, None, ev))
242
+
243
+ # 解析内层指令
244
+ for group in meta.inner_instructions:
245
+ for inner_idx, ix in enumerate(group.instructions):
246
+ ev = _parse_rpc_instruction(
247
+ ix,
248
+ full_account_keys,
249
+ signature,
250
+ slot,
251
+ block_tx_index,
252
+ block_time_us,
253
+ grpc_recv_us,
254
+ filter,
255
+ inner=True,
256
+ is_created_buy=has_pumpfun_create_log,
257
+ )
258
+ if ev:
259
+ indexed_instruction_events.append((int(group.index), inner_idx, ev))
260
+
261
+ instruction_events = merge_instruction_events(indexed_instruction_events)
262
+ enrich_pumpfun_same_tx_post_merge(instruction_events)
263
+ if msg.recent_blockhash:
264
+ for ev in instruction_events:
265
+ if hasattr(ev.data, "metadata"):
266
+ ev.data.metadata.recent_blockhash = msg.recent_blockhash
223
267
 
224
268
  # 解析日志
225
- is_created_buy = False
226
269
  recent_blockhash = msg.recent_blockhash
227
270
  log_events: List[DexEvent] = []
228
271
 
229
- from .parser import parse_log_optimized
272
+ from .parser import parse_invoke_info, parse_log_optimized_with_program_id, parse_program_complete_info
230
273
 
274
+ active_program_stack: List[str] = []
231
275
  for log in meta.log_messages:
232
- ev = parse_log_optimized(
276
+ invoke = parse_invoke_info(log)
277
+ if invoke is not None:
278
+ program_id, depth = invoke
279
+ del active_program_stack[max(0, depth - 1):]
280
+ active_program_stack.append(program_id)
281
+
282
+ ev = parse_log_optimized_with_program_id(
233
283
  log,
234
284
  signature,
235
285
  slot,
@@ -237,14 +287,20 @@ def parse_rpc_transaction(
237
287
  block_time_us,
238
288
  grpc_recv_us,
239
289
  filter,
240
- is_created_buy,
290
+ has_pumpfun_create_log,
241
291
  recent_blockhash,
292
+ active_program_stack[-1] if active_program_stack else None,
242
293
  )
243
294
  if ev:
244
- if ev.type in (EventType.PUMP_FUN_CREATE, EventType.PUMP_FUN_CREATE_V2):
245
- is_created_buy = True
246
295
  log_events.append(ev)
247
296
 
297
+ completed = parse_program_complete_info(log)
298
+ if completed is not None:
299
+ for i in range(len(active_program_stack) - 1, -1, -1):
300
+ if active_program_stack[i] == completed:
301
+ del active_program_stack[i:]
302
+ break
303
+
248
304
  tx_pb, meta_pb = rpc_response_to_solana_storage(tx)
249
305
  if tx_pb is not None and meta_pb is not None:
250
306
  from .grpc_instruction_parser import apply_account_fill_to_events
@@ -273,6 +329,8 @@ def _parse_rpc_instruction(
273
329
  block_time_us: Optional[int],
274
330
  grpc_recv_us: int,
275
331
  filter: Optional[EventTypeFilter],
332
+ inner: bool = False,
333
+ is_created_buy: bool = False,
276
334
  ) -> Optional[DexEvent]:
277
335
  """解析 RPC 指令"""
278
336
  # 获取程序 ID
@@ -292,9 +350,37 @@ def _parse_rpc_instruction(
292
350
  if acc_idx < len(account_keys):
293
351
  accounts.append(account_keys[acc_idx])
294
352
 
295
- f: EventTypeFilter = filter if filter is not None else IncludeOnlyFilter([])
353
+ if inner:
354
+ ev = parse_inner_compiled_instruction_if_supported(
355
+ data,
356
+ accounts,
357
+ signature,
358
+ slot,
359
+ tx_index,
360
+ block_time_us,
361
+ grpc_recv_us,
362
+ filter,
363
+ program_id,
364
+ )
365
+ if ev:
366
+ return ev
367
+ return parse_inner_instruction(
368
+ data,
369
+ program_id,
370
+ {
371
+ "signature": signature,
372
+ "slot": slot,
373
+ "tx_index": tx_index,
374
+ "block_time_us": 0 if block_time_us is None else block_time_us,
375
+ "grpc_recv_us": grpc_recv_us,
376
+ "recent_blockhash": "",
377
+ },
378
+ filter,
379
+ is_created_buy,
380
+ )
381
+
296
382
  return parse_instruction_unified(
297
- data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us, f, program_id
383
+ data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us, filter, program_id
298
384
  )
299
385
 
300
386
 
@@ -18,7 +18,11 @@ import grpc
18
18
  from .entries_decode import decode_entries_bincode_flat
19
19
  from .event_types import DexEvent
20
20
  from .dex_parsers import Z
21
- from .grpc_types import EventTypeFilter, event_type_filter_includes_pumpfun
21
+ from .grpc_types import (
22
+ EventTypeFilter,
23
+ event_type_filter_allows_instruction_parsing,
24
+ event_type_filter_includes_pumpfun,
25
+ )
22
26
  from .instructions import (
23
27
  METEORA_DAMM_V2_PROGRAM_ID,
24
28
  METEORA_DLMM_PROGRAM_ID,
@@ -118,6 +122,17 @@ def _filter_parsed_event(ev: Optional[DexEvent], filter: Optional[EventTypeFilte
118
122
  return ev if filter.should_include(ev.type) else None
119
123
 
120
124
 
125
+ def _should_parse_shred_instructions(filter: Optional[EventTypeFilter]) -> bool:
126
+ if filter is None:
127
+ return True
128
+ include_only = getattr(filter, "include_only", None)
129
+ if include_only is None:
130
+ return True
131
+ if not include_only:
132
+ return False
133
+ return event_type_filter_allows_instruction_parsing(list(include_only))
134
+
135
+
121
136
  def _events_from_versioned_tx_wire(
122
137
  raw: bytes,
123
138
  signature: str,
@@ -126,6 +141,9 @@ def _events_from_versioned_tx_wire(
126
141
  recv_us: int,
127
142
  filter: Optional[EventTypeFilter],
128
143
  ) -> List[DexEvent]:
144
+ if not _should_parse_shred_instructions(filter):
145
+ return []
146
+
129
147
  try:
130
148
  from solders.message import Message as LegacyMessage # type: ignore
131
149
  from solders.message import MessageV0 # type: ignore
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sol-parser-sdk-python
3
- Version: 0.5.5
3
+ Version: 0.5.7
4
4
  Summary: Solana DEX program log parsing (pure Python, API aligned with sol-parser-sdk)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: base58>=2.0
@@ -67,6 +67,14 @@ Description-Content-Type: text/markdown
67
67
 
68
68
  ## Release notes
69
69
 
70
+ ### v0.5.6
71
+
72
+ - Adds Meteora DBC log parsing with program-context routing and filter parity.
73
+ - Adds Raydium CLMM/CPMM and Orca account parsers and exports.
74
+ - Fixes default no-filter RPC/Yellowstone instruction parsing so it no longer behaves like an empty include-only filter.
75
+ - Merges RPC ALT loaded writable/readonly keys into the full instruction account table.
76
+ - Skips ShredStream/RPC instruction parsing early for account-only or empty include-only filters.
77
+
70
78
  ### v0.5.5
71
79
 
72
80
  - Aligns ShredStream parsing with Rust/Node.js/Go for low-latency static-account paths.
@@ -84,7 +92,7 @@ Description-Content-Type: text/markdown
84
92
  **From PyPI**
85
93
 
86
94
  ```bash
87
- pip install sol-parser-sdk-python==0.5.5
95
+ pip install sol-parser-sdk-python==0.5.7
88
96
  ```
89
97
 
90
98
  **From source**