From 512f6375bbedf78ff8e0d651c606701c0f7fb2ac Mon Sep 17 00:00:00 2001 From: Tejas Badadare <17058023+tejasbadadare@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:51:53 -0800 Subject: [PATCH] feat(HermesClient): Add TWAP endpoint support (#2165) * add latest twap endpoint * refactor: add logs, clean up * fix: update HermesClient usage in post_twap_update * fix: fix window_seconds arg, better log --- apps/hermes/client/js/package.json | 2 +- apps/hermes/client/js/src/HermesClient.ts | 48 ++++++++++++++++++- .../client/js/src/examples/HermesClient.ts | 8 ++++ .../examples/post_twap_update.ts | 11 +++-- .../sdk/js/pyth_solana_receiver/package.json | 2 +- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/apps/hermes/client/js/package.json b/apps/hermes/client/js/package.json index f2ac6fa201..3686b8d034 100644 --- a/apps/hermes/client/js/package.json +++ b/apps/hermes/client/js/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/hermes-client", - "version": "1.2.0", + "version": "1.3.0", "description": "Pyth Hermes Client", "author": { "name": "Pyth Data Association" diff --git a/apps/hermes/client/js/src/HermesClient.ts b/apps/hermes/client/js/src/HermesClient.ts index 42f5ba34ef..79635b0e5e 100644 --- a/apps/hermes/client/js/src/HermesClient.ts +++ b/apps/hermes/client/js/src/HermesClient.ts @@ -10,6 +10,7 @@ export type EncodingType = z.infer; export type PriceFeedMetadata = z.infer; export type PriceIdInput = z.infer; export type PriceUpdate = z.infer; +export type TwapsResponse = z.infer; export type PublisherCaps = z.infer< typeof schemas.LatestPublisherStakeCapsUpdateDataResponse >; @@ -80,7 +81,12 @@ export class HermesClient { const response = await fetch(url, options); clearTimeout(timeout); // Clear the timeout if the request completes in time if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + const errorBody = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}${ + errorBody ? `, body: ${errorBody}` : "" + }` + ); } const data = await response.json(); return schema.parse(data); @@ -258,6 +264,46 @@ export class HermesClient { return new EventSource(url.toString(), { headers: this.headers }); } + /** + * Fetch the latest TWAP (time weighted average price) for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the calculated TWAP using the options object. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param window_seconds The time window in seconds over which to calculate the TWAP, ending at the current time. + * For example, a value of 300 would return the most recent 5 minute TWAP. Must be greater than 0 and less than or equal to 600 seconds (10 minutes). + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the TWAP binary data in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the calculated TWAP should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns TwapsResponse object containing the latest TWAPs. + */ + async getLatestTwaps( + ids: HexString[], + window_seconds: number, + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL( + `v2/updates/twap/${window_seconds}/latest`, + this.baseURL + ); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.TwapsResponse); + } + private appendUrlSearchParams( url: URL, params: Record diff --git a/apps/hermes/client/js/src/examples/HermesClient.ts b/apps/hermes/client/js/src/examples/HermesClient.ts index c1f3959ec8..20d9e1f0e5 100644 --- a/apps/hermes/client/js/src/examples/HermesClient.ts +++ b/apps/hermes/client/js/src/examples/HermesClient.ts @@ -61,6 +61,7 @@ async function run() { const priceIds = argv.priceIds as string[]; // Get price feeds + console.log(`Price feeds matching "btc" with asset type "crypto":`); const priceFeeds = await connection.getPriceFeeds({ query: "btc", filter: "crypto", @@ -68,10 +69,17 @@ async function run() { console.log(priceFeeds); // Latest price updates + console.log(`Latest price updates for price IDs ${priceIds}:`); const priceUpdates = await connection.getLatestPriceUpdates(priceIds); console.log(priceUpdates); + // Get the latest 5 second TWAPs + console.log(`Latest 5 second TWAPs for price IDs ${priceIds}`); + const twapUpdates = await connection.getLatestTwaps(priceIds, 5); + console.log(twapUpdates); + // Streaming price updates + console.log(`Streaming latest prices for price IDs ${priceIds}...`); const eventSource = await connection.getPriceUpdatesStream(priceIds, { encoding: "hex", parsed: true, diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/examples/post_twap_update.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/examples/post_twap_update.ts index afe704ad1f..4109be8290 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/examples/post_twap_update.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/examples/post_twap_update.ts @@ -40,8 +40,9 @@ async function main() { // Post the TWAP updates to ephemeral accounts, one per price feed await transactionBuilder.addPostTwapUpdates(twapUpdateData); console.log( - "The SOL/USD TWAP update will get posted to:", - transactionBuilder.getTwapUpdateAccount(SOL_PRICE_FEED_ID).toBase58() + `\nThe SOL/USD TWAP update will get posted to: ${transactionBuilder + .getTwapUpdateAccount(SOL_PRICE_FEED_ID) + .toBase58()}\n` ); await transactionBuilder.addTwapConsumerInstructions( @@ -69,10 +70,10 @@ async function main() { async function getTwapUpdateData() { const hermesConnection = new HermesClient("https://hermes.pyth.network/", {}); - // Request TWAP updates for the last hour (3600 seconds) - const response = await hermesConnection.getLatestTwapUpdates( + // Request TWAP updates with a 5 minute window (300 seconds) + const response = await hermesConnection.getLatestTwaps( [SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID], - 3600, + 300, { encoding: "base64" } ); diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json index cb510cf492..ff826b26f4 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/pyth-solana-receiver", - "version": "0.9.0", + "version": "0.9.1", "description": "Pyth solana receiver SDK", "homepage": "https://pyth.network", "main": "lib/index.js",