sol-parser-sdk-python 0.5.12__py3-none-any.whl → 0.5.14__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 +5 -0
- sol_parser/event_type_parity.py +1 -0
- sol_parser/event_types.py +13 -1
- sol_parser/grpc_types.py +3 -0
- sol_parser/pump_routes.py +96 -0
- sol_parser/pump_upgrade.py +4 -11
- sol_parser/shredstream_client.py +93 -98
- sol_parser/wire_transaction.py +25 -9
- {sol_parser_sdk_python-0.5.12.dist-info → sol_parser_sdk_python-0.5.14.dist-info}/METADATA +44 -18
- {sol_parser_sdk_python-0.5.12.dist-info → sol_parser_sdk_python-0.5.14.dist-info}/RECORD +12 -11
- {sol_parser_sdk_python-0.5.12.dist-info → sol_parser_sdk_python-0.5.14.dist-info}/WHEEL +1 -1
- {sol_parser_sdk_python-0.5.12.dist-info → sol_parser_sdk_python-0.5.14.dist-info}/entry_points.txt +0 -0
sol_parser/__init__.py
CHANGED
|
@@ -511,3 +511,8 @@ from .pump_upgrade import decode_pump_multi_hop_intent
|
|
|
511
511
|
|
|
512
512
|
from .pump_upgrade import PumpFunPostCompleteBuyEvent, PumpFunSweepBondingCurveFeeEvent, PumpFunCompleteEvent, PumpSwapSweepPoolFeeEvent
|
|
513
513
|
__all__ += ['decode_pump_multi_hop_intent','PumpFunPostCompleteBuyEvent','PumpFunSweepBondingCurveFeeEvent','PumpFunCompleteEvent','PumpSwapSweepPoolFeeEvent']
|
|
514
|
+
|
|
515
|
+
from .pump_routes import PumpRouteHop, PumpRouteIntentEvent
|
|
516
|
+
from .shredstream_client import parse_shred_transaction, parse_shred_transaction_with_loaded_addresses, parse_shred_transaction_best_effort
|
|
517
|
+
|
|
518
|
+
__all__ += ["PumpRouteHop", "PumpRouteIntentEvent", "parse_shred_transaction", "parse_shred_transaction_with_loaded_addresses", "parse_shred_transaction_best_effort"]
|
sol_parser/event_type_parity.py
CHANGED
sol_parser/event_types.py
CHANGED
|
@@ -19,10 +19,13 @@
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
from dataclasses import dataclass, field
|
|
22
|
-
from typing import Union, List, Optional, Any, Generic, TypeVar, Dict, Tuple
|
|
22
|
+
from typing import TYPE_CHECKING, Union, List, Optional, Any, Generic, TypeVar, Dict, Tuple
|
|
23
23
|
|
|
24
24
|
from .grpc_types import EventMetadata, EventType
|
|
25
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .pump_routes import PumpRouteIntentEvent
|
|
28
|
+
|
|
26
29
|
T = TypeVar('T')
|
|
27
30
|
|
|
28
31
|
|
|
@@ -1620,6 +1623,7 @@ class RaydiumLaunchlabMigrateAmmEvent(DexEventBase):
|
|
|
1620
1623
|
# ============================================================
|
|
1621
1624
|
|
|
1622
1625
|
TypedDexEvent = Union[
|
|
1626
|
+
"PumpRouteIntentEvent",
|
|
1623
1627
|
PumpFunTradeEvent,
|
|
1624
1628
|
PumpFunCreateEvent,
|
|
1625
1629
|
PumpFunCreateV2TokenEvent,
|
|
@@ -1812,6 +1816,14 @@ def to_typed_event(event: dict) -> Optional[TypedDexEvent]:
|
|
|
1812
1816
|
values={name:(_get_str(data,name) if typ=='pubkey' else _get_int(data,name)) for name,typ in fields}
|
|
1813
1817
|
return cls(metadata=meta,**values)
|
|
1814
1818
|
|
|
1819
|
+
if event_type == EventType.PUMP_ROUTE_INTENT:
|
|
1820
|
+
from .pump_routes import PumpRouteHop, PumpRouteIntentEvent
|
|
1821
|
+
return PumpRouteIntentEvent(metadata=meta,kind=data['kind'],program=data['program'],
|
|
1822
|
+
instruction_index=int(data['instruction_index']),user=data['user'],input_account=data['input_account'],output_account=data['output_account'],
|
|
1823
|
+
amount_in=None if data.get('amount_in') is None else int(data['amount_in']),min_amount_out=None if data.get('min_amount_out') is None else int(data['min_amount_out']),
|
|
1824
|
+
hops=[PumpRouteHop(**h) for h in data['hops']],instruction_data=list(data['instruction_data']),
|
|
1825
|
+
is_buy=data.get('is_buy'),ix_name=data.get('ix_name'))
|
|
1826
|
+
|
|
1815
1827
|
# PumpFun events
|
|
1816
1828
|
if event_type in (EventType.PUMP_FUN_TRADE, EventType.PUMP_FUN_BUY,
|
|
1817
1829
|
EventType.PUMP_FUN_SELL, EventType.PUMP_FUN_BUY_EXACT_SOL_IN):
|
sol_parser/grpc_types.py
CHANGED
|
@@ -47,6 +47,7 @@ class EventType(str, Enum):
|
|
|
47
47
|
RAYDIUM_LAUNCHLAB_TRADE = "RaydiumLaunchlabTrade"
|
|
48
48
|
RAYDIUM_LAUNCHLAB_POOL_CREATE = "RaydiumLaunchlabPoolCreate"
|
|
49
49
|
RAYDIUM_LAUNCHLAB_MIGRATE_AMM = "RaydiumLaunchlabMigrateAmm"
|
|
50
|
+
PUMP_ROUTE_INTENT = "PumpRouteIntent"
|
|
50
51
|
# PumpFun
|
|
51
52
|
PUMP_FUN_TRADE = "PumpFunTrade"
|
|
52
53
|
PUMP_FUN_BUY = "PumpFunBuy"
|
|
@@ -312,6 +313,7 @@ RAYDIUM_LAUNCHLAB_FILTER_TYPES = (
|
|
|
312
313
|
EventType.RAYDIUM_LAUNCHLAB_MIGRATE_AMM,
|
|
313
314
|
)
|
|
314
315
|
INSTRUCTION_EVENT_TYPES = (
|
|
316
|
+
EventType.PUMP_ROUTE_INTENT,
|
|
315
317
|
*PUMPFUN_FILTER_TYPES,
|
|
316
318
|
*PUMP_FEES_EVENT_TYPES,
|
|
317
319
|
*PUMPSWAP_FILTER_TYPES,
|
|
@@ -458,6 +460,7 @@ def all_event_types() -> List[EventType]:
|
|
|
458
460
|
EventType.ACCOUNT_ORCA_TICK_ARRAY,
|
|
459
461
|
EventType.ACCOUNT_ORCA_FEE_TIER,
|
|
460
462
|
EventType.ACCOUNT_ORCA_WHIRLPOOLS_CONFIG,
|
|
463
|
+
EventType.PUMP_ROUTE_INTENT,
|
|
461
464
|
]
|
|
462
465
|
|
|
463
466
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Program-bound offline route intents. These are never confirmed fills."""
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .event_types import DexEvent, DexEventBase
|
|
5
|
+
from .grpc_types import EventType
|
|
6
|
+
|
|
7
|
+
PUMP='6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'
|
|
8
|
+
AMM='pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA'
|
|
9
|
+
RAPID='rapidV5DAKtecYzetwCNoxXWHpezjeg4hGt9BGiVcGW'
|
|
10
|
+
DELTA='J7pourVwqP1VtRNBiFNqbdGxwkx7ta2LHAURURRRJqmd'
|
|
11
|
+
TOKEN='TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
|
12
|
+
TOKEN22='TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
|
|
13
|
+
SYSTEM='11111111111111111111111111111111'
|
|
14
|
+
ATA='ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
|
|
15
|
+
GLOBAL='4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf'
|
|
16
|
+
EVENT='Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1'
|
|
17
|
+
FEES='pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ'
|
|
18
|
+
MULTI=bytes([43,100,73,19,233,246,111,148])
|
|
19
|
+
CURVE=bytes([225,154,117,22,215,86,246,103])
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class PumpRouteHop:
|
|
23
|
+
base_mint: str
|
|
24
|
+
quote_mint: str
|
|
25
|
+
venue: str
|
|
26
|
+
base_vault: str
|
|
27
|
+
quote_vault: str
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class PumpRouteIntentEvent(DexEventBase):
|
|
31
|
+
kind: str = ''
|
|
32
|
+
program: str = ''
|
|
33
|
+
instruction_index: int = 0
|
|
34
|
+
user: str = ''
|
|
35
|
+
input_account: str = ''
|
|
36
|
+
output_account: str = ''
|
|
37
|
+
amount_in: Optional[int] = None
|
|
38
|
+
min_amount_out: Optional[int] = None
|
|
39
|
+
hops: list[PumpRouteHop] = field(default_factory=list)
|
|
40
|
+
instruction_data: list[int] = field(default_factory=list)
|
|
41
|
+
# Intended direction relative to the wrapper hop's base mint, not a fill.
|
|
42
|
+
is_buy: Optional[bool] = None
|
|
43
|
+
# Official underlying instruction name encoded in the wrapper; unknown is None.
|
|
44
|
+
ix_name: Optional[str] = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def allows_route(filter, program):
|
|
48
|
+
if filter is None:
|
|
49
|
+
return True
|
|
50
|
+
excluded=getattr(filter,'exclude_types',())
|
|
51
|
+
if EventType.PUMP_ROUTE_INTENT in excluded or EventType.PUMP_FUN_TRADE in excluded or (program==AMM and EventType.PUMP_SWAP_TRADE in excluded):
|
|
52
|
+
return False
|
|
53
|
+
included=getattr(filter,'include_only',None)
|
|
54
|
+
if included is not None:
|
|
55
|
+
return any(k in included for k in (EventType.PUMP_ROUTE_INTENT,EventType.PUMP_FUN_TRADE)) or (program==AMM and EventType.PUMP_SWAP_TRADE in included)
|
|
56
|
+
return filter.should_include(EventType.PUMP_ROUTE_INTENT)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def may_parse_route(program, data):
|
|
60
|
+
return ((program==AMM and data[:8]==MULTI) or (program==PUMP and data[:8]==CURVE)
|
|
61
|
+
or (program==DELTA and len(data)==28 and data[:8]==bytes([122,28,94,147,180,45,240,97]))
|
|
62
|
+
or (program==RAPID and len(data)==41 and data[0]==0 and data[9:17]==bytes([1,0,0,27,15,14,8,24]) and data[17:25]==bytes([194,171,28,70,104,77,91,47]) and data[25:33]==bytes(8)))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def multi_hop_args(program,data,a):
|
|
66
|
+
if program!=AMM or len(data)!=24 or data[:8]!=MULTI or len(a)<21 or (len(a)-16)%5 or any(a[i]!=v for i,v in {7:TOKEN,8:TOKEN22,9:SYSTEM,11:AMM,12:PUMP}.items()):
|
|
67
|
+
return None
|
|
68
|
+
amount=int.from_bytes(data[8:16],'little')
|
|
69
|
+
return (amount,int.from_bytes(data[16:24],'little')) if amount else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_route(program,data,a,metadata,index,filter=None):
|
|
73
|
+
if not may_parse_route(program,data) or not allows_route(filter,program):
|
|
74
|
+
return None
|
|
75
|
+
u=lambda at:int.from_bytes(data[at:at+8],'little')
|
|
76
|
+
if program==AMM:
|
|
77
|
+
args=multi_hop_args(program,data,a)
|
|
78
|
+
if args is None:return None
|
|
79
|
+
kind,fixed,user,source,dest,amount,minimum='MultiHopSwap',16,0,1,2,*args
|
|
80
|
+
elif program==PUMP:
|
|
81
|
+
if len(data)<20:return None
|
|
82
|
+
count=int.from_bytes(data[16:20],'little')
|
|
83
|
+
if not count or len(data)!=20+count or len(a)!=13+count*5 or any(a[i]!=v for i,v in {8:TOKEN,9:TOKEN22,10:SYSTEM,11:EVENT,12:PUMP}.items()):return None
|
|
84
|
+
kind,fixed,user,source,dest,amount,minimum='MultiHopCurveSwap',13,3,5,6,u(8),None
|
|
85
|
+
elif program==RAPID:
|
|
86
|
+
if len(a)!=33 or a[0]!=a[19] or a[2]!=a[8] or a[9] not in (TOKEN,TOKEN22) or any(a[i]!=v for i,v in {3:TOKEN,4:SYSTEM,5:PUMP,6:GLOBAL,10:TOKEN,11:ATA,29:FEES,30:SYSTEM,31:EVENT,32:PUMP}.items()):return None
|
|
87
|
+
kind,fixed,user,source,dest,amount,minimum='RapidBuy',0,0,21,20,u(1),u(33)
|
|
88
|
+
else:
|
|
89
|
+
if len(a)!=30 or a[0]!=a[14] or a[4] not in (TOKEN,TOKEN22) or a[5] not in (TOKEN,TOKEN22) or any(a[i]!=v for i,v in {1:GLOBAL,6:ATA,24:FEES,25:SYSTEM,26:EVENT,27:PUMP}.items()):return None
|
|
90
|
+
kind,fixed,user,source,dest,amount,minimum='BuyPumpDelta',0,0,16,15,None,None
|
|
91
|
+
if amount==0:return None
|
|
92
|
+
hops=[PumpRouteHop(*a[i:i+5]) for i in range(fixed,len(a),5)] if fixed else [PumpRouteHop(*(a[i] for i in ((7,2,16,17,18) if program==RAPID else (2,3,11,12,13))))]
|
|
93
|
+
return DexEvent(EventType.PUMP_ROUTE_INTENT,PumpRouteIntentEvent(
|
|
94
|
+
metadata,kind,program,index,a[user],a[source],a[dest],amount,minimum,hops,list(data),
|
|
95
|
+
is_buy=True if kind in ('RapidBuy', 'BuyPumpDelta') else None,
|
|
96
|
+
ix_name='buy_exact_quote_in_v2' if kind == 'RapidBuy' else None))
|
sol_parser/pump_upgrade.py
CHANGED
|
@@ -163,18 +163,11 @@ def parse_pump_upgrade_event(disc, data, metadata, program_id=None):
|
|
|
163
163
|
|
|
164
164
|
def decode_pump_multi_hop_intent(program, data, accounts):
|
|
165
165
|
"""Instruction limits only. Actual execution is reported by per-hop trade events."""
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
or data[:8] != bytes([43, 100, 73, 19, 233, 246, 111, 148])
|
|
170
|
-
or len(accounts) < 21
|
|
171
|
-
or (len(accounts) - 16) % 5
|
|
172
|
-
):
|
|
173
|
-
return None
|
|
174
|
-
amount_in = int.from_bytes(data[8:16], "little")
|
|
175
|
-
minimum = int.from_bytes(data[16:24], "little")
|
|
176
|
-
if not amount_in or not minimum:
|
|
166
|
+
from .pump_routes import multi_hop_args
|
|
167
|
+
args = multi_hop_args(program, data, accounts)
|
|
168
|
+
if args is None:
|
|
177
169
|
return None
|
|
170
|
+
amount_in, minimum = args
|
|
178
171
|
return dict(
|
|
179
172
|
user=accounts[0],
|
|
180
173
|
input_account=accounts[1],
|
sol_parser/shredstream_client.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""ShredStream gRPC 客户端:SubscribeEntries + bincode 解码 + DEX 外层指令。
|
|
2
2
|
|
|
3
3
|
限制:与 Rust 文档一致——不解析 inner CPI 日志;当前 Python 热路径只使用静态账户,
|
|
4
|
-
|
|
5
|
-
会按候选 program id 尝试 discriminator 解析。需安装 ``solders``(``pip install 'sol-parser-sdk-python[shredstream]'``)。
|
|
4
|
+
默认跳过未解析账户;ALT 只使用调用方预热缓存,不回退 RPC。Legacy/V0/V1 使用原生线格式解码。
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -133,102 +132,94 @@ def _should_parse_shred_instructions(filter: Optional[EventTypeFilter]) -> bool:
|
|
|
133
132
|
return event_type_filter_allows_instruction_parsing(list(include_only))
|
|
134
133
|
|
|
135
134
|
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
slot: int,
|
|
140
|
-
tx_index: int,
|
|
141
|
-
recv_us: int,
|
|
142
|
-
filter: Optional[EventTypeFilter],
|
|
143
|
-
) -> List[DexEvent]:
|
|
135
|
+
def _parse_decoded_shred(tx, keys, signature, slot, tx_index, recv_us, filter, best_effort=False):
|
|
136
|
+
from .pump_routes import may_parse_route, parse_route
|
|
137
|
+
from .grpc_types import EventMetadata
|
|
144
138
|
if not _should_parse_shred_instructions(filter):
|
|
145
139
|
return []
|
|
140
|
+
msg = tx['message']
|
|
141
|
+
signature = signature or tx['signatures'][0]
|
|
142
|
+
instructions = [(keys[ix['programIdIndex']] if ix['programIdIndex'] < len(keys) else None,
|
|
143
|
+
(ix['data'] if isinstance(ix['data'], bytes) else base58.b58decode(ix['data'])), bytes(ix['accounts'])) for ix in msg['instructions']]
|
|
144
|
+
created, mayhem = set(), set()
|
|
145
|
+
if _pumpfun_filter_allows(filter):
|
|
146
|
+
for pid, data, indices in instructions:
|
|
147
|
+
if pid == PUMPFUN_PROGRAM_ID and (best_effort or all(i < len(keys) for i in indices)):
|
|
148
|
+
c, m = detect_pumpfun_create_mints(pid, data, indices, keys)
|
|
149
|
+
created |= c
|
|
150
|
+
mayhem |= m
|
|
151
|
+
out = []
|
|
152
|
+
for index, (pid, data, indices) in enumerate(instructions):
|
|
153
|
+
resolved = pid is not None and all(i < len(keys) for i in indices)
|
|
154
|
+
if not resolved and not best_effort:
|
|
155
|
+
continue
|
|
156
|
+
if pid and may_parse_route(pid, data):
|
|
157
|
+
if resolved:
|
|
158
|
+
metadata = EventMetadata(signature=signature, slot=slot, tx_index=tx_index, grpc_recv_us=recv_us,
|
|
159
|
+
recent_blockhash=msg['recentBlockhash'])
|
|
160
|
+
event = parse_route(pid, data, [keys[i] for i in indices], metadata, index, filter)
|
|
161
|
+
if event:out.append(event)
|
|
162
|
+
continue
|
|
163
|
+
if pid == PUMPFUN_PROGRAM_ID:
|
|
164
|
+
if not _pumpfun_filter_allows(filter):continue
|
|
165
|
+
event = _filter_parsed_event(parse_pumpfun_shred_ix(data, keys, indices, pid, signature,
|
|
166
|
+
slot, tx_index, recv_us, created, mayhem), filter)
|
|
167
|
+
else:
|
|
168
|
+
accounts = _static_ix_account_strings(keys, indices)
|
|
169
|
+
event = None
|
|
170
|
+
for program in ([pid] if pid else _UNKNOWN_PROGRAM_CANDIDATES):
|
|
171
|
+
event = parse_instruction_unified(data, accounts, signature, slot, tx_index, None, recv_us, filter, program)
|
|
172
|
+
if event:break
|
|
173
|
+
if event:
|
|
174
|
+
if hasattr(event.data, 'metadata'):event.data.metadata.recent_blockhash = msg['recentBlockhash']
|
|
175
|
+
out.append(event)
|
|
176
|
+
enrich_pumpfun_same_tx_post_merge(out)
|
|
177
|
+
return out
|
|
146
178
|
|
|
147
|
-
try:
|
|
148
|
-
from solders.message import Message as LegacyMessage # type: ignore
|
|
149
|
-
from solders.message import MessageV0 # type: ignore
|
|
150
|
-
from solders.transaction import VersionedTransaction # type: ignore
|
|
151
|
-
except ImportError:
|
|
152
|
-
return []
|
|
153
179
|
|
|
180
|
+
def _decode_shred(raw, raw_instructions=True):
|
|
181
|
+
from .wire_transaction import _decode_wire_transaction
|
|
182
|
+
return _decode_wire_transaction(raw, raw_instructions=raw_instructions)[0]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def parse_shred_transaction_with_loaded_addresses(raw, writable, readonly, signature='', slot=0, tx_index=0, recv_us=0, filter=None):
|
|
186
|
+
"""Strict, offline parsing. Reject inconsistent ALT counts before producing events."""
|
|
187
|
+
tx = _decode_shred(raw)
|
|
188
|
+
return _parse_loaded_shred(tx, writable, readonly, signature, slot, tx_index, recv_us, filter)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _parse_loaded_shred(tx, writable, readonly, signature, slot, tx_index, recv_us, filter):
|
|
192
|
+
msg = tx['message']
|
|
193
|
+
lookups = msg.get('addressTableLookups', [])
|
|
194
|
+
if len(writable) != sum(len(l['writableIndexes']) for l in lookups) or len(readonly) != sum(len(l['readonlyIndexes']) for l in lookups):
|
|
195
|
+
raise ValueError('ALT address counts do not match transaction')
|
|
196
|
+
keys = [*msg['accountKeys'], *writable, *readonly]
|
|
197
|
+
if any(ix['programIdIndex'] >= len(keys) or any(i >= len(keys) for i in ix['accounts']) for ix in msg['instructions']):
|
|
198
|
+
raise ValueError('Unresolved instruction index')
|
|
199
|
+
return _parse_decoded_shred(tx, keys, signature, slot, tx_index, recv_us, filter)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def parse_shred_transaction(raw, signature='', slot=0, tx_index=0, recv_us=0, filter=None, *, best_effort=False, address_lookup_resolver=None):
|
|
203
|
+
"""Static keys by default; optional slot-aware resolver must read only a prewarmed cache."""
|
|
154
204
|
try:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
vt.sanitize()
|
|
159
|
-
except Exception:
|
|
160
|
-
return []
|
|
161
|
-
if not vt.signatures:
|
|
162
|
-
return []
|
|
163
|
-
sig = signature or base58.b58encode(bytes(vt.signatures[0])).decode("ascii")
|
|
164
|
-
msg = vt.message
|
|
165
|
-
out: List[DexEvent] = []
|
|
166
|
-
|
|
167
|
-
if isinstance(msg, MessageV0):
|
|
168
|
-
keys = [str(k) for k in msg.account_keys]
|
|
169
|
-
ixs: List[tuple] = []
|
|
170
|
-
for cix in msg.instructions:
|
|
171
|
-
pid = keys[cix.program_id_index] if cix.program_id_index < len(keys) else None
|
|
172
|
-
ixs.append((pid, bytes(cix.data), _ix_accounts_bytes(cix.accounts)))
|
|
173
|
-
elif isinstance(msg, LegacyMessage):
|
|
174
|
-
keys = [str(k) for k in msg.account_keys]
|
|
175
|
-
ixs = []
|
|
176
|
-
for ix in msg.instructions:
|
|
177
|
-
pid = keys[ix.program_id_index] if ix.program_id_index < len(keys) else None
|
|
178
|
-
ixs.append((pid, bytes(ix.data), _ix_accounts_bytes(ix.accounts)))
|
|
179
|
-
else:
|
|
205
|
+
# Resolver callbacks retain the public JSON-compatible decoded shape.
|
|
206
|
+
tx = _decode_shred(raw, raw_instructions=address_lookup_resolver is None)
|
|
207
|
+
except (ValueError, IndexError, TypeError):
|
|
180
208
|
return []
|
|
209
|
+
if address_lookup_resolver is not None and tx['message'].get('addressTableLookups'):
|
|
210
|
+
loaded = address_lookup_resolver(tx, slot)
|
|
211
|
+
if loaded is None:return []
|
|
212
|
+
return _parse_loaded_shred(tx, *loaded, signature, slot, tx_index, recv_us, filter)
|
|
213
|
+
return _parse_decoded_shred(tx, tx['message']['accountKeys'], signature, slot, tx_index, recv_us, filter, best_effort)
|
|
181
214
|
|
|
182
|
-
created: set = set()
|
|
183
|
-
mayhem: set = set()
|
|
184
|
-
if _pumpfun_filter_allows(filter):
|
|
185
|
-
for pid, data, ix_acc in ixs:
|
|
186
|
-
if pid is not None and pid != PUMPFUN_PROGRAM_ID:
|
|
187
|
-
continue
|
|
188
|
-
c, m = detect_pumpfun_create_mints(PUMPFUN_PROGRAM_ID, data, ix_acc, keys)
|
|
189
|
-
created |= c
|
|
190
|
-
mayhem |= m
|
|
191
|
-
|
|
192
|
-
for pid, data, ix_acc in ixs:
|
|
193
|
-
if pid == PUMPFUN_PROGRAM_ID:
|
|
194
|
-
if not _pumpfun_filter_allows(filter):
|
|
195
|
-
continue
|
|
196
|
-
ev = parse_pumpfun_shred_ix(
|
|
197
|
-
data, keys, ix_acc, pid, sig, slot, tx_index, recv_us, created, mayhem
|
|
198
|
-
)
|
|
199
|
-
ev = _filter_parsed_event(ev, filter)
|
|
200
|
-
if ev:
|
|
201
|
-
out.append(ev)
|
|
202
|
-
continue
|
|
203
|
-
accounts = _static_ix_account_strings(keys, ix_acc)
|
|
204
|
-
if pid is None:
|
|
205
|
-
for candidate in _UNKNOWN_PROGRAM_CANDIDATES:
|
|
206
|
-
if candidate == PUMPFUN_PROGRAM_ID:
|
|
207
|
-
if not _pumpfun_filter_allows(filter):
|
|
208
|
-
continue
|
|
209
|
-
ev = parse_pumpfun_shred_ix(
|
|
210
|
-
data, keys, ix_acc, PUMPFUN_PROGRAM_ID, sig, slot, tx_index, recv_us, created, mayhem
|
|
211
|
-
)
|
|
212
|
-
ev = _filter_parsed_event(ev, filter)
|
|
213
|
-
else:
|
|
214
|
-
ev = parse_instruction_unified(
|
|
215
|
-
bytes(data), accounts, sig, slot, tx_index, None, recv_us, filter, candidate
|
|
216
|
-
)
|
|
217
|
-
if ev:
|
|
218
|
-
out.append(ev)
|
|
219
|
-
break
|
|
220
|
-
continue
|
|
221
|
-
ev = parse_instruction_unified(
|
|
222
|
-
bytes(data), accounts, sig, slot, tx_index, None, recv_us, filter, pid
|
|
223
|
-
)
|
|
224
|
-
if ev:
|
|
225
|
-
out.append(ev)
|
|
226
215
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
216
|
+
def _events_from_versioned_tx_wire(raw, signature, slot, tx_index, recv_us, filter):
|
|
217
|
+
return parse_shred_transaction(raw, signature, slot, tx_index, recv_us, filter)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def parse_shred_transaction_best_effort(raw, signature='', slot=0, tx_index=0, recv_us=0, filter=None):
|
|
221
|
+
"""Explicit legacy placeholder mapping; route adapters still require resolved keys."""
|
|
222
|
+
return parse_shred_transaction(raw, signature, slot, tx_index, recv_us, filter, best_effort=True)
|
|
232
223
|
|
|
233
224
|
|
|
234
225
|
class ShredStreamClient:
|
|
@@ -237,6 +228,12 @@ class ShredStreamClient:
|
|
|
237
228
|
def __init__(self, endpoint: str, config: Optional[ShredStreamConfig] = None):
|
|
238
229
|
self.endpoint = endpoint
|
|
239
230
|
self.config = config or ShredStreamConfig()
|
|
231
|
+
self.address_lookup_resolver = None
|
|
232
|
+
|
|
233
|
+
def with_address_lookup_resolver(self, resolver):
|
|
234
|
+
"""Set a synchronous slot-aware cache callback; no automatic RPC fallback."""
|
|
235
|
+
self.address_lookup_resolver = resolver
|
|
236
|
+
return self
|
|
240
237
|
|
|
241
238
|
@classmethod
|
|
242
239
|
def new_with_config(cls, endpoint: str, config: ShredStreamConfig) -> ShredStreamClient:
|
|
@@ -273,14 +270,12 @@ class ShredStreamClient:
|
|
|
273
270
|
on_error(e)
|
|
274
271
|
continue
|
|
275
272
|
for tx_counter, raw in enumerate(raws):
|
|
276
|
-
sig0 = ""
|
|
277
273
|
try:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
yield ev
|
|
274
|
+
events = parse_shred_transaction(raw, slot=slot, tx_index=tx_counter, recv_us=recv_us,
|
|
275
|
+
filter=f, address_lookup_resolver=self.address_lookup_resolver)
|
|
276
|
+
except Exception as error:
|
|
277
|
+
if on_error:on_error(error)
|
|
278
|
+
continue
|
|
279
|
+
yield from events
|
|
285
280
|
finally:
|
|
286
281
|
channel.close()
|
sol_parser/wire_transaction.py
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import base58
|
|
5
|
+
try:
|
|
6
|
+
from solders.pubkey import Pubkey
|
|
7
|
+
from solders.signature import Signature
|
|
8
|
+
except ImportError:
|
|
9
|
+
# solders is an optional ShredStream accelerator; core parsing stays usable.
|
|
10
|
+
def _key_string(raw):
|
|
11
|
+
return base58.b58encode(raw).decode()
|
|
12
|
+
_signature_string = _key_string
|
|
13
|
+
else:
|
|
14
|
+
def _key_string(raw):
|
|
15
|
+
return str(Pubkey.from_bytes(raw))
|
|
16
|
+
def _signature_string(raw):
|
|
17
|
+
return str(Signature.from_bytes(raw))
|
|
5
18
|
|
|
6
19
|
|
|
7
20
|
class _Reader:
|
|
@@ -32,9 +45,12 @@ class _Reader:
|
|
|
32
45
|
raise ValueError("Invalid short-u16")
|
|
33
46
|
|
|
34
47
|
|
|
35
|
-
def decode_wire_transaction(
|
|
36
|
-
data
|
|
37
|
-
|
|
48
|
+
def decode_wire_transaction(data: bytes, offset: int = 0, require_complete: bool = True) -> tuple[dict, int]:
|
|
49
|
+
return _decode_wire_transaction(data, offset, require_complete)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _decode_wire_transaction(data: bytes, offset: int = 0, require_complete: bool = True,
|
|
53
|
+
*, raw_instructions: bool = False) -> tuple[dict, int]:
|
|
38
54
|
if not isinstance(offset, int) or offset < 0 or offset >= len(data):
|
|
39
55
|
raise ValueError("Invalid wire offset")
|
|
40
56
|
r = _Reader(data, offset)
|
|
@@ -69,7 +85,7 @@ def decode_wire_transaction(
|
|
|
69
85
|
{
|
|
70
86
|
"programIdIndex": program,
|
|
71
87
|
"accounts": list(r.take(accounts)),
|
|
72
|
-
"data": base58.b58encode(r.take(length)).decode(),
|
|
88
|
+
"data": (r.take(length) if raw_instructions else base58.b58encode(r.take(length)).decode()),
|
|
73
89
|
}
|
|
74
90
|
for program, accounts, length in headers
|
|
75
91
|
]
|
|
@@ -103,7 +119,7 @@ def decode_wire_transaction(
|
|
|
103
119
|
{
|
|
104
120
|
"programIdIndex": program,
|
|
105
121
|
"accounts": accounts,
|
|
106
|
-
"data": base58.b58encode(payload).decode(),
|
|
122
|
+
"data": (payload if raw_instructions else base58.b58encode(payload).decode()),
|
|
107
123
|
}
|
|
108
124
|
)
|
|
109
125
|
if version == 0:
|
|
@@ -113,7 +129,7 @@ def decode_wire_transaction(
|
|
|
113
129
|
readonly = list(r.take(r.short()))
|
|
114
130
|
lookups.append(
|
|
115
131
|
{
|
|
116
|
-
"accountKey":
|
|
132
|
+
"accountKey": _key_string(key),
|
|
117
133
|
"writableIndexes": writable,
|
|
118
134
|
"readonlyIndexes": readonly,
|
|
119
135
|
}
|
|
@@ -152,15 +168,15 @@ def decode_wire_transaction(
|
|
|
152
168
|
header,
|
|
153
169
|
)
|
|
154
170
|
),
|
|
155
|
-
"accountKeys": [
|
|
156
|
-
"recentBlockhash":
|
|
171
|
+
"accountKeys": [_key_string(key) for key in keys],
|
|
172
|
+
"recentBlockhash": _key_string(blockhash),
|
|
157
173
|
"instructions": instructions,
|
|
158
174
|
"addressTableLookups": lookups,
|
|
159
175
|
}
|
|
160
176
|
if config is not None:
|
|
161
177
|
message["config"] = config
|
|
162
178
|
return {
|
|
163
|
-
"signatures": [
|
|
179
|
+
"signatures": [_signature_string(s) for s in signatures],
|
|
164
180
|
"message": message,
|
|
165
181
|
"version": version,
|
|
166
182
|
}, r.pos - offset
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sol-parser-sdk-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.14
|
|
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
|
|
@@ -13,8 +13,11 @@ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
|
13
13
|
Requires-Dist: pytest-benchmark>=4.0; extra == 'dev'
|
|
14
14
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
15
15
|
Requires-Dist: requests>=2.31; extra == 'dev'
|
|
16
|
+
Requires-Dist: solders==0.26.0; extra == 'dev'
|
|
16
17
|
Provides-Extra: shredstream
|
|
17
18
|
Requires-Dist: solders>=0.21.0; extra == 'shredstream'
|
|
19
|
+
Provides-Extra: simulation
|
|
20
|
+
Requires-Dist: solders==0.26.0; extra == 'simulation'
|
|
18
21
|
Description-Content-Type: text/markdown
|
|
19
22
|
|
|
20
23
|
<div align="center">
|
|
@@ -57,15 +60,27 @@ Parser SDK language versions and related Rust SDKs:
|
|
|
57
60
|
|
|
58
61
|
| Language | Repository | Description | Version |
|
|
59
62
|
|----------|------------|-------------|---------|
|
|
60
|
-
| Rust | [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk) | Solana DEX transaction and account event parsing | `v0.7.
|
|
61
|
-
| Node.js | [sol-parser-sdk-nodejs](https://github.com/0xfnzero/sol-parser-sdk-nodejs) | TypeScript/JavaScript for Node.js | `v0.5.
|
|
62
|
-
| Python | [sol-parser-sdk-python](https://github.com/0xfnzero/sol-parser-sdk-python) | Async/await native support | `v0.5.
|
|
63
|
-
| Go | [sol-parser-sdk-golang](https://github.com/0xfnzero/sol-parser-sdk-golang) | Go transaction and account event parsing | `v0.5.
|
|
64
|
-
| **Rust** | [sol-shred-sdk](https://github.com/0xfnzero/sol-shred-sdk) | Raw Solana shred decoding and ShredStream DEX event parsing | `v4.0.
|
|
65
|
-
| **Rust** | [sol-trade-sdk](https://github.com/0xfnzero/sol-trade-sdk) | Solana DEX trade construction and transaction execution | `v6.0.
|
|
63
|
+
| Rust | [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk) | Solana DEX transaction and account event parsing | `v0.7.15` |
|
|
64
|
+
| Node.js | [sol-parser-sdk-nodejs](https://github.com/0xfnzero/sol-parser-sdk-nodejs) | TypeScript/JavaScript for Node.js | `v0.5.21` |
|
|
65
|
+
| Python | [sol-parser-sdk-python](https://github.com/0xfnzero/sol-parser-sdk-python) | Async/await native support | `v0.5.14` |
|
|
66
|
+
| Go | [sol-parser-sdk-golang](https://github.com/0xfnzero/sol-parser-sdk-golang) | Go transaction and account event parsing | `v0.5.14` |
|
|
67
|
+
| **Rust** | [sol-shred-sdk](https://github.com/0xfnzero/sol-shred-sdk) | Raw Solana shred decoding and ShredStream DEX event parsing | `v4.0.6` |
|
|
68
|
+
| **Rust** | [sol-trade-sdk](https://github.com/0xfnzero/sol-trade-sdk) | Solana DEX trade construction and transaction execution | `v6.0.2` |
|
|
66
69
|
|
|
67
70
|
---
|
|
68
71
|
|
|
72
|
+
## v0.5.14 — Pump route direction and instruction names
|
|
73
|
+
|
|
74
|
+
Route intents now expose `is_buy` and `ix_name`, following existing trade field names and official IDL instruction names. RapidBuy reports a buy intent and `buy_exact_quote_in_v2`; BuyPumpDelta reports only a buy intent. Multi-hop direction and unknown methods remain null. Read currencies from `hops[].base_mint` / `quote_mint`. These hints do not confirm execution, and filters do not convert intents into trades.
|
|
75
|
+
|
|
76
|
+
Older JSON without the fields remains readable as unknown. Rust struct literals must supply the new fields; Go uses pointers and Node.js/Python use null/None for unknown values. Streamer now forwards `DexEvent::PumpRouteIntentEvent` with protocol and event filtering.
|
|
77
|
+
|
|
78
|
+
## v0.5.13 — Pump routes and offline replay
|
|
79
|
+
|
|
80
|
+
Adds native Pump route-intent parsing for official multi-hop instructions and the exact RapidBuy/BuyPumpDelta wrapper layouts captured in reported transactions. Adds locally cached ALT resolution and rejects unresolved account identities by default, with explicit best-effort compatibility APIs where supported. Route intents expose instruction constraints and hop identities; confirmed execution amounts require execution metadata.
|
|
81
|
+
|
|
82
|
+
Includes the latest complete implementation, signed transaction fixtures, native offline-bank regression examples and reproducible local hot-path benchmarks. Unknown wrapper layouts are not inferred from embedded bytes. Local tests do not measure production network latency.
|
|
83
|
+
|
|
69
84
|
## v0.5.12 — Pump depth-2 compatibility
|
|
70
85
|
|
|
71
86
|
Adds signed Pump depth-2 create-event replay coverage, including ALT-loaded accounts and executed logs. Existing event layouts and public parsing interfaces remain unchanged.
|
|
@@ -141,7 +156,7 @@ This is the Python implementation of the FnZero Solana DEX parser SDK for asynci
|
|
|
141
156
|
**From PyPI**
|
|
142
157
|
|
|
143
158
|
```bash
|
|
144
|
-
pip install sol-parser-sdk-python==0.5.
|
|
159
|
+
pip install sol-parser-sdk-python==0.5.14
|
|
145
160
|
```
|
|
146
161
|
|
|
147
162
|
**From source**
|
|
@@ -246,7 +261,7 @@ asyncio.run(main())
|
|
|
246
261
|
|
|
247
262
|
Python includes a native ShredStream client (`ShredStreamClient`, `ShredStreamConfig`) plus the same env/CLI helpers as Node (`parse_shredstream_url`: `SHREDSTREAM_URL` / `SHRED_URL`, `--url` / `-u` / `--endpoint=`). Install the optional extra with `pip install 'sol-parser-sdk-python[shredstream]'`. ShredStream uses its own endpoint, **not** `GRPC_URL`.
|
|
248
263
|
|
|
249
|
-
The Python ShredStream hot path uses static
|
|
264
|
+
The Python ShredStream hot path uses strict static-account parsing by default. Supply loaded addresses or a slot-aware prewarmed-cache resolver for V0 ALT instructions; unresolved instructions are skipped. Legacy placeholders require the explicit `parse_shred_transaction_best_effort` API. Recognized wrapper/official routes emit `PumpRouteIntent`; raw shreds cannot reconstruct arbitrary CPI. Inner CPI/log-only events still require the Yellowstone/RPC paths.
|
|
250
265
|
|
|
251
266
|
---
|
|
252
267
|
|
|
@@ -257,18 +272,18 @@ From the **package root** after `pip install -e .`. Run with `python examples/<f
|
|
|
257
272
|
| Description | Run command | Source |
|
|
258
273
|
|-------------|-------------|--------|
|
|
259
274
|
| **PumpFun** | | |
|
|
260
|
-
| PumpFun trade filtering | `python examples/pumpfun_trade_filter.py` | [pumpfun_trade_filter.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_trade_filter.py) | `v0.5.
|
|
261
|
-
| PumpFun events + metrics | `python examples/pumpfun_with_metrics.py` | [pumpfun_with_metrics.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_with_metrics.py) | `v0.5.
|
|
262
|
-
| Quick PumpFun connection test | `python examples/pumpfun_quick_test.py` | [pumpfun_quick_test.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_quick_test.py) | `v0.5.
|
|
275
|
+
| PumpFun trade filtering | `python examples/pumpfun_trade_filter.py` | [pumpfun_trade_filter.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_trade_filter.py) | `v0.5.14` |
|
|
276
|
+
| PumpFun events + metrics | `python examples/pumpfun_with_metrics.py` | [pumpfun_with_metrics.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_with_metrics.py) | `v0.5.14` |
|
|
277
|
+
| Quick PumpFun connection test | `python examples/pumpfun_quick_test.py` | [pumpfun_quick_test.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpfun_quick_test.py) | `v0.5.14` |
|
|
263
278
|
| **PumpSwap** | | |
|
|
264
|
-
| PumpSwap ultra-low latency | `python examples/pumpswap_low_latency.py` | [pumpswap_low_latency.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpswap_low_latency.py) | `v0.5.
|
|
265
|
-
| PumpSwap events + metrics | `python examples/pumpswap_with_metrics.py` | [pumpswap_with_metrics.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpswap_with_metrics.py) | `v0.5.
|
|
279
|
+
| PumpSwap ultra-low latency | `python examples/pumpswap_low_latency.py` | [pumpswap_low_latency.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpswap_low_latency.py) | `v0.5.14` |
|
|
280
|
+
| PumpSwap events + metrics | `python examples/pumpswap_with_metrics.py` | [pumpswap_with_metrics.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/pumpswap_with_metrics.py) | `v0.5.14` |
|
|
266
281
|
| **Meteora DAMM** | | |
|
|
267
|
-
| Meteora DAMM V2 events | `python examples/meteora_damm_grpc.py` | [meteora_damm_grpc.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/meteora_damm_grpc.py) | `v0.5.
|
|
282
|
+
| Meteora DAMM V2 events | `python examples/meteora_damm_grpc.py` | [meteora_damm_grpc.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/meteora_damm_grpc.py) | `v0.5.14` |
|
|
268
283
|
| **Multi-protocol** | | |
|
|
269
|
-
| Subscribe to every program in Rust `Protocol` / `program_ids` | `python examples/multi_protocol_grpc.py` | [multi_protocol_grpc.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/multi_protocol_grpc.py) | `v0.5.
|
|
284
|
+
| Subscribe to every program in Rust `Protocol` / `program_ids` | `python examples/multi_protocol_grpc.py` | [multi_protocol_grpc.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/multi_protocol_grpc.py) | `v0.5.14` |
|
|
270
285
|
| **Utility** | | |
|
|
271
|
-
| Parse tx by signature (HTTP RPC; not gRPC). `TX_SIGNATURE` / `RPC_URL` or `SOLANA_RPC_URL` in `.env` or `--sig` / `--rpc`. | `python examples/parse_tx_by_signature.py` | [parse_tx_by_signature.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/parse_tx_by_signature.py) | `v0.5.
|
|
286
|
+
| Parse tx by signature (HTTP RPC; not gRPC). `TX_SIGNATURE` / `RPC_URL` or `SOLANA_RPC_URL` in `.env` or `--sig` / `--rpc`. | `python examples/parse_tx_by_signature.py` | [parse_tx_by_signature.py](https://github.com/0xfnzero/sol-parser-sdk-python/blob/main/examples/parse_tx_by_signature.py) | `v0.5.14` |
|
|
272
287
|
|
|
273
288
|
**Env:** gRPC examples need **`GRPC_URL`** or **`GRPC_ENDPOINT`**, plus **`GRPC_AUTH_TOKEN`** or **`GRPC_TOKEN`** (or legacy `GEYSER_*`). See **`.env.example`**.
|
|
274
289
|
|
|
@@ -371,3 +386,14 @@ Full cross-language API parity is still in progress. See the [API migration guid
|
|
|
371
386
|
实时 parser → trade 接入使用 Yellowstone **gRPC**;见 [gRPC 缓存接入与三语言示例](docs/USAGE.md#grpc-cache)。此路径不使用 WebSocket,报价和构建热路径不调用 RPC。
|
|
372
387
|
|
|
373
388
|
Shared PumpFun create/create_v2 mainnet fixtures, replay instructions and verification limits are documented in the [validation guide](https://github.com/0xfnzero/sol-parser-sdk-golang/tree/main/validation/pumpfun_create_20261007).
|
|
389
|
+
|
|
390
|
+
### Offline Pump ShredStream regressions
|
|
391
|
+
|
|
392
|
+
Pump route intents, strict cached ALT resolution and native signed replay/SBF simulation examples are documented in [examples/offline_bank/README.md](examples/offline_bank/README.md). Run the language-native examples and tests from this repository; no other-language test driver or RPC endpoint is needed. Unresolved instructions now skip by default; use the explicit best-effort API only for legacy behavior. Route intents must not be treated as confirmed fills.
|
|
393
|
+
|
|
394
|
+
Native offline CPU benchmark: `python -m examples.hot_path_benchmark`.
|
|
395
|
+
`examples/hot_path_benchmark.json` fixes warmup and measured iterations for six
|
|
396
|
+
signed wrapped-CPI/ALT transactions. Reported percentiles are per six-transaction
|
|
397
|
+
batch; fixture decoding and I/O occur before timing. Parsing performs no RPC.
|
|
398
|
+
Public wire-decoder dictionaries and resolver callbacks retain base58 strings;
|
|
399
|
+
the internal ShredStream path avoids an instruction-data base58 round trip.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sol_parser/__init__.py,sha256=
|
|
1
|
+
sol_parser/__init__.py,sha256=0SBivj4hJynvlD617cEWSEUJScO04zIA0e_KUnw36r8,17535
|
|
2
2
|
sol_parser/account_dispatcher.py,sha256=WgWD0833ArPtXuRGMO7_URDxPhLaCcnHc-e8RNeGCJI,19947
|
|
3
3
|
sol_parser/block_meta.py,sha256=LuJ4oy6xvRmFjSIUDUG9k4Dg7q9l2xW9z35wYJdBmCw,854
|
|
4
4
|
sol_parser/check_migration.py,sha256=PsRE_Zhnw2g_iaP_iYtlGsQutE2w_FsaZIP4GPeN8-o,537
|
|
@@ -6,13 +6,13 @@ sol_parser/clock.py,sha256=RHF99qMerKvEsT55krkftIh-uqy_uTzpgdKCqKBMDmA,278
|
|
|
6
6
|
sol_parser/dex_parsers.py,sha256=eOigBKLpMQGZT_3dJwq18niDhY0fSGJrULDx_TGNC8Q,146032
|
|
7
7
|
sol_parser/entries_decode.py,sha256=MOhGadNxWLerxBhGvuNatoWa2hzw2MXVz9NmNUhWvS8,3665
|
|
8
8
|
sol_parser/env_config.py,sha256=Lw0SdZuy_MvUaVkyFxS0qPBgMzR9UBbxbN0CVz8nxg4,7296
|
|
9
|
-
sol_parser/event_type_parity.py,sha256=
|
|
10
|
-
sol_parser/event_types.py,sha256=
|
|
9
|
+
sol_parser/event_type_parity.py,sha256=mD9Lg43aPzJGvz0Ft94NxKw6XnpgBio5yL2xin9Wfdg,4521
|
|
10
|
+
sol_parser/event_types.py,sha256=m63dNNVxjx9baTSsKIcwZXK1EAlx4cdh2Vy0TBrAq0o,98387
|
|
11
11
|
sol_parser/geyser_pb2.py,sha256=Hq66wmWxXD2jSbSVQW7vmBRB7nWmR1DuYDMQ6nKgl3o,20123
|
|
12
12
|
sol_parser/geyser_pb2_grpc.py,sha256=ASPmZsCNvyCzt_j4IrhKjSacWUNLVuAiLfpjmyd-2Qc,15322
|
|
13
13
|
sol_parser/grpc_client.py,sha256=fusMuSJsTPEUxgobdeimQ7s3Hu9n0bD8pIH7rEL40jM,37370
|
|
14
14
|
sol_parser/grpc_instruction_parser.py,sha256=ogpnD3dTEeduxORH5VmwCnubGkQe9bbMCTC-NSH9Wkw,17107
|
|
15
|
-
sol_parser/grpc_types.py,sha256=
|
|
15
|
+
sol_parser/grpc_types.py,sha256=cqBuHmUuRqE8MvCwGzPtRR-T7BKNonT5-k4P6yDzspI,41388
|
|
16
16
|
sol_parser/inner_instruction_parser.py,sha256=i5tkKS7F1aZWUdE9pzm2o5_1EXWM8-v71cjo45jM-d8,21668
|
|
17
17
|
sol_parser/instr_account_utils.py,sha256=MY8bgoIDiv2yJKeWxqhpfKc3pV4xUFTcYKnSXtnQMWc,1990
|
|
18
18
|
sol_parser/instructions.py,sha256=XZt73ZJmCZE5LWF8A-yLmxGmF0dcPxnVuRmhMf-7ynY,88373
|
|
@@ -23,12 +23,13 @@ sol_parser/market.py,sha256=p_N6n3b4hEtUjHAQQyGSnrKdHYmkHdY1hXXObHRKc0Y,1858
|
|
|
23
23
|
sol_parser/merger.py,sha256=hVIeocXYnLTP5qtXb4lZtED5EteE_g3a84ZeUwUmda0,13498
|
|
24
24
|
sol_parser/order_buffer.py,sha256=f3yvvYglHX800hyF8JhFbjb1VoNtOvrqUR8lm3lvF-Y,7874
|
|
25
25
|
sol_parser/parser.py,sha256=aSDbsbnUqBHEJ6UMdnZIuR-01fWs4_GxUwTJ3vQ0GXU,11906
|
|
26
|
-
sol_parser/
|
|
26
|
+
sol_parser/pump_routes.py,sha256=U7FWxZYyP860jUZh5F8YKAoauki-hgmfu2DNDjEcplM,4868
|
|
27
|
+
sol_parser/pump_upgrade.py,sha256=BnVB8PVHfPtILBOlI29nbXABihVuXZUO82Yw6upFhRM,5212
|
|
27
28
|
sol_parser/pumpfun_fee_enrich.py,sha256=NHC_chCv2wuaOW5taYG8R6M1bu8cmdxcLf8cjSfo7Kc,4688
|
|
28
29
|
sol_parser/rpc_parser.py,sha256=Ft1VSBk66RLbwURKIoGtsRFDwjorh4EE6Gdqkil5tHY,25871
|
|
29
30
|
sol_parser/rust_api_inventory.py,sha256=V5Bx1M3GT-ku_vj_kD6lnbqWExv1K_A00PrmhCFfRec,2257
|
|
30
31
|
sol_parser/rust_event_json.py,sha256=7hho9TGfSOQjHev_8uRa4YHU1hrEsW1RfwcU9qHYz1o,1649
|
|
31
|
-
sol_parser/shredstream_client.py,sha256=
|
|
32
|
+
sol_parser/shredstream_client.py,sha256=0D6dPZ366mmSuE-bMfjyt-Y3vrB35VMt1cJlCUuZQ_Y,11660
|
|
32
33
|
sol_parser/shredstream_pb2.py,sha256=BMrgCTcvRu5sY_QG1nj5AhWK9RL1fqF9Ga0xYihQfio,1703
|
|
33
34
|
sol_parser/shredstream_pb2_grpc.py,sha256=zfRJaDj-76Gbr6tC3x4gFOz6Xpu2fmJFWWcTOTmyIXs,3013
|
|
34
35
|
sol_parser/shredstream_pumpfun.py,sha256=agTryEilG7c9Ttlek2-d2fyhzwGHIKrUD9YMAJjvXfk,11187
|
|
@@ -39,7 +40,7 @@ sol_parser/stonkfun_registry.py,sha256=el8xm3FGU5Eo4D5yq7-NCINSdsn7Pzl-9i1nKNgLa
|
|
|
39
40
|
sol_parser/transaction_route.py,sha256=aPUy95_RHUDl6MFGzQ9hJbpv69LSHxFYCeRTBVc9SLY,19668
|
|
40
41
|
sol_parser/u128_parity.py,sha256=D8PYuy1l-1QwkIEeGCLvrGfzAfTzpBDcGINxRIJAEPs,3487
|
|
41
42
|
sol_parser/verify_discriminators.py,sha256=oqCBWL065ixBIjhHW255nk4S-F1F0x4zfTzs68nXMqA,3011
|
|
42
|
-
sol_parser/wire_transaction.py,sha256=
|
|
43
|
+
sol_parser/wire_transaction.py,sha256=ATJSDY1D-m0vCBMooEe2g8F5a1QVOZyl7k-lEDSheSQ,6858
|
|
43
44
|
sol_parser/account_fillers/__init__.py,sha256=q4q7HZF0Z4-rxhKoRMzV52HSkJ_n_POm4vimYUQlCOA,235
|
|
44
45
|
sol_parser/account_fillers/meteora.py,sha256=ecY3F4KkjnVxBc-Zsnzru52DggkYOdbeauaYrznDxFY,2111
|
|
45
46
|
sol_parser/account_fillers/orca.py,sha256=2s-hfi20fa5sy6p1rGc7_7NxzkpaleJdqn3YD41aBAw,895
|
|
@@ -58,7 +59,7 @@ sol_parser/grpc/subscribe_builder.py,sha256=xH9NZ1Lp-7AQKcgxoqyE0PrB3vdOqnScm_Ht
|
|
|
58
59
|
sol_parser/grpc/transaction_meta.py,sha256=NmIffIQ1_OIfWGjy6JY-f8pIK5ucjDZXXJryjnlgoQs,5894
|
|
59
60
|
sol_parser/instr/__init__.py,sha256=0_Ky8KpVEoXzxszHcHLTuY2SDsJwu0uAF2Q0wMY1e-c,503
|
|
60
61
|
sol_parser/logs/__init__.py,sha256=e1cbhbCNeXy-NZvVsYCpGksBpZf4NJBLmdlky06og-U,349
|
|
61
|
-
sol_parser_sdk_python-0.5.
|
|
62
|
-
sol_parser_sdk_python-0.5.
|
|
63
|
-
sol_parser_sdk_python-0.5.
|
|
64
|
-
sol_parser_sdk_python-0.5.
|
|
62
|
+
sol_parser_sdk_python-0.5.14.dist-info/METADATA,sha256=SruOAOSNW0NilLlGbiS2CnYu4M19rLdyl5Q2H8iM9Gc,20745
|
|
63
|
+
sol_parser_sdk_python-0.5.14.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
64
|
+
sol_parser_sdk_python-0.5.14.dist-info/entry_points.txt,sha256=ZGXRb-2jBdc56d8PCAZFiJBLTAzUKFeHPohysJnKVbA,206
|
|
65
|
+
sol_parser_sdk_python-0.5.14.dist-info/RECORD,,
|
{sol_parser_sdk_python-0.5.12.dist-info → sol_parser_sdk_python-0.5.14.dist-info}/entry_points.txt
RENAMED
|
File without changes
|