Skip to content

Commit 512f637

Browse files
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
1 parent 48690ec commit 512f637

File tree

5 files changed

+63
-8
lines changed

5 files changed

+63
-8
lines changed

apps/hermes/client/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/hermes-client",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "Pyth Hermes Client",
55
"author": {
66
"name": "Pyth Data Association"

apps/hermes/client/js/src/HermesClient.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type EncodingType = z.infer<typeof schemas.EncodingType>;
1010
export type PriceFeedMetadata = z.infer<typeof schemas.PriceFeedMetadata>;
1111
export type PriceIdInput = z.infer<typeof schemas.PriceIdInput>;
1212
export type PriceUpdate = z.infer<typeof schemas.PriceUpdate>;
13+
export type TwapsResponse = z.infer<typeof schemas.TwapsResponse>;
1314
export type PublisherCaps = z.infer<
1415
typeof schemas.LatestPublisherStakeCapsUpdateDataResponse
1516
>;
@@ -80,7 +81,12 @@ export class HermesClient {
8081
const response = await fetch(url, options);
8182
clearTimeout(timeout); // Clear the timeout if the request completes in time
8283
if (!response.ok) {
83-
throw new Error(`HTTP error! status: ${response.status}`);
84+
const errorBody = await response.text();
85+
throw new Error(
86+
`HTTP error! status: ${response.status}${
87+
errorBody ? `, body: ${errorBody}` : ""
88+
}`
89+
);
8490
}
8591
const data = await response.json();
8692
return schema.parse(data);
@@ -258,6 +264,46 @@ export class HermesClient {
258264
return new EventSource(url.toString(), { headers: this.headers });
259265
}
260266

267+
/**
268+
* Fetch the latest TWAP (time weighted average price) for a set of price feed IDs.
269+
* This endpoint can be customized by specifying the encoding type and whether the results should also return the calculated TWAP using the options object.
270+
* This will throw an error if there is a network problem or the price service returns a non-ok response.
271+
*
272+
* @param ids Array of hex-encoded price feed IDs for which updates are requested.
273+
* @param window_seconds The time window in seconds over which to calculate the TWAP, ending at the current time.
274+
* 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).
275+
* @param options Optional parameters:
276+
* - encoding: Encoding type. If specified, return the TWAP binary data in the encoding specified by the encoding parameter. Default is hex.
277+
* - parsed: Boolean to specify if the calculated TWAP should be included in the response. Default is false.
278+
* - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false.
279+
*
280+
* @returns TwapsResponse object containing the latest TWAPs.
281+
*/
282+
async getLatestTwaps(
283+
ids: HexString[],
284+
window_seconds: number,
285+
options?: {
286+
encoding?: EncodingType;
287+
parsed?: boolean;
288+
ignoreInvalidPriceIds?: boolean;
289+
}
290+
): Promise<TwapsResponse> {
291+
const url = new URL(
292+
`v2/updates/twap/${window_seconds}/latest`,
293+
this.baseURL
294+
);
295+
for (const id of ids) {
296+
url.searchParams.append("ids[]", id);
297+
}
298+
299+
if (options) {
300+
const transformedOptions = camelToSnakeCaseObject(options);
301+
this.appendUrlSearchParams(url, transformedOptions);
302+
}
303+
304+
return this.httpRequest(url.toString(), schemas.TwapsResponse);
305+
}
306+
261307
private appendUrlSearchParams(
262308
url: URL,
263309
params: Record<string, string | boolean>

apps/hermes/client/js/src/examples/HermesClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,25 @@ async function run() {
6161
const priceIds = argv.priceIds as string[];
6262

6363
// Get price feeds
64+
console.log(`Price feeds matching "btc" with asset type "crypto":`);
6465
const priceFeeds = await connection.getPriceFeeds({
6566
query: "btc",
6667
filter: "crypto",
6768
});
6869
console.log(priceFeeds);
6970

7071
// Latest price updates
72+
console.log(`Latest price updates for price IDs ${priceIds}:`);
7173
const priceUpdates = await connection.getLatestPriceUpdates(priceIds);
7274
console.log(priceUpdates);
7375

76+
// Get the latest 5 second TWAPs
77+
console.log(`Latest 5 second TWAPs for price IDs ${priceIds}`);
78+
const twapUpdates = await connection.getLatestTwaps(priceIds, 5);
79+
console.log(twapUpdates);
80+
7481
// Streaming price updates
82+
console.log(`Streaming latest prices for price IDs ${priceIds}...`);
7583
const eventSource = await connection.getPriceUpdatesStream(priceIds, {
7684
encoding: "hex",
7785
parsed: true,

target_chains/solana/sdk/js/pyth_solana_receiver/examples/post_twap_update.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ async function main() {
4040
// Post the TWAP updates to ephemeral accounts, one per price feed
4141
await transactionBuilder.addPostTwapUpdates(twapUpdateData);
4242
console.log(
43-
"The SOL/USD TWAP update will get posted to:",
44-
transactionBuilder.getTwapUpdateAccount(SOL_PRICE_FEED_ID).toBase58()
43+
`\nThe SOL/USD TWAP update will get posted to: ${transactionBuilder
44+
.getTwapUpdateAccount(SOL_PRICE_FEED_ID)
45+
.toBase58()}\n`
4546
);
4647

4748
await transactionBuilder.addTwapConsumerInstructions(
@@ -69,10 +70,10 @@ async function main() {
6970
async function getTwapUpdateData() {
7071
const hermesConnection = new HermesClient("https://hermes.pyth.network/", {});
7172

72-
// Request TWAP updates for the last hour (3600 seconds)
73-
const response = await hermesConnection.getLatestTwapUpdates(
73+
// Request TWAP updates with a 5 minute window (300 seconds)
74+
const response = await hermesConnection.getLatestTwaps(
7475
[SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID],
75-
3600,
76+
300,
7677
{ encoding: "base64" }
7778
);
7879

target_chains/solana/sdk/js/pyth_solana_receiver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/pyth-solana-receiver",
3-
"version": "0.9.0",
3+
"version": "0.9.1",
44
"description": "Pyth solana receiver SDK",
55
"homepage": "https://pyth.network",
66
"main": "lib/index.js",

0 commit comments

Comments
 (0)