sol-parser-sdk-python 0.5.5__py3-none-any.whl → 0.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,515 @@
1
+ """Raydium CLMM/CPMM and Orca Whirlpool account parsers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+ from typing import Any, Callable, Optional, TypeVar
7
+
8
+ import base58
9
+
10
+ from ..dex_parsers import DexEvent
11
+ from ..grpc_types import EventMetadata, EventType
12
+
13
+ RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"
14
+ RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
15
+ ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"
16
+
17
+ CLMM_AMM_CONFIG_DISC = bytes([218, 244, 33, 104, 203, 203, 43, 111])
18
+ CLMM_POOL_STATE_DISC = bytes([247, 237, 227, 245, 215, 195, 222, 70])
19
+ CLMM_TICK_ARRAY_STATE_DISC = bytes([192, 155, 85, 205, 49, 249, 129, 42])
20
+ CLMM_AMM_CONFIG_BODY = 109
21
+ CLMM_POOL_STATE_BODY = 1536
22
+ CLMM_TICK_ARRAY_STATE_BODY = 10232
23
+ CLMM_TICK_ARRAY_LEN = 60
24
+
25
+ CPMM_AMM_CONFIG_DISC = CLMM_AMM_CONFIG_DISC
26
+ CPMM_POOL_STATE_DISC = CLMM_POOL_STATE_DISC
27
+ CPMM_AMM_CONFIG_BODY = 228
28
+ CPMM_POOL_STATE_BODY = 629
29
+
30
+ ORCA_WHIRLPOOL_DISC = bytes([63, 149, 209, 12, 225, 128, 99, 9])
31
+ ORCA_POSITION_DISC = bytes([170, 188, 143, 228, 122, 64, 247, 208])
32
+ ORCA_TICK_ARRAY_DISC = bytes([69, 97, 189, 190, 110, 7, 66, 187])
33
+ ORCA_FEE_TIER_DISC = bytes([56, 75, 159, 76, 142, 68, 190, 105])
34
+ ORCA_WHIRLPOOLS_CONFIG_DISC = bytes([157, 20, 49, 224, 217, 87, 193, 254])
35
+ ORCA_WHIRLPOOL_BODY = 645
36
+ ORCA_POSITION_BODY = 208
37
+ ORCA_TICK_ARRAY_BODY = 9980
38
+ ORCA_FEE_TIER_BODY = 36
39
+ ORCA_WHIRLPOOLS_CONFIG_BODY = 98
40
+ ORCA_TICK_ARRAY_LEN = 88
41
+
42
+ T = TypeVar("T")
43
+
44
+
45
+ class Reader:
46
+ def __init__(self, data: bytes):
47
+ self.data = data
48
+ self.offset = 0
49
+
50
+ def _take(self, size: int) -> int:
51
+ if self.offset + size > len(self.data):
52
+ raise ValueError("account data is truncated")
53
+ start = self.offset
54
+ self.offset += size
55
+ return start
56
+
57
+ def u8(self) -> int:
58
+ return self.data[self._take(1)]
59
+
60
+ def bool(self) -> bool:
61
+ return self.u8() != 0
62
+
63
+ def u16(self) -> int:
64
+ return struct.unpack_from("<H", self.data, self._take(2))[0]
65
+
66
+ def u32(self) -> int:
67
+ return struct.unpack_from("<I", self.data, self._take(4))[0]
68
+
69
+ def i32(self) -> int:
70
+ return struct.unpack_from("<i", self.data, self._take(4))[0]
71
+
72
+ def u64(self) -> int:
73
+ return struct.unpack_from("<Q", self.data, self._take(8))[0]
74
+
75
+ def u128(self) -> int:
76
+ start = self._take(16)
77
+ return int.from_bytes(self.data[start : start + 16], "little", signed=False)
78
+
79
+ def i128(self) -> int:
80
+ start = self._take(16)
81
+ return int.from_bytes(self.data[start : start + 16], "little", signed=True)
82
+
83
+ def pubkey(self) -> str:
84
+ start = self._take(32)
85
+ return base58.b58encode(self.data[start : start + 32]).decode("ascii")
86
+
87
+ def bytes(self, length: int) -> list[int]:
88
+ start = self._take(length)
89
+ return list(self.data[start : start + length])
90
+
91
+
92
+ def _event(event_type: EventType, data: dict[str, Any]) -> DexEvent:
93
+ return DexEvent(type=event_type, data=data)
94
+
95
+
96
+ def _parse_body(data: bytes, parse: Callable[[Reader], T]) -> Optional[T]:
97
+ try:
98
+ return parse(Reader(data))
99
+ except (IndexError, struct.error, ValueError):
100
+ return None
101
+
102
+
103
+ def _body_after_discriminator(data: bytes, disc: bytes, body_size: int) -> Optional[bytes]:
104
+ if len(data) < 8 + body_size or not data.startswith(disc):
105
+ return None
106
+ return data[8:]
107
+
108
+
109
+ def _array(count: int, read: Callable[[], T]) -> list[T]:
110
+ return [read() for _ in range(count)]
111
+
112
+
113
+ def parse_raydium_clmm_account(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
114
+ if account.owner != RAYDIUM_CLMM_PROGRAM_ID:
115
+ return None
116
+ return (
117
+ parse_raydium_clmm_amm_config(account, metadata)
118
+ or parse_raydium_clmm_pool_state(account, metadata)
119
+ or parse_raydium_clmm_tick_array_state(account, metadata)
120
+ )
121
+
122
+
123
+ def parse_raydium_cpmm_account(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
124
+ if account.owner != RAYDIUM_CPMM_PROGRAM_ID:
125
+ return None
126
+ return parse_raydium_cpmm_amm_config(account, metadata) or parse_raydium_cpmm_pool_state(
127
+ account, metadata
128
+ )
129
+
130
+
131
+ def parse_orca_whirlpool_account(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
132
+ if account.owner != ORCA_WHIRLPOOL_PROGRAM_ID:
133
+ return None
134
+ return (
135
+ parse_orca_whirlpool(account, metadata)
136
+ or parse_orca_position(account, metadata)
137
+ or parse_orca_tick_array(account, metadata)
138
+ or parse_orca_fee_tier(account, metadata)
139
+ or parse_orca_whirlpools_config(account, metadata)
140
+ )
141
+
142
+
143
+ def parse_raydium_clmm_amm_config(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
144
+ body = _body_after_discriminator(account.data, CLMM_AMM_CONFIG_DISC, CLMM_AMM_CONFIG_BODY)
145
+ if body is None:
146
+ return None
147
+ amm_config = _parse_body(
148
+ body,
149
+ lambda r: {
150
+ "bump": r.u8(),
151
+ "index": r.u16(),
152
+ "owner": r.pubkey(),
153
+ "protocol_fee_rate": r.u32(),
154
+ "trade_fee_rate": r.u32(),
155
+ "tick_spacing": r.u16(),
156
+ "fund_fee_rate": r.u32(),
157
+ "padding_u32": r.u32(),
158
+ "fund_owner": r.pubkey(),
159
+ "padding": _array(3, r.u64),
160
+ },
161
+ )
162
+ if amm_config is None:
163
+ return None
164
+ return _event(
165
+ EventType.ACCOUNT_RAYDIUM_CLMM_AMM_CONFIG,
166
+ {"metadata": metadata, "pubkey": account.pubkey, "amm_config": amm_config},
167
+ )
168
+
169
+
170
+ def parse_raydium_clmm_pool_state(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
171
+ body = _body_after_discriminator(account.data, CLMM_POOL_STATE_DISC, CLMM_POOL_STATE_BODY)
172
+ if body is None:
173
+ return None
174
+ pool_state = _parse_body(body, _read_clmm_pool_state)
175
+ if pool_state is None:
176
+ return None
177
+ return _event(
178
+ EventType.ACCOUNT_RAYDIUM_CLMM_POOL_STATE,
179
+ {"metadata": metadata, "pubkey": account.pubkey, "pool_state": pool_state},
180
+ )
181
+
182
+
183
+ def _read_clmm_pool_state(r: Reader) -> dict[str, Any]:
184
+ return {
185
+ "bump": [r.u8()],
186
+ "amm_config": r.pubkey(),
187
+ "owner": r.pubkey(),
188
+ "token_mint_0": r.pubkey(),
189
+ "token_mint_1": r.pubkey(),
190
+ "token_vault_0": r.pubkey(),
191
+ "token_vault_1": r.pubkey(),
192
+ "observation_key": r.pubkey(),
193
+ "mint_decimals_0": r.u8(),
194
+ "mint_decimals_1": r.u8(),
195
+ "tick_spacing": r.u16(),
196
+ "liquidity": r.u128(),
197
+ "sqrt_price_x64": r.u128(),
198
+ "tick_current": r.i32(),
199
+ "padding3": r.u16(),
200
+ "padding4": r.u16(),
201
+ "fee_growth_global_0_x64": r.u128(),
202
+ "fee_growth_global_1_x64": r.u128(),
203
+ "protocol_fees_token_0": r.u64(),
204
+ "protocol_fees_token_1": r.u64(),
205
+ "padding5": _array(4, r.u128),
206
+ "status": r.u8(),
207
+ "fee_on": r.u8(),
208
+ "padding": r.bytes(6),
209
+ "reward_infos": _array(3, lambda: _read_clmm_reward_info(r)),
210
+ "tick_array_bitmap": _array(16, r.u64),
211
+ "padding6": _array(4, r.u64),
212
+ "fund_fees_token_0": r.u64(),
213
+ "fund_fees_token_1": r.u64(),
214
+ "open_time": r.u64(),
215
+ "recent_epoch": r.u64(),
216
+ "dynamic_fee_info": _read_clmm_dynamic_fee_info(r),
217
+ "padding1": _array(14, r.u64),
218
+ "padding2": _array(32, r.u64),
219
+ }
220
+
221
+
222
+ def parse_raydium_clmm_tick_array_state(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
223
+ body = _body_after_discriminator(account.data, CLMM_TICK_ARRAY_STATE_DISC, CLMM_TICK_ARRAY_STATE_BODY)
224
+ if body is None:
225
+ return None
226
+ tick_array_state = _parse_body(
227
+ body,
228
+ lambda r: {
229
+ "pool_id": r.pubkey(),
230
+ "start_tick_index": r.i32(),
231
+ "ticks": _array(CLMM_TICK_ARRAY_LEN, lambda: _read_clmm_tick(r)),
232
+ "initialized_tick_count": r.u8(),
233
+ "recent_epoch": r.u64(),
234
+ "padding": r.bytes(107),
235
+ },
236
+ )
237
+ if tick_array_state is None:
238
+ return None
239
+ return _event(
240
+ EventType.ACCOUNT_RAYDIUM_CLMM_TICK_ARRAY_STATE,
241
+ {"metadata": metadata, "pubkey": account.pubkey, "tick_array_state": tick_array_state},
242
+ )
243
+
244
+
245
+ def _read_clmm_reward_info(r: Reader) -> dict[str, Any]:
246
+ return {
247
+ "reward_state": r.u8(),
248
+ "open_time": r.u64(),
249
+ "end_time": r.u64(),
250
+ "last_update_time": r.u64(),
251
+ "emissions_per_second_x64": r.u128(),
252
+ "reward_total_emitted": r.u64(),
253
+ "reward_claimed": r.u64(),
254
+ "token_mint": r.pubkey(),
255
+ "token_vault": r.pubkey(),
256
+ "authority": r.pubkey(),
257
+ "reward_growth_global_x64": r.u128(),
258
+ }
259
+
260
+
261
+ def _read_clmm_dynamic_fee_info(r: Reader) -> dict[str, Any]:
262
+ return {
263
+ "filter_period": r.u16(),
264
+ "decay_period": r.u16(),
265
+ "reduction_factor": r.u16(),
266
+ "dynamic_fee_control": r.u32(),
267
+ "max_volatility_accumulator": r.u32(),
268
+ "tick_spacing_index_reference": r.i32(),
269
+ "volatility_reference": r.u32(),
270
+ "volatility_accumulator": r.u32(),
271
+ "last_update_timestamp": r.u64(),
272
+ "padding": r.bytes(46),
273
+ }
274
+
275
+
276
+ def _read_clmm_tick(r: Reader) -> dict[str, Any]:
277
+ return {
278
+ "tick": r.i32(),
279
+ "liquidity_net": r.i128(),
280
+ "liquidity_gross": r.u128(),
281
+ "fee_growth_outside_0_x64": r.u128(),
282
+ "fee_growth_outside_1_x64": r.u128(),
283
+ "reward_growths_outside_x64": _array(3, r.u128),
284
+ "order_phase": r.u64(),
285
+ "orders_amount": r.u64(),
286
+ "part_filled_orders_remaining": r.u64(),
287
+ "unfilled_ratio_x64": r.u128(),
288
+ "padding": _array(3, r.u32),
289
+ }
290
+
291
+
292
+ def parse_raydium_cpmm_amm_config(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
293
+ body = _body_after_discriminator(account.data, CPMM_AMM_CONFIG_DISC, CPMM_AMM_CONFIG_BODY)
294
+ if body is None:
295
+ return None
296
+ amm_config = _parse_body(
297
+ body,
298
+ lambda r: {
299
+ "bump": r.u8(),
300
+ "disable_create_pool": r.bool(),
301
+ "index": r.u16(),
302
+ "trade_fee_rate": r.u64(),
303
+ "protocol_fee_rate": r.u64(),
304
+ "fund_fee_rate": r.u64(),
305
+ "create_pool_fee": r.u64(),
306
+ "protocol_owner": r.pubkey(),
307
+ "fund_owner": r.pubkey(),
308
+ "creator_fee_rate": r.u64(),
309
+ "padding": _array(15, r.u64),
310
+ },
311
+ )
312
+ if amm_config is None:
313
+ return None
314
+ return _event(
315
+ EventType.ACCOUNT_RAYDIUM_CPMM_AMM_CONFIG,
316
+ {"metadata": metadata, "pubkey": account.pubkey, "amm_config": amm_config},
317
+ )
318
+
319
+
320
+ def parse_raydium_cpmm_pool_state(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
321
+ body = _body_after_discriminator(account.data, CPMM_POOL_STATE_DISC, CPMM_POOL_STATE_BODY)
322
+ if body is None:
323
+ return None
324
+ pool_state = _parse_body(body, _read_cpmm_pool_state)
325
+ if pool_state is None:
326
+ return None
327
+ return _event(
328
+ EventType.ACCOUNT_RAYDIUM_CPMM_POOL_STATE,
329
+ {"metadata": metadata, "pubkey": account.pubkey, "pool_state": pool_state},
330
+ )
331
+
332
+
333
+ def _read_cpmm_pool_state(r: Reader) -> dict[str, Any]:
334
+ return {
335
+ "amm_config": r.pubkey(),
336
+ "pool_creator": r.pubkey(),
337
+ "token_0_vault": r.pubkey(),
338
+ "token_1_vault": r.pubkey(),
339
+ "lp_mint": r.pubkey(),
340
+ "token_0_mint": r.pubkey(),
341
+ "token_1_mint": r.pubkey(),
342
+ "token_0_program": r.pubkey(),
343
+ "token_1_program": r.pubkey(),
344
+ "observation_key": r.pubkey(),
345
+ "auth_bump": r.u8(),
346
+ "status": r.u8(),
347
+ "lp_mint_decimals": r.u8(),
348
+ "mint_0_decimals": r.u8(),
349
+ "mint_1_decimals": r.u8(),
350
+ "lp_supply": r.u64(),
351
+ "protocol_fees_token_0": r.u64(),
352
+ "protocol_fees_token_1": r.u64(),
353
+ "fund_fees_token_0": r.u64(),
354
+ "fund_fees_token_1": r.u64(),
355
+ "open_time": r.u64(),
356
+ "recent_epoch": r.u64(),
357
+ "creator_fee_on": r.u8(),
358
+ "enable_creator_fee": r.bool(),
359
+ "padding1": r.bytes(6),
360
+ "creator_fees_token_0": r.u64(),
361
+ "creator_fees_token_1": r.u64(),
362
+ "padding": _array(28, r.u64),
363
+ }
364
+
365
+
366
+ def parse_orca_whirlpool(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
367
+ body = _body_after_discriminator(account.data, ORCA_WHIRLPOOL_DISC, ORCA_WHIRLPOOL_BODY)
368
+ if body is None:
369
+ return None
370
+ whirlpool = _parse_body(
371
+ body,
372
+ lambda r: {
373
+ "whirlpools_config": r.pubkey(),
374
+ "whirlpool_bump": r.u8(),
375
+ "tick_spacing": r.u16(),
376
+ "tick_spacing_seed": r.bytes(2),
377
+ "fee_rate": r.u16(),
378
+ "protocol_fee_rate": r.u16(),
379
+ "liquidity": r.u128(),
380
+ "sqrt_price": r.u128(),
381
+ "tick_current_index": r.i32(),
382
+ "protocol_fee_owed_a": r.u64(),
383
+ "protocol_fee_owed_b": r.u64(),
384
+ "token_mint_a": r.pubkey(),
385
+ "token_vault_a": r.pubkey(),
386
+ "fee_growth_global_a": r.u128(),
387
+ "token_mint_b": r.pubkey(),
388
+ "token_vault_b": r.pubkey(),
389
+ "fee_growth_global_b": r.u128(),
390
+ "reward_last_updated_timestamp": r.u64(),
391
+ "reward_infos": _array(3, lambda: _read_orca_whirlpool_reward_info(r)),
392
+ },
393
+ )
394
+ if whirlpool is None:
395
+ return None
396
+ return _event(
397
+ EventType.ACCOUNT_ORCA_WHIRLPOOL,
398
+ {"metadata": metadata, "pubkey": account.pubkey, "whirlpool": whirlpool},
399
+ )
400
+
401
+
402
+ def parse_orca_position(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
403
+ body = _body_after_discriminator(account.data, ORCA_POSITION_DISC, ORCA_POSITION_BODY)
404
+ if body is None:
405
+ return None
406
+ position = _parse_body(
407
+ body,
408
+ lambda r: {
409
+ "whirlpool": r.pubkey(),
410
+ "position_mint": r.pubkey(),
411
+ "liquidity": r.u128(),
412
+ "tick_lower_index": r.i32(),
413
+ "tick_upper_index": r.i32(),
414
+ "fee_growth_checkpoint_a": r.u128(),
415
+ "fee_owed_a": r.u64(),
416
+ "fee_growth_checkpoint_b": r.u128(),
417
+ "fee_owed_b": r.u64(),
418
+ "reward_infos": _array(3, lambda: _read_orca_position_reward_info(r)),
419
+ },
420
+ )
421
+ if position is None:
422
+ return None
423
+ return _event(
424
+ EventType.ACCOUNT_ORCA_POSITION,
425
+ {"metadata": metadata, "pubkey": account.pubkey, "position": position},
426
+ )
427
+
428
+
429
+ def parse_orca_tick_array(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
430
+ body = _body_after_discriminator(account.data, ORCA_TICK_ARRAY_DISC, ORCA_TICK_ARRAY_BODY)
431
+ if body is None:
432
+ return None
433
+ tick_array = _parse_body(
434
+ body,
435
+ lambda r: {
436
+ "start_tick_index": r.i32(),
437
+ "ticks": _array(ORCA_TICK_ARRAY_LEN, lambda: _read_orca_tick(r)),
438
+ "whirlpool": r.pubkey(),
439
+ },
440
+ )
441
+ if tick_array is None:
442
+ return None
443
+ return _event(
444
+ EventType.ACCOUNT_ORCA_TICK_ARRAY,
445
+ {"metadata": metadata, "pubkey": account.pubkey, "tick_array": tick_array},
446
+ )
447
+
448
+
449
+ def parse_orca_fee_tier(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
450
+ body = _body_after_discriminator(account.data, ORCA_FEE_TIER_DISC, ORCA_FEE_TIER_BODY)
451
+ if body is None:
452
+ return None
453
+ fee_tier = _parse_body(
454
+ body,
455
+ lambda r: {
456
+ "whirlpools_config": r.pubkey(),
457
+ "tick_spacing": r.u16(),
458
+ "default_fee_rate": r.u16(),
459
+ },
460
+ )
461
+ if fee_tier is None:
462
+ return None
463
+ return _event(
464
+ EventType.ACCOUNT_ORCA_FEE_TIER,
465
+ {"metadata": metadata, "pubkey": account.pubkey, "fee_tier": fee_tier},
466
+ )
467
+
468
+
469
+ def parse_orca_whirlpools_config(account: Any, metadata: EventMetadata) -> Optional[DexEvent]:
470
+ body = _body_after_discriminator(account.data, ORCA_WHIRLPOOLS_CONFIG_DISC, ORCA_WHIRLPOOLS_CONFIG_BODY)
471
+ if body is None:
472
+ return None
473
+ config = _parse_body(
474
+ body,
475
+ lambda r: {
476
+ "fee_authority": r.pubkey(),
477
+ "collect_protocol_fees_authority": r.pubkey(),
478
+ "reward_emissions_super_authority": r.pubkey(),
479
+ "default_protocol_fee_rate": r.u16(),
480
+ },
481
+ )
482
+ if config is None:
483
+ return None
484
+ return _event(
485
+ EventType.ACCOUNT_ORCA_WHIRLPOOLS_CONFIG,
486
+ {"metadata": metadata, "pubkey": account.pubkey, "config": config},
487
+ )
488
+
489
+
490
+ def _read_orca_whirlpool_reward_info(r: Reader) -> dict[str, Any]:
491
+ return {
492
+ "mint": r.pubkey(),
493
+ "vault": r.pubkey(),
494
+ "authority": r.pubkey(),
495
+ "emissions_per_second_x64": r.u128(),
496
+ "growth_global_x64": r.u128(),
497
+ }
498
+
499
+
500
+ def _read_orca_position_reward_info(r: Reader) -> dict[str, Any]:
501
+ return {
502
+ "growth_inside_checkpoint": r.u128(),
503
+ "amount_owed": r.u64(),
504
+ }
505
+
506
+
507
+ def _read_orca_tick(r: Reader) -> dict[str, Any]:
508
+ return {
509
+ "initialized": r.bool(),
510
+ "liquidity_net": r.i128(),
511
+ "liquidity_gross": r.u128(),
512
+ "fee_growth_outside_a": r.u128(),
513
+ "fee_growth_outside_b": r.u128(),
514
+ "reward_growths_outside": _array(3, r.u128),
515
+ }
@@ -6,11 +6,14 @@ import sys
6
6
 
7
7
  from .u128_parity import run_all_u128_checks
8
8
  from .verify_discriminators import main as _verify_main
9
+ from .event_type_parity import run_event_type_parity_check
9
10
 
10
11
 
11
12
  def main() -> None:
12
13
  if _verify_main() != 0:
13
14
  sys.exit(1)
15
+ if run_event_type_parity_check() != 0:
16
+ sys.exit(1)
14
17
  sys.exit(run_all_u128_checks())
15
18
 
16
19