11from __future__ import annotations
2- from typing import List , Dict , Tuple , Optional , Any
2+ from typing import List , Dict , Tuple , Optional , Any , ClassVar
33import base64
44from enum import Enum
5+ from dataclasses import dataclass , field
56import struct
67
78from loguru import logger
@@ -205,7 +206,6 @@ class PythProductAccount(PythAccount):
205206 first_price_account_key (SolanaPublicKey): the public key of the first price account (the price accounts form a linked list)
206207 attrs (dict): a dictionary of metadata attributes
207208 """
208-
209209 def __init__ (self , key : SolanaPublicKey , solana : SolanaClient ) -> None :
210210 super ().__init__ (key , solana )
211211 self ._prices : Optional [Dict [PythPriceType , PythPriceAccount ]] = None
@@ -229,7 +229,6 @@ def symbol(self) -> str:
229229 """
230230 Gets this account's symbol, or 'Unknown' if there is no 'symbol' attribute.
231231 """
232-
233232 return self .attrs .get ("symbol" , "Unknown" )
234233
235234 async def get_prices (self ) -> Dict [PythPriceType , PythPriceAccount ]:
@@ -258,7 +257,10 @@ async def refresh_prices(self) -> Dict[PythPriceType, PythPriceAccount]:
258257 self ._prices = prices
259258 return prices
260259
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 ]]:
262264 """
263265 Checks for changes to the list of price accounts of this product.
264266
@@ -345,7 +347,13 @@ def __str__(self) -> str:
345347 def __repr__ (self ) -> str :
346348 return str (self )
347349
350+ def __iter__ (self ):
351+ for key , val in self .__dict__ .items ():
352+ if not key .startswith ('_' ):
353+ yield key , val
354+
348355
356+ @dataclass
349357class PythPriceInfo :
350358 """
351359 Contains price information.
@@ -360,15 +368,18 @@ class PythPriceInfo:
360368 exponent (int): the power-of-10 order of the price
361369 """
362370
363- LENGTH = 32
371+ LENGTH : ClassVar [ int ] = 32
364372
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 )
371381
382+ def __post_init__ (self ):
372383 self .price = self .raw_price * (10 ** self .exponent )
373384 self .confidence_interval = self .raw_confidence_interval * \
374385 (10 ** self .exponent )
@@ -397,9 +408,11 @@ def __repr__(self) -> str:
397408 return str (self )
398409
399410
400- class PythPriceComponent : # This has the individual prices each publisher
411+ @dataclass
412+ class PythPriceComponent :
401413 """
402- Represents a price component.
414+ Represents a price component. This is the individual prices each
415+ publisher sends in addition to their aggregate.
403416
404417 Attributes:
405418 publisher_key (SolanaPublicKey): the public key of the publisher
@@ -411,13 +424,12 @@ class PythPriceComponent: # This has the individual prices each publisher
411424 in this price component
412425 """
413426
414- LENGTH = SolanaPublicKey .LENGTH + 2 * PythPriceInfo .LENGTH
427+ LENGTH : ClassVar [ int ] = SolanaPublicKey .LENGTH + 2 * PythPriceInfo .LENGTH
415428
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
421433
422434 @staticmethod
423435 def deserialise (buffer : bytes , offset : int = 0 , * , exponent : int ) -> Optional [PythPriceComponent ]:
@@ -477,6 +489,7 @@ def __init__(self, key: SolanaPublicKey, solana: SolanaClient, *, product: Optio
477489 self .aggregate_price_info : Optional [PythPriceInfo ] = None
478490 self .price_components : List [PythPriceComponent ] = []
479491 self .derivations : Dict [TwEmaType , int ] = {}
492+ self .min_publishers : Optional [int ] = None
480493
481494 @property
482495 def aggregate_price (self ) -> Optional [float ]:
@@ -501,7 +514,8 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
501514 unused (u32)
502515 currently accumulating price slot (u64)
503516 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], )
505519 product account key (char[32])
506520 next price account key (char[32])
507521 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:
510524 """
511525 if version == _VERSION_2 :
512526 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 )
514528 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 ))
517531 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
519536 product_account_key_bytes , next_price_account_key_bytes = struct .unpack_from ("32s32s" , buffer , offset )
520- offset += 96 # 32s32s32s
537+ offset += 96 # 32s32s32s
521538 elif version == _VERSION_1 :
522539 price_type , exponent , num_components , _ , last_slot , valid_slot , product_account_key_bytes , next_price_account_key_bytes , aggregator_key_bytes = struct .unpack_from (
523540 "<IiIIQQ32s32s32s" , buffer , offset )
524541 self .derivations = {}
525- offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
542+ offset += 128 # struct.calcsize("<IiIIQQ32s32s32s")
526543 else :
527544 assert False
528545
@@ -552,6 +569,7 @@ def update_from(self, buffer: bytes, *, version: int, offset: int = 0) -> None:
552569 next_price_account_key_bytes )
553570 self .aggregate_price_info = aggregate_price_info
554571 self .price_components = price_components
572+ self .min_publishers = min_publishers
555573
556574 def __str__ (self ) -> str :
557575 if self .product :
0 commit comments