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,655 @@
1
+ """RPC Transaction Parser - 支持直接从 RPC 解析交易"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union
7
+ from dataclasses import dataclass
8
+
9
+ import base58
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
14
+ from .log_instr_dedup import dedupe_log_instruction_events
15
+ from .pumpfun_fee_enrich import enrich_pumpfun_same_tx_post_merge
16
+
17
+
18
+ class ParseError(Exception):
19
+ """RPC 解析错误"""
20
+ def __init__(self, kind: str, message: str):
21
+ self.kind = kind
22
+ self.message = message
23
+ super().__init__(f"{kind}: {message}")
24
+
25
+
26
+ @dataclass
27
+ class RpcCompiledInstruction:
28
+ """编译指令"""
29
+ program_id_index: int
30
+ accounts: Union[List[int], bytes]
31
+ data: bytes
32
+
33
+
34
+ @dataclass
35
+ class RpcInnerInstructionGroup:
36
+ """内部指令组"""
37
+ index: int
38
+ instructions: List[RpcCompiledInstruction]
39
+
40
+
41
+ @dataclass
42
+ class RpcTokenBalance:
43
+ """Token 余额"""
44
+ account_index: int
45
+ mint: str
46
+ ui_token_amount: Dict[str, Any]
47
+
48
+
49
+ @dataclass
50
+ class RpcLoadedAddresses:
51
+ """加载地址"""
52
+ writable: List[str]
53
+ readonly: List[str]
54
+
55
+
56
+ @dataclass
57
+ class RpcTransactionMeta:
58
+ """交易元数据"""
59
+ fee: int
60
+ pre_balances: List[int]
61
+ post_balances: List[int]
62
+ log_messages: List[str]
63
+ inner_instructions: List[RpcInnerInstructionGroup]
64
+ pre_token_balances: List[RpcTokenBalance]
65
+ post_token_balances: List[RpcTokenBalance]
66
+ loaded_addresses: Optional[RpcLoadedAddresses]
67
+ compute_units_consumed: Optional[int]
68
+
69
+
70
+ @dataclass
71
+ class RpcMessageHeader:
72
+ """消息头"""
73
+ num_required_signatures: int
74
+ num_readonly_signed_accounts: int
75
+ num_readonly_unsigned_accounts: int
76
+
77
+
78
+ @dataclass
79
+ class RpcMessageAddressTableLookup:
80
+ """地址表查找"""
81
+ account_key: str
82
+ writable_indexes: List[int]
83
+ readonly_indexes: List[int]
84
+
85
+
86
+ @dataclass
87
+ class RpcMessage:
88
+ """消息"""
89
+ account_keys: List[str]
90
+ header: Optional[RpcMessageHeader]
91
+ recent_blockhash: str
92
+ instructions: List[RpcCompiledInstruction]
93
+ address_table_lookups: List[RpcMessageAddressTableLookup]
94
+
95
+
96
+ @dataclass
97
+ class RpcTransaction:
98
+ """交易"""
99
+ signatures: List[str]
100
+ message: Optional[RpcMessage]
101
+
102
+
103
+ @dataclass
104
+ class RpcTransactionResponse:
105
+ """RPC 交易响应"""
106
+
107
+ slot: int
108
+ block_time: Optional[int]
109
+ meta: Optional[RpcTransactionMeta]
110
+ transaction: Optional[RpcTransaction]
111
+ #: 该笔交易在区块中的序号(``getTransaction`` 的 ``transactionIndex``;单交易拉取时可能为 0)
112
+ transaction_index: int = 0
113
+
114
+
115
+ class RpcClient:
116
+ """RPC 客户端接口"""
117
+ def get_transaction(
118
+ self,
119
+ signature: str,
120
+ max_supported_transaction_version: int = 0
121
+ ) -> Optional[RpcTransactionResponse]:
122
+ raise NotImplementedError
123
+
124
+
125
+ def parse_transaction_from_rpc(
126
+ rpc_client: RpcClient,
127
+ signature: str,
128
+ filter: Optional[EventTypeFilter] = None,
129
+ ) -> Tuple[List[DexEvent], Optional[ParseError]]:
130
+ """通过 RPC 拉取交易并解析
131
+
132
+ Args:
133
+ rpc_client: RPC 客户端
134
+ signature: 交易签名
135
+ filter: 可选的事件类型过滤器
136
+
137
+ Returns:
138
+ (events, error) 元组
139
+ """
140
+ try:
141
+ tx = rpc_client.get_transaction(signature, max_supported_transaction_version=0)
142
+ except Exception as e:
143
+ return [], ParseError("RpcError", f"Failed to fetch transaction: {e}")
144
+
145
+ if tx is None:
146
+ return [], ParseError("RpcError", "Transaction not found or null response (try archive RPC for old txs)")
147
+
148
+ grpc_recv_us = int(time.time() * 1_000_000)
149
+ return parse_rpc_transaction(tx, signature, filter, grpc_recv_us)
150
+
151
+
152
+ def parse_rpc_transaction(
153
+ tx: RpcTransactionResponse,
154
+ signature: str,
155
+ filter: Optional[EventTypeFilter],
156
+ grpc_recv_us: int,
157
+ ) -> Tuple[List[DexEvent], Optional[ParseError]]:
158
+ """解析已获取的 RPC 交易
159
+
160
+ Args:
161
+ tx: RPC 交易响应
162
+ signature: 交易签名
163
+ filter: 可选的事件类型过滤器
164
+ grpc_recv_us: gRPC 接收时间戳(微秒)
165
+
166
+ Returns:
167
+ (events, error) 元组
168
+ """
169
+ if tx.transaction is None or tx.transaction.message is None:
170
+ return [], ParseError("ConversionError", "Transaction message is nil")
171
+
172
+ msg = tx.transaction.message
173
+ meta = tx.meta
174
+ if meta is None:
175
+ meta = RpcTransactionMeta(
176
+ fee=0,
177
+ pre_balances=[],
178
+ post_balances=[],
179
+ log_messages=[],
180
+ inner_instructions=[],
181
+ pre_token_balances=[],
182
+ post_token_balances=[],
183
+ loaded_addresses=None,
184
+ compute_units_consumed=None,
185
+ )
186
+
187
+ slot = tx.slot
188
+ block_time_us = tx.block_time * 1_000_000 if tx.block_time else None
189
+ block_tx_index = int(getattr(tx, "transaction_index", 0) or 0)
190
+
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)
207
+
208
+ # 解析内层指令
209
+ for group in meta.inner_instructions:
210
+ for ix in group.instructions:
211
+ ev = _parse_rpc_instruction(
212
+ ix,
213
+ msg.account_keys,
214
+ signature,
215
+ slot,
216
+ block_tx_index,
217
+ block_time_us,
218
+ grpc_recv_us,
219
+ filter,
220
+ )
221
+ if ev:
222
+ instruction_events.append(ev)
223
+
224
+ # 解析日志
225
+ is_created_buy = False
226
+ recent_blockhash = msg.recent_blockhash
227
+ log_events: List[DexEvent] = []
228
+
229
+ from .parser import parse_log_optimized
230
+
231
+ for log in meta.log_messages:
232
+ ev = parse_log_optimized(
233
+ log,
234
+ signature,
235
+ slot,
236
+ block_tx_index,
237
+ block_time_us,
238
+ grpc_recv_us,
239
+ filter,
240
+ is_created_buy,
241
+ recent_blockhash,
242
+ )
243
+ if ev:
244
+ if ev.type in (EventType.PUMP_FUN_CREATE, EventType.PUMP_FUN_CREATE_V2):
245
+ is_created_buy = True
246
+ log_events.append(ev)
247
+
248
+ tx_pb, meta_pb = rpc_response_to_solana_storage(tx)
249
+ if tx_pb is not None and meta_pb is not None:
250
+ from .grpc_instruction_parser import apply_account_fill_to_events
251
+
252
+ apply_account_fill_to_events(instruction_events, tx_pb, meta_pb)
253
+ apply_account_fill_to_events(log_events, tx_pb, meta_pb)
254
+
255
+ events = dedupe_log_instruction_events(log_events, instruction_events)
256
+ enrich_pumpfun_same_tx_post_merge(events)
257
+
258
+ if tx_pb is not None and meta_pb is not None:
259
+ from .grpc_instruction_parser import apply_account_fill_to_events
260
+
261
+ apply_account_fill_to_events(events, tx_pb, meta_pb)
262
+ enrich_pumpfun_same_tx_post_merge(events)
263
+
264
+ return events, None
265
+
266
+
267
+ def _parse_rpc_instruction(
268
+ ix: RpcCompiledInstruction,
269
+ account_keys: List[str],
270
+ signature: str,
271
+ slot: int,
272
+ tx_index: int,
273
+ block_time_us: Optional[int],
274
+ grpc_recv_us: int,
275
+ filter: Optional[EventTypeFilter],
276
+ ) -> Optional[DexEvent]:
277
+ """解析 RPC 指令"""
278
+ # 获取程序 ID
279
+ if ix.program_id_index >= len(account_keys):
280
+ return None
281
+ program_id = account_keys[ix.program_id_index]
282
+
283
+ # 解析指令数据
284
+ data = ix.data
285
+ if len(data) == 0:
286
+ return None
287
+
288
+ # 构建账户列表
289
+ accounts = []
290
+ acc_iter = ix.accounts if isinstance(ix.accounts, (list, tuple)) else list(ix.accounts)
291
+ for acc_idx in acc_iter:
292
+ if acc_idx < len(account_keys):
293
+ accounts.append(account_keys[acc_idx])
294
+
295
+ f: EventTypeFilter = filter if filter is not None else IncludeOnlyFilter([])
296
+ return parse_instruction_unified(
297
+ data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us, f, program_id
298
+ )
299
+
300
+
301
+ def convert_rpc_to_grpc(
302
+ rpc_tx: RpcTransactionResponse,
303
+ ) -> Tuple[Optional[Any], Optional[Any], Optional[ParseError]]:
304
+ """将 RPC 格式转换为 gRPC 格式
305
+
306
+ Returns:
307
+ (grpc_meta, grpc_tx, error) 元组
308
+ """
309
+ try:
310
+ from . import geyser_pb2
311
+ except ImportError:
312
+ return None, None, ParseError(
313
+ "ImportError",
314
+ "需要 protobuf 生成的代码。请从 https://github.com/rpcpool/yellowstone-grpc 获取 proto 文件并生成 Python 代码。"
315
+ )
316
+
317
+ meta = rpc_tx.meta
318
+ if meta is None:
319
+ return None, None, ParseError("ConversionError", "meta is nil")
320
+
321
+ # 转换 TransactionStatusMeta
322
+ grpc_meta = geyser_pb2.TransactionStatusMeta(
323
+ fee=meta.fee,
324
+ pre_balances=meta.pre_balances,
325
+ post_balances=meta.post_balances,
326
+ log_messages=meta.log_messages,
327
+ )
328
+
329
+ # 转换内部指令
330
+ for group in meta.inner_instructions:
331
+ grpc_group = grpc_meta.inner_instructions.add()
332
+ grpc_group.index = group.index
333
+ for ix in group.instructions:
334
+ grpc_ix = grpc_group.instructions.add()
335
+ grpc_ix.program_id_index = ix.program_id_index
336
+ grpc_ix.accounts = bytes(ix.accounts)
337
+ grpc_ix.data = ix.data
338
+
339
+ # 转换加载的地址
340
+ if meta.loaded_addresses:
341
+ for addr in meta.loaded_addresses.writable:
342
+ grpc_meta.loaded_writable_addresses.append(base58.b58decode(addr))
343
+ for addr in meta.loaded_addresses.readonly:
344
+ grpc_meta.loaded_readonly_addresses.append(base58.b58decode(addr))
345
+
346
+ # 转换交易
347
+ if rpc_tx.transaction is None:
348
+ return None, None, ParseError("ConversionError", "transaction is nil")
349
+
350
+ tx = rpc_tx.transaction
351
+ grpc_tx = geyser_pb2.Transaction()
352
+
353
+ # 转换签名
354
+ for sig in tx.signatures:
355
+ grpc_tx.signatures.append(base58.b58decode(sig))
356
+
357
+ # 转换消息
358
+ if tx.message:
359
+ msg = tx.message
360
+ grpc_msg = grpc_tx.message
361
+
362
+ # 转换账户密钥
363
+ for key in msg.account_keys:
364
+ grpc_msg.account_keys.append(base58.b58decode(key))
365
+
366
+ # 转换最近区块哈希
367
+ if msg.recent_blockhash:
368
+ grpc_msg.recent_blockhash = base58.b58decode(msg.recent_blockhash)
369
+
370
+ # 转换指令
371
+ for ix in msg.instructions:
372
+ grpc_ix = grpc_msg.instructions.add()
373
+ grpc_ix.program_id_index = ix.program_id_index
374
+ grpc_ix.accounts = bytes(ix.accounts)
375
+ grpc_ix.data = ix.data
376
+
377
+ # 转换地址表查找
378
+ for lookup in msg.address_table_lookups:
379
+ grpc_lookup = grpc_msg.address_table_lookups.add()
380
+ grpc_lookup.account_key = base58.b58decode(lookup.account_key)
381
+ grpc_lookup.writable_indexes = bytes(lookup.writable_indexes)
382
+ grpc_lookup.readonly_indexes = bytes(lookup.readonly_indexes)
383
+
384
+ # 转换消息头
385
+ if msg.header:
386
+ grpc_msg.header.num_required_signatures = msg.header.num_required_signatures
387
+ grpc_msg.header.num_readonly_signed_accounts = msg.header.num_readonly_signed_accounts
388
+ grpc_msg.header.num_readonly_unsigned_accounts = msg.header.num_readonly_unsigned_accounts
389
+
390
+ return grpc_meta, grpc_tx, None
391
+
392
+
393
+ def _ix_data_from_rpc(ix: dict) -> bytes:
394
+ raw = ix.get("data")
395
+ if raw is None or raw == "":
396
+ return b""
397
+ if isinstance(raw, str):
398
+ try:
399
+ return base58.b58decode(raw)
400
+ except Exception:
401
+ return b""
402
+ return b""
403
+
404
+
405
+ def _account_keys_from_message(msg: dict) -> List[str]:
406
+ keys = msg.get("accountKeys") or msg.get("account_keys") or []
407
+ out: List[str] = []
408
+ for k in keys:
409
+ if isinstance(k, str):
410
+ out.append(k)
411
+ elif isinstance(k, dict):
412
+ pk = k.get("pubkey") or k.get("pubKey")
413
+ if pk:
414
+ out.append(str(pk))
415
+ return out
416
+
417
+
418
+ def _parse_rpc_compiled_ix(ix: dict, account_keys: List[str]) -> RpcCompiledInstruction:
419
+ if "programIdIndex" in ix:
420
+ pidx = int(ix["programIdIndex"])
421
+ elif "programId" in ix:
422
+ pid = ix["programId"]
423
+ try:
424
+ pidx = account_keys.index(pid)
425
+ except ValueError:
426
+ pidx = 0
427
+ else:
428
+ pidx = 0
429
+ accounts = ix.get("accounts") or []
430
+ if isinstance(accounts, str):
431
+ try:
432
+ accounts = list(base58.b58decode(accounts))
433
+ except Exception:
434
+ accounts = []
435
+ elif not isinstance(accounts, list):
436
+ accounts = []
437
+ acc_list = [int(x) for x in accounts]
438
+ return RpcCompiledInstruction(
439
+ program_id_index=pidx,
440
+ accounts=acc_list,
441
+ data=_ix_data_from_rpc(ix),
442
+ )
443
+
444
+
445
+ def rpc_get_transaction_result_dict_to_response(
446
+ result: Optional[dict],
447
+ ) -> Optional[RpcTransactionResponse]:
448
+ """将 ``getTransaction`` 的 JSON ``result`` 转为 :class:`RpcTransactionResponse`。
449
+
450
+ 支持 ``encoding: json`` / ``jsonParsed`` 下的 ``transaction`` 对象;若为仅 ``base64`` 数组则返回 ``None``(需另行解码)。
451
+ """
452
+ if not result or not isinstance(result, dict):
453
+ return None
454
+ tfield = result.get("transaction")
455
+ if tfield is None:
456
+ return None
457
+ if isinstance(tfield, list):
458
+ return None
459
+ if not isinstance(tfield, dict):
460
+ return None
461
+
462
+ slot = int(result.get("slot", 0))
463
+ block_time = result.get("blockTime")
464
+ if block_time is not None:
465
+ block_time = int(block_time)
466
+ tx_idx_raw = result.get("transactionIndex")
467
+ if tx_idx_raw is None:
468
+ tx_idx_raw = result.get("transaction_index")
469
+ transaction_index = int(tx_idx_raw) if tx_idx_raw is not None else 0
470
+
471
+ tx_body = tfield
472
+ sigs = tx_body.get("signatures") or []
473
+ if not isinstance(sigs, list):
474
+ sigs = []
475
+ msg_dict = tx_body.get("message")
476
+ if not isinstance(msg_dict, dict):
477
+ return None
478
+
479
+ account_keys = _account_keys_from_message(msg_dict)
480
+ instructions: List[RpcCompiledInstruction] = []
481
+ for ix in msg_dict.get("instructions") or []:
482
+ if not isinstance(ix, dict):
483
+ continue
484
+ if "programIdIndex" not in ix and "programId" not in ix:
485
+ continue
486
+ instructions.append(_parse_rpc_compiled_ix(ix, account_keys))
487
+
488
+ header = None
489
+ h = msg_dict.get("header")
490
+ if isinstance(h, dict):
491
+ header = RpcMessageHeader(
492
+ num_required_signatures=int(h.get("numRequiredSignatures", 0)),
493
+ num_readonly_signed_accounts=int(h.get("numReadonlySignedAccounts", 0)),
494
+ num_readonly_unsigned_accounts=int(h.get("numReadonlyUnsignedAccounts", 0)),
495
+ )
496
+
497
+ lookups: List[RpcMessageAddressTableLookup] = []
498
+ for lu in msg_dict.get("addressTableLookups") or []:
499
+ if not isinstance(lu, dict):
500
+ continue
501
+ wi = lu.get("writableIndexes") or lu.get("writable_indexes") or []
502
+ ri = lu.get("readonlyIndexes") or lu.get("readonly_indexes") or []
503
+ ak = lu.get("accountKey") or lu.get("account_key") or ""
504
+ lookups.append(
505
+ RpcMessageAddressTableLookup(
506
+ account_key=str(ak),
507
+ writable_indexes=[int(x) for x in wi],
508
+ readonly_indexes=[int(x) for x in ri],
509
+ )
510
+ )
511
+
512
+ recent = msg_dict.get("recentBlockhash") or msg_dict.get("recent_blockhash") or ""
513
+ message = RpcMessage(
514
+ account_keys=account_keys,
515
+ header=header,
516
+ recent_blockhash=str(recent),
517
+ instructions=instructions,
518
+ address_table_lookups=lookups,
519
+ )
520
+
521
+ rpc_tx = RpcTransaction(signatures=[str(s) for s in sigs], message=message)
522
+
523
+ meta_dict = result.get("meta")
524
+ if meta_dict is None:
525
+ meta = RpcTransactionMeta(
526
+ fee=0,
527
+ pre_balances=[],
528
+ post_balances=[],
529
+ log_messages=[],
530
+ inner_instructions=[],
531
+ pre_token_balances=[],
532
+ post_token_balances=[],
533
+ loaded_addresses=None,
534
+ compute_units_consumed=None,
535
+ )
536
+ else:
537
+ inner_groups: List[RpcInnerInstructionGroup] = []
538
+ for g in meta_dict.get("innerInstructions") or meta_dict.get("inner_instructions") or []:
539
+ if not isinstance(g, dict):
540
+ continue
541
+ ixs: List[RpcCompiledInstruction] = []
542
+ for ix in g.get("instructions") or []:
543
+ if not isinstance(ix, dict):
544
+ continue
545
+ ixs.append(_parse_rpc_compiled_ix(ix, account_keys))
546
+ inner_groups.append(
547
+ RpcInnerInstructionGroup(index=int(g.get("index", 0)), instructions=ixs)
548
+ )
549
+ loaded = None
550
+ la = meta_dict.get("loadedAddresses") or meta_dict.get("loaded_addresses")
551
+ if isinstance(la, dict):
552
+ loaded = RpcLoadedAddresses(
553
+ writable=[str(x) for x in la.get("writable", [])],
554
+ readonly=[str(x) for x in la.get("readonly", [])],
555
+ )
556
+ logs = meta_dict.get("logMessages") or meta_dict.get("log_messages") or []
557
+ if not isinstance(logs, list):
558
+ logs = []
559
+ meta = RpcTransactionMeta(
560
+ fee=int(meta_dict.get("fee", 0)),
561
+ pre_balances=[int(x) for x in meta_dict.get("preBalances") or meta_dict.get("pre_balances") or []],
562
+ post_balances=[int(x) for x in meta_dict.get("postBalances") or meta_dict.get("post_balances") or []],
563
+ log_messages=[str(x) for x in logs],
564
+ inner_instructions=inner_groups,
565
+ pre_token_balances=[],
566
+ post_token_balances=[],
567
+ loaded_addresses=loaded,
568
+ compute_units_consumed=meta_dict.get("computeUnitsConsumed"),
569
+ )
570
+
571
+ return RpcTransactionResponse(
572
+ slot=slot,
573
+ block_time=block_time,
574
+ meta=meta,
575
+ transaction=rpc_tx,
576
+ transaction_index=transaction_index,
577
+ )
578
+
579
+
580
+ def rpc_response_to_solana_storage(
581
+ rpc_tx: RpcTransactionResponse,
582
+ ) -> Tuple[Optional[Any], Optional[Any]]:
583
+ """将 :class:`RpcTransactionResponse` 转为 ``solana_storage_pb2`` 的 Transaction + TransactionStatusMeta。"""
584
+ try:
585
+ from . import solana_storage_pb2 as sol_pb
586
+ except ImportError:
587
+ return None, None
588
+ if rpc_tx.transaction is None or rpc_tx.transaction.message is None:
589
+ return None, None
590
+ if rpc_tx.meta is None:
591
+ return None, None
592
+
593
+ tx = sol_pb.Transaction()
594
+ for sig in rpc_tx.transaction.signatures:
595
+ tx.signatures.append(base58.b58decode(sig))
596
+
597
+ msg = rpc_tx.transaction.message
598
+ out_msg = tx.message
599
+ for k in msg.account_keys:
600
+ out_msg.account_keys.append(base58.b58decode(k))
601
+ if msg.recent_blockhash:
602
+ out_msg.recent_blockhash = base58.b58decode(msg.recent_blockhash)
603
+ for ix in msg.instructions:
604
+ c = out_msg.instructions.add()
605
+ c.program_id_index = ix.program_id_index
606
+ acc = ix.accounts
607
+ c.accounts = bytes(acc) if not isinstance(acc, bytes) else acc
608
+ c.data = ix.data
609
+ if msg.header:
610
+ out_msg.header.num_required_signatures = msg.header.num_required_signatures
611
+ out_msg.header.num_readonly_signed_accounts = msg.header.num_readonly_signed_accounts
612
+ out_msg.header.num_readonly_unsigned_accounts = msg.header.num_readonly_unsigned_accounts
613
+ for lu in msg.address_table_lookups:
614
+ l = out_msg.address_table_lookups.add()
615
+ l.account_key = base58.b58decode(lu.account_key)
616
+ l.writable_indexes = bytes(lu.writable_indexes)
617
+ l.readonly_indexes = bytes(lu.readonly_indexes)
618
+
619
+ m = rpc_tx.meta
620
+ meta = sol_pb.TransactionStatusMeta()
621
+ meta.fee = m.fee
622
+ meta.pre_balances.extend(m.pre_balances)
623
+ meta.post_balances.extend(m.post_balances)
624
+ meta.log_messages.extend(m.log_messages)
625
+ for group in m.inner_instructions:
626
+ g = meta.inner_instructions.add()
627
+ g.index = group.index
628
+ for ix in group.instructions:
629
+ ii = g.instructions.add()
630
+ ii.program_id_index = ix.program_id_index
631
+ acc = ix.accounts
632
+ ii.accounts = bytes(acc) if not isinstance(acc, bytes) else acc
633
+ ii.data = ix.data
634
+ if m.loaded_addresses:
635
+ for w in m.loaded_addresses.writable:
636
+ meta.loaded_writable_addresses.append(base58.b58decode(w))
637
+ for r in m.loaded_addresses.readonly:
638
+ meta.loaded_readonly_addresses.append(base58.b58decode(r))
639
+ return tx, meta
640
+
641
+
642
+ def enrich_dex_events_from_rpc_get_transaction_result(
643
+ events: List[DexEvent],
644
+ result: Optional[dict],
645
+ ) -> None:
646
+ """对已有事件列表用 ``getTransaction`` 的 JSON ``result`` 做与 gRPC 相同的账户补全。"""
647
+ resp = rpc_get_transaction_result_dict_to_response(result)
648
+ if resp is None:
649
+ return
650
+ tx_pb, meta_pb = rpc_response_to_solana_storage(resp)
651
+ if tx_pb is None or meta_pb is None:
652
+ return
653
+ from .grpc_instruction_parser import apply_account_fill_to_events
654
+
655
+ apply_account_fill_to_events(events, tx_pb, meta_pb)
@@ -0,0 +1,42 @@
1
+ """Rust ``sol-parser-sdk`` 公开 API 与 Python 映射(维护对照,非运行时依赖)。
2
+
3
+ 本模块仅作文档与静态清单;实际导出见 ``sol_parser.__init__`` 及各子包。
4
+ """
5
+
6
+ # crate 根: sol-parser-sdk/src/lib.rs
7
+ CRATE_ROOT_EXPORTS = {
8
+ "parse_logs_only": "sol_parser.parser.parse_logs_only",
9
+ "parse_logs_streaming": "sol_parser.parser.parse_logs_streaming",
10
+ "parse_transaction_events": "sol_parser.parser.parse_transaction_events",
11
+ "parse_transaction_events_streaming": "sol_parser.parser.parse_transaction_events_streaming",
12
+ "parse_transaction_with_listener": "sol_parser.parser.parse_transaction_with_listener",
13
+ "parse_transaction_with_streaming_listener": "sol_parser.parser.parse_transaction_with_streaming_listener",
14
+ "DexEvent": "sol_parser.dex_parsers.DexEvent (dict 形态) / event_types.TypedDexEvent",
15
+ "EventListener": "sol_parser.parser.EventListener",
16
+ "EventMetadata": "sol_parser.grpc_types.EventMetadata",
17
+ "ParsedEvent": "同 DexEvent",
18
+ "StreamingEventListener": "sol_parser.parser.StreamingEventListener",
19
+ "warmup_parser": "sol_parser.parser.warmup_parser",
20
+ "convert_rpc_to_grpc": "sol_parser.rpc_parser.convert_rpc_to_grpc",
21
+ "parse_rpc_transaction": "sol_parser.rpc_parser.parse_rpc_transaction",
22
+ "parse_transaction_from_rpc": "sol_parser.rpc_parser.parse_transaction_from_rpc",
23
+ "ParseError": "sol_parser.rpc_parser.ParseError",
24
+ "rpc_resolve_user_wallet_pubkey": "sol_parser.accounts.rpc_wallet.rpc_resolve_user_wallet_pubkey",
25
+ "user_wallet_pubkey_for_onchain_account": "sol_parser.accounts.utils.user_wallet_pubkey_for_onchain_account",
26
+ }
27
+
28
+ SUBMODULES = {
29
+ "accounts": "sol_parser.accounts",
30
+ "common": "sol_parser.common (见 common 子模块或常量分散)",
31
+ "core": "sol_parser.parser / merger / dex_parsers / event_types",
32
+ "instr": "sol_parser.instructions",
33
+ "logs": "sol_parser.logs",
34
+ "utils": "sol_parser.json_util 等",
35
+ "warmup": "sol_parser.parser.warmup_parser",
36
+ "grpc": "sol_parser.grpc",
37
+ "shredstream": "sol_parser.shredstream_client / shredstream 配置",
38
+ "rpc_parser": "sol_parser.rpc_parser",
39
+ "parser": "sol_parser.parser (core 别名)",
40
+ }
41
+
42
+ __all__ = ["CRATE_ROOT_EXPORTS", "SUBMODULES"]