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/__init__.py +26 -0
- sol_parser/account_dispatcher.py +2 -0
- sol_parser/account_fillers/meteora.py +15 -0
- sol_parser/account_fillers/pumpfun.py +11 -0
- sol_parser/accounts/__init__.py +61 -0
- sol_parser/accounts/raydium_orca.py +515 -0
- sol_parser/check_migration.py +3 -0
- sol_parser/dex_parsers.py +1067 -64
- sol_parser/event_type_parity.py +129 -0
- sol_parser/event_types.py +291 -0
- sol_parser/grpc_client.py +25 -6
- sol_parser/grpc_instruction_parser.py +21 -20
- sol_parser/grpc_types.py +49 -21
- sol_parser/inner_instruction_parser.py +138 -12
- sol_parser/instructions.py +299 -58
- sol_parser/log_instr_dedup.py +34 -4
- sol_parser/merger.py +115 -41
- sol_parser/parser.py +95 -7
- sol_parser/pumpfun_fee_enrich.py +12 -1
- sol_parser/rpc_parser.py +119 -33
- sol_parser/shredstream_client.py +19 -1
- {sol_parser_sdk_python-0.5.5.dist-info → sol_parser_sdk_python-0.5.7.dist-info}/METADATA +10 -2
- {sol_parser_sdk_python-0.5.5.dist-info → sol_parser_sdk_python-0.5.7.dist-info}/RECORD +25 -23
- {sol_parser_sdk_python-0.5.5.dist-info → sol_parser_sdk_python-0.5.7.dist-info}/WHEEL +1 -1
- {sol_parser_sdk_python-0.5.5.dist-info → sol_parser_sdk_python-0.5.7.dist-info}/entry_points.txt +0 -0
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
base.
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
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
|
|
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(
|
|
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,
|
sol_parser/pumpfun_fee_enrich.py
CHANGED
|
@@ -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 (
|
|
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
|
|
12
|
-
from .grpc_types import EventTypeFilter, EventType,
|
|
13
|
-
from .
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
210
|
-
for ix in
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
383
|
+
data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us, filter, program_id
|
|
298
384
|
)
|
|
299
385
|
|
|
300
386
|
|
sol_parser/shredstream_client.py
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
95
|
+
pip install sol-parser-sdk-python==0.5.7
|
|
88
96
|
```
|
|
89
97
|
|
|
90
98
|
**From source**
|