@pythnetwork/hermes-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Pyth Contributors.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Hermes Client
2
+
3
+ [Pyth Network](https://pyth.network/) provides real-time pricing data in a variety of asset classes, including cryptocurrency, equities, FX and commodities.
4
+ These prices are available either via HTTP or Streaming from [Hermes](/apps/hermes).
5
+ This library is a client for interacting with Hermes, allowing your application to consume Pyth real-time prices in on- and off-chain Javascript/Typescript applications.
6
+
7
+ ## Installation
8
+
9
+ ### npm
10
+
11
+ ```
12
+ $ npm install --save @pythnetwork/hermes-client
13
+ ```
14
+
15
+ ### Yarn
16
+
17
+ ```
18
+ $ yarn add @pythnetwork/hermes-client
19
+ ```
20
+
21
+ ## Quickstart
22
+
23
+ Typical usage of the connection is along the following lines:
24
+
25
+ ```typescript
26
+ const connection = new HermesClient("https://hermes.pyth.network", {}); // See Hermes endpoints section below for other endpoints
27
+
28
+ const priceIds = [
29
+ // You can find the ids of prices at https://pyth.network/developers/price-feed-ids
30
+ "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC/USD price id
31
+ "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", // ETH/USD price id
32
+ ];
33
+
34
+ // Get price feeds
35
+ const priceFeeds = await connection.getPriceFeeds("btc", "crypto");
36
+ console.log(priceFeeds);
37
+
38
+ // Latest price updates
39
+ const priceUpdates = await connection.getLatestPriceUpdates(priceIds);
40
+ console.log(priceUpdates);
41
+ ```
42
+
43
+ `HermesClient` also allows subscribing to real-time price updates over a Server-Sent Events (SSE) connection:
44
+
45
+ ```typescript
46
+ // Streaming price updates
47
+ const eventSource = await connection.getStreamingPriceUpdates(priceIds);
48
+
49
+ eventSource.onmessage = (event) => {
50
+ console.log("Received price update:", event.data);
51
+ };
52
+
53
+ eventSource.onerror = (error) => {
54
+ console.error("Error receiving updates:", error);
55
+ eventSource.close();
56
+ };
57
+
58
+ await sleep(5000);
59
+
60
+ // To stop listening to the updates, you can call eventSource.close();
61
+ console.log("Closing event source.");
62
+ eventSource.close();
63
+ ```
64
+
65
+ ### On-chain Applications
66
+
67
+ On-chain applications will need to submit the price updates returned by Hermes to the Pyth contract on their blockchain.
68
+ By default, these updates are returned as binary data and is serialized as either a base64 string or a hex string depending on the chosen encoding. This binary data will need to be submitted to the Pyth contract.
69
+
70
+ ### Examples
71
+
72
+ The [HermesClient](./src/examples/HermesClient.ts) example demonstrates both the examples above.
73
+ You can run it with `npm run example`.
74
+ A full command that prints BTC and ETH price feeds, in the testnet network, looks like so:
75
+
76
+ ```bash
77
+ npm run example -- \
78
+ --endpoint https://hermes.pyth.network \
79
+ --price-ids \
80
+ 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 \
81
+ 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
82
+ ```
83
+
84
+ ## Hermes endpoints
85
+
86
+ Pyth offers a free public endpoint at [https://hermes.pyth.network](https://hermes.pyth.network). However, it is
87
+ recommended to obtain a private endpoint from one of the Hermes RPC providers for more reliability. You can find more
88
+ information about Hermes RPC providers
89
+ [here](https://docs.pyth.network/documentation/pythnet-price-feeds/hermes#public-endpoint).
@@ -0,0 +1,107 @@
1
+ import EventSource from "eventsource";
2
+ import { schemas } from "./zodSchemas";
3
+ import { z } from "zod";
4
+ export type AssetType = z.infer<typeof schemas.AssetType>;
5
+ export type BinaryPriceUpdate = z.infer<typeof schemas.BinaryPriceUpdate>;
6
+ export type EncodingType = z.infer<typeof schemas.EncodingType>;
7
+ export type PriceFeedMetadata = z.infer<typeof schemas.PriceFeedMetadata>;
8
+ export type PriceIdInput = z.infer<typeof schemas.PriceIdInput>;
9
+ export type PriceUpdate = z.infer<typeof schemas.PriceUpdate>;
10
+ export type UnixTimestamp = number;
11
+ export type DurationInSeconds = number;
12
+ export type HexString = string;
13
+ export type DurationInMs = number;
14
+ export type HermesClientConfig = {
15
+ timeout?: DurationInMs;
16
+ /**
17
+ * Number of times a HTTP request will be retried before the API returns a failure. Default: 3.
18
+ *
19
+ * The connection uses exponential back-off for the delay between retries. However,
20
+ * it will timeout regardless of the retries at the configured `timeout` time.
21
+ */
22
+ httpRetries?: number;
23
+ };
24
+ export declare class HermesClient {
25
+ private baseURL;
26
+ private timeout;
27
+ private httpRetries;
28
+ /**
29
+ * Constructs a new Connection.
30
+ *
31
+ * @param endpoint endpoint URL to the price service. Example: https://website/example/
32
+ * @param config Optional HermesClientConfig for custom configurations.
33
+ */
34
+ constructor(endpoint: string, config?: HermesClientConfig);
35
+ private httpRequest;
36
+ /**
37
+ * Fetch the set of available price feeds.
38
+ * This endpoint can be filtered by asset type and query string.
39
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
40
+ *
41
+ * @param options Optional parameters:
42
+ * - 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".
43
+ * - filter: String to filter the price feeds by asset type. Possible values are "crypto", "equity", "fx", "metal", "rates". Filter string is case insensitive.
44
+ *
45
+ * @returns Array of PriceFeedMetadata objects.
46
+ */
47
+ getPriceFeeds(options?: {
48
+ query?: string;
49
+ filter?: string;
50
+ }): Promise<PriceFeedMetadata[]>;
51
+ /**
52
+ * Fetch the latest price updates for a set of price feed IDs.
53
+ * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update using the options object.
54
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
55
+ *
56
+ * @param ids Array of hex-encoded price feed IDs for which updates are requested.
57
+ * @param options Optional parameters:
58
+ * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex.
59
+ * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false.
60
+ *
61
+ * @returns PriceUpdate object containing the latest updates.
62
+ */
63
+ getLatestPriceUpdates(ids: HexString[], options?: {
64
+ encoding?: EncodingType;
65
+ parsed?: boolean;
66
+ }): Promise<PriceUpdate>;
67
+ /**
68
+ * Fetch the price updates for a set of price feed IDs at a given timestamp.
69
+ * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
70
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
71
+ *
72
+ * @param publishTime Unix timestamp in seconds.
73
+ * @param ids Array of hex-encoded price feed IDs for which updates are requested.
74
+ * @param options Optional parameters:
75
+ * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex.
76
+ * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false.
77
+ *
78
+ * @returns PriceUpdate object containing the updates at the specified timestamp.
79
+ */
80
+ getPriceUpdatesAtTimestamp(publishTime: UnixTimestamp, ids: HexString[], options?: {
81
+ encoding?: EncodingType;
82
+ parsed?: boolean;
83
+ }): Promise<PriceUpdate>;
84
+ /**
85
+ * Fetch streaming price updates for a set of price feed IDs.
86
+ * This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates,
87
+ * and if unordered updates or only benchmark updates are allowed.
88
+ * This will return an EventSource that can be used to listen to streaming updates.
89
+ * If an invalid hex-encoded ID is passed, it will throw an error.
90
+ *
91
+ *
92
+ * @param ids Array of hex-encoded price feed IDs for which streaming updates are requested.
93
+ * @param encoding Optional encoding type. If specified, updates are returned in the specified encoding. Default is hex.
94
+ * @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
95
+ * @param allow_unordered Optional boolean to specify if unordered updates are allowed to be included in the stream. Default is false.
96
+ * @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.
97
+ * @returns An EventSource instance for receiving streaming updates.
98
+ */
99
+ getPriceUpdatesStream(ids: HexString[], options?: {
100
+ encoding?: EncodingType;
101
+ parsed?: boolean;
102
+ allow_unordered?: boolean;
103
+ benchmarks_only?: boolean;
104
+ }): Promise<EventSource>;
105
+ private appendUrlSearchParams;
106
+ }
107
+ //# sourceMappingURL=HermesClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HermesClient.d.ts","sourceRoot":"","sources":["../src/HermesClient.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;AAC1D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAC1E,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAC1E,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC;AAK9D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AACnC,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACvC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAE/B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,WAAW,CAAS;IAE5B;;;;;OAKG;gBACS,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB;YAM3C,WAAW;IAqCzB;;;;;;;;;;OAUG;IACG,aAAa,CAAC,OAAO,CAAC,EAAE;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAWhC;;;;;;;;;;;OAWG;IACG,qBAAqB,CACzB,GAAG,EAAE,SAAS,EAAE,EAChB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,GACA,OAAO,CAAC,WAAW,CAAC;IAavB;;;;;;;;;;;;OAYG;IACG,0BAA0B,CAC9B,WAAW,EAAE,aAAa,EAC1B,GAAG,EAAE,SAAS,EAAE,EAChB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,GACA,OAAO,CAAC,WAAW,CAAC;IAavB;;;;;;;;;;;;;;OAcG;IACG,qBAAqB,CACzB,GAAG,EAAE,SAAS,EAAE,EAChB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,YAAY,CAAC;QACxB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,GACA,OAAO,CAAC,WAAW,CAAC;IAavB,OAAO,CAAC,qBAAqB;CAU9B"}
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HermesClient = void 0;
7
+ const eventsource_1 = __importDefault(require("eventsource"));
8
+ const zodSchemas_1 = require("./zodSchemas");
9
+ const DEFAULT_TIMEOUT = 5000;
10
+ const DEFAULT_HTTP_RETRIES = 3;
11
+ class HermesClient {
12
+ baseURL;
13
+ timeout;
14
+ httpRetries;
15
+ /**
16
+ * Constructs a new Connection.
17
+ *
18
+ * @param endpoint endpoint URL to the price service. Example: https://website/example/
19
+ * @param config Optional HermesClientConfig for custom configurations.
20
+ */
21
+ constructor(endpoint, config) {
22
+ this.baseURL = endpoint;
23
+ this.timeout = config?.timeout ?? DEFAULT_TIMEOUT;
24
+ this.httpRetries = config?.httpRetries ?? DEFAULT_HTTP_RETRIES;
25
+ }
26
+ async httpRequest(url, schema, options, retries = this.httpRetries, backoff = 100 + Math.floor(Math.random() * 100), // Adding randomness to the initial backoff to avoid "thundering herd" scenario where a lot of clients that get kicked off all at the same time (say some script or something) and fail to connect all retry at exactly the same time too
27
+ externalAbortController) {
28
+ const controller = externalAbortController ?? new AbortController();
29
+ const { signal } = controller;
30
+ options = { ...options, signal }; // Merge any existing options with the signal
31
+ // Set a timeout to abort the request if it takes too long
32
+ const timeout = setTimeout(() => controller.abort(), this.timeout);
33
+ try {
34
+ const response = await fetch(url, options);
35
+ clearTimeout(timeout); // Clear the timeout if the request completes in time
36
+ if (!response.ok) {
37
+ throw new Error(`HTTP error! status: ${response.status}`);
38
+ }
39
+ const data = await response.json();
40
+ return schema.parse(data);
41
+ }
42
+ catch (error) {
43
+ clearTimeout(timeout);
44
+ if (retries > 0 &&
45
+ !(error instanceof Error && error.name === "AbortError")) {
46
+ // Wait for a backoff period before retrying
47
+ await new Promise((resolve) => setTimeout(resolve, backoff));
48
+ return this.httpRequest(url, schema, options, retries - 1, backoff * 2); // Exponential backoff
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ /**
54
+ * Fetch the set of available price feeds.
55
+ * This endpoint can be filtered by asset type and query string.
56
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
57
+ *
58
+ * @param options Optional parameters:
59
+ * - 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".
60
+ * - filter: String to filter the price feeds by asset type. Possible values are "crypto", "equity", "fx", "metal", "rates". Filter string is case insensitive.
61
+ *
62
+ * @returns Array of PriceFeedMetadata objects.
63
+ */
64
+ async getPriceFeeds(options) {
65
+ const url = new URL("/v2/price_feeds", this.baseURL);
66
+ if (options) {
67
+ this.appendUrlSearchParams(url, options);
68
+ }
69
+ return await this.httpRequest(url.toString(), zodSchemas_1.schemas.PriceFeedMetadata.array());
70
+ }
71
+ /**
72
+ * Fetch the latest price updates for a set of price feed IDs.
73
+ * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update using the options object.
74
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
75
+ *
76
+ * @param ids Array of hex-encoded price feed IDs for which updates are requested.
77
+ * @param options Optional parameters:
78
+ * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex.
79
+ * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false.
80
+ *
81
+ * @returns PriceUpdate object containing the latest updates.
82
+ */
83
+ async getLatestPriceUpdates(ids, options) {
84
+ const url = new URL(`${this.baseURL}/v2/updates/price/latest`);
85
+ for (const id of ids) {
86
+ url.searchParams.append("ids[]", id);
87
+ }
88
+ if (options) {
89
+ this.appendUrlSearchParams(url, options);
90
+ }
91
+ return this.httpRequest(url.toString(), zodSchemas_1.schemas.PriceUpdate);
92
+ }
93
+ /**
94
+ * Fetch the price updates for a set of price feed IDs at a given timestamp.
95
+ * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
96
+ * This will throw an error if there is a network problem or the price service returns a non-ok response.
97
+ *
98
+ * @param publishTime Unix timestamp in seconds.
99
+ * @param ids Array of hex-encoded price feed IDs for which updates are requested.
100
+ * @param options Optional parameters:
101
+ * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex.
102
+ * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false.
103
+ *
104
+ * @returns PriceUpdate object containing the updates at the specified timestamp.
105
+ */
106
+ async getPriceUpdatesAtTimestamp(publishTime, ids, options) {
107
+ const url = new URL(`${this.baseURL}/v2/updates/price/${publishTime}`);
108
+ for (const id of ids) {
109
+ url.searchParams.append("ids[]", id);
110
+ }
111
+ if (options) {
112
+ this.appendUrlSearchParams(url, options);
113
+ }
114
+ return this.httpRequest(url.toString(), zodSchemas_1.schemas.PriceUpdate);
115
+ }
116
+ /**
117
+ * Fetch streaming price updates for a set of price feed IDs.
118
+ * This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates,
119
+ * and if unordered updates or only benchmark updates are allowed.
120
+ * This will return an EventSource that can be used to listen to streaming updates.
121
+ * If an invalid hex-encoded ID is passed, it will throw an error.
122
+ *
123
+ *
124
+ * @param ids Array of hex-encoded price feed IDs for which streaming updates are requested.
125
+ * @param encoding Optional encoding type. If specified, updates are returned in the specified encoding. Default is hex.
126
+ * @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
127
+ * @param allow_unordered Optional boolean to specify if unordered updates are allowed to be included in the stream. Default is false.
128
+ * @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.
129
+ * @returns An EventSource instance for receiving streaming updates.
130
+ */
131
+ async getPriceUpdatesStream(ids, options) {
132
+ const url = new URL("/v2/updates/price/stream", this.baseURL);
133
+ ids.forEach((id) => {
134
+ url.searchParams.append("ids[]", id);
135
+ });
136
+ if (options) {
137
+ this.appendUrlSearchParams(url, options);
138
+ }
139
+ return new eventsource_1.default(url.toString());
140
+ }
141
+ appendUrlSearchParams(url, params) {
142
+ Object.entries(params).forEach(([key, value]) => {
143
+ if (value !== undefined) {
144
+ url.searchParams.append(key, String(value));
145
+ }
146
+ });
147
+ }
148
+ }
149
+ exports.HermesClient = HermesClient;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HermesClient.d.ts","sourceRoot":"","sources":["../../src/examples/HermesClient.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const yargs_1 = __importDefault(require("yargs"));
7
+ const helpers_1 = require("yargs/helpers");
8
+ const HermesClient_1 = require("../HermesClient");
9
+ function sleep(ms) {
10
+ return new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+ const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
13
+ .option("endpoint", {
14
+ description: "Endpoint URL for the price service. e.g: https://endpoint/example",
15
+ type: "string",
16
+ required: true,
17
+ })
18
+ .option("price-ids", {
19
+ description: "Space separated price feed ids (in hex without leading 0x) to fetch." +
20
+ " e.g: f9c0172ba10dfa4d19088d...",
21
+ type: "array",
22
+ required: true,
23
+ })
24
+ .help()
25
+ .alias("help", "h")
26
+ .parserConfiguration({
27
+ "parse-numbers": false,
28
+ })
29
+ .parseSync();
30
+ async function run() {
31
+ const connection = new HermesClient_1.HermesClient(argv.endpoint);
32
+ const priceIds = argv.priceIds;
33
+ // Get price feeds
34
+ const priceFeeds = await connection.getPriceFeeds({
35
+ query: "btc",
36
+ filter: "crypto",
37
+ });
38
+ console.log(priceFeeds);
39
+ // Latest price updates
40
+ const priceUpdates = await connection.getLatestPriceUpdates(priceIds);
41
+ console.log(priceUpdates);
42
+ // Streaming price updates
43
+ const eventSource = await connection.getPriceUpdatesStream(priceIds);
44
+ eventSource.onmessage = (event) => {
45
+ console.log("Received price update:", event.data);
46
+ };
47
+ eventSource.onerror = (error) => {
48
+ console.error("Error receiving updates:", error);
49
+ eventSource.close();
50
+ };
51
+ await sleep(5000);
52
+ // To stop listening to the updates, you can call eventSource.close();
53
+ console.log("Closing event source.");
54
+ eventSource.close();
55
+ }
56
+ run();