1
- import {
2
- EncodingType ,
3
- HexString ,
4
- PriceFeed ,
5
- PriceFeedMetadataV2 ,
6
- PriceUpdate ,
7
- UnixTimestamp ,
8
- } from "@pythnetwork/price-service-sdk" ;
1
+ import { HexString , PriceFeed } from "@pythnetwork/price-service-sdk" ;
2
+ import axios , { AxiosInstance } from "axios" ;
3
+ import axiosRetry from "axios-retry" ;
9
4
import * as WebSocket from "isomorphic-ws" ;
10
5
import { Logger } from "ts-log" ;
11
6
import { ResilientWebSocket } from "./ResillientWebSocket" ;
12
7
import { makeWebsocketUrl , removeLeading0xIfExists } from "./utils" ;
13
- import EventSource from "eventsource" ;
14
8
15
9
export type DurationInMs = number ;
16
10
@@ -65,12 +59,14 @@ type ServerMessage = ServerResponse | ServerPriceUpdate;
65
59
export type PriceFeedUpdateCallback = ( priceFeed : PriceFeed ) => void ;
66
60
67
61
export class PriceServiceConnection {
68
- private baseURL : string ;
69
- private timeout : DurationInMs ;
62
+ private httpClient : AxiosInstance ;
63
+
70
64
private priceFeedCallbacks : Map < HexString , Set < PriceFeedUpdateCallback > > ;
71
65
private wsClient : undefined | ResilientWebSocket ;
72
66
private wsEndpoint : undefined | string ;
67
+
73
68
private logger : Logger ;
69
+
74
70
private priceFeedRequestConfig : PriceFeedRequestConfig ;
75
71
76
72
/**
@@ -87,8 +83,14 @@ export class PriceServiceConnection {
87
83
* @param config Optional PriceServiceConnectionConfig for custom configurations.
88
84
*/
89
85
constructor ( endpoint : string , config ?: PriceServiceConnectionConfig ) {
90
- this . baseURL = endpoint ;
91
- this . timeout = config ?. timeout || 5000 ;
86
+ this . httpClient = axios . create ( {
87
+ baseURL : endpoint ,
88
+ timeout : config ?. timeout || 5000 ,
89
+ } ) ;
90
+ axiosRetry ( this . httpClient , {
91
+ retries : config ?. httpRetries || 3 ,
92
+ retryDelay : axiosRetry . exponentialDelay ,
93
+ } ) ;
92
94
93
95
this . priceFeedRequestConfig = {
94
96
binary : config ?. priceFeedRequestConfig ?. binary ,
@@ -127,166 +129,6 @@ export class PriceServiceConnection {
127
129
this . wsEndpoint = makeWebsocketUrl ( endpoint ) ;
128
130
}
129
131
130
- private async httpRequest (
131
- url : string ,
132
- options ?: RequestInit ,
133
- retries = 3 ,
134
- backoff = 300
135
- ) : Promise < any > {
136
- const controller = new AbortController ( ) ;
137
- const { signal } = controller ;
138
- options = { ...options , signal } ; // Merge any existing options with the signal
139
-
140
- // Set a timeout to abort the request if it takes too long
141
- const timeout = setTimeout ( ( ) => controller . abort ( ) , this . timeout ) ;
142
-
143
- try {
144
- const response = await fetch ( url , options ) ;
145
- clearTimeout ( timeout ) ; // Clear the timeout if the request completes in time
146
- if ( ! response . ok ) {
147
- throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
148
- }
149
- return await response . json ( ) ;
150
- } catch ( error ) {
151
- clearTimeout ( timeout ) ;
152
- if (
153
- retries > 0 &&
154
- ! ( error instanceof Error && error . name === "AbortError" )
155
- ) {
156
- // Wait for a backoff period before retrying
157
- await new Promise ( ( resolve ) => setTimeout ( resolve , backoff ) ) ;
158
- return this . httpRequest ( url , options , retries - 1 , backoff * 2 ) ; // Exponential backoff
159
- }
160
- if ( error instanceof Error ) {
161
- this . logger . error ( "HTTP Request Failed" , error ) ;
162
- throw error ;
163
- } else {
164
- // If the caught error is not an instance of Error, handle it as an unknown error.
165
- this . logger . error ( "An unknown error occurred" , error ) ;
166
- throw new Error ( "An unknown error occurred" ) ;
167
- }
168
- }
169
- }
170
-
171
- /**
172
- * Fetch the set of available price feeds.
173
- * This endpoint can be filtered by asset type and query string.
174
- * This will throw an axios error if there is a network problem or the price service returns a non-ok response.
175
- *
176
- * @param query Optional query string to filter the price feeds. If provided, the results will be filtered to all price feeds whose symbol contains the query string. Query string is case insensitive. Example : bitcoin
177
- * @param filter Optional filter string to filter the price feeds. If provided, the results will be filtered by asset type. Possible values are crypto, equity, fx, metal, rates. Filter string is case insensitive. Available values : crypto, fx, equity, metals, rates
178
- * @returns Array of hex-encoded price ids.
179
- */
180
- async getV2PriceFeeds (
181
- query ?: string ,
182
- filter ?: string
183
- ) : Promise < PriceFeedMetadataV2 [ ] > {
184
- const url = new URL ( `${ this . baseURL } /v2/price_feeds` ) ;
185
- if ( query ) {
186
- url . searchParams . append ( "query" , query ) ;
187
- }
188
- if ( filter ) {
189
- url . searchParams . append ( "filter" , filter ) ;
190
- }
191
- return await this . httpRequest ( url . toString ( ) ) ;
192
- }
193
-
194
- /**
195
- * Fetch the latest price updates for a set of price feed IDs.
196
- * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
197
- * This will throw an axios error if there is a network problem or the price service returns a non-ok response.
198
- *
199
- * @param ids Array of hex-encoded price feed IDs for which updates are requested.
200
- * @param encoding Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is hex.
201
- * @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
202
- * @returns Array of PriceFeed objects containing the latest updates.
203
- */
204
- async getV2LatestPriceUpdates (
205
- ids : HexString [ ] ,
206
- encoding ?: EncodingType ,
207
- parsed ?: boolean
208
- ) : Promise < PriceUpdate [ ] > {
209
- const url = new URL ( `${ this . baseURL } /v2/updates/price/latest` ) ;
210
- // Append parameters to the URL search parameters
211
- ids . forEach ( ( id ) => url . searchParams . append ( "ids[]" , id ) ) ;
212
- if ( encoding ) {
213
- url . searchParams . append ( "encoding" , encoding ) ;
214
- }
215
- if ( parsed !== undefined ) {
216
- url . searchParams . append ( "parsed" , String ( parsed ) ) ;
217
- }
218
- return await this . httpRequest ( url . toString ( ) ) ;
219
- }
220
-
221
- /**
222
- * Fetch the price updates for a set of price feed IDs at a given timestamp.
223
- * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
224
- * This will throw an axios error if there is a network problem or the price service returns a non-ok response.
225
- *
226
- * @param publishTime Unix timestamp in seconds.
227
- * @param ids Array of hex-encoded price feed IDs for which updates are requested.
228
- * @param encoding Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is hex.
229
- * @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
230
- * @returns Array of PriceFeed objects containing the latest updates.
231
- */
232
- async getV2TimestampPriceUpdates (
233
- publishTime : UnixTimestamp ,
234
- ids : HexString [ ] ,
235
- encoding ?: EncodingType ,
236
- parsed ?: boolean
237
- ) : Promise < PriceUpdate [ ] > {
238
- const url = new URL ( `${ this . baseURL } /v2/updates/price/${ publishTime } ` ) ;
239
- ids . forEach ( ( id ) => url . searchParams . append ( "ids[]" , id ) ) ;
240
- if ( encoding ) {
241
- url . searchParams . append ( "encoding" , encoding ) ;
242
- }
243
- if ( parsed !== undefined ) {
244
- url . searchParams . append ( "parsed" , String ( parsed ) ) ;
245
- }
246
- return await this . httpRequest ( url . toString ( ) ) ;
247
- }
248
-
249
- /**
250
- * Fetch streaming price updates for a set of price feed IDs.
251
- * This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates,
252
- * and if unordered updates or only benchmark updates are allowed.
253
- * This will return an EventSource that can be used to listen to streaming updates.
254
- *
255
- * @param ids Array of hex-encoded price feed IDs for which streaming updates are requested.
256
- * @param encoding Optional encoding type. If specified, updates are returned in the specified encoding. Default is hex.
257
- * @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
258
- * @param allow_unordered Optional boolean to specify if unordered updates are allowed to be included in the stream. Default is false.
259
- * @param benchmarks_only Optional boolean to specify if only benchmark prices that are the initial price updates at a given timestamp (i.e., prevPubTime != pubTime) should be returned. Default is false.
260
- * @returns An EventSource instance for receiving streaming updates.
261
- */
262
- async getV2StreamingPriceUpdates (
263
- ids : HexString [ ] ,
264
- encoding ?: EncodingType ,
265
- parsed ?: boolean ,
266
- allow_unordered ?: boolean ,
267
- benchmarks_only ?: boolean
268
- ) : Promise < EventSource > {
269
- const url = new URL ( "/v2/updates/price/stream" , this . baseURL ) ;
270
- ids . forEach ( ( id ) => {
271
- url . searchParams . append ( "ids[]" , id ) ;
272
- } ) ;
273
- const params = {
274
- encoding,
275
- parsed : parsed !== undefined ? String ( parsed ) : undefined ,
276
- allow_unordered :
277
- allow_unordered !== undefined ? String ( allow_unordered ) : undefined ,
278
- benchmarks_only :
279
- benchmarks_only !== undefined ? String ( benchmarks_only ) : undefined ,
280
- } ;
281
- Object . entries ( params ) . forEach ( ( [ key , value ] ) => {
282
- if ( value !== undefined ) {
283
- url . searchParams . append ( key , value ) ;
284
- }
285
- } ) ;
286
- const eventSource = new EventSource ( url . toString ( ) ) ;
287
- return eventSource ;
288
- }
289
-
290
132
/**
291
133
* Fetch Latest PriceFeeds of given price ids.
292
134
* This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price ids)
@@ -301,19 +143,15 @@ export class PriceServiceConnection {
301
143
return [ ] ;
302
144
}
303
145
304
- const url = new URL ( `${ this . baseURL } /api/latest_price_feeds` ) ;
305
- priceIds . forEach ( ( id ) => url . searchParams . append ( "ids[]" , id ) ) ;
306
- url . searchParams . append (
307
- "verbose" ,
308
- String ( this . priceFeedRequestConfig . verbose )
309
- ) ;
310
- url . searchParams . append (
311
- "binary" ,
312
- String ( this . priceFeedRequestConfig . binary )
313
- ) ;
314
-
315
- const priceFeedsJson = await this . httpRequest ( url . toString ( ) ) ;
316
- return priceFeedsJson . map ( ( priceFeedJson : any ) =>
146
+ const response = await this . httpClient . get ( "/api/latest_price_feeds" , {
147
+ params : {
148
+ ids : priceIds ,
149
+ verbose : this . priceFeedRequestConfig . verbose ,
150
+ binary : this . priceFeedRequestConfig . binary ,
151
+ } ,
152
+ } ) ;
153
+ const priceFeedsJson = response . data as any [ ] ;
154
+ return priceFeedsJson . map ( ( priceFeedJson ) =>
317
155
PriceFeed . fromJson ( priceFeedJson )
318
156
) ;
319
157
}
@@ -328,11 +166,12 @@ export class PriceServiceConnection {
328
166
* @returns Array of base64 encoded VAAs.
329
167
*/
330
168
async getLatestVaas ( priceIds : HexString [ ] ) : Promise < string [ ] > {
331
- const url = new URL ( `${ this . baseURL } /api/latest_vaas` ) ;
332
- priceIds . forEach ( ( id ) => url . searchParams . append ( "ids[]" , id ) ) ;
333
-
334
- const vaasJson = await this . httpRequest ( url . toString ( ) ) ;
335
- return vaasJson as string [ ] ;
169
+ const response = await this . httpClient . get ( "/api/latest_vaas" , {
170
+ params : {
171
+ ids : priceIds ,
172
+ } ,
173
+ } ) ;
174
+ return response . data ;
336
175
}
337
176
338
177
/**
@@ -351,12 +190,13 @@ export class PriceServiceConnection {
351
190
priceId : HexString ,
352
191
publishTime : EpochTimeStamp
353
192
) : Promise < [ string , EpochTimeStamp ] > {
354
- const url = new URL ( `${ this . baseURL } /api/get_vaa` ) ;
355
- url . searchParams . append ( "id" , priceId ) ;
356
- url . searchParams . append ( "publish_time" , publishTime . toString ( ) ) ;
357
-
358
- const responseJson = await this . httpRequest ( url . toString ( ) ) ;
359
- return [ responseJson . vaa , responseJson . publishTime ] ;
193
+ const response = await this . httpClient . get ( "/api/get_vaa" , {
194
+ params : {
195
+ id : priceId ,
196
+ publish_time : publishTime ,
197
+ } ,
198
+ } ) ;
199
+ return [ response . data . vaa , response . data . publishTime ] ;
360
200
}
361
201
362
202
/**
@@ -373,20 +213,16 @@ export class PriceServiceConnection {
373
213
priceId : HexString ,
374
214
publishTime : EpochTimeStamp
375
215
) : Promise < PriceFeed > {
376
- const url = new URL ( `${ this . baseURL } /api/get_price_feed` ) ;
377
- url . searchParams . append ( "id" , priceId ) ;
378
- url . searchParams . append ( "publish_time" , publishTime . toString ( ) ) ;
379
- url . searchParams . append (
380
- "verbose" ,
381
- String ( this . priceFeedRequestConfig . verbose )
382
- ) ;
383
- url . searchParams . append (
384
- "binary" ,
385
- String ( this . priceFeedRequestConfig . binary )
386
- ) ;
216
+ const response = await this . httpClient . get ( "/api/get_price_feed" , {
217
+ params : {
218
+ id : priceId ,
219
+ publish_time : publishTime ,
220
+ verbose : this . priceFeedRequestConfig . verbose ,
221
+ binary : this . priceFeedRequestConfig . binary ,
222
+ } ,
223
+ } ) ;
387
224
388
- const responseJson = await this . httpRequest ( url . toString ( ) ) ;
389
- return PriceFeed . fromJson ( responseJson ) ;
225
+ return PriceFeed . fromJson ( response . data ) ;
390
226
}
391
227
392
228
/**
@@ -396,10 +232,8 @@ export class PriceServiceConnection {
396
232
* @returns Array of hex-encoded price ids.
397
233
*/
398
234
async getPriceFeedIds ( ) : Promise < HexString [ ] > {
399
- const url = new URL ( `${ this . baseURL } /api/price_feed_ids` ) ;
400
-
401
- const responseJson = await this . httpRequest ( url . toString ( ) ) ;
402
- return responseJson as HexString [ ] ;
235
+ const response = await this . httpClient . get ( "/api/price_feed_ids" ) ;
236
+ return response . data ;
403
237
}
404
238
405
239
/**
0 commit comments