sol-parser-sdk-python 0.5.8__py3-none-any.whl → 0.5.10__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/__init__.py CHANGED
@@ -466,3 +466,27 @@ __all__ = [
466
466
  "ShredStreamClient",
467
467
  "ShredStreamConfig",
468
468
  ]
469
+
470
+ # Native route and subscription state APIs aligned with Rust 0.7.7.
471
+ from .transaction_route import (analyze_rpc_transaction_routes, TransactionRoute, RouteSwapLeg,
472
+ RouteTokenTransfer, RouteUnknownInvocation, RouteNativeTokenAction, InstructionPosition,
473
+ stonkfun_mode_from_platform_config)
474
+ from .stonkfun_registry import StonkFunPoolRegistry, StonkFunGraduatedPool
475
+ from .liquidity_snapshot import RawAccountSnapshotEvent, LiquidityAccountSnapshotEvent, parse_liquidity_account
476
+ from .wire_transaction import decode_wire_transaction
477
+
478
+ from .simulation_route import analyze_simulation_routes
479
+
480
+ __all__.append("analyze_simulation_routes")
481
+
482
+ from .block_meta import BlockMetaEvent, parse_block_meta_update
483
+ from .grpc.subscribe_builder import build_subscribe_request_with_event_filter
484
+
485
+ from .event_types import (
486
+ LaunchLabPoolCreateEvent, StonkFunPoolCreateEvent,
487
+ LaunchLabTradeEvent, StonkFunTradeEvent,
488
+ )
489
+ __all__ += ["LaunchLabPoolCreateEvent", "StonkFunPoolCreateEvent", "LaunchLabTradeEvent", "StonkFunTradeEvent"]
490
+
491
+ from .grpc_client import DexEventQueue, GrpcStreamStatus
492
+ __all__ += ["DexEventQueue", "GrpcStreamStatus"]
@@ -82,11 +82,26 @@ def fill_accounts_with_owned_keys(
82
82
  w = [bytes(x) for x in meta_pb.loaded_writable_addresses]
83
83
  r = [bytes(x) for x in meta_pb.loaded_readonly_addresses]
84
84
 
85
- def run(program_b58: str, filler) -> None:
85
+ def run(program_b58: str, filler, anchor=None, unambiguous=False) -> None:
86
86
  pid = base58.b58decode(program_b58)
87
87
  inv = invokes.get(pid)
88
88
  if not inv:
89
89
  return
90
+ if anchor is not None or unambiguous:
91
+ pool, index = anchor or (None,1)
92
+ has_anchor=pool and pool != "11111111111111111111111111111111"
93
+ if not has_anchor and not unambiguous:return
94
+ selected = None
95
+ for invocation in inv:
96
+ candidate = get_instruction_account_getter(meta_pb, transaction_pb, static_keys, w, r, invocation)
97
+ if candidate is None or (has_anchor and candidate(index) != pool):
98
+ continue
99
+ if selected is not None and any(selected(i) != candidate(i) for i in range(18)):
100
+ return # Same pool, conflicting wallet/account context: do not guess.
101
+ selected = candidate
102
+ if selected is not None:
103
+ filler(selected)
104
+ return
90
105
  ix = find_instruction_invoke(inv, meta_pb, transaction_pb)
91
106
  if ix is None:
92
107
  return
@@ -95,6 +110,31 @@ def fill_accounts_with_owned_keys(
95
110
  return
96
111
  filler(get)
97
112
 
113
+ def fill_create(v2_only=False):
114
+ selected = None
115
+ for ix in invokes.get(base58.b58decode(PUMPFUN_PROGRAM_ID), []):
116
+ raw = get_instruction_data(meta_pb, transaction_pb, ix)
117
+ if raw is None:
118
+ continue
119
+ v2 = raw[:8] == bytes([214,144,76,236,95,139,49,180])
120
+ legacy = raw[:8] == bytes([24,30,200,40,5,28,7,119])
121
+ if not v2 and (v2_only or not legacy):
122
+ continue
123
+ get = get_instruction_account_getter(meta_pb, transaction_pb, static_keys, w, r, ix)
124
+ oi, ii = ix
125
+ instruction = (transaction_pb.message.instructions[oi] if ii < 0 else
126
+ next(g for g in meta_pb.inner_instructions if g.index == oi).instructions[ii])
127
+ if len(instruction.accounts) < (16 if v2 else 14) or get is None:
128
+ continue
129
+ if data.mint and data.mint != "11111111111111111111111111111111" and get(0) != data.mint:
130
+ continue
131
+ if selected is not None:
132
+ return # Multiple matching creates: do not guess.
133
+ selected = (get, v2)
134
+ if selected:
135
+ get, v2 = selected
136
+ (pumpfun.fill_create_v2_accounts if v2 else pumpfun.fill_create_accounts)(data, get)
137
+
98
138
  et = event.type
99
139
  data = event.data
100
140
 
@@ -106,9 +146,9 @@ def fill_accounts_with_owned_keys(
106
146
  ):
107
147
  run(PUMPFUN_PROGRAM_ID, lambda g: pumpfun.fill_trade_accounts(data, g))
108
148
  elif et == EventType.PUMP_FUN_CREATE:
109
- run(PUMPFUN_PROGRAM_ID, lambda g: pumpfun.fill_create_accounts(data, g))
149
+ fill_create()
110
150
  elif et == EventType.PUMP_FUN_CREATE_V2:
111
- run(PUMPFUN_PROGRAM_ID, lambda g: pumpfun.fill_create_v2_accounts(data, g))
151
+ fill_create(True)
112
152
  elif et == EventType.PUMP_FUN_MIGRATE:
113
153
  run(PUMPFUN_PROGRAM_ID, lambda g: pumpfun.fill_migrate_accounts(data, g))
114
154
  elif et == EventType.PUMP_SWAP_BUY:
@@ -146,7 +186,7 @@ def fill_accounts_with_owned_keys(
146
186
  elif et == EventType.RAYDIUM_CPMM_INITIALIZE:
147
187
  run(RAYDIUM_CPMM_PROGRAM_ID, lambda g: raydium.fill_cpmm_initialize_accounts(data, g))
148
188
  elif et == EventType.RAYDIUM_AMM_V4_SWAP:
149
- run(RAYDIUM_AMM_V4_PROGRAM_ID, lambda g: raydium.fill_amm_v4_swap_accounts(data, g))
189
+ run(RAYDIUM_AMM_V4_PROGRAM_ID, lambda g: raydium.fill_amm_v4_swap_accounts(data, g), (data.amm,1), True)
150
190
  elif et == EventType.RAYDIUM_AMM_V4_DEPOSIT:
151
191
  run(RAYDIUM_AMM_V4_PROGRAM_ID, lambda g: raydium.fill_amm_v4_deposit_accounts(data, g))
152
192
  elif et == EventType.RAYDIUM_AMM_V4_WITHDRAW:
@@ -182,9 +222,9 @@ def fill_accounts_with_owned_keys(
182
222
  elif et == EventType.METEORA_DLMM_REMOVE_LIQUIDITY:
183
223
  run(METEORA_DLMM_PROGRAM_ID, lambda g: meteora.fill_dlmm_remove_liquidity_accounts(data, g))
184
224
  elif et == EventType.RAYDIUM_LAUNCHLAB_TRADE:
185
- run(RAYDIUM_LAUNCHLAB_PROGRAM_ID, lambda g: raydium_launchlab.fill_trade_accounts(data, g))
225
+ run(RAYDIUM_LAUNCHLAB_PROGRAM_ID, lambda g: raydium_launchlab.fill_trade_accounts(data, g), (data.pool_state,4))
186
226
  elif et == EventType.RAYDIUM_LAUNCHLAB_POOL_CREATE:
187
- run(RAYDIUM_LAUNCHLAB_PROGRAM_ID, lambda g: raydium_launchlab.fill_pool_create_accounts(data, g))
227
+ run(RAYDIUM_LAUNCHLAB_PROGRAM_ID, lambda g: raydium_launchlab.fill_pool_create_accounts(data, g), (data.pool_state,5))
188
228
 
189
229
 
190
230
  def fill_data(
@@ -113,15 +113,12 @@ def fill_trade_accounts(e: PumpFunTradeEvent, get: AccountGetter) -> None:
113
113
 
114
114
 
115
115
  def fill_create_accounts(e: PumpFunCreateEvent, get: AccountGetter) -> None:
116
- if get(15) == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P":
117
- fill_create_v2_accounts(e, get) # type: ignore[arg-type]
118
- return
119
- if _empty(e.mint):
120
- e.mint = get(0)
121
- if _empty(e.bonding_curve):
122
- e.bonding_curve = get(2)
123
- if _empty(e.user):
124
- e.user = get(7)
116
+ for field, index in (("mint",0),("mint_authority",1),("bonding_curve",2),
117
+ ("associated_bonding_curve",3),("global_account",4),("user",7),
118
+ ("system_program",8),("token_program",9),("associated_token_program",10),
119
+ ("event_authority",12),("program",13)):
120
+ if _empty(getattr(e, field, "")):
121
+ setattr(e, field, get(index))
125
122
 
126
123
 
127
124
  def fill_create_v2_accounts(e: PumpFunCreateV2TokenEvent, get: AccountGetter) -> None:
@@ -157,12 +154,6 @@ def fill_create_v2_accounts(e: PumpFunCreateV2TokenEvent, get: AccountGetter) ->
157
154
  e.event_authority = get(14)
158
155
  if _empty(e.program):
159
156
  e.program = get(15)
160
- if _empty(e.quote_mint) or e.quote_mint == "So11111111111111111111111111111111111111111":
161
- e.quote_mint = get(16)
162
- if _empty(e.quote_vault):
163
- e.quote_vault = get(17)
164
- if _empty(e.quote_token_program):
165
- e.quote_token_program = get(18)
166
157
  if getattr(e, "ix_name", "") in ("", "create"):
167
158
  e.ix_name = "create_v2"
168
159
 
@@ -100,8 +100,12 @@ def fill_cpmm_initialize_accounts(e: RaydiumCpmmInitializeEvent, get: AccountGet
100
100
 
101
101
 
102
102
  def fill_amm_v4_swap_accounts(e: RaydiumAmmV4SwapEvent, get: AccountGetter) -> None:
103
- if _empty(e.amm):
104
- e.amm = get(1)
103
+ if _empty(e.amm):e.amm=get(1)
104
+ modern=_empty(get(8)) and not _empty(get(7))
105
+ if modern:
106
+ for field,index in [('token_program',0),('amm_authority',2),('pool_coin_token_account',3),('pool_pc_token_account',4),('user_source_token_account',5),('user_destination_token_account',6),('user_source_owner',7)]:
107
+ if _empty(getattr(e,field)):setattr(e,field,get(index))
108
+ elif _empty(e.user_source_owner):e.user_source_owner=get(16) if _empty(get(17)) else get(17)
105
109
 
106
110
 
107
111
  def fill_amm_v4_deposit_accounts(e: RaydiumAmmV4DepositEvent, get: AccountGetter) -> None:
@@ -43,6 +43,10 @@ def fill_trade_accounts(e: RaydiumLaunchlabTradeEvent, get: AccountGetter) -> No
43
43
  if _empty(e.quote_token_program):
44
44
  e.quote_token_program = get(12)
45
45
 
46
+ for name, index in [("system_program", 15), ("platform_associated_account", 16), ("creator_associated_account", 17)]:
47
+ if _empty(getattr(e,name,"")):
48
+ setattr(e,name,get(index))
49
+
46
50
 
47
51
  def fill_pool_create_accounts(e: RaydiumLaunchlabPoolCreateEvent, get: AccountGetter) -> None:
48
52
  if _empty(e.pool_state):
@@ -206,6 +206,11 @@ def parse_account_unified(
206
206
  event_type_filter: Optional[EventTypeFilter] = None,
207
207
  ) -> Optional[DexEvent]:
208
208
  """对齐 Rust ``parse_account_unified``(含 ``Option<EventTypeFilter>`` 语义)。"""
209
+ from ..liquidity_snapshot import parse_liquidity_account
210
+ if event_type_filter is None or event_type_filter.should_include(EventType.ACCOUNT_LIQUIDITY_SNAPSHOT):
211
+ snapshot = parse_liquidity_account(account, metadata)
212
+ if snapshot is not None:
213
+ return DexEvent(type=EventType.ACCOUNT_LIQUIDITY_SNAPSHOT, data=snapshot)
209
214
  data = account.data
210
215
  if not data:
211
216
  return None
@@ -0,0 +1,29 @@
1
+ """Local BlockMeta adapter, matching Rust's saturated timestamp conversion."""
2
+
3
+ from dataclasses import dataclass
4
+ from .grpc_types import EventType, EventMetadata
5
+ from .event_types import DexEvent
6
+
7
+
8
+ @dataclass
9
+ class BlockMetaEvent:
10
+ metadata: EventMetadata
11
+
12
+
13
+ def parse_block_meta_update(meta, grpc_recv_us, fallback_block_us=None):
14
+ block_us = fallback_block_us or 0
15
+ if meta.block_time is not None:
16
+ block_us = max(-(1 << 63), min((1 << 63) - 1, meta.block_time * 1_000_000))
17
+ return DexEvent(
18
+ EventType.BLOCK_META,
19
+ BlockMetaEvent(
20
+ EventMetadata(
21
+ signature="1" * 64,
22
+ slot=meta.slot,
23
+ tx_index=0,
24
+ block_time_us=block_us,
25
+ grpc_recv_us=grpc_recv_us,
26
+ recent_blockhash=meta.blockhash,
27
+ )
28
+ ),
29
+ )
sol_parser/dex_parsers.py CHANGED
@@ -324,6 +324,10 @@ def parse_create_from_data(data: bytes, meta: dict) -> DexEvent:
324
324
  uri, o = _borsh_str(data, o)
325
325
  except Exception:
326
326
  return DexEvent()
327
+ if len(data) - o == 96:
328
+ return DexEvent(type=EventType.PUMP_FUN_CREATE, data=PumpFunCreateEvent(
329
+ metadata=_make_meta(meta), name=name, symbol=sym, uri=uri,
330
+ mint=_pub(data, o), bonding_curve=_pub(data, o + 32), user=_pub(data, o + 64)))
327
331
  if len(data) < o + 32 * 4 + 8 * 5 + 32 + 1:
328
332
  return DexEvent()
329
333
  mint = _pub(data, o)
@@ -2727,7 +2731,7 @@ def parse_raydium_launchlab_from_discriminator(disc: int, data: bytes, meta: dic
2727
2731
 
2728
2732
 
2729
2733
  def _parse_raydium_launchlab_trade(data: bytes, meta: dict) -> Optional[DexEvent]:
2730
- if len(data) < 139:
2734
+ if len(data) != 139 or data[136] > 1 or data[137] > 2 or data[138] > 1:
2731
2735
  return None
2732
2736
  pool = _pub(data, 0)
2733
2737
  ai = _u64le(data, 88)
@@ -2746,6 +2750,19 @@ def _parse_raydium_launchlab_trade(data: bytes, meta: dict) -> Optional[DexEvent
2746
2750
  is_buy=is_buy,
2747
2751
  trade_direction=d,
2748
2752
  exact_in=ex_in,
2753
+ pool_status=("Fund","Migrate","Trade")[data[137]],
2754
+ total_base_sell=_u64le(data,32),
2755
+ virtual_base=_u64le(data,40),
2756
+ virtual_quote=_u64le(data,48),
2757
+ real_base_before=_u64le(data,56),
2758
+ real_quote_before=_u64le(data,64),
2759
+ real_base_after=_u64le(data,72),
2760
+ real_quote_after=_u64le(data,80),
2761
+ protocol_fee=_u64le(data,104),
2762
+ platform_fee=_u64le(data,112),
2763
+ creator_fee=_u64le(data,120),
2764
+ share_fee=_u64le(data,128),
2765
+
2749
2766
  ),
2750
2767
  )
2751
2768
 
@@ -46,87 +46,13 @@ class _ParsedTx:
46
46
 
47
47
 
48
48
  def parse_transaction_wire(buf: bytes, pos: int) -> Optional[_ParsedTx]:
49
- start = pos
50
- if pos >= len(buf):
51
- return None
52
- sig_count_enc = decode_compact_u16(buf, pos)
53
- if not sig_count_enc:
54
- return None
55
- p = pos + sig_count_enc[1]
56
- sig_count = sig_count_enc[0]
57
- sigs_end = p + sig_count * 64
58
- if sigs_end > len(buf):
59
- return None
60
- sigs: List[bytes] = []
61
- for _ in range(sig_count):
62
- sigs.append(buf[p : p + 64])
63
- p += 64
64
- if p >= len(buf):
65
- return None
66
- msg_first = buf[p]
67
- is_v0 = msg_first >= 0x80
68
- if is_v0:
69
- p += 1
70
- p += 3
71
- if p > len(buf):
72
- return None
73
- acct_enc = decode_compact_u16(buf, p)
74
- if not acct_enc:
75
- return None
76
- p += acct_enc[1]
77
- p += acct_enc[0] * 32
78
- if p > len(buf):
79
- return None
80
- ix_count_enc = decode_compact_u16(buf, p)
81
- if not ix_count_enc:
49
+ from .wire_transaction import decode_wire_transaction
50
+ import base58
51
+ try:
52
+ transaction, length = decode_wire_transaction(buf, pos, require_complete=False)
53
+ return _ParsedTx(length, [base58.b58decode(s) for s in transaction['signatures']])
54
+ except (ValueError, IndexError):
82
55
  return None
83
- p += ix_count_enc[1]
84
- ix_count = ix_count_enc[0]
85
- for _ in range(ix_count):
86
- p += 1
87
- if p > len(buf):
88
- return None
89
- acct_len_enc = decode_compact_u16(buf, p)
90
- if not acct_len_enc:
91
- return None
92
- p += acct_len_enc[1]
93
- p += acct_len_enc[0]
94
- if p > len(buf):
95
- return None
96
- data_len_enc = decode_compact_u16(buf, p)
97
- if not data_len_enc:
98
- return None
99
- p += data_len_enc[1]
100
- p += data_len_enc[0]
101
- if p > len(buf):
102
- return None
103
- if is_v0:
104
- if p >= len(buf):
105
- return None
106
- atl_count_enc = decode_compact_u16(buf, p)
107
- if not atl_count_enc:
108
- return None
109
- p += atl_count_enc[1]
110
- atl_count = atl_count_enc[0]
111
- for _ in range(atl_count):
112
- p += 32
113
- if p > len(buf):
114
- return None
115
- w_len_enc = decode_compact_u16(buf, p)
116
- if not w_len_enc:
117
- return None
118
- p += w_len_enc[1]
119
- p += w_len_enc[0]
120
- if p > len(buf):
121
- return None
122
- r_len_enc = decode_compact_u16(buf, p)
123
- if not r_len_enc:
124
- return None
125
- p += r_len_enc[1]
126
- p += r_len_enc[0]
127
- if p > len(buf):
128
- return None
129
- return _ParsedTx(tx_len=p - start, signatures=sigs)
130
56
 
131
57
 
132
58
  class _BatchDecoder:
sol_parser/event_types.py CHANGED
@@ -219,6 +219,20 @@ class PumpFunCreateEvent(DexEventBase):
219
219
  virtual_quote_reserves: int = 0
220
220
  creator_fee_bps: int = 0
221
221
  is_holder_reward: bool = False
222
+ mint_authority: str = ""
223
+ associated_bonding_curve: str = ""
224
+ global_account: str = "" # Rust `global` PumpFun global config
225
+ system_program: str = ""
226
+ associated_token_program: str = ""
227
+ mayhem_program_id: str = ""
228
+ global_params: str = ""
229
+ sol_vault: str = ""
230
+ mayhem_state: str = ""
231
+ mayhem_token_vault: str = ""
232
+ event_authority: str = ""
233
+ program: str = ""
234
+ observed_fee_recipient: str = ""
235
+ ix_name: str = "create"
222
236
 
223
237
 
224
238
  @dataclass
@@ -1410,6 +1424,28 @@ class RaydiumLaunchlabTradeEvent(DexEventBase):
1410
1424
  base_token_program: str = ""
1411
1425
  quote_token_program: str = ""
1412
1426
 
1427
+ total_base_sell: int = 0
1428
+ virtual_base: int = 0
1429
+ virtual_quote: int = 0
1430
+ real_base_before: int = 0
1431
+ real_quote_before: int = 0
1432
+ real_base_after: int = 0
1433
+ real_quote_after: int = 0
1434
+ protocol_fee: int = 0
1435
+ platform_fee: int = 0
1436
+ creator_fee: int = 0
1437
+ share_fee: int = 0
1438
+ pool_status: str = "Fund"
1439
+ system_program: str = ""
1440
+ platform_associated_account: str = ""
1441
+ creator_associated_account: str = ""
1442
+
1443
+ def stonkfun_mode(self):
1444
+ from .transaction_route import stonkfun_mode_from_platform_config
1445
+ return stonkfun_mode_from_platform_config(self.platform_config)
1446
+
1447
+ def is_stonkfun(self):
1448
+ return self.stonkfun_mode() is not None
1413
1449
 
1414
1450
  @dataclass
1415
1451
  class RaydiumLaunchlabPoolCreateEvent(DexEventBase):
@@ -1428,6 +1464,14 @@ class RaydiumLaunchlabPoolCreateEvent(DexEventBase):
1428
1464
  quote_token_program: str = ""
1429
1465
 
1430
1466
 
1467
+
1468
+ def stonkfun_mode(self):
1469
+ from .transaction_route import stonkfun_mode_from_platform_config
1470
+ return stonkfun_mode_from_platform_config(self.platform_config)
1471
+
1472
+ def is_stonkfun(self):
1473
+ return self.stonkfun_mode() is not None
1474
+
1431
1475
  @dataclass
1432
1476
  class RaydiumLaunchlabMigrateAmmEvent(DexEventBase):
1433
1477
  """RaydiumLaunchlab 迁移 AMM 事件"""
@@ -1436,6 +1480,18 @@ class RaydiumLaunchlabMigrateAmmEvent(DexEventBase):
1436
1480
  user: str = ""
1437
1481
  liquidity_amount: int = 0
1438
1482
 
1483
+ liquidity_amount_known: bool = False
1484
+ base_mint: str = ""
1485
+ quote_mint: str = ""
1486
+ platform_config: str = ""
1487
+ destination_program: str = ""
1488
+
1489
+ def stonkfun_mode(self):
1490
+ from .transaction_route import stonkfun_mode_from_platform_config
1491
+ return stonkfun_mode_from_platform_config(self.platform_config)
1492
+
1493
+ def is_stonkfun(self):
1494
+ return self.stonkfun_mode() is not None
1439
1495
 
1440
1496
  # ============================================================
1441
1497
  # 类型联合
@@ -2379,3 +2435,9 @@ def legacy_dict_to_dex_event(d: dict) -> Optional[DexEvent]:
2379
2435
  except ValueError:
2380
2436
  return None
2381
2437
  return DexEvent(type=et, data=typed)
2438
+
2439
+ # Same classes: preserve isinstance, serialization and platform attribution.
2440
+ LaunchLabPoolCreateEvent = RaydiumLaunchlabPoolCreateEvent
2441
+ StonkFunPoolCreateEvent = RaydiumLaunchlabPoolCreateEvent
2442
+ LaunchLabTradeEvent = RaydiumLaunchlabTradeEvent
2443
+ StonkFunTradeEvent = RaydiumLaunchlabTradeEvent
@@ -131,3 +131,11 @@ __all__ = [
131
131
  "build_subscribe_request_with_commitment",
132
132
  "build_subscribe_transaction_filters_named",
133
133
  ]
134
+
135
+
136
+ def build_subscribe_request_with_event_filter(tx_filters, acc_filters, event_type_filter=None):
137
+ from ..grpc_types import EventType
138
+ request=build_subscribe_request(tx_filters,acc_filters)
139
+ if event_type_filter is not None and event_type_filter.should_include(EventType.BLOCK_META):
140
+ request.blocks_meta["block_meta"].CopyFrom(geyser_pb2.SubscribeRequestFilterBlocksMeta())
141
+ return request