1
1
from __future__ import annotations
2
- from typing import List , Dict , Tuple , Optional , Any
2
+ from typing import List , Dict , Tuple , Optional , Any , ClassVar
3
3
import base64
4
4
from enum import Enum
5
+ from dataclasses import dataclass , field
5
6
import struct
6
7
7
8
from loguru import logger
@@ -205,7 +206,6 @@ class PythProductAccount(PythAccount):
205
206
first_price_account_key (SolanaPublicKey): the public key of the first price account (the price accounts form a linked list)
206
207
attrs (dict): a dictionary of metadata attributes
207
208
"""
208
-
209
209
def __init__ (self , key : SolanaPublicKey , solana : SolanaClient ) -> None :
210
210
super ().__init__ (key , solana )
211
211
self ._prices : Optional [Dict [PythPriceType , PythPriceAccount ]] = None
@@ -229,7 +229,6 @@ def symbol(self) -> str:
229
229
"""
230
230
Gets this account's symbol, or 'Unknown' if there is no 'symbol' attribute.
231
231
"""
232
-
233
232
return self .attrs .get ("symbol" , "Unknown" )
234
233
235
234
async def get_prices (self ) -> Dict [PythPriceType , PythPriceAccount ]:
@@ -258,7 +257,10 @@ async def refresh_prices(self) -> Dict[PythPriceType, PythPriceAccount]:
258
257
self ._prices = prices
259
258
return prices
260
259
261
- async def check_price_changes (self , update_accounts : bool = True ) -> Tuple [List [PythPriceAccount ], List [PythPriceAccount ]]:
260
+ async def check_price_changes (
261
+ self ,
262
+ update_accounts : bool = True
263
+ ) -> Tuple [List [PythPriceAccount ], List [PythPriceAccount ]]:
262
264
"""
263
265
Checks for changes to the list of price accounts of this product.
264
266
@@ -345,7 +347,13 @@ def __str__(self) -> str:
345
347
def __repr__ (self ) -> str :
346
348
return str (self )
347
349
350
+ def __iter__ (self ):
351
+ for key , val in self .__dict__ .items ():
352
+ if not key .startswith ('_' ):
353
+ yield key , val
354
+
348
355
356
+ @dataclass
349
357
class PythPriceInfo :
350
358
"""
351
359
Contains price information.
@@ -360,15 +368,18 @@ class PythPriceInfo:
360
368
exponent (int): the power-of-10 order of the price
361
369
"""
362
370
363
- LENGTH = 32
371
+ LENGTH : ClassVar [ int ] = 32
364
372
365
- def __init__ (self , raw_price : int , raw_confidence_interval : int , price_status : PythPriceStatus , slot : int , exponent : int ) -> None :
366
- self .raw_price = raw_price
367
- self .raw_confidence_interval = raw_confidence_interval
368
- self .price_status = price_status
369
- self .slot = slot
370
- self .exponent = exponent
373
+ raw_price : int
374
+ raw_confidence_interval : int
375
+ price_status : PythPriceStatus
376
+ slot : int
377
+ exponent : int
378
+
379
+ price : float = field (init = False )
380
+ confidence_interval : float = field (init = False )
371
381
382
+ def __post_init__ (self ):
372
383
self .price = self .raw_price * (10 ** self .exponent )
373
384
self .confidence_interval = self .raw_confidence_interval * \
374
385
(10 ** self .exponent )
@@ -397,9 +408,11 @@ def __repr__(self) -> str:
397
408
return str (self )
398
409
399
410
400
- class PythPriceComponent : # This has the individual prices each publisher
411
+ @dataclass
412
+ class PythPriceComponent :
401
413
"""
402
- Represents a price component.
414
+ Represents a price component. This is the individual prices each
415
+ publisher sends in addition to their aggregate.
403
416
404
417
Attributes:
405
418
publisher_key (SolanaPublicKey): the public key of the publisher
@@ -411,13 +424,12 @@ class PythPriceComponent: # This has the individual prices each publisher
411
424
in this price component
412
425
"""
413
426
414
- LENGTH = SolanaPublicKey .LENGTH + 2 * PythPriceInfo .LENGTH
427
+ LENGTH : ClassVar [ int ] = SolanaPublicKey .LENGTH + 2 * PythPriceInfo .LENGTH
415
428
416
- def __init__ (self , publisher_key : SolanaPublicKey , last_aggregate_price_info : PythPriceInfo , latest_price_info : PythPriceInfo , exponent : int ) -> None :
417
- self .publisher_key = publisher_key
418
- self .last_aggregate_price_info = last_aggregate_price_info
419
- self .latest_price_info = latest_price_info
420
- self .exponent = exponent
429
+ publisher_key : SolanaPublicKey
430
+ last_aggregate_price_info : PythPriceInfo
431
+ latest_price_info : PythPriceInfo
432
+ exponent : int
421
433
422
434
@staticmethod
423
435
def deserialise (buffer : bytes , offset : int = 0 , * , exponent : int ) -> Optional [PythPriceComponent ]:
@@ -477,6 +489,7 @@ def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optio
477
489
self .aggregate_price_info : Optional [PythPriceInfo ] = None
478
490
self .price_components : List [PythPriceComponent ] = []
479
491
self .derivations : Dict [TwEmaType , int ] = {}
492
+ self .min_publishers : Optional [int ] = None
480
493
481
494
@property
482
495
def aggregate_price (self ) -> Optional [float ]:
@@ -501,7 +514,8 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
501
514
unused (u32)
502
515
currently accumulating price slot (u64)
503
516
slot of current aggregate price (u64)
504
- derivations (u64[8] - array index corresponds to (DeriveType - 1) - v2 only)
517
+ derivations (u64[6] - array index corresponds to (DeriveType - 1) - v2 only)
518
+ unused derivation values and minimum publishers (u64[2], i32[2], )
505
519
product account key (char[32])
506
520
next price account key (char[32])
507
521
account key of quoter who computed last aggregate price (char[32])
@@ -510,19 +524,22 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
510
524
"""
511
525
if version == _VERSION_2 :
512
526
price_type , exponent , num_components = struct .unpack_from ("<IiI" , buffer , offset )
513
- offset += 16 # struct.calcsize("IiII") (last I unused )
527
+ offset += 16 # struct.calcsize("IiII") (last I is the number of quoters that make up the aggregate )
514
528
last_slot , valid_slot = struct .unpack_from ("<QQ" , buffer , offset )
515
- offset += 16 # QQ
516
- derivations = list (struct .unpack_from ("<8q " , buffer , offset ))
529
+ offset += 16 # QQ
530
+ derivations = list (struct .unpack_from ("<6q " , buffer , offset ))
517
531
self .derivations = dict ((type_ , derivations [type_ .value - 1 ]) for type_ in [TwEmaType .TWACVALUE , TwEmaType .TWAPVALUE ])
518
- offset += 64 # 8q
532
+ offset += 48 # 6q
533
+ # All drv*_ fields sans min_publishers are currently unused
534
+ _ , min_publishers = struct .unpack_from ("<qQ" , buffer , offset )
535
+ offset += 16 # <qQ
519
536
product_account_key_bytes , next_price_account_key_bytes = struct .unpack_from ("32s32s" , buffer , offset )
520
- offset += 96 # 32s32s32s
537
+ offset += 96 # 32s32s32s
521
538
elif version == _VERSION_1 :
522
539
price_type , exponent , num_components , _ , last_slot , valid_slot , product_account_key_bytes , next_price_account_key_bytes , aggregator_key_bytes = struct .unpack_from (
523
540
"<IiIIQQ32s32s32s" , buffer , offset )
524
541
self .derivations = {}
525
- offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
542
+ offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
526
543
else :
527
544
assert False
528
545
@@ -552,6 +569,7 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
552
569
next_price_account_key_bytes )
553
570
self .aggregate_price_info = aggregate_price_info
554
571
self .price_components = price_components
572
+ self .min_publishers = min_publishers
555
573
556
574
def __str__ (self ) -> str :
557
575
if self .product :
0 commit comments