11import {
22 HexString ,
3- PriceFeed ,
4- PriceServiceConnection ,
5- } from "@pythnetwork/price-service -client" ;
3+ HermesClient ,
4+ PriceUpdate ,
5+ } from "@pythnetwork/hermes -client" ;
66import { PriceInfo , IPriceListener , PriceItem } from "./interface" ;
77import { Logger } from "pino" ;
8+ import { PriceFeed } from "@pythnetwork/price-service-sdk" ;
89
910type TimestampInMs = number & { readonly _ : unique symbol } ;
1011
1112export class PythPriceListener implements IPriceListener {
12- private connection : PriceServiceConnection ;
13+ private connection : HermesClient ;
1314 private priceIds : HexString [ ] ;
1415 private priceIdToAlias : Map < HexString , string > ;
1516 private latestPriceInfo : Map < HexString , PriceInfo > ;
@@ -18,7 +19,7 @@ export class PythPriceListener implements IPriceListener {
1819 private healthCheckInterval ?: NodeJS . Timeout ;
1920
2021 constructor (
21- connection : PriceServiceConnection ,
22+ connection : HermesClient ,
2223 priceItems : PriceItem [ ] ,
2324 logger : Logger
2425 ) {
@@ -34,107 +35,29 @@ export class PythPriceListener implements IPriceListener {
3435 // This method should be awaited on and once it finishes it has the latest value
3536 // for the given price feeds (if they exist).
3637 async start ( ) {
37- // Set custom error handler for websocket errors
38- this . connection . onWsError = ( error : Error ) => {
39- if ( error . message . includes ( "not found" ) ) {
40- // Extract invalid feed IDs from error message
41- const match = error . message . match ( / \[ ( .* ?) \] / ) ;
42- if ( match ) {
43- const invalidFeedIds = match [ 1 ] . split ( "," ) . map ( ( id ) => {
44- // Remove '0x' prefix if present to match our stored IDs
45- return id . trim ( ) . replace ( / ^ 0 x / , "" ) ;
46- } ) ;
47-
48- // Log invalid feeds with their aliases
49- invalidFeedIds . forEach ( ( id ) => {
50- this . logger . error (
51- `Price feed ${ id } (${ this . priceIdToAlias . get (
52- id
53- ) } ) not found for subscribePriceFeedUpdates`
54- ) ;
55- } ) ;
56-
57- // Filter out invalid feeds and resubscribe with valid ones
58- const validFeeds = this . priceIds . filter (
59- ( id ) => ! invalidFeedIds . includes ( id )
60- ) ;
61-
62- this . priceIds = validFeeds ;
63-
64- if ( validFeeds . length > 0 ) {
65- this . logger . info ( "Resubscribing with valid feeds only" ) ;
66- this . connection . subscribePriceFeedUpdates (
67- validFeeds ,
68- this . onNewPriceFeed . bind ( this )
69- ) ;
70- }
71- }
72- } else {
73- this . logger . error ( "Websocket error occurred:" , error ) ;
74- }
75- } ;
76-
77- this . connection . subscribePriceFeedUpdates (
38+ this . connection . getPriceUpdatesStream (
7839 this . priceIds ,
79- this . onNewPriceFeed . bind ( this )
8040 ) ;
8141
8242 try {
83- const priceFeeds = await this . connection . getLatestPriceFeeds (
84- this . priceIds
43+ const priceUpdates = await this . connection . getLatestPriceUpdates (
44+ this . priceIds ,
45+ {
46+ encoding : "hex" ,
47+ parsed : true ,
48+ ignoreInvalidPriceIds : true ,
49+ }
8550 ) ;
86- priceFeeds ?. forEach ( ( priceFeed ) => {
87- const latestAvailablePrice = priceFeed . getPriceUnchecked ( ) ;
88- this . latestPriceInfo . set ( priceFeed . id , {
89- price : latestAvailablePrice . price ,
90- conf : latestAvailablePrice . conf ,
91- publishTime : latestAvailablePrice . publishTime ,
51+ priceUpdates . parsed ?. forEach ( ( priceUpdate ) => {
52+ this . latestPriceInfo . set ( priceUpdate . id , {
53+ price : priceUpdate . price . price ,
54+ conf : priceUpdate . price . conf ,
55+ publishTime : priceUpdate . price . publish_time ,
9256 } ) ;
9357 } ) ;
9458 } catch ( error : any ) {
9559 // Always log the HTTP error first
9660 this . logger . error ( "Failed to get latest price feeds:" , error ) ;
97-
98- if ( error . response . data . includes ( "Price ids not found:" ) ) {
99- // Extract invalid feed IDs from error message
100- const invalidFeedIds = error . response . data
101- . split ( "Price ids not found:" ) [ 1 ]
102- . split ( "," )
103- . map ( ( id : string ) => id . trim ( ) . replace ( / ^ 0 x / , "" ) ) ;
104-
105- // Log invalid feeds with their aliases
106- invalidFeedIds . forEach ( ( id : string ) => {
107- this . logger . error (
108- `Price feed ${ id } (${ this . priceIdToAlias . get (
109- id
110- ) } ) not found for getLatestPriceFeeds`
111- ) ;
112- } ) ;
113-
114- // Filter out invalid feeds and retry
115- const validFeeds = this . priceIds . filter (
116- ( id ) => ! invalidFeedIds . includes ( id )
117- ) ;
118-
119- this . priceIds = validFeeds ;
120-
121- if ( validFeeds . length > 0 ) {
122- this . logger . info (
123- "Retrying getLatestPriceFeeds with valid feeds only"
124- ) ;
125- const validPriceFeeds = await this . connection . getLatestPriceFeeds (
126- validFeeds
127- ) ;
128- validPriceFeeds ?. forEach ( ( priceFeed ) => {
129- const latestAvailablePrice = priceFeed . getPriceUnchecked ( ) ;
130- this . latestPriceInfo . set ( priceFeed . id , {
131- price : latestAvailablePrice . price ,
132- conf : latestAvailablePrice . conf ,
133- publishTime : latestAvailablePrice . publishTime ,
134- } ) ;
135- } ) ;
136- }
137- }
13861 }
13962
14063 // Store health check interval reference
@@ -148,29 +71,6 @@ export class PythPriceListener implements IPriceListener {
14871 } , 5000 ) ;
14972 }
15073
151- private onNewPriceFeed ( priceFeed : PriceFeed ) {
152- this . logger . debug (
153- `Received new price feed update from Pyth price service: ${ this . priceIdToAlias . get (
154- priceFeed . id
155- ) } ${ priceFeed . id } `
156- ) ;
157-
158- // Consider price to be currently available if it is not older than 60s
159- const currentPrice = priceFeed . getPriceNoOlderThan ( 60 ) ;
160- if ( currentPrice === undefined ) {
161- return ;
162- }
163-
164- const priceInfo : PriceInfo = {
165- conf : currentPrice . conf ,
166- price : currentPrice . price ,
167- publishTime : currentPrice . publishTime ,
168- } ;
169-
170- this . latestPriceInfo . set ( priceFeed . id , priceInfo ) ;
171- this . lastUpdated = Date . now ( ) as TimestampInMs ;
172- }
173-
17474 getLatestPriceInfo ( priceId : string ) : PriceInfo | undefined {
17575 return this . latestPriceInfo . get ( priceId ) ;
17676 }
0 commit comments