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,1026 @@
|
|
|
1
|
+
"""指令解析器 - 对齐 Rust 实现"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
|
|
8
|
+
from .grpc_types import EventTypeFilter, EventType, EventMetadata, IncludeOnlyFilter, ExcludeFilter
|
|
9
|
+
from .dex_parsers import Z, read_pump_fees_fee_tiers_vec, read_pump_fees_shareholders_vec
|
|
10
|
+
from .event_types import (
|
|
11
|
+
DexEvent,
|
|
12
|
+
PumpFeesCreateFeeSharingConfigEvent,
|
|
13
|
+
PumpFeesFees,
|
|
14
|
+
PumpFeesInitializeFeeConfigEvent,
|
|
15
|
+
PumpFeesResetFeeSharingConfigEvent,
|
|
16
|
+
PumpFeesRevokeFeeSharingAuthorityEvent,
|
|
17
|
+
PumpFeesTransferFeeSharingAuthorityEvent,
|
|
18
|
+
PumpFeesUpdateAdminEvent,
|
|
19
|
+
PumpFeesUpdateFeeConfigEvent,
|
|
20
|
+
PumpFeesUpdateFeeSharesEvent,
|
|
21
|
+
PumpFeesUpsertFeeTiersEvent,
|
|
22
|
+
PumpFunCreateEvent,
|
|
23
|
+
PumpFunCreateV2TokenEvent,
|
|
24
|
+
PumpFunTradeEvent,
|
|
25
|
+
legacy_dict_to_dex_event,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# 程序 ID 常量(与 Rust ``instr::program_ids`` 一致,用于 inner / outer 路由)
|
|
29
|
+
PUMPFUN_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
|
30
|
+
PUMPSWAP_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"
|
|
31
|
+
METEORA_DAMM_V2_PROGRAM_ID = "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"
|
|
32
|
+
RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUQtcaMpgYqJPXBDvfE"
|
|
33
|
+
RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
|
|
34
|
+
RAYDIUM_AMM_V4_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"
|
|
35
|
+
ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"
|
|
36
|
+
METEORA_POOLS_PROGRAM_ID = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB"
|
|
37
|
+
METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"
|
|
38
|
+
BONK_LAUNCHPAD_PROGRAM_ID = "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1"
|
|
39
|
+
PUMPSWAP_FEES_PROGRAM_ID = "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
|
|
40
|
+
PUMP_FEES_PROGRAM_ID = PUMPSWAP_FEES_PROGRAM_ID
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _d(*xs: int) -> int:
|
|
44
|
+
return struct.unpack("<Q", bytes(xs))[0]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Discriminator 常量
|
|
48
|
+
_DISC_PUMPSWAP_BUY = _d(103, 244, 82, 31, 44, 245, 119, 119)
|
|
49
|
+
_DISC_PUMPSWAP_SELL = _d(62, 47, 55, 10, 165, 3, 220, 42)
|
|
50
|
+
|
|
51
|
+
_DISC_DAMM_SWAP = _d(27, 60, 21, 213, 138, 170, 187, 147)
|
|
52
|
+
_DISC_DAMM_SWAP2 = _d(189, 66, 51, 168, 38, 80, 117, 153)
|
|
53
|
+
_DISC_DAMM_ADD_LIQ = _d(175, 242, 8, 157, 30, 247, 185, 169)
|
|
54
|
+
_DISC_DAMM_REM_LIQ = _d(87, 46, 88, 98, 175, 96, 34, 91)
|
|
55
|
+
_DISC_DAMM_CREATE = _d(156, 15, 119, 198, 29, 181, 221, 55)
|
|
56
|
+
_DISC_DAMM_CLOSE = _d(20, 145, 144, 68, 143, 142, 214, 178)
|
|
57
|
+
_DISC_DAMM_INIT = _d(228, 50, 246, 85, 203, 66, 134, 37)
|
|
58
|
+
|
|
59
|
+
_DISC_CLMM_SWAP = _d(248, 198, 158, 145, 225, 117, 135, 200)
|
|
60
|
+
_DISC_CLMM_SWAP_V2 = _d(43, 4, 237, 11, 26, 201, 30, 98)
|
|
61
|
+
_DISC_CLMM_INC_LIQ = _d(133, 29, 89, 223, 69, 238, 176, 10)
|
|
62
|
+
_DISC_CLMM_DEC_LIQ = _d(58, 127, 188, 62, 79, 82, 196, 96)
|
|
63
|
+
_DISC_CLMM_CREATE = _d(233, 146, 209, 142, 207, 104, 64, 188)
|
|
64
|
+
_DISC_CLMM_OPEN_POSITION_V2 = _d(77, 184, 74, 214, 112, 86, 241, 199)
|
|
65
|
+
_DISC_CLMM_OPEN_POSITION_WITH_TOKEN_22_NFT = _d(77, 255, 174, 82, 125, 29, 201, 46)
|
|
66
|
+
_DISC_CLMM_CLOSE_POSITION = _d(123, 134, 81, 0, 49, 68, 98, 98)
|
|
67
|
+
|
|
68
|
+
_DISC_CPMM_SWAP = _d(143, 190, 90, 218, 196, 30, 51, 222)
|
|
69
|
+
_DISC_CPMM_DEP = _d(242, 35, 198, 137, 82, 225, 242, 182)
|
|
70
|
+
_DISC_CPMM_WIT = _d(183, 18, 70, 156, 148, 109, 161, 34)
|
|
71
|
+
|
|
72
|
+
_DISC_ORCA_SWAP = _d(225, 202, 73, 175, 147, 43, 160, 150)
|
|
73
|
+
_DISC_ORCA_INC_LIQ = _d(30, 7, 144, 181, 102, 254, 155, 161)
|
|
74
|
+
_DISC_ORCA_DEC_LIQ = _d(166, 1, 36, 71, 112, 202, 181, 171)
|
|
75
|
+
|
|
76
|
+
_DISC_BONK_TRADE = _d(2, 3, 4, 5, 6, 7, 8, 9)
|
|
77
|
+
_DISC_BONK_POOL_CREATE = _d(1, 2, 3, 4, 5, 6, 7, 8)
|
|
78
|
+
|
|
79
|
+
_DISC_PFEES_CREATE_FEE_SHARING = _d(195, 78, 86, 76, 111, 52, 251, 213)
|
|
80
|
+
_DISC_PFEES_INITIALIZE_FEE_CONFIG = _d(62, 162, 20, 133, 121, 65, 145, 27)
|
|
81
|
+
_DISC_PFEES_RESET_FEE_SHARING = _d(10, 2, 182, 95, 16, 127, 129, 186)
|
|
82
|
+
_DISC_PFEES_REVOKE_FEE_SHARING = _d(18, 233, 158, 39, 185, 207, 58, 104)
|
|
83
|
+
_DISC_PFEES_TRANSFER_FEE_SHARING = _d(202, 10, 75, 200, 164, 34, 210, 96)
|
|
84
|
+
_DISC_PFEES_UPDATE_ADMIN = _d(161, 176, 40, 213, 60, 184, 179, 228)
|
|
85
|
+
_DISC_PFEES_UPDATE_FEE_CONFIG = _d(104, 184, 103, 242, 88, 151, 107, 20)
|
|
86
|
+
_DISC_PFEES_UPDATE_FEE_SHARES = _d(189, 13, 136, 99, 187, 164, 237, 35)
|
|
87
|
+
_DISC_PFEES_UPSERT_FEE_TIERS = _d(227, 23, 150, 12, 77, 86, 94, 4)
|
|
88
|
+
|
|
89
|
+
_DISC_PUMPFUN_BUY_V2 = _d(184, 23, 238, 97, 103, 197, 211, 61)
|
|
90
|
+
_DISC_PUMPFUN_SELL_V2 = _d(93, 246, 130, 60, 231, 233, 64, 178)
|
|
91
|
+
_DISC_PUMPFUN_BUY_EXACT_QUOTE_IN_V2 = _d(194, 171, 28, 70, 104, 77, 91, 47)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _get_account_safe(accounts: List[str], index: int) -> str:
|
|
95
|
+
"""安全获取账户地址"""
|
|
96
|
+
if index < 0 or index >= len(accounts):
|
|
97
|
+
return Z
|
|
98
|
+
return accounts[index]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _make_meta(
|
|
102
|
+
signature: str,
|
|
103
|
+
slot: int,
|
|
104
|
+
tx_index: int,
|
|
105
|
+
block_time_us: Optional[int],
|
|
106
|
+
grpc_recv_us: int,
|
|
107
|
+
) -> EventMetadata:
|
|
108
|
+
return EventMetadata(
|
|
109
|
+
signature=signature,
|
|
110
|
+
slot=slot,
|
|
111
|
+
tx_index=tx_index,
|
|
112
|
+
block_time_us=block_time_us or 0,
|
|
113
|
+
grpc_recv_us=grpc_recv_us,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_instruction_unified(
|
|
118
|
+
instruction_data: bytes,
|
|
119
|
+
accounts: List[str],
|
|
120
|
+
signature: str,
|
|
121
|
+
slot: int,
|
|
122
|
+
tx_index: int,
|
|
123
|
+
block_time_us: Optional[int],
|
|
124
|
+
grpc_recv_us: int,
|
|
125
|
+
filter: EventTypeFilter,
|
|
126
|
+
program_id: str,
|
|
127
|
+
) -> Optional[DexEvent]:
|
|
128
|
+
"""统一的指令解析入口
|
|
129
|
+
|
|
130
|
+
对齐 Rust `parse_instruction_unified`
|
|
131
|
+
"""
|
|
132
|
+
if not instruction_data:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
if program_id == PUMPFUN_PROGRAM_ID:
|
|
136
|
+
if not _filter_includes_pumpfun(filter):
|
|
137
|
+
return None
|
|
138
|
+
return parse_pumpfun_instruction(
|
|
139
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
elif program_id == PUMPSWAP_PROGRAM_ID:
|
|
143
|
+
if not _filter_includes_pumpswap(filter):
|
|
144
|
+
return None
|
|
145
|
+
return parse_pumpswap_instruction(
|
|
146
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
elif program_id == METEORA_DAMM_V2_PROGRAM_ID:
|
|
150
|
+
if not _filter_includes_meteora_damm_v2(filter):
|
|
151
|
+
return None
|
|
152
|
+
return parse_meteora_damm_instruction(
|
|
153
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
elif program_id == PUMP_FEES_PROGRAM_ID:
|
|
157
|
+
if not _filter_includes_pump_fees(filter):
|
|
158
|
+
return None
|
|
159
|
+
return parse_pump_fees_instruction(
|
|
160
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
elif program_id == RAYDIUM_CLMM_PROGRAM_ID:
|
|
164
|
+
if not _filter_includes_raydium_clmm(filter):
|
|
165
|
+
return None
|
|
166
|
+
return parse_raydium_clmm_instruction(
|
|
167
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
elif program_id == RAYDIUM_CPMM_PROGRAM_ID:
|
|
171
|
+
if not _filter_includes_raydium_cpmm(filter):
|
|
172
|
+
return None
|
|
173
|
+
return parse_raydium_cpmm_instruction(
|
|
174
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
elif program_id == RAYDIUM_AMM_V4_PROGRAM_ID:
|
|
178
|
+
if not _filter_includes_raydium_amm_v4(filter):
|
|
179
|
+
return None
|
|
180
|
+
return parse_raydium_amm_v4_instruction(
|
|
181
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
elif program_id == ORCA_WHIRLPOOL_PROGRAM_ID:
|
|
185
|
+
if not _filter_includes_orca_whirlpool(filter):
|
|
186
|
+
return None
|
|
187
|
+
return parse_orca_whirlpool_instruction(
|
|
188
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
elif program_id == BONK_LAUNCHPAD_PROGRAM_ID:
|
|
192
|
+
if not _filter_includes_bonk(filter):
|
|
193
|
+
return None
|
|
194
|
+
return parse_bonk_instruction(
|
|
195
|
+
instruction_data, accounts, signature, slot, tx_index, block_time_us, grpc_recv_us
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def parse_pumpfun_instruction(
|
|
202
|
+
data: bytes,
|
|
203
|
+
accounts: List[str],
|
|
204
|
+
signature: str,
|
|
205
|
+
slot: int,
|
|
206
|
+
tx_index: int,
|
|
207
|
+
block_time_us: Optional[int],
|
|
208
|
+
grpc_recv_us: int,
|
|
209
|
+
) -> Optional[DexEvent]:
|
|
210
|
+
"""解析 PumpFun 指令
|
|
211
|
+
|
|
212
|
+
注意:PumpFun 的 Buy/Sell 操作通过统一的 TRADE 日志事件捕获,
|
|
213
|
+
在 dex_parsers.py 的 parse_trade_from_data 中处理。
|
|
214
|
+
这里只处理 Create 和 CreateV2 指令。
|
|
215
|
+
|
|
216
|
+
Discriminators(8字节小端):
|
|
217
|
+
- CREATE: [24, 30, 200, 40, 5, 28, 7, 119] = 8576854823835016728
|
|
218
|
+
- CREATE_V2: [214, 144, 76, 236, 95, 139, 49, 180] = 12992944682502211062
|
|
219
|
+
"""
|
|
220
|
+
if len(data) < 8:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
224
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
225
|
+
|
|
226
|
+
# PumpFun Create: [24, 30, 200, 40, 5, 28, 7, 119]
|
|
227
|
+
if discriminator == 8576854823835016728:
|
|
228
|
+
return _parse_pumpfun_create(data, accounts, meta)
|
|
229
|
+
|
|
230
|
+
# PumpFun CreateV2: [214, 144, 76, 236, 95, 139, 49, 180]
|
|
231
|
+
if discriminator == 12992944682502211062:
|
|
232
|
+
return _parse_pumpfun_create_v2(data, accounts, meta)
|
|
233
|
+
|
|
234
|
+
if discriminator == _DISC_PUMPFUN_BUY_V2:
|
|
235
|
+
return _parse_pumpfun_trade_v2("buy_v2", data[8:], accounts, meta)
|
|
236
|
+
|
|
237
|
+
if discriminator == _DISC_PUMPFUN_BUY_EXACT_QUOTE_IN_V2:
|
|
238
|
+
return _parse_pumpfun_trade_v2("buy_exact_quote_in_v2", data[8:], accounts, meta)
|
|
239
|
+
|
|
240
|
+
if discriminator == _DISC_PUMPFUN_SELL_V2:
|
|
241
|
+
return _parse_pumpfun_trade_v2("sell_v2", data[8:], accounts, meta)
|
|
242
|
+
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _u64_payload(data: bytes, offset: int) -> int:
|
|
247
|
+
if offset + 8 <= len(data):
|
|
248
|
+
return struct.unpack_from("<Q", data, offset)[0]
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _parse_pumpfun_trade_v2(
|
|
253
|
+
ix_name: str,
|
|
254
|
+
payload: bytes,
|
|
255
|
+
accounts: List[str],
|
|
256
|
+
meta: EventMetadata,
|
|
257
|
+
) -> Optional[DexEvent]:
|
|
258
|
+
min_accounts = 26 if ix_name == "sell_v2" else 27
|
|
259
|
+
if len(accounts) < min_accounts:
|
|
260
|
+
return None
|
|
261
|
+
first = _u64_payload(payload, 0)
|
|
262
|
+
second = _u64_payload(payload, 8)
|
|
263
|
+
if ix_name == "buy_exact_quote_in_v2":
|
|
264
|
+
sol_amount, token_amount = first, second
|
|
265
|
+
else:
|
|
266
|
+
token_amount, sol_amount = first, second
|
|
267
|
+
|
|
268
|
+
return DexEvent(
|
|
269
|
+
type=EventType.PUMP_FUN_TRADE,
|
|
270
|
+
data=PumpFunTradeEvent(
|
|
271
|
+
metadata=meta,
|
|
272
|
+
mint=_get_account_safe(accounts, 1),
|
|
273
|
+
bonding_curve=_get_account_safe(accounts, 10),
|
|
274
|
+
user=_get_account_safe(accounts, 13),
|
|
275
|
+
sol_amount=sol_amount,
|
|
276
|
+
token_amount=token_amount,
|
|
277
|
+
fee_recipient=_get_account_safe(accounts, 6),
|
|
278
|
+
is_buy=ix_name != "sell_v2",
|
|
279
|
+
is_created_buy=False,
|
|
280
|
+
ix_name=ix_name,
|
|
281
|
+
associated_bonding_curve=_get_account_safe(accounts, 11),
|
|
282
|
+
token_program=_get_account_safe(accounts, 3),
|
|
283
|
+
creator_vault=_get_account_safe(accounts, 16),
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _parse_pumpfun_create(data: bytes, accounts: List[str], meta: EventMetadata) -> Optional[DexEvent]:
|
|
289
|
+
"""解析 PumpFun Create 指令"""
|
|
290
|
+
offset = 8 # Skip discriminator
|
|
291
|
+
try:
|
|
292
|
+
name_len = struct.unpack_from("<I", data, offset)[0]
|
|
293
|
+
offset += 4
|
|
294
|
+
name = data[offset:offset + name_len].decode('utf-8')
|
|
295
|
+
offset += name_len
|
|
296
|
+
|
|
297
|
+
symbol_len = struct.unpack_from("<I", data, offset)[0]
|
|
298
|
+
offset += 4
|
|
299
|
+
symbol = data[offset:offset + symbol_len].decode('utf-8')
|
|
300
|
+
offset += symbol_len
|
|
301
|
+
|
|
302
|
+
uri_len = struct.unpack_from("<I", data, offset)[0]
|
|
303
|
+
offset += 4
|
|
304
|
+
uri = data[offset:offset + uri_len].decode('utf-8')
|
|
305
|
+
offset += uri_len
|
|
306
|
+
except Exception:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
creator = Z
|
|
310
|
+
if offset + 32 <= len(data):
|
|
311
|
+
import base58
|
|
312
|
+
creator = base58.b58encode(data[offset:offset + 32]).decode('ascii')
|
|
313
|
+
|
|
314
|
+
return DexEvent(
|
|
315
|
+
type=EventType.PUMP_FUN_CREATE,
|
|
316
|
+
data=PumpFunCreateEvent(
|
|
317
|
+
metadata=meta,
|
|
318
|
+
name=name,
|
|
319
|
+
symbol=symbol,
|
|
320
|
+
uri=uri,
|
|
321
|
+
mint=_get_account_safe(accounts, 0),
|
|
322
|
+
bonding_curve=_get_account_safe(accounts, 2),
|
|
323
|
+
user=_get_account_safe(accounts, 7),
|
|
324
|
+
creator=creator,
|
|
325
|
+
token_program=Z,
|
|
326
|
+
timestamp=0,
|
|
327
|
+
virtual_token_reserves=0,
|
|
328
|
+
virtual_sol_reserves=0,
|
|
329
|
+
real_token_reserves=0,
|
|
330
|
+
token_total_supply=0,
|
|
331
|
+
is_mayhem_mode=False,
|
|
332
|
+
is_cashback_enabled=False,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _parse_pumpfun_create_v2(data: bytes, accounts: List[str], meta: EventMetadata) -> Optional[DexEvent]:
|
|
338
|
+
"""解析 PumpFun CreateV2 指令
|
|
339
|
+
|
|
340
|
+
对齐 TS pumpfun_ix.ts CREATE_V2 处理
|
|
341
|
+
"""
|
|
342
|
+
if len(accounts) < 16:
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
offset = 8 # Skip discriminator
|
|
346
|
+
try:
|
|
347
|
+
name_len = struct.unpack_from("<I", data, offset)[0]
|
|
348
|
+
offset += 4
|
|
349
|
+
name = data[offset:offset + name_len].decode('utf-8')
|
|
350
|
+
offset += name_len
|
|
351
|
+
|
|
352
|
+
symbol_len = struct.unpack_from("<I", data, offset)[0]
|
|
353
|
+
offset += 4
|
|
354
|
+
symbol = data[offset:offset + symbol_len].decode('utf-8')
|
|
355
|
+
offset += symbol_len
|
|
356
|
+
|
|
357
|
+
uri_len = struct.unpack_from("<I", data, offset)[0]
|
|
358
|
+
offset += 4
|
|
359
|
+
uri = data[offset:offset + uri_len].decode('utf-8')
|
|
360
|
+
offset += uri_len
|
|
361
|
+
except Exception:
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
creator = Z
|
|
365
|
+
if offset + 32 <= len(data):
|
|
366
|
+
import base58
|
|
367
|
+
creator = base58.b58encode(data[offset:offset + 32]).decode('ascii')
|
|
368
|
+
|
|
369
|
+
return DexEvent(
|
|
370
|
+
type=EventType.PUMP_FUN_CREATE_V2,
|
|
371
|
+
data=PumpFunCreateV2TokenEvent(
|
|
372
|
+
metadata=meta,
|
|
373
|
+
name=name,
|
|
374
|
+
symbol=symbol,
|
|
375
|
+
uri=uri,
|
|
376
|
+
mint=_get_account_safe(accounts, 0),
|
|
377
|
+
mint_authority=_get_account_safe(accounts, 1),
|
|
378
|
+
bonding_curve=_get_account_safe(accounts, 2),
|
|
379
|
+
associated_bonding_curve=_get_account_safe(accounts, 3),
|
|
380
|
+
global_account=_get_account_safe(accounts, 4),
|
|
381
|
+
user=_get_account_safe(accounts, 5),
|
|
382
|
+
system_program=_get_account_safe(accounts, 6),
|
|
383
|
+
token_program=_get_account_safe(accounts, 7),
|
|
384
|
+
associated_token_program=_get_account_safe(accounts, 8),
|
|
385
|
+
mayhem_program_id=_get_account_safe(accounts, 9),
|
|
386
|
+
global_params=_get_account_safe(accounts, 10),
|
|
387
|
+
sol_vault=_get_account_safe(accounts, 11),
|
|
388
|
+
mayhem_state=_get_account_safe(accounts, 12),
|
|
389
|
+
mayhem_token_vault=_get_account_safe(accounts, 13),
|
|
390
|
+
event_authority=_get_account_safe(accounts, 14),
|
|
391
|
+
program=_get_account_safe(accounts, 15),
|
|
392
|
+
creator=creator,
|
|
393
|
+
timestamp=0,
|
|
394
|
+
virtual_token_reserves=0,
|
|
395
|
+
virtual_sol_reserves=0,
|
|
396
|
+
real_token_reserves=0,
|
|
397
|
+
token_total_supply=0,
|
|
398
|
+
is_mayhem_mode=False,
|
|
399
|
+
is_cashback_enabled=False,
|
|
400
|
+
observed_fee_recipient="",
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def parse_pumpswap_instruction(
|
|
406
|
+
data: bytes,
|
|
407
|
+
accounts: List[str],
|
|
408
|
+
signature: str,
|
|
409
|
+
slot: int,
|
|
410
|
+
tx_index: int,
|
|
411
|
+
block_time_us: Optional[int],
|
|
412
|
+
grpc_recv_us: int,
|
|
413
|
+
) -> Optional[DexEvent]:
|
|
414
|
+
"""解析 PumpSwap 指令"""
|
|
415
|
+
if len(data) < 8:
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
419
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
420
|
+
|
|
421
|
+
if discriminator == _DISC_PUMPSWAP_BUY:
|
|
422
|
+
return legacy_dict_to_dex_event({"PumpSwapBuy": {"metadata": meta}})
|
|
423
|
+
if discriminator == _DISC_PUMPSWAP_SELL:
|
|
424
|
+
return legacy_dict_to_dex_event({"PumpSwapSell": {"metadata": meta}})
|
|
425
|
+
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def parse_meteora_damm_instruction(
|
|
430
|
+
data: bytes,
|
|
431
|
+
accounts: List[str],
|
|
432
|
+
signature: str,
|
|
433
|
+
slot: int,
|
|
434
|
+
tx_index: int,
|
|
435
|
+
block_time_us: Optional[int],
|
|
436
|
+
grpc_recv_us: int,
|
|
437
|
+
) -> Optional[DexEvent]:
|
|
438
|
+
"""解析 Meteora DAMM V2 指令"""
|
|
439
|
+
if len(data) < 8:
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
443
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
444
|
+
|
|
445
|
+
if discriminator in (_DISC_DAMM_SWAP, _DISC_DAMM_SWAP2):
|
|
446
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2Swap": {"metadata": meta}})
|
|
447
|
+
if discriminator == _DISC_DAMM_ADD_LIQ:
|
|
448
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2AddLiquidity": {"metadata": meta}})
|
|
449
|
+
if discriminator == _DISC_DAMM_REM_LIQ:
|
|
450
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2RemoveLiquidity": {"metadata": meta}})
|
|
451
|
+
if discriminator == _DISC_DAMM_CREATE:
|
|
452
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2CreatePosition": {"metadata": meta}})
|
|
453
|
+
if discriminator == _DISC_DAMM_CLOSE:
|
|
454
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2ClosePosition": {"metadata": meta}})
|
|
455
|
+
if discriminator == _DISC_DAMM_INIT:
|
|
456
|
+
return legacy_dict_to_dex_event({"MeteoraDammV2InitializePool": {"metadata": meta}})
|
|
457
|
+
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _read_pump_fees_fees_at(data: bytes, o: List[int]) -> Optional[PumpFeesFees]:
|
|
462
|
+
if o[0] + 24 > len(data):
|
|
463
|
+
return None
|
|
464
|
+
lp_fee_bps = struct.unpack_from("<Q", data, o[0])[0]
|
|
465
|
+
o[0] += 8
|
|
466
|
+
protocol_fee_bps = struct.unpack_from("<Q", data, o[0])[0]
|
|
467
|
+
o[0] += 8
|
|
468
|
+
creator_fee_bps = struct.unpack_from("<Q", data, o[0])[0]
|
|
469
|
+
o[0] += 8
|
|
470
|
+
return PumpFeesFees(
|
|
471
|
+
lp_fee_bps=lp_fee_bps,
|
|
472
|
+
protocol_fee_bps=protocol_fee_bps,
|
|
473
|
+
creator_fee_bps=creator_fee_bps,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def parse_pump_fees_instruction(
|
|
478
|
+
data: bytes,
|
|
479
|
+
accounts: List[str],
|
|
480
|
+
signature: str,
|
|
481
|
+
slot: int,
|
|
482
|
+
tx_index: int,
|
|
483
|
+
block_time_us: Optional[int],
|
|
484
|
+
grpc_recv_us: int,
|
|
485
|
+
) -> Optional[DexEvent]:
|
|
486
|
+
"""解析 Pump Fees 外层指令(对齐 Rust `pump_fees::parse_instruction`)"""
|
|
487
|
+
if len(data) < 8:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
491
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
492
|
+
|
|
493
|
+
if discriminator == _DISC_PFEES_CREATE_FEE_SHARING:
|
|
494
|
+
admin = _get_account_safe(accounts, 2)
|
|
495
|
+
mint = _get_account_safe(accounts, 4)
|
|
496
|
+
if admin == Z or mint == Z:
|
|
497
|
+
return None
|
|
498
|
+
return DexEvent(
|
|
499
|
+
type=EventType.PUMP_FEES_CREATE_FEE_SHARING_CONFIG,
|
|
500
|
+
data=PumpFeesCreateFeeSharingConfigEvent(
|
|
501
|
+
metadata=meta,
|
|
502
|
+
timestamp=0,
|
|
503
|
+
mint=mint,
|
|
504
|
+
bonding_curve=_get_account_safe(accounts, 7),
|
|
505
|
+
pool=accounts[10] if len(accounts) > 10 else "",
|
|
506
|
+
sharing_config=_get_account_safe(accounts, 5),
|
|
507
|
+
admin=admin,
|
|
508
|
+
initial_shareholders=[],
|
|
509
|
+
status="Active",
|
|
510
|
+
),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
if discriminator == _DISC_PFEES_UPDATE_FEE_SHARES:
|
|
514
|
+
if len(accounts) < 8:
|
|
515
|
+
return None
|
|
516
|
+
o = [8]
|
|
517
|
+
shareholders = read_pump_fees_shareholders_vec(data, o)
|
|
518
|
+
if shareholders is None or o[0] != len(data):
|
|
519
|
+
return None
|
|
520
|
+
return DexEvent(
|
|
521
|
+
type=EventType.PUMP_FEES_UPDATE_FEE_SHARES,
|
|
522
|
+
data=PumpFeesUpdateFeeSharesEvent(
|
|
523
|
+
metadata=meta,
|
|
524
|
+
timestamp=0,
|
|
525
|
+
mint=_get_account_safe(accounts, 4),
|
|
526
|
+
sharing_config=_get_account_safe(accounts, 5),
|
|
527
|
+
admin=_get_account_safe(accounts, 2),
|
|
528
|
+
bonding_curve=_get_account_safe(accounts, 6),
|
|
529
|
+
pump_creator_vault=_get_account_safe(accounts, 7),
|
|
530
|
+
new_shareholders=shareholders,
|
|
531
|
+
),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if discriminator == _DISC_PFEES_INITIALIZE_FEE_CONFIG:
|
|
535
|
+
if len(accounts) < 2:
|
|
536
|
+
return None
|
|
537
|
+
return DexEvent(
|
|
538
|
+
type=EventType.PUMP_FEES_INITIALIZE_FEE_CONFIG,
|
|
539
|
+
data=PumpFeesInitializeFeeConfigEvent(
|
|
540
|
+
metadata=meta,
|
|
541
|
+
timestamp=0,
|
|
542
|
+
admin=accounts[0],
|
|
543
|
+
fee_config=accounts[1],
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
if discriminator == _DISC_PFEES_RESET_FEE_SHARING:
|
|
548
|
+
if len(accounts) < 5:
|
|
549
|
+
return None
|
|
550
|
+
return DexEvent(
|
|
551
|
+
type=EventType.PUMP_FEES_RESET_FEE_SHARING_CONFIG,
|
|
552
|
+
data=PumpFeesResetFeeSharingConfigEvent(
|
|
553
|
+
metadata=meta,
|
|
554
|
+
timestamp=0,
|
|
555
|
+
mint=accounts[3],
|
|
556
|
+
sharing_config=accounts[4],
|
|
557
|
+
old_admin=accounts[0],
|
|
558
|
+
old_shareholders=[],
|
|
559
|
+
new_admin=accounts[2],
|
|
560
|
+
new_shareholders=[],
|
|
561
|
+
),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
if discriminator == _DISC_PFEES_REVOKE_FEE_SHARING:
|
|
565
|
+
if len(accounts) < 4:
|
|
566
|
+
return None
|
|
567
|
+
return DexEvent(
|
|
568
|
+
type=EventType.PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY,
|
|
569
|
+
data=PumpFeesRevokeFeeSharingAuthorityEvent(
|
|
570
|
+
metadata=meta,
|
|
571
|
+
timestamp=0,
|
|
572
|
+
mint=accounts[2],
|
|
573
|
+
sharing_config=accounts[3],
|
|
574
|
+
admin=accounts[0],
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
if discriminator == _DISC_PFEES_TRANSFER_FEE_SHARING:
|
|
579
|
+
if len(accounts) < 5:
|
|
580
|
+
return None
|
|
581
|
+
return DexEvent(
|
|
582
|
+
type=EventType.PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY,
|
|
583
|
+
data=PumpFeesTransferFeeSharingAuthorityEvent(
|
|
584
|
+
metadata=meta,
|
|
585
|
+
timestamp=0,
|
|
586
|
+
mint=accounts[2],
|
|
587
|
+
sharing_config=accounts[3],
|
|
588
|
+
old_admin=accounts[0],
|
|
589
|
+
new_admin=accounts[4],
|
|
590
|
+
),
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
if discriminator == _DISC_PFEES_UPDATE_ADMIN:
|
|
594
|
+
if len(accounts) < 3:
|
|
595
|
+
return None
|
|
596
|
+
return DexEvent(
|
|
597
|
+
type=EventType.PUMP_FEES_UPDATE_ADMIN,
|
|
598
|
+
data=PumpFeesUpdateAdminEvent(
|
|
599
|
+
metadata=meta,
|
|
600
|
+
timestamp=0,
|
|
601
|
+
old_admin=accounts[0],
|
|
602
|
+
new_admin=accounts[2],
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
if discriminator == _DISC_PFEES_UPDATE_FEE_CONFIG:
|
|
607
|
+
if len(accounts) < 2:
|
|
608
|
+
return None
|
|
609
|
+
o = [8]
|
|
610
|
+
fee_tiers = read_pump_fees_fee_tiers_vec(data, o)
|
|
611
|
+
if fee_tiers is None:
|
|
612
|
+
return None
|
|
613
|
+
flat_fees = _read_pump_fees_fees_at(data, o)
|
|
614
|
+
if flat_fees is None or o[0] != len(data):
|
|
615
|
+
return None
|
|
616
|
+
return DexEvent(
|
|
617
|
+
type=EventType.PUMP_FEES_UPDATE_FEE_CONFIG,
|
|
618
|
+
data=PumpFeesUpdateFeeConfigEvent(
|
|
619
|
+
metadata=meta,
|
|
620
|
+
timestamp=0,
|
|
621
|
+
admin=accounts[1],
|
|
622
|
+
fee_config=accounts[0],
|
|
623
|
+
fee_tiers=fee_tiers,
|
|
624
|
+
flat_fees=flat_fees,
|
|
625
|
+
),
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
if discriminator == _DISC_PFEES_UPSERT_FEE_TIERS:
|
|
629
|
+
if len(accounts) < 2:
|
|
630
|
+
return None
|
|
631
|
+
o = [8]
|
|
632
|
+
fee_tiers = read_pump_fees_fee_tiers_vec(data, o)
|
|
633
|
+
if fee_tiers is None or o[0] >= len(data):
|
|
634
|
+
return None
|
|
635
|
+
offset = data[o[0]]
|
|
636
|
+
o[0] += 1
|
|
637
|
+
if o[0] != len(data):
|
|
638
|
+
return None
|
|
639
|
+
return DexEvent(
|
|
640
|
+
type=EventType.PUMP_FEES_UPSERT_FEE_TIERS,
|
|
641
|
+
data=PumpFeesUpsertFeeTiersEvent(
|
|
642
|
+
metadata=meta,
|
|
643
|
+
timestamp=0,
|
|
644
|
+
admin=accounts[1],
|
|
645
|
+
fee_config=accounts[0],
|
|
646
|
+
fee_tiers=fee_tiers,
|
|
647
|
+
offset=offset,
|
|
648
|
+
),
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def parse_raydium_clmm_instruction(
|
|
655
|
+
data: bytes,
|
|
656
|
+
accounts: List[str],
|
|
657
|
+
signature: str,
|
|
658
|
+
slot: int,
|
|
659
|
+
tx_index: int,
|
|
660
|
+
block_time_us: Optional[int],
|
|
661
|
+
grpc_recv_us: int,
|
|
662
|
+
) -> Optional[DexEvent]:
|
|
663
|
+
"""解析 Raydium CLMM 指令"""
|
|
664
|
+
if len(data) < 8:
|
|
665
|
+
return None
|
|
666
|
+
|
|
667
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
668
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
669
|
+
|
|
670
|
+
if discriminator in (_DISC_CLMM_SWAP, _DISC_CLMM_SWAP_V2):
|
|
671
|
+
if len(data) < 8 + 8 + 8 + 8 + 1:
|
|
672
|
+
return None
|
|
673
|
+
sqrt_price_x64 = struct.unpack_from("<Q", data, 24)[0]
|
|
674
|
+
is_base_input = data[32] == 1
|
|
675
|
+
return legacy_dict_to_dex_event({"RaydiumClmmSwap": {
|
|
676
|
+
"metadata": meta,
|
|
677
|
+
"pool_state": _get_account_safe(accounts, 0),
|
|
678
|
+
"sender": _get_account_safe(accounts, 1),
|
|
679
|
+
"token_account_0": Z, "token_account_1": Z,
|
|
680
|
+
"amount_0": 0, "amount_1": 0, "zero_for_one": is_base_input,
|
|
681
|
+
"sqrt_price_x64": str(sqrt_price_x64), "liquidity": "0",
|
|
682
|
+
"transfer_fee_0": 0, "transfer_fee_1": 0, "tick": 0,
|
|
683
|
+
}})
|
|
684
|
+
if discriminator == _DISC_CLMM_INC_LIQ:
|
|
685
|
+
if len(data) < 8 + 8 + 8 + 8:
|
|
686
|
+
return None
|
|
687
|
+
liquidity, amount0_max, amount1_max = struct.unpack_from("<QQQ", data, 8)
|
|
688
|
+
return legacy_dict_to_dex_event({"RaydiumClmmIncreaseLiquidity": {
|
|
689
|
+
"metadata": meta,
|
|
690
|
+
"pool": _get_account_safe(accounts, 0),
|
|
691
|
+
"position_nft_mint": _get_account_safe(accounts, 1),
|
|
692
|
+
"user": _get_account_safe(accounts, 2),
|
|
693
|
+
"liquidity": str(liquidity), "amount0_max": amount0_max, "amount1_max": amount1_max,
|
|
694
|
+
}})
|
|
695
|
+
if discriminator == _DISC_CLMM_DEC_LIQ:
|
|
696
|
+
if len(data) < 8 + 8 + 8 + 8:
|
|
697
|
+
return None
|
|
698
|
+
liquidity, amount0_min, amount1_min = struct.unpack_from("<QQQ", data, 8)
|
|
699
|
+
return legacy_dict_to_dex_event({"RaydiumClmmDecreaseLiquidity": {
|
|
700
|
+
"metadata": meta,
|
|
701
|
+
"pool": _get_account_safe(accounts, 0),
|
|
702
|
+
"position_nft_mint": _get_account_safe(accounts, 1),
|
|
703
|
+
"user": _get_account_safe(accounts, 2),
|
|
704
|
+
"liquidity": str(liquidity), "amount0_min": amount0_min, "amount1_min": amount1_min,
|
|
705
|
+
}})
|
|
706
|
+
if discriminator == _DISC_CLMM_CREATE:
|
|
707
|
+
if len(data) < 8 + 8 + 8:
|
|
708
|
+
return None
|
|
709
|
+
sqrt_price_x64, open_time = struct.unpack_from("<QQ", data, 8)
|
|
710
|
+
return legacy_dict_to_dex_event({"RaydiumClmmCreatePool": {
|
|
711
|
+
"metadata": meta,
|
|
712
|
+
"pool": _get_account_safe(accounts, 0),
|
|
713
|
+
"creator": _get_account_safe(accounts, 1),
|
|
714
|
+
"token_0_mint": _get_account_safe(accounts, 2),
|
|
715
|
+
"token_1_mint": _get_account_safe(accounts, 3),
|
|
716
|
+
"tick_spacing": 0, "fee_rate": 0, "sqrt_price_x64": str(sqrt_price_x64), "open_time": open_time,
|
|
717
|
+
}})
|
|
718
|
+
if discriminator in (_DISC_CLMM_OPEN_POSITION_V2, _DISC_CLMM_OPEN_POSITION_WITH_TOKEN_22_NFT):
|
|
719
|
+
if len(data) < 8 + 4 + 4 + 4 + 4 + 8 + 8 + 8:
|
|
720
|
+
return None
|
|
721
|
+
tick_lower_index, tick_upper_index = struct.unpack_from("<ii", data, 8)
|
|
722
|
+
liquidity = struct.unpack_from("<Q", data, 24)[0]
|
|
723
|
+
return legacy_dict_to_dex_event({"RaydiumClmmOpenPosition": {
|
|
724
|
+
"metadata": meta,
|
|
725
|
+
"pool": _get_account_safe(accounts, 0),
|
|
726
|
+
"user": _get_account_safe(accounts, 1),
|
|
727
|
+
"position_nft_mint": _get_account_safe(accounts, 2),
|
|
728
|
+
"tick_lower_index": tick_lower_index,
|
|
729
|
+
"tick_upper_index": tick_upper_index,
|
|
730
|
+
"liquidity": str(liquidity),
|
|
731
|
+
}})
|
|
732
|
+
if discriminator == _DISC_CLMM_CLOSE_POSITION:
|
|
733
|
+
return legacy_dict_to_dex_event({"RaydiumClmmClosePosition": {
|
|
734
|
+
"metadata": meta,
|
|
735
|
+
"pool": _get_account_safe(accounts, 0),
|
|
736
|
+
"user": _get_account_safe(accounts, 1),
|
|
737
|
+
"position_nft_mint": _get_account_safe(accounts, 2),
|
|
738
|
+
}})
|
|
739
|
+
|
|
740
|
+
return None
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def parse_raydium_cpmm_instruction(
|
|
744
|
+
data: bytes,
|
|
745
|
+
accounts: List[str],
|
|
746
|
+
signature: str,
|
|
747
|
+
slot: int,
|
|
748
|
+
tx_index: int,
|
|
749
|
+
block_time_us: Optional[int],
|
|
750
|
+
grpc_recv_us: int,
|
|
751
|
+
) -> Optional[DexEvent]:
|
|
752
|
+
"""解析 Raydium CPMM 指令"""
|
|
753
|
+
if len(data) < 8:
|
|
754
|
+
return None
|
|
755
|
+
|
|
756
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
757
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
758
|
+
|
|
759
|
+
if discriminator == _DISC_CPMM_SWAP:
|
|
760
|
+
return legacy_dict_to_dex_event({"RaydiumCpmmSwap": {
|
|
761
|
+
"metadata": meta,
|
|
762
|
+
"pool_id": _get_account_safe(accounts, 2),
|
|
763
|
+
"input_amount": 0, "output_amount": 0,
|
|
764
|
+
"input_vault_before": 0, "output_vault_before": 0,
|
|
765
|
+
"input_transfer_fee": 0, "output_transfer_fee": 0,
|
|
766
|
+
"base_input": True,
|
|
767
|
+
}})
|
|
768
|
+
if discriminator == _DISC_CPMM_DEP:
|
|
769
|
+
return legacy_dict_to_dex_event({"RaydiumCpmmDeposit": {
|
|
770
|
+
"metadata": meta,
|
|
771
|
+
"pool": _get_account_safe(accounts, 2),
|
|
772
|
+
"user": _get_account_safe(accounts, 0),
|
|
773
|
+
"lp_token_amount": 0, "token0_amount": 0, "token1_amount": 0,
|
|
774
|
+
}})
|
|
775
|
+
if discriminator == _DISC_CPMM_WIT:
|
|
776
|
+
return legacy_dict_to_dex_event({"RaydiumCpmmWithdraw": {
|
|
777
|
+
"metadata": meta,
|
|
778
|
+
"pool": _get_account_safe(accounts, 2),
|
|
779
|
+
"user": _get_account_safe(accounts, 0),
|
|
780
|
+
"lp_token_amount": 0, "token0_amount": 0, "token1_amount": 0,
|
|
781
|
+
}})
|
|
782
|
+
|
|
783
|
+
return None
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def parse_raydium_amm_v4_instruction(
|
|
787
|
+
data: bytes,
|
|
788
|
+
accounts: List[str],
|
|
789
|
+
signature: str,
|
|
790
|
+
slot: int,
|
|
791
|
+
tx_index: int,
|
|
792
|
+
block_time_us: Optional[int],
|
|
793
|
+
grpc_recv_us: int,
|
|
794
|
+
) -> Optional[DexEvent]:
|
|
795
|
+
"""解析 Raydium AMM V4 指令(单字节 discriminator)"""
|
|
796
|
+
if len(data) < 1:
|
|
797
|
+
return None
|
|
798
|
+
|
|
799
|
+
instr_type = data[0]
|
|
800
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
801
|
+
|
|
802
|
+
if instr_type in (9, 11): # SwapBaseIn / SwapBaseOut
|
|
803
|
+
return legacy_dict_to_dex_event({"RaydiumAmmV4Swap": {
|
|
804
|
+
"metadata": meta,
|
|
805
|
+
"amm": _get_account_safe(accounts, 1),
|
|
806
|
+
"user_source_owner": _get_account_safe(accounts, 17),
|
|
807
|
+
"amount_in": 0, "minimum_amount_out": 0,
|
|
808
|
+
"max_amount_in": 0, "amount_out": 0,
|
|
809
|
+
"token_program": Z, "amm_authority": Z, "amm_open_orders": Z,
|
|
810
|
+
"pool_coin_token_account": Z, "pool_pc_token_account": Z,
|
|
811
|
+
"serum_program": Z, "serum_market": Z, "serum_bids": Z,
|
|
812
|
+
"serum_asks": Z, "serum_event_queue": Z,
|
|
813
|
+
"serum_coin_vault_account": Z, "serum_pc_vault_account": Z,
|
|
814
|
+
"serum_vault_signer": Z,
|
|
815
|
+
"user_source_token_account": Z,
|
|
816
|
+
"user_destination_token_account": Z,
|
|
817
|
+
}})
|
|
818
|
+
|
|
819
|
+
return None
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def parse_orca_whirlpool_instruction(
|
|
823
|
+
data: bytes,
|
|
824
|
+
accounts: List[str],
|
|
825
|
+
signature: str,
|
|
826
|
+
slot: int,
|
|
827
|
+
tx_index: int,
|
|
828
|
+
block_time_us: Optional[int],
|
|
829
|
+
grpc_recv_us: int,
|
|
830
|
+
) -> Optional[DexEvent]:
|
|
831
|
+
"""解析 Orca Whirlpool 指令"""
|
|
832
|
+
if len(data) < 8:
|
|
833
|
+
return None
|
|
834
|
+
|
|
835
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
836
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
837
|
+
|
|
838
|
+
if discriminator == _DISC_ORCA_SWAP:
|
|
839
|
+
return legacy_dict_to_dex_event({"OrcaWhirlpoolSwap": {
|
|
840
|
+
"metadata": meta,
|
|
841
|
+
"whirlpool": _get_account_safe(accounts, 2),
|
|
842
|
+
"a_to_b": True,
|
|
843
|
+
"pre_sqrt_price": "0", "post_sqrt_price": "0",
|
|
844
|
+
"input_amount": 0, "output_amount": 0,
|
|
845
|
+
"input_transfer_fee": 0, "output_transfer_fee": 0,
|
|
846
|
+
"lp_fee": 0, "protocol_fee": 0,
|
|
847
|
+
}})
|
|
848
|
+
if discriminator == _DISC_ORCA_INC_LIQ:
|
|
849
|
+
return legacy_dict_to_dex_event({"OrcaWhirlpoolLiquidityIncreased": {
|
|
850
|
+
"metadata": meta,
|
|
851
|
+
"whirlpool": _get_account_safe(accounts, 1),
|
|
852
|
+
"position": _get_account_safe(accounts, 3),
|
|
853
|
+
"tick_lower_index": 0, "tick_upper_index": 0,
|
|
854
|
+
"liquidity": "0",
|
|
855
|
+
"token_a_amount": 0, "token_b_amount": 0,
|
|
856
|
+
"token_a_transfer_fee": 0, "token_b_transfer_fee": 0,
|
|
857
|
+
}})
|
|
858
|
+
if discriminator == _DISC_ORCA_DEC_LIQ:
|
|
859
|
+
return legacy_dict_to_dex_event({"OrcaWhirlpoolLiquidityDecreased": {
|
|
860
|
+
"metadata": meta,
|
|
861
|
+
"whirlpool": _get_account_safe(accounts, 1),
|
|
862
|
+
"position": _get_account_safe(accounts, 3),
|
|
863
|
+
"tick_lower_index": 0, "tick_upper_index": 0,
|
|
864
|
+
"liquidity": "0",
|
|
865
|
+
"token_a_amount": 0, "token_b_amount": 0,
|
|
866
|
+
"token_a_transfer_fee": 0, "token_b_transfer_fee": 0,
|
|
867
|
+
}})
|
|
868
|
+
|
|
869
|
+
return None
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def parse_bonk_instruction(
|
|
873
|
+
data: bytes,
|
|
874
|
+
accounts: List[str],
|
|
875
|
+
signature: str,
|
|
876
|
+
slot: int,
|
|
877
|
+
tx_index: int,
|
|
878
|
+
block_time_us: Optional[int],
|
|
879
|
+
grpc_recv_us: int,
|
|
880
|
+
) -> Optional[DexEvent]:
|
|
881
|
+
"""解析 Bonk (Raydium Launchpad) 指令"""
|
|
882
|
+
if len(data) < 8:
|
|
883
|
+
return None
|
|
884
|
+
|
|
885
|
+
discriminator = struct.unpack_from("<Q", data, 0)[0]
|
|
886
|
+
meta = _make_meta(signature, slot, tx_index, block_time_us, grpc_recv_us)
|
|
887
|
+
|
|
888
|
+
if discriminator == _DISC_BONK_TRADE:
|
|
889
|
+
return legacy_dict_to_dex_event({"BonkTrade": {
|
|
890
|
+
"metadata": meta,
|
|
891
|
+
"pool_state": _get_account_safe(accounts, 1),
|
|
892
|
+
"user": _get_account_safe(accounts, 0),
|
|
893
|
+
"amount_in": 0, "amount_out": 0,
|
|
894
|
+
"is_buy": True, "trade_direction": "Buy", "exact_in": True,
|
|
895
|
+
}})
|
|
896
|
+
if discriminator == _DISC_BONK_POOL_CREATE:
|
|
897
|
+
return legacy_dict_to_dex_event({"BonkPoolCreate": {
|
|
898
|
+
"metadata": meta,
|
|
899
|
+
"base_mint_param": {"symbol": "BONK", "name": "Bonk Pool", "uri": "https://bonk.com", "decimals": 5},
|
|
900
|
+
"pool_state": _get_account_safe(accounts, 1),
|
|
901
|
+
"creator": _get_account_safe(accounts, 8),
|
|
902
|
+
}})
|
|
903
|
+
|
|
904
|
+
return None
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
# --- 过滤器辅助函数 ---
|
|
908
|
+
|
|
909
|
+
def _filter_includes_any(filter: Optional[EventTypeFilter], types: List[EventType]) -> bool:
|
|
910
|
+
if filter is None:
|
|
911
|
+
return True
|
|
912
|
+
if isinstance(filter, IncludeOnlyFilter):
|
|
913
|
+
return any(t in types for t in filter.include_only)
|
|
914
|
+
if isinstance(filter, ExcludeFilter):
|
|
915
|
+
return not any(t in types for t in filter.exclude_types)
|
|
916
|
+
return any(filter.should_include(t) for t in types)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def _filter_includes_pumpfun(filter: Optional[EventTypeFilter]) -> bool:
|
|
920
|
+
pumpfun_types = [
|
|
921
|
+
EventType.PUMP_FUN_TRADE, EventType.PUMP_FUN_BUY, EventType.PUMP_FUN_SELL,
|
|
922
|
+
EventType.PUMP_FUN_BUY_EXACT_SOL_IN, EventType.PUMP_FUN_CREATE,
|
|
923
|
+
EventType.PUMP_FUN_CREATE_V2, EventType.PUMP_FUN_COMPLETE, EventType.PUMP_FUN_MIGRATE,
|
|
924
|
+
EventType.PUMP_FEES_CREATE_FEE_SHARING_CONFIG,
|
|
925
|
+
EventType.PUMP_FEES_INITIALIZE_FEE_CONFIG,
|
|
926
|
+
EventType.PUMP_FEES_RESET_FEE_SHARING_CONFIG,
|
|
927
|
+
EventType.PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY,
|
|
928
|
+
EventType.PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY,
|
|
929
|
+
EventType.PUMP_FEES_UPDATE_ADMIN,
|
|
930
|
+
EventType.PUMP_FEES_UPDATE_FEE_CONFIG,
|
|
931
|
+
EventType.PUMP_FEES_UPDATE_FEE_SHARES,
|
|
932
|
+
EventType.PUMP_FEES_UPSERT_FEE_TIERS,
|
|
933
|
+
EventType.PUMP_FUN_MIGRATE_BONDING_CURVE_CREATOR,
|
|
934
|
+
EventType.ACCOUNT_PUMP_FUN_GLOBAL,
|
|
935
|
+
]
|
|
936
|
+
return _filter_includes_any(filter, pumpfun_types)
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _filter_includes_pump_fees(filter: Optional[EventTypeFilter]) -> bool:
|
|
940
|
+
types = [
|
|
941
|
+
EventType.PUMP_FEES_CREATE_FEE_SHARING_CONFIG,
|
|
942
|
+
EventType.PUMP_FEES_INITIALIZE_FEE_CONFIG,
|
|
943
|
+
EventType.PUMP_FEES_RESET_FEE_SHARING_CONFIG,
|
|
944
|
+
EventType.PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY,
|
|
945
|
+
EventType.PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY,
|
|
946
|
+
EventType.PUMP_FEES_UPDATE_ADMIN,
|
|
947
|
+
EventType.PUMP_FEES_UPDATE_FEE_CONFIG,
|
|
948
|
+
EventType.PUMP_FEES_UPDATE_FEE_SHARES,
|
|
949
|
+
EventType.PUMP_FEES_UPSERT_FEE_TIERS,
|
|
950
|
+
]
|
|
951
|
+
return _filter_includes_any(filter, types)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _filter_includes_pumpswap(filter: Optional[EventTypeFilter]) -> bool:
|
|
955
|
+
pumpswap_types = [
|
|
956
|
+
EventType.PUMP_SWAP_BUY, EventType.PUMP_SWAP_SELL,
|
|
957
|
+
EventType.PUMP_SWAP_CREATE_POOL, EventType.PUMP_SWAP_LIQUIDITY_ADDED,
|
|
958
|
+
EventType.PUMP_SWAP_LIQUIDITY_REMOVED,
|
|
959
|
+
]
|
|
960
|
+
return _filter_includes_any(filter, pumpswap_types)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def _filter_includes_meteora_damm_v2(filter: Optional[EventTypeFilter]) -> bool:
|
|
964
|
+
meteora_types = [
|
|
965
|
+
EventType.METEORA_DAMM_V2_SWAP, EventType.METEORA_DAMM_V2_ADD_LIQUIDITY,
|
|
966
|
+
EventType.METEORA_DAMM_V2_CREATE_POSITION, EventType.METEORA_DAMM_V2_CLOSE_POSITION,
|
|
967
|
+
EventType.METEORA_DAMM_V2_INITIALIZE_POOL, EventType.METEORA_DAMM_V2_REMOVE_LIQUIDITY,
|
|
968
|
+
]
|
|
969
|
+
return _filter_includes_any(filter, meteora_types)
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _filter_includes_raydium_clmm(filter: Optional[EventTypeFilter]) -> bool:
|
|
973
|
+
types = [
|
|
974
|
+
EventType.RAYDIUM_CLMM_SWAP, EventType.RAYDIUM_CLMM_INCREASE_LIQUIDITY,
|
|
975
|
+
EventType.RAYDIUM_CLMM_DECREASE_LIQUIDITY, EventType.RAYDIUM_CLMM_CREATE_POOL,
|
|
976
|
+
EventType.RAYDIUM_CLMM_OPEN_POSITION, EventType.RAYDIUM_CLMM_OPEN_POSITION_WITH_TOKEN_EXT_NFT,
|
|
977
|
+
EventType.RAYDIUM_CLMM_CLOSE_POSITION,
|
|
978
|
+
EventType.RAYDIUM_CLMM_COLLECT_FEE,
|
|
979
|
+
]
|
|
980
|
+
return _filter_includes_any(filter, types)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def _filter_includes_raydium_cpmm(filter: Optional[EventTypeFilter]) -> bool:
|
|
984
|
+
types = [
|
|
985
|
+
EventType.RAYDIUM_CPMM_SWAP, EventType.RAYDIUM_CPMM_DEPOSIT,
|
|
986
|
+
EventType.RAYDIUM_CPMM_WITHDRAW, EventType.RAYDIUM_CPMM_INITIALIZE,
|
|
987
|
+
]
|
|
988
|
+
return _filter_includes_any(filter, types)
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def _filter_includes_raydium_amm_v4(filter: Optional[EventTypeFilter]) -> bool:
|
|
992
|
+
types = [
|
|
993
|
+
EventType.RAYDIUM_AMM_V4_SWAP, EventType.RAYDIUM_AMM_V4_DEPOSIT,
|
|
994
|
+
EventType.RAYDIUM_AMM_V4_WITHDRAW, EventType.RAYDIUM_AMM_V4_WITHDRAW_PNL,
|
|
995
|
+
EventType.RAYDIUM_AMM_V4_INITIALIZE2,
|
|
996
|
+
]
|
|
997
|
+
return _filter_includes_any(filter, types)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def _filter_includes_orca_whirlpool(filter: Optional[EventTypeFilter]) -> bool:
|
|
1001
|
+
types = [
|
|
1002
|
+
EventType.ORCA_WHIRLPOOL_SWAP, EventType.ORCA_WHIRLPOOL_LIQUIDITY_INCREASED,
|
|
1003
|
+
EventType.ORCA_WHIRLPOOL_LIQUIDITY_DECREASED, EventType.ORCA_WHIRLPOOL_POOL_INITIALIZED,
|
|
1004
|
+
]
|
|
1005
|
+
return _filter_includes_any(filter, types)
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def _filter_includes_bonk(filter: Optional[EventTypeFilter]) -> bool:
|
|
1009
|
+
types = [
|
|
1010
|
+
EventType.BONK_TRADE, EventType.BONK_POOL_CREATE, EventType.BONK_MIGRATE_AMM,
|
|
1011
|
+
]
|
|
1012
|
+
return _filter_includes_any(filter, types)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
__all__ = [
|
|
1016
|
+
"parse_instruction_unified",
|
|
1017
|
+
"parse_pumpfun_instruction",
|
|
1018
|
+
"parse_pumpswap_instruction",
|
|
1019
|
+
"parse_meteora_damm_instruction",
|
|
1020
|
+
"parse_pump_fees_instruction",
|
|
1021
|
+
"parse_raydium_clmm_instruction",
|
|
1022
|
+
"parse_raydium_cpmm_instruction",
|
|
1023
|
+
"parse_raydium_amm_v4_instruction",
|
|
1024
|
+
"parse_orca_whirlpool_instruction",
|
|
1025
|
+
"parse_bonk_instruction",
|
|
1026
|
+
]
|