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.
- sol_parser/__init__.py +400 -0
- sol_parser/account_dispatcher.py +209 -0
- sol_parser/account_fillers/__init__.py +5 -0
- sol_parser/account_fillers/bonk.py +30 -0
- sol_parser/account_fillers/meteora.py +51 -0
- sol_parser/account_fillers/orca.py +40 -0
- sol_parser/account_fillers/pumpfun.py +97 -0
- sol_parser/account_fillers/pumpswap.py +93 -0
- sol_parser/account_fillers/raydium.py +119 -0
- sol_parser/accounts/__init__.py +461 -0
- sol_parser/accounts/rpc_wallet.py +64 -0
- sol_parser/accounts/utils.py +71 -0
- sol_parser/check_migration.py +18 -0
- sol_parser/clock.py +10 -0
- sol_parser/common/__init__.py +27 -0
- sol_parser/dex_parsers.py +2576 -0
- sol_parser/entries_decode.py +186 -0
- sol_parser/env_config.py +215 -0
- sol_parser/event_types.py +1750 -0
- sol_parser/geyser_pb2.py +148 -0
- sol_parser/geyser_pb2_grpc.py +398 -0
- sol_parser/grpc/__init__.py +61 -0
- sol_parser/grpc/geyser_connect.py +42 -0
- sol_parser/grpc/subscribe_builder.py +133 -0
- sol_parser/grpc/transaction_meta.py +183 -0
- sol_parser/grpc_client.py +870 -0
- sol_parser/grpc_instruction_parser.py +318 -0
- sol_parser/grpc_types.py +919 -0
- sol_parser/inner_instruction_parser.py +281 -0
- sol_parser/instr/__init__.py +15 -0
- sol_parser/instr_account_utils.py +58 -0
- sol_parser/instructions.py +1026 -0
- sol_parser/json_util.py +41 -0
- sol_parser/log_instr_dedup.py +284 -0
- sol_parser/logs/__init__.py +15 -0
- sol_parser/merger.py +233 -0
- sol_parser/order_buffer.py +171 -0
- sol_parser/parser.py +300 -0
- sol_parser/pumpfun_fee_enrich.py +75 -0
- sol_parser/rpc_parser.py +655 -0
- sol_parser/rust_api_inventory.py +42 -0
- sol_parser/rust_event_json.py +50 -0
- sol_parser/shredstream_client.py +191 -0
- sol_parser/shredstream_pb2.py +40 -0
- sol_parser/shredstream_pb2_grpc.py +81 -0
- sol_parser/shredstream_pumpfun.py +296 -0
- sol_parser/solana_storage_pb2.py +75 -0
- sol_parser/solana_storage_pb2_grpc.py +24 -0
- sol_parser/u128_parity.py +115 -0
- sol_parser/verify_discriminators.py +85 -0
- sol_parser_sdk_python-0.4.4.dist-info/METADATA +14 -0
- sol_parser_sdk_python-0.4.4.dist-info/RECORD +54 -0
- sol_parser_sdk_python-0.4.4.dist-info/WHEEL +4 -0
- sol_parser_sdk_python-0.4.4.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""账户解析器 — 对齐 Rust ``accounts`` 模块(含 ``utils`` / ``rpc_wallet`` 子模块)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
import base58
|
|
10
|
+
|
|
11
|
+
from ..grpc_types import EventTypeFilter, EventType, EventMetadata
|
|
12
|
+
from ..dex_parsers import DexEvent
|
|
13
|
+
|
|
14
|
+
from . import rpc_wallet
|
|
15
|
+
from . import utils as acc_utils
|
|
16
|
+
|
|
17
|
+
# 程序 ID(与 Rust ``accounts/program_ids`` / ``instr/program_ids`` 一致)
|
|
18
|
+
PUMPFUN_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
|
19
|
+
PUMPSWAP_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"
|
|
20
|
+
|
|
21
|
+
_DISC_PUMPFUN_GLOBAL = bytes([167, 232, 232, 177, 200, 108, 114, 127])
|
|
22
|
+
_DISC_GLOBAL_CONFIG = bytes([149, 8, 156, 202, 160, 252, 176, 217])
|
|
23
|
+
_DISC_POOL = bytes([241, 154, 109, 4, 17, 177, 109, 188])
|
|
24
|
+
_DISC_NONCE = bytes([1, 0, 0, 0, 1, 0, 0, 0])
|
|
25
|
+
|
|
26
|
+
MINT_SIZE = 82
|
|
27
|
+
TOKEN_ACCOUNT_SIZE = 165
|
|
28
|
+
NONCE_ACCOUNT_SIZE = 80
|
|
29
|
+
PUMPFUN_GLOBAL_BODY = 1021
|
|
30
|
+
GLOBAL_CONFIG_BODY = 634
|
|
31
|
+
POOL_BODY = 244
|
|
32
|
+
|
|
33
|
+
SUPPLY_OFFSET = 36
|
|
34
|
+
DECIMALS_OFFSET = 44
|
|
35
|
+
AMOUNT_OFFSET = 64
|
|
36
|
+
NONCE_AUTHORITY_OFFSET = 8
|
|
37
|
+
NONCE_NONCE_OFFSET = 40
|
|
38
|
+
|
|
39
|
+
EMPTY_PUBKEY = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _account_event(event_type: EventType, data: dict) -> DexEvent:
|
|
43
|
+
return DexEvent(type=event_type, data=data)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class AccountData:
|
|
48
|
+
pubkey: str
|
|
49
|
+
executable: bool
|
|
50
|
+
lamports: int
|
|
51
|
+
owner: str
|
|
52
|
+
rent_epoch: int
|
|
53
|
+
data: bytes
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def has_discriminator(data: bytes, discriminator: bytes) -> bool:
|
|
57
|
+
return acc_utils.has_discriminator(data, discriminator)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def base58_encode_32(data: bytes) -> str:
|
|
61
|
+
return base58.b58encode(data).decode("ascii")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def read_pubkey_fast(data: bytes, offset: int) -> str:
|
|
65
|
+
if offset + 32 > len(data):
|
|
66
|
+
return EMPTY_PUBKEY
|
|
67
|
+
return base58.b58encode(data[offset : offset + 32]).decode("ascii")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def read_u64_fast(data: bytes, offset: int) -> int:
|
|
71
|
+
return struct.unpack_from("<Q", data, offset)[0]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def read_u16_fast(data: bytes, offset: int) -> int:
|
|
75
|
+
return struct.unpack_from("<H", data, offset)[0]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_account_unified(
|
|
79
|
+
account: AccountData,
|
|
80
|
+
metadata: EventMetadata,
|
|
81
|
+
event_type_filter: Optional[EventTypeFilter] = None,
|
|
82
|
+
) -> Optional[DexEvent]:
|
|
83
|
+
"""对齐 Rust ``parse_account_unified``(含 ``Option<EventTypeFilter>`` 语义)。"""
|
|
84
|
+
data = account.data
|
|
85
|
+
if not data:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
if event_type_filter is not None:
|
|
89
|
+
inc = getattr(event_type_filter, "include_only", None)
|
|
90
|
+
if inc is not None and len(inc) > 0:
|
|
91
|
+
need = {
|
|
92
|
+
EventType.TOKEN_ACCOUNT,
|
|
93
|
+
EventType.TOKEN_INFO,
|
|
94
|
+
EventType.NONCE_ACCOUNT,
|
|
95
|
+
EventType.ACCOUNT_PUMP_FUN_GLOBAL,
|
|
96
|
+
EventType.ACCOUNT_PUMP_SWAP_GLOBAL_CONFIG,
|
|
97
|
+
EventType.ACCOUNT_PUMP_SWAP_POOL,
|
|
98
|
+
}
|
|
99
|
+
if not any(t in need for t in inc):
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
if account.owner == PUMPSWAP_PROGRAM_ID and event_type_filter is not None:
|
|
103
|
+
if event_type_filter.should_include(
|
|
104
|
+
EventType.ACCOUNT_PUMP_SWAP_GLOBAL_CONFIG
|
|
105
|
+
) or event_type_filter.should_include(EventType.ACCOUNT_PUMP_SWAP_POOL):
|
|
106
|
+
ev = _parse_pumpswap_account(account, metadata)
|
|
107
|
+
if ev is not None:
|
|
108
|
+
return ev
|
|
109
|
+
|
|
110
|
+
if account.owner == PUMPFUN_PROGRAM_ID and event_type_filter is not None:
|
|
111
|
+
if event_type_filter.should_include(EventType.ACCOUNT_PUMP_FUN_GLOBAL):
|
|
112
|
+
ev = _parse_pumpfun_account(account, metadata)
|
|
113
|
+
if ev is not None:
|
|
114
|
+
return ev
|
|
115
|
+
|
|
116
|
+
if acc_utils.is_nonce_account(data):
|
|
117
|
+
if event_type_filter is not None:
|
|
118
|
+
if not event_type_filter.should_include(EventType.NONCE_ACCOUNT):
|
|
119
|
+
return None
|
|
120
|
+
return _parse_nonce_fast(account, metadata)
|
|
121
|
+
|
|
122
|
+
if event_type_filter is not None:
|
|
123
|
+
if not event_type_filter.should_include(EventType.TOKEN_ACCOUNT):
|
|
124
|
+
return None
|
|
125
|
+
return parse_token_account(account, metadata)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _parse_pumpswap_account(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
129
|
+
if acc_utils.has_discriminator(account.data, _DISC_GLOBAL_CONFIG):
|
|
130
|
+
return parse_pumpswap_global_config(account, metadata)
|
|
131
|
+
if acc_utils.has_discriminator(account.data, _DISC_POOL):
|
|
132
|
+
return parse_pumpswap_pool(account, metadata)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _parse_pumpfun_account(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
137
|
+
if acc_utils.has_discriminator(account.data, _DISC_PUMPFUN_GLOBAL):
|
|
138
|
+
return parse_pumpfun_global(account, metadata)
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _parse_mint_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
143
|
+
data = account.data
|
|
144
|
+
return _account_event(
|
|
145
|
+
EventType.TOKEN_INFO,
|
|
146
|
+
{
|
|
147
|
+
"metadata": metadata,
|
|
148
|
+
"pubkey": account.pubkey,
|
|
149
|
+
"executable": account.executable,
|
|
150
|
+
"lamports": account.lamports,
|
|
151
|
+
"owner": account.owner,
|
|
152
|
+
"rent_epoch": account.rent_epoch,
|
|
153
|
+
"supply": struct.unpack_from("<Q", data, SUPPLY_OFFSET)[0],
|
|
154
|
+
"decimals": data[DECIMALS_OFFSET],
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _parse_token_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
160
|
+
data = account.data
|
|
161
|
+
return _account_event(
|
|
162
|
+
EventType.TOKEN_ACCOUNT,
|
|
163
|
+
{
|
|
164
|
+
"metadata": metadata,
|
|
165
|
+
"pubkey": account.pubkey,
|
|
166
|
+
"executable": account.executable,
|
|
167
|
+
"lamports": account.lamports,
|
|
168
|
+
"owner": account.owner,
|
|
169
|
+
"rent_epoch": account.rent_epoch,
|
|
170
|
+
"amount": struct.unpack_from("<Q", data, AMOUNT_OFFSET)[0],
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _parse_nonce_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
176
|
+
data = account.data
|
|
177
|
+
authority = base58_encode_32(data[NONCE_AUTHORITY_OFFSET : NONCE_AUTHORITY_OFFSET + 32])
|
|
178
|
+
nonce = base58_encode_32(data[NONCE_NONCE_OFFSET : NONCE_NONCE_OFFSET + 32])
|
|
179
|
+
return _account_event(
|
|
180
|
+
EventType.NONCE_ACCOUNT,
|
|
181
|
+
{
|
|
182
|
+
"metadata": metadata,
|
|
183
|
+
"pubkey": account.pubkey,
|
|
184
|
+
"executable": account.executable,
|
|
185
|
+
"lamports": account.lamports,
|
|
186
|
+
"owner": account.owner,
|
|
187
|
+
"rent_epoch": account.rent_epoch,
|
|
188
|
+
"nonce": nonce,
|
|
189
|
+
"authority": authority,
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _parse_pumpfun_global_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
195
|
+
data = account.data[8:]
|
|
196
|
+
o = 0
|
|
197
|
+
initialized = data[o] != 0
|
|
198
|
+
o += 1
|
|
199
|
+
authority = read_pubkey_fast(data, o)
|
|
200
|
+
o += 32
|
|
201
|
+
fee_recipient = read_pubkey_fast(data, o)
|
|
202
|
+
o += 32
|
|
203
|
+
initial_virtual_token_reserves = read_u64_fast(data, o)
|
|
204
|
+
o += 8
|
|
205
|
+
initial_virtual_sol_reserves = read_u64_fast(data, o)
|
|
206
|
+
o += 8
|
|
207
|
+
initial_real_token_reserves = read_u64_fast(data, o)
|
|
208
|
+
o += 8
|
|
209
|
+
token_total_supply = read_u64_fast(data, o)
|
|
210
|
+
o += 8
|
|
211
|
+
fee_basis_points = read_u64_fast(data, o)
|
|
212
|
+
o += 8
|
|
213
|
+
withdraw_authority = read_pubkey_fast(data, o)
|
|
214
|
+
o += 32
|
|
215
|
+
enable_migrate = data[o] != 0
|
|
216
|
+
o += 1
|
|
217
|
+
pool_migration_fee = read_u64_fast(data, o)
|
|
218
|
+
o += 8
|
|
219
|
+
creator_fee_basis_points = read_u64_fast(data, o)
|
|
220
|
+
o += 8
|
|
221
|
+
fee_recipients = []
|
|
222
|
+
for _ in range(8):
|
|
223
|
+
fee_recipients.append(read_pubkey_fast(data, o))
|
|
224
|
+
o += 32
|
|
225
|
+
set_creator_authority = read_pubkey_fast(data, o)
|
|
226
|
+
o += 32
|
|
227
|
+
admin_set_creator_authority = read_pubkey_fast(data, o)
|
|
228
|
+
o += 32
|
|
229
|
+
create_v2_enabled = data[o] != 0
|
|
230
|
+
o += 1
|
|
231
|
+
whitelist_pda = read_pubkey_fast(data, o)
|
|
232
|
+
o += 32
|
|
233
|
+
reserved_fee_recipient = read_pubkey_fast(data, o)
|
|
234
|
+
o += 32
|
|
235
|
+
mayhem_mode_enabled = data[o] != 0
|
|
236
|
+
o += 1
|
|
237
|
+
reserved_fee_recipients = []
|
|
238
|
+
for _ in range(7):
|
|
239
|
+
reserved_fee_recipients.append(read_pubkey_fast(data, o))
|
|
240
|
+
o += 32
|
|
241
|
+
return _account_event(
|
|
242
|
+
EventType.ACCOUNT_PUMP_FUN_GLOBAL,
|
|
243
|
+
{
|
|
244
|
+
"metadata": metadata,
|
|
245
|
+
"pubkey": account.pubkey,
|
|
246
|
+
"global": {
|
|
247
|
+
"initialized": initialized,
|
|
248
|
+
"authority": authority,
|
|
249
|
+
"fee_recipient": fee_recipient,
|
|
250
|
+
"initial_virtual_token_reserves": initial_virtual_token_reserves,
|
|
251
|
+
"initial_virtual_sol_reserves": initial_virtual_sol_reserves,
|
|
252
|
+
"initial_real_token_reserves": initial_real_token_reserves,
|
|
253
|
+
"token_total_supply": token_total_supply,
|
|
254
|
+
"fee_basis_points": fee_basis_points,
|
|
255
|
+
"withdraw_authority": withdraw_authority,
|
|
256
|
+
"enable_migrate": enable_migrate,
|
|
257
|
+
"pool_migration_fee": pool_migration_fee,
|
|
258
|
+
"creator_fee_basis_points": creator_fee_basis_points,
|
|
259
|
+
"fee_recipients": fee_recipients,
|
|
260
|
+
"set_creator_authority": set_creator_authority,
|
|
261
|
+
"admin_set_creator_authority": admin_set_creator_authority,
|
|
262
|
+
"create_v2_enabled": create_v2_enabled,
|
|
263
|
+
"whitelist_pda": whitelist_pda,
|
|
264
|
+
"reserved_fee_recipient": reserved_fee_recipient,
|
|
265
|
+
"mayhem_mode_enabled": mayhem_mode_enabled,
|
|
266
|
+
"reserved_fee_recipients": reserved_fee_recipients,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _parse_pumpswap_global_config_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
273
|
+
data = account.data[8:]
|
|
274
|
+
o = 0
|
|
275
|
+
admin = read_pubkey_fast(data, o)
|
|
276
|
+
o += 32
|
|
277
|
+
lp_fee = read_u64_fast(data, o)
|
|
278
|
+
o += 8
|
|
279
|
+
protocol_fee = read_u64_fast(data, o)
|
|
280
|
+
o += 8
|
|
281
|
+
disable_flags = data[o]
|
|
282
|
+
o += 1
|
|
283
|
+
recipients = [
|
|
284
|
+
read_pubkey_fast(data, o),
|
|
285
|
+
read_pubkey_fast(data, o + 32),
|
|
286
|
+
read_pubkey_fast(data, o + 64),
|
|
287
|
+
read_pubkey_fast(data, o + 96),
|
|
288
|
+
read_pubkey_fast(data, o + 128),
|
|
289
|
+
read_pubkey_fast(data, o + 160),
|
|
290
|
+
read_pubkey_fast(data, o + 192),
|
|
291
|
+
read_pubkey_fast(data, o + 224),
|
|
292
|
+
]
|
|
293
|
+
o += 256
|
|
294
|
+
coin_creator_fee = read_u64_fast(data, o)
|
|
295
|
+
o += 8
|
|
296
|
+
admin_auth = read_pubkey_fast(data, o)
|
|
297
|
+
o += 32
|
|
298
|
+
whitelist = read_pubkey_fast(data, o)
|
|
299
|
+
o += 32
|
|
300
|
+
reserved = read_pubkey_fast(data, o)
|
|
301
|
+
o += 32
|
|
302
|
+
mayhem = data[o] != 0
|
|
303
|
+
o += 1
|
|
304
|
+
reserved_list = [
|
|
305
|
+
read_pubkey_fast(data, o),
|
|
306
|
+
read_pubkey_fast(data, o + 32),
|
|
307
|
+
read_pubkey_fast(data, o + 64),
|
|
308
|
+
read_pubkey_fast(data, o + 96),
|
|
309
|
+
read_pubkey_fast(data, o + 128),
|
|
310
|
+
read_pubkey_fast(data, o + 160),
|
|
311
|
+
read_pubkey_fast(data, o + 192),
|
|
312
|
+
]
|
|
313
|
+
return _account_event(
|
|
314
|
+
EventType.ACCOUNT_PUMP_SWAP_GLOBAL_CONFIG,
|
|
315
|
+
{
|
|
316
|
+
"metadata": metadata,
|
|
317
|
+
"pubkey": account.pubkey,
|
|
318
|
+
"config": {
|
|
319
|
+
"admin": admin,
|
|
320
|
+
"lp_fee_basis_points": lp_fee,
|
|
321
|
+
"protocol_fee_basis_points": protocol_fee,
|
|
322
|
+
"disable_flags": disable_flags,
|
|
323
|
+
"protocol_fee_recipients": recipients,
|
|
324
|
+
"coin_creator_fee_basis_points": coin_creator_fee,
|
|
325
|
+
"admin_set_coin_creator_authority": admin_auth,
|
|
326
|
+
"whitelist_pda": whitelist,
|
|
327
|
+
"reserved_fee_recipient": reserved,
|
|
328
|
+
"mayhem_mode_enabled": mayhem,
|
|
329
|
+
"reserved_fee_recipients": reserved_list,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _parse_pumpswap_pool_fast(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
336
|
+
data = account.data[8:]
|
|
337
|
+
o = 0
|
|
338
|
+
pool_bump = data[o]
|
|
339
|
+
o += 1
|
|
340
|
+
index = read_u16_fast(data, o)
|
|
341
|
+
o += 2
|
|
342
|
+
creator = read_pubkey_fast(data, o)
|
|
343
|
+
base_mint = read_pubkey_fast(data, o + 32)
|
|
344
|
+
quote_mint = read_pubkey_fast(data, o + 64)
|
|
345
|
+
lp_mint = read_pubkey_fast(data, o + 96)
|
|
346
|
+
pool_base = read_pubkey_fast(data, o + 128)
|
|
347
|
+
pool_quote = read_pubkey_fast(data, o + 160)
|
|
348
|
+
o += 192
|
|
349
|
+
lp_supply = read_u64_fast(data, o)
|
|
350
|
+
o += 8
|
|
351
|
+
coin_creator = read_pubkey_fast(data, o)
|
|
352
|
+
o += 32
|
|
353
|
+
is_mayhem = data[o] != 0
|
|
354
|
+
is_cashback = data[o + 1] != 0
|
|
355
|
+
return _account_event(
|
|
356
|
+
EventType.ACCOUNT_PUMP_SWAP_POOL,
|
|
357
|
+
{
|
|
358
|
+
"metadata": metadata,
|
|
359
|
+
"pubkey": account.pubkey,
|
|
360
|
+
"pool": {
|
|
361
|
+
"pool_bump": pool_bump,
|
|
362
|
+
"index": index,
|
|
363
|
+
"creator": creator,
|
|
364
|
+
"base_mint": base_mint,
|
|
365
|
+
"quote_mint": quote_mint,
|
|
366
|
+
"lp_mint": lp_mint,
|
|
367
|
+
"pool_base_token_account": pool_base,
|
|
368
|
+
"pool_quote_token_account": pool_quote,
|
|
369
|
+
"lp_supply": lp_supply,
|
|
370
|
+
"coin_creator": coin_creator,
|
|
371
|
+
"is_mayhem_mode": is_mayhem,
|
|
372
|
+
"is_cashback_coin": is_cashback,
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def parse_token_account(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
379
|
+
if len(account.data) <= 100:
|
|
380
|
+
event = _parse_mint_fast(account, metadata)
|
|
381
|
+
if event:
|
|
382
|
+
return event
|
|
383
|
+
return _parse_token_fast(account, metadata)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def parse_nonce_account(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
387
|
+
if len(account.data) != NONCE_ACCOUNT_SIZE:
|
|
388
|
+
return None
|
|
389
|
+
if not has_discriminator(account.data, _DISC_NONCE):
|
|
390
|
+
return None
|
|
391
|
+
return _parse_nonce_fast(account, metadata)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def parse_pumpfun_global(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
395
|
+
if len(account.data) < 8 + PUMPFUN_GLOBAL_BODY:
|
|
396
|
+
return None
|
|
397
|
+
if not has_discriminator(account.data, _DISC_PUMPFUN_GLOBAL):
|
|
398
|
+
return None
|
|
399
|
+
return _parse_pumpfun_global_fast(account, metadata)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def is_nonce_account(data: bytes) -> bool:
|
|
403
|
+
return acc_utils.is_nonce_account(data)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def parse_pumpswap_global_config(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
407
|
+
if len(account.data) < 8 + GLOBAL_CONFIG_BODY:
|
|
408
|
+
return None
|
|
409
|
+
if not has_discriminator(account.data, _DISC_GLOBAL_CONFIG):
|
|
410
|
+
return None
|
|
411
|
+
return _parse_pumpswap_global_config_fast(account, metadata)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def parse_pumpswap_pool(account: AccountData, metadata: EventMetadata) -> Optional[DexEvent]:
|
|
415
|
+
if len(account.data) < 8 + POOL_BODY:
|
|
416
|
+
return None
|
|
417
|
+
if not has_discriminator(account.data, _DISC_POOL):
|
|
418
|
+
return None
|
|
419
|
+
return _parse_pumpswap_pool_fast(account, metadata)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def is_global_config_account(data: bytes) -> bool:
|
|
423
|
+
return has_discriminator(data, _DISC_GLOBAL_CONFIG)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def is_pool_account(data: bytes) -> bool:
|
|
427
|
+
return has_discriminator(data, _DISC_POOL)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def is_pumpfun_global_account(data: bytes) -> bool:
|
|
431
|
+
return has_discriminator(data, _DISC_PUMPFUN_GLOBAL)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
base58_encode = base58_encode_32
|
|
435
|
+
read_pubkey = read_pubkey_fast
|
|
436
|
+
read_u64_le = read_u64_fast
|
|
437
|
+
read_u8 = lambda data, offset: data[offset] if offset < len(data) else 0
|
|
438
|
+
|
|
439
|
+
rpc_resolve_user_wallet_pubkey = rpc_wallet.rpc_resolve_user_wallet_pubkey
|
|
440
|
+
user_wallet_pubkey_for_onchain_account = acc_utils.user_wallet_pubkey_for_onchain_account
|
|
441
|
+
|
|
442
|
+
__all__ = [
|
|
443
|
+
"AccountData",
|
|
444
|
+
"parse_account_unified",
|
|
445
|
+
"parse_token_account",
|
|
446
|
+
"parse_nonce_account",
|
|
447
|
+
"parse_pumpfun_global",
|
|
448
|
+
"is_nonce_account",
|
|
449
|
+
"is_pumpfun_global_account",
|
|
450
|
+
"parse_pumpswap_global_config",
|
|
451
|
+
"parse_pumpswap_pool",
|
|
452
|
+
"is_global_config_account",
|
|
453
|
+
"is_pool_account",
|
|
454
|
+
"has_discriminator",
|
|
455
|
+
"PUMPFUN_PROGRAM_ID",
|
|
456
|
+
"PUMPSWAP_PROGRAM_ID",
|
|
457
|
+
"rpc_resolve_user_wallet_pubkey",
|
|
458
|
+
"user_wallet_pubkey_for_onchain_account",
|
|
459
|
+
"rpc_wallet",
|
|
460
|
+
"acc_utils",
|
|
461
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""对齐 Rust ``accounts/rpc_wallet.rs``:RPC getAccountInfo + ``user_wallet_pubkey_for_onchain_account``。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import urllib.request
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from .utils import user_wallet_pubkey_for_onchain_account
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_account_info(rpc_url: str, address_bs58: str, timeout_s: float = 15.0) -> Optional[Dict[str, Any]]:
|
|
13
|
+
import base64
|
|
14
|
+
|
|
15
|
+
body = json.dumps(
|
|
16
|
+
{
|
|
17
|
+
"jsonrpc": "2.0",
|
|
18
|
+
"id": 1,
|
|
19
|
+
"method": "getAccountInfo",
|
|
20
|
+
"params": [address_bs58, {"encoding": "base64"}],
|
|
21
|
+
}
|
|
22
|
+
).encode("utf-8")
|
|
23
|
+
req = urllib.request.Request(
|
|
24
|
+
rpc_url,
|
|
25
|
+
data=body,
|
|
26
|
+
headers={"Content-Type": "application/json"},
|
|
27
|
+
method="POST",
|
|
28
|
+
)
|
|
29
|
+
try:
|
|
30
|
+
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
|
|
31
|
+
raw = json.loads(resp.read().decode("utf-8"))
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
34
|
+
err = raw.get("error")
|
|
35
|
+
if err:
|
|
36
|
+
return None
|
|
37
|
+
val = (raw.get("result") or {}).get("value")
|
|
38
|
+
if not val:
|
|
39
|
+
return None
|
|
40
|
+
data_b64 = val.get("data")
|
|
41
|
+
if isinstance(data_b64, list) and data_b64:
|
|
42
|
+
data_b64 = data_b64[0]
|
|
43
|
+
if not isinstance(data_b64, str):
|
|
44
|
+
return None
|
|
45
|
+
try:
|
|
46
|
+
data = base64.b64decode(data_b64)
|
|
47
|
+
except Exception:
|
|
48
|
+
return None
|
|
49
|
+
owner = val.get("owner") or ""
|
|
50
|
+
exe = bool(val.get("executable", False))
|
|
51
|
+
return {"data": data, "owner": owner, "executable": exe}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def rpc_resolve_user_wallet_pubkey(rpc_url: str, address_bs58: str) -> Optional[str]:
|
|
55
|
+
"""同步 JSON-RPC:``getAccountInfo`` → 用户钱包公钥 base58(与 Rust 语义一致)。"""
|
|
56
|
+
info = _get_account_info(rpc_url, address_bs58)
|
|
57
|
+
if not info:
|
|
58
|
+
return None
|
|
59
|
+
return user_wallet_pubkey_for_onchain_account(
|
|
60
|
+
address_bs58,
|
|
61
|
+
info["owner"],
|
|
62
|
+
info["data"],
|
|
63
|
+
info["executable"],
|
|
64
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""对齐 Rust ``accounts/utils.rs`` 的通用工具。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
# Solana 系统程序、SPL Token / Token-2022(与 Rust 一致)
|
|
9
|
+
SYSTEM_PROGRAM_ID = "11111111111111111111111111111111"
|
|
10
|
+
SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
11
|
+
SPL_TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
12
|
+
|
|
13
|
+
SPL_TOKEN_ACCOUNT_LEN = 165
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_pubkey(data: bytes, offset: int) -> Optional[str]:
|
|
17
|
+
"""32 字节 → base58;越界返回 None。"""
|
|
18
|
+
import base58
|
|
19
|
+
|
|
20
|
+
if len(data) < offset + 32:
|
|
21
|
+
return None
|
|
22
|
+
return base58.b58encode(data[offset : offset + 32]).decode("ascii")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_u64_le(data: bytes, offset: int) -> Optional[int]:
|
|
26
|
+
if len(data) < offset + 8:
|
|
27
|
+
return None
|
|
28
|
+
return struct.unpack_from("<Q", data, offset)[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_u16_le(data: bytes, offset: int) -> Optional[int]:
|
|
32
|
+
if len(data) < offset + 2:
|
|
33
|
+
return None
|
|
34
|
+
return struct.unpack_from("<H", data, offset)[0]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_u8(data: bytes, offset: int) -> Optional[int]:
|
|
38
|
+
if offset >= len(data):
|
|
39
|
+
return None
|
|
40
|
+
return data[offset]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_nonce_account(data: bytes) -> bool:
|
|
44
|
+
return len(data) >= 8 and data[:8] == bytes([1, 0, 0, 0, 1, 0, 0, 0])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_token_program_account(owner: str) -> bool:
|
|
48
|
+
return owner in (SPL_TOKEN_PROGRAM_ID, SPL_TOKEN_2022_PROGRAM_ID)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def has_discriminator(data: bytes, discriminator: bytes) -> bool:
|
|
52
|
+
if len(data) < len(discriminator):
|
|
53
|
+
return False
|
|
54
|
+
return data[: len(discriminator)] == discriminator
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def user_wallet_pubkey_for_onchain_account(
|
|
58
|
+
address: str,
|
|
59
|
+
owner: str,
|
|
60
|
+
data: bytes,
|
|
61
|
+
executable: bool,
|
|
62
|
+
) -> Optional[str]:
|
|
63
|
+
"""对齐 Rust ``user_wallet_pubkey_for_onchain_account``:系统钱包或 SPL token 账户 owner。"""
|
|
64
|
+
if executable:
|
|
65
|
+
return None
|
|
66
|
+
if owner == SYSTEM_PROGRAM_ID:
|
|
67
|
+
return address if len(data) == 0 else None
|
|
68
|
+
if is_token_program_account(owner) and len(data) == SPL_TOKEN_ACCOUNT_LEN:
|
|
69
|
+
# spl_token::state::Account: owner at offset 32
|
|
70
|
+
return read_pubkey(data, 32)
|
|
71
|
+
return None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Python 迁移门禁入口(与 TS `npm run check:migration` 中的 discriminator 交叉校验步骤对应)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .u128_parity import run_all_u128_checks
|
|
8
|
+
from .verify_discriminators import main as _verify_main
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
if _verify_main() != 0:
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
sys.exit(run_all_u128_checks())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
sol_parser/clock.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""对齐 Rust ``common`` 模块:常量与共享类型占位。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
AnyResult = Any # Rust: Result<T, Box<dyn Error + Send + Sync>>
|
|
9
|
+
|
|
10
|
+
DEFAULT_CONNECT_TIMEOUT = 10
|
|
11
|
+
DEFAULT_REQUEST_TIMEOUT = 60
|
|
12
|
+
DEFAULT_CHANNEL_SIZE = 1000
|
|
13
|
+
DEFAULT_MAX_DECODING_MESSAGE_SIZE = 10 * 1024 * 1024
|
|
14
|
+
DEFAULT_METRICS_WINDOW_SECONDS = 5
|
|
15
|
+
DEFAULT_METRICS_PRINT_INTERVAL_SECONDS = 10
|
|
16
|
+
SLOW_PROCESSING_THRESHOLD_US = 3000.0
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AnyResult",
|
|
20
|
+
"DEFAULT_CONNECT_TIMEOUT",
|
|
21
|
+
"DEFAULT_REQUEST_TIMEOUT",
|
|
22
|
+
"DEFAULT_CHANNEL_SIZE",
|
|
23
|
+
"DEFAULT_MAX_DECODING_MESSAGE_SIZE",
|
|
24
|
+
"DEFAULT_METRICS_WINDOW_SECONDS",
|
|
25
|
+
"DEFAULT_METRICS_PRINT_INTERVAL_SECONDS",
|
|
26
|
+
"SLOW_PROCESSING_THRESHOLD_US",
|
|
27
|
+
]
|