@drift-labs/sdk 2.156.0-beta.1 → 2.156.0-beta.3
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/VERSION +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.js +3 -1
- package/lib/browser/pyth/index.d.ts +1 -0
- package/lib/browser/pyth/index.js +3 -1
- package/lib/browser/pyth/pythLazerSubscriber.d.ts +89 -0
- package/lib/browser/pyth/pythLazerSubscriber.js +264 -0
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -1
- package/lib/node/pyth/index.d.ts +1 -0
- package/lib/node/pyth/index.d.ts.map +1 -1
- package/lib/node/pyth/index.js +3 -1
- package/lib/node/pyth/pythLazerSubscriber.d.ts +90 -0
- package/lib/node/pyth/pythLazerSubscriber.d.ts.map +1 -0
- package/lib/node/pyth/pythLazerSubscriber.js +264 -0
- package/package.json +1 -1
- package/src/pyth/pythLazerSubscriber.ts +50 -3
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.156.0-beta.
|
|
1
|
+
2.156.0-beta.3
|
package/lib/browser/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export * from './accounts/types';
|
|
|
33
33
|
export * from './addresses/pda';
|
|
34
34
|
export * from './adminClient';
|
|
35
35
|
export * from './assert/assert';
|
|
36
|
+
export { PythLazerSubscriber, PythLazerPriceFeedArray } from './pyth';
|
|
36
37
|
export * from './testClient';
|
|
37
38
|
export * from './user';
|
|
38
39
|
export * from './userConfig';
|
package/lib/browser/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.pyth = exports.PublicKey = exports.BN = exports.CustomizedCadenceBulkAccountLoader = exports.WebSocketDriftClientAccountSubscriberV2 = exports.WebSocketProgramAccountsSubscriberV2 = exports.WebSocketProgramUserAccountSubscriber = exports.WebSocketProgramAccountSubscriber = exports.WebSocketAccountSubscriberV2 = void 0;
|
|
20
|
+
exports.pyth = exports.PublicKey = exports.BN = exports.PythLazerSubscriber = exports.CustomizedCadenceBulkAccountLoader = exports.WebSocketDriftClientAccountSubscriberV2 = exports.WebSocketProgramAccountsSubscriberV2 = exports.WebSocketProgramUserAccountSubscriber = exports.WebSocketProgramAccountSubscriber = exports.WebSocketAccountSubscriberV2 = void 0;
|
|
21
21
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
22
22
|
Object.defineProperty(exports, "BN", { enumerable: true, get: function () { return anchor_1.BN; } });
|
|
23
23
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -62,6 +62,8 @@ __exportStar(require("./accounts/types"), exports);
|
|
|
62
62
|
__exportStar(require("./addresses/pda"), exports);
|
|
63
63
|
__exportStar(require("./adminClient"), exports);
|
|
64
64
|
__exportStar(require("./assert/assert"), exports);
|
|
65
|
+
var pyth_1 = require("./pyth");
|
|
66
|
+
Object.defineProperty(exports, "PythLazerSubscriber", { enumerable: true, get: function () { return pyth_1.PythLazerSubscriber; } });
|
|
65
67
|
__exportStar(require("./testClient"), exports);
|
|
66
68
|
__exportStar(require("./user"), exports);
|
|
67
69
|
__exportStar(require("./userConfig"), exports);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { WormholeCoreBridgeSolana, WORMHOLE_CORE_BRIDGE_SOLANA_IDL, PythSolanaReceiver, PriceUpdateAccount, } from './types';
|
|
2
2
|
export { DEFAULT_WORMHOLE_PROGRAM_ID, DEFAULT_RECEIVER_PROGRAM_ID, } from './constants';
|
|
3
3
|
export { getGuardianSetPda } from './utils';
|
|
4
|
+
export { PythLazerSubscriber, PythLazerPriceFeedArray, } from './pythLazerSubscriber';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getGuardianSetPda = exports.DEFAULT_RECEIVER_PROGRAM_ID = exports.DEFAULT_WORMHOLE_PROGRAM_ID = exports.WORMHOLE_CORE_BRIDGE_SOLANA_IDL = void 0;
|
|
3
|
+
exports.PythLazerSubscriber = exports.getGuardianSetPda = exports.DEFAULT_RECEIVER_PROGRAM_ID = exports.DEFAULT_WORMHOLE_PROGRAM_ID = exports.WORMHOLE_CORE_BRIDGE_SOLANA_IDL = void 0;
|
|
4
4
|
var types_1 = require("./types");
|
|
5
5
|
Object.defineProperty(exports, "WORMHOLE_CORE_BRIDGE_SOLANA_IDL", { enumerable: true, get: function () { return types_1.WORMHOLE_CORE_BRIDGE_SOLANA_IDL; } });
|
|
6
6
|
var constants_1 = require("./constants");
|
|
@@ -8,3 +8,5 @@ Object.defineProperty(exports, "DEFAULT_WORMHOLE_PROGRAM_ID", { enumerable: true
|
|
|
8
8
|
Object.defineProperty(exports, "DEFAULT_RECEIVER_PROGRAM_ID", { enumerable: true, get: function () { return constants_1.DEFAULT_RECEIVER_PROGRAM_ID; } });
|
|
9
9
|
var utils_1 = require("./utils");
|
|
10
10
|
Object.defineProperty(exports, "getGuardianSetPda", { enumerable: true, get: function () { return utils_1.getGuardianSetPda; } });
|
|
11
|
+
var pythLazerSubscriber_1 = require("./pythLazerSubscriber");
|
|
12
|
+
Object.defineProperty(exports, "PythLazerSubscriber", { enumerable: true, get: function () { return pythLazerSubscriber_1.PythLazerSubscriber; } });
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Channel } from '@pythnetwork/pyth-lazer-sdk';
|
|
3
|
+
import { DriftEnv } from '../config';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for a group of Pyth Lazer price feeds.
|
|
6
|
+
*/
|
|
7
|
+
export type PythLazerPriceFeedArray = {
|
|
8
|
+
/** Optional channel for update frequency (e.g., 'fixed_rate@200ms') */
|
|
9
|
+
channel?: Channel;
|
|
10
|
+
/** Array of Pyth Lazer price feed IDs to subscribe to */
|
|
11
|
+
priceFeedIds: number[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data.
|
|
15
|
+
* Automatically filters out non-stable feeds and handles reconnection logic.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PythLazerSubscriber {
|
|
18
|
+
private endpoints;
|
|
19
|
+
private token;
|
|
20
|
+
private priceFeedArrays;
|
|
21
|
+
private resubTimeoutMs;
|
|
22
|
+
private sdkLogging;
|
|
23
|
+
private static readonly SYMBOLS_API_URL;
|
|
24
|
+
private symbolsCache;
|
|
25
|
+
private pythLazerClient?;
|
|
26
|
+
feedIdChunkToPriceMessage: Map<string, string>;
|
|
27
|
+
feedIdToPrice: Map<number, number>;
|
|
28
|
+
feedIdHashToFeedIds: Map<string, number[]>;
|
|
29
|
+
subscriptionIdsToFeedIdsHash: Map<number, string>;
|
|
30
|
+
allSubscribedIds: number[];
|
|
31
|
+
timeoutId?: NodeJS.Timeout;
|
|
32
|
+
receivingData: boolean;
|
|
33
|
+
isUnsubscribing: boolean;
|
|
34
|
+
marketIndextoPriceFeedIdChunk: Map<number, number[]>;
|
|
35
|
+
marketIndextoPriceFeedId: Map<number, number>;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new PythLazerSubscriber instance.
|
|
38
|
+
* @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer
|
|
39
|
+
* @param token - Authentication token for Pyth Lazer API
|
|
40
|
+
* @param priceFeedArrays - Array of price feed configurations to subscribe to
|
|
41
|
+
* @param env - Drift environment (mainnet-beta, devnet, etc.)
|
|
42
|
+
* @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout
|
|
43
|
+
* @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
44
|
+
*/
|
|
45
|
+
constructor(endpoints: string[], token: string, priceFeedArrays: PythLazerPriceFeedArray[], env?: DriftEnv, resubTimeoutMs?: number, sdkLogging?: boolean);
|
|
46
|
+
private fetchSymbolsIfNeeded;
|
|
47
|
+
private filterStableFeeds;
|
|
48
|
+
/**
|
|
49
|
+
* Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds
|
|
50
|
+
* and establishes WebSocket connections for real-time price updates.
|
|
51
|
+
*/
|
|
52
|
+
subscribe(): Promise<void>;
|
|
53
|
+
protected setTimeout(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Unsubscribes from all Pyth Lazer price feeds and shuts down WebSocket connections.
|
|
56
|
+
*/
|
|
57
|
+
unsubscribe(): Promise<void>;
|
|
58
|
+
hash(arr: number[]): string;
|
|
59
|
+
/**
|
|
60
|
+
* Retrieves the latest Solana-format price message for a group of feed IDs.
|
|
61
|
+
* @param feedIds - Array of price feed IDs
|
|
62
|
+
* @returns Hex-encoded price message data, or undefined if not available
|
|
63
|
+
*/
|
|
64
|
+
getLatestPriceMessage(feedIds: number[]): Promise<string | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the latest Solana-format price message for a specific market.
|
|
67
|
+
* @param marketIndex - The market index to get price data for
|
|
68
|
+
* @returns Hex-encoded price message data, or undefined if not found
|
|
69
|
+
*/
|
|
70
|
+
getLatestPriceMessageForMarketIndex(marketIndex: number): Promise<string | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Gets the array of price feed IDs associated with a market index.
|
|
73
|
+
* @param marketIndex - The market index to look up
|
|
74
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
75
|
+
*/
|
|
76
|
+
getPriceFeedIdsFromMarketIndex(marketIndex: number): number[];
|
|
77
|
+
/**
|
|
78
|
+
* Gets the array of price feed IDs from a subscription hash.
|
|
79
|
+
* @param hash - The subscription hash
|
|
80
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
81
|
+
*/
|
|
82
|
+
getPriceFeedIdsFromHash(hash: string): number[];
|
|
83
|
+
/**
|
|
84
|
+
* Gets the current parsed price for a specific market index.
|
|
85
|
+
* @param marketIndex - The market index to get the price for
|
|
86
|
+
* @returns The price as a number, or undefined if not available
|
|
87
|
+
*/
|
|
88
|
+
getPriceFromMarketIndex(marketIndex: number): number | undefined;
|
|
89
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PythLazerSubscriber = void 0;
|
|
4
|
+
const pyth_lazer_sdk_1 = require("@pythnetwork/pyth-lazer-sdk");
|
|
5
|
+
const perpMarkets_1 = require("../constants/perpMarkets");
|
|
6
|
+
/**
|
|
7
|
+
* Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data.
|
|
8
|
+
* Automatically filters out non-stable feeds and handles reconnection logic.
|
|
9
|
+
*/
|
|
10
|
+
class PythLazerSubscriber {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new PythLazerSubscriber instance.
|
|
13
|
+
* @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer
|
|
14
|
+
* @param token - Authentication token for Pyth Lazer API
|
|
15
|
+
* @param priceFeedArrays - Array of price feed configurations to subscribe to
|
|
16
|
+
* @param env - Drift environment (mainnet-beta, devnet, etc.)
|
|
17
|
+
* @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout
|
|
18
|
+
* @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
19
|
+
*/
|
|
20
|
+
constructor(endpoints, token, priceFeedArrays, env = 'devnet', resubTimeoutMs = 2000, sdkLogging = false) {
|
|
21
|
+
this.endpoints = endpoints;
|
|
22
|
+
this.token = token;
|
|
23
|
+
this.priceFeedArrays = priceFeedArrays;
|
|
24
|
+
this.resubTimeoutMs = resubTimeoutMs;
|
|
25
|
+
this.sdkLogging = sdkLogging;
|
|
26
|
+
this.symbolsCache = null;
|
|
27
|
+
this.feedIdChunkToPriceMessage = new Map();
|
|
28
|
+
this.feedIdToPrice = new Map();
|
|
29
|
+
this.feedIdHashToFeedIds = new Map();
|
|
30
|
+
this.subscriptionIdsToFeedIdsHash = new Map();
|
|
31
|
+
this.allSubscribedIds = [];
|
|
32
|
+
this.receivingData = false;
|
|
33
|
+
this.isUnsubscribing = false;
|
|
34
|
+
this.marketIndextoPriceFeedIdChunk = new Map();
|
|
35
|
+
this.marketIndextoPriceFeedId = new Map();
|
|
36
|
+
const markets = perpMarkets_1.PerpMarkets[env].filter((market) => market.pythLazerId !== undefined);
|
|
37
|
+
this.allSubscribedIds = this.priceFeedArrays
|
|
38
|
+
.map((array) => array.priceFeedIds)
|
|
39
|
+
.flat();
|
|
40
|
+
for (const priceFeedIds of priceFeedArrays) {
|
|
41
|
+
const filteredMarkets = markets.filter((market) => priceFeedIds.priceFeedIds.includes(market.pythLazerId));
|
|
42
|
+
for (const market of filteredMarkets) {
|
|
43
|
+
this.marketIndextoPriceFeedIdChunk.set(market.marketIndex, priceFeedIds.priceFeedIds);
|
|
44
|
+
this.marketIndextoPriceFeedId.set(market.marketIndex, market.pythLazerId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async fetchSymbolsIfNeeded() {
|
|
49
|
+
if (this.symbolsCache !== null)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(PythLazerSubscriber.SYMBOLS_API_URL);
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw new Error(`HTTP ${response.status}`);
|
|
55
|
+
const symbols = await response.json();
|
|
56
|
+
this.symbolsCache = new Map();
|
|
57
|
+
for (const symbol of symbols) {
|
|
58
|
+
this.symbolsCache.set(symbol.pyth_lazer_id, {
|
|
59
|
+
name: symbol.name,
|
|
60
|
+
state: symbol.state,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn(`Failed to fetch Pyth Lazer symbols, proceeding with all feeds: ${error}`);
|
|
66
|
+
this.symbolsCache = new Map(); // Empty map = no filtering
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
filterStableFeeds(feedIds) {
|
|
70
|
+
if (this.symbolsCache === null || this.symbolsCache.size === 0) {
|
|
71
|
+
return feedIds; // No filtering if cache unavailable
|
|
72
|
+
}
|
|
73
|
+
return feedIds.filter((feedId) => {
|
|
74
|
+
const info = this.symbolsCache.get(feedId);
|
|
75
|
+
if (!info) {
|
|
76
|
+
console.warn(`Feed ID ${feedId} not found in symbols API, including anyway`);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (info.state !== 'stable') {
|
|
80
|
+
console.warn(`Removing feed ID ${feedId} (${info.name}) - state is "${info.state}", not "stable"`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds
|
|
88
|
+
* and establishes WebSocket connections for real-time price updates.
|
|
89
|
+
*/
|
|
90
|
+
async subscribe() {
|
|
91
|
+
var _a;
|
|
92
|
+
await this.fetchSymbolsIfNeeded();
|
|
93
|
+
this.pythLazerClient = await pyth_lazer_sdk_1.PythLazerClient.create({
|
|
94
|
+
token: this.token,
|
|
95
|
+
logger: this.sdkLogging ? console : undefined,
|
|
96
|
+
webSocketPoolConfig: {
|
|
97
|
+
urls: this.endpoints,
|
|
98
|
+
numConnections: 4, // Optionally specify number of parallel redundant connections to reduce the chance of dropped messages. The connections will round-robin across the provided URLs. Default is 4.
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
console.error('⛔️ PythLazerClient error:', error.message);
|
|
101
|
+
},
|
|
102
|
+
onWebSocketError: (error) => {
|
|
103
|
+
console.error('⛔️ WebSocket error:', error.message);
|
|
104
|
+
},
|
|
105
|
+
onWebSocketPoolError: (error) => {
|
|
106
|
+
console.error('⛔️ WebSocket pool error:', error.message);
|
|
107
|
+
},
|
|
108
|
+
// Optional configuration for resilient WebSocket connections
|
|
109
|
+
rwsConfig: {
|
|
110
|
+
heartbeatTimeoutDurationMs: 5000, // Optional heartbeat timeout duration in milliseconds
|
|
111
|
+
maxRetryDelayMs: 1000, // Optional maximum retry delay in milliseconds
|
|
112
|
+
logAfterRetryCount: 10, // Optional log after how many retries
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Reset allSubscribedIds to rebuild with only stable feeds
|
|
117
|
+
this.allSubscribedIds = [];
|
|
118
|
+
let subscriptionId = 1;
|
|
119
|
+
for (const priceFeedArray of this.priceFeedArrays) {
|
|
120
|
+
const filteredFeedIds = this.filterStableFeeds(priceFeedArray.priceFeedIds);
|
|
121
|
+
if (filteredFeedIds.length === 0) {
|
|
122
|
+
console.warn(`All feeds filtered out for subscription ${subscriptionId}, skipping`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Update allSubscribedIds with only stable feeds
|
|
126
|
+
this.allSubscribedIds.push(...filteredFeedIds);
|
|
127
|
+
const feedIdsHash = this.hash(filteredFeedIds);
|
|
128
|
+
this.feedIdHashToFeedIds.set(feedIdsHash, filteredFeedIds);
|
|
129
|
+
this.subscriptionIdsToFeedIdsHash.set(subscriptionId, feedIdsHash);
|
|
130
|
+
// Update marketIndextoPriceFeedIdChunk to use filtered feeds
|
|
131
|
+
for (const [marketIndex, chunk,] of this.marketIndextoPriceFeedIdChunk.entries()) {
|
|
132
|
+
if (this.hash(chunk) === this.hash(priceFeedArray.priceFeedIds)) {
|
|
133
|
+
this.marketIndextoPriceFeedIdChunk.set(marketIndex, filteredFeedIds);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Remove entries from marketIndextoPriceFeedId for filtered-out feeds
|
|
137
|
+
for (const [marketIndex, feedId,] of this.marketIndextoPriceFeedId.entries()) {
|
|
138
|
+
if (!filteredFeedIds.includes(feedId) &&
|
|
139
|
+
priceFeedArray.priceFeedIds.includes(feedId)) {
|
|
140
|
+
this.marketIndextoPriceFeedId.delete(marketIndex);
|
|
141
|
+
this.marketIndextoPriceFeedIdChunk.delete(marketIndex);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.pythLazerClient.addMessageListener((message) => {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
this.receivingData = true;
|
|
147
|
+
clearTimeout(this.timeoutId);
|
|
148
|
+
switch (message.type) {
|
|
149
|
+
case 'json': {
|
|
150
|
+
if (message.value.type == 'streamUpdated') {
|
|
151
|
+
if ((_a = message.value.solana) === null || _a === void 0 ? void 0 : _a.data) {
|
|
152
|
+
this.feedIdChunkToPriceMessage.set(this.subscriptionIdsToFeedIdsHash.get(message.value.subscriptionId), message.value.solana.data);
|
|
153
|
+
}
|
|
154
|
+
if ((_b = message.value.parsed) === null || _b === void 0 ? void 0 : _b.priceFeeds) {
|
|
155
|
+
for (const priceFeed of message.value.parsed.priceFeeds) {
|
|
156
|
+
const price = Number(priceFeed.price) *
|
|
157
|
+
Math.pow(10, Number(priceFeed.exponent));
|
|
158
|
+
this.feedIdToPrice.set(priceFeed.priceFeedId, price);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
default: {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.setTimeout();
|
|
169
|
+
});
|
|
170
|
+
this.pythLazerClient.send({
|
|
171
|
+
type: 'subscribe',
|
|
172
|
+
subscriptionId,
|
|
173
|
+
priceFeedIds: filteredFeedIds,
|
|
174
|
+
properties: ['price', 'bestAskPrice', 'bestBidPrice', 'exponent'],
|
|
175
|
+
formats: ['solana'],
|
|
176
|
+
deliveryFormat: 'json',
|
|
177
|
+
channel: (_a = priceFeedArray.channel) !== null && _a !== void 0 ? _a : 'fixed_rate@200ms',
|
|
178
|
+
jsonBinaryEncoding: 'hex',
|
|
179
|
+
});
|
|
180
|
+
subscriptionId++;
|
|
181
|
+
}
|
|
182
|
+
this.receivingData = true;
|
|
183
|
+
this.setTimeout();
|
|
184
|
+
}
|
|
185
|
+
setTimeout() {
|
|
186
|
+
this.timeoutId = setTimeout(async () => {
|
|
187
|
+
if (this.isUnsubscribing) {
|
|
188
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.receivingData) {
|
|
192
|
+
console.log(`No ws data from pyth lazer client resubscribing`);
|
|
193
|
+
await this.unsubscribe();
|
|
194
|
+
this.receivingData = false;
|
|
195
|
+
await this.subscribe();
|
|
196
|
+
}
|
|
197
|
+
}, this.resubTimeoutMs);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Unsubscribes from all Pyth Lazer price feeds and shuts down WebSocket connections.
|
|
201
|
+
*/
|
|
202
|
+
async unsubscribe() {
|
|
203
|
+
var _a;
|
|
204
|
+
this.isUnsubscribing = true;
|
|
205
|
+
(_a = this.pythLazerClient) === null || _a === void 0 ? void 0 : _a.shutdown();
|
|
206
|
+
this.pythLazerClient = undefined;
|
|
207
|
+
clearTimeout(this.timeoutId);
|
|
208
|
+
this.timeoutId = undefined;
|
|
209
|
+
this.isUnsubscribing = false;
|
|
210
|
+
}
|
|
211
|
+
hash(arr) {
|
|
212
|
+
return 'h:' + arr.join('|');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves the latest Solana-format price message for a group of feed IDs.
|
|
216
|
+
* @param feedIds - Array of price feed IDs
|
|
217
|
+
* @returns Hex-encoded price message data, or undefined if not available
|
|
218
|
+
*/
|
|
219
|
+
async getLatestPriceMessage(feedIds) {
|
|
220
|
+
return this.feedIdChunkToPriceMessage.get(this.hash(feedIds));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Retrieves the latest Solana-format price message for a specific market.
|
|
224
|
+
* @param marketIndex - The market index to get price data for
|
|
225
|
+
* @returns Hex-encoded price message data, or undefined if not found
|
|
226
|
+
*/
|
|
227
|
+
async getLatestPriceMessageForMarketIndex(marketIndex) {
|
|
228
|
+
const feedIds = this.marketIndextoPriceFeedIdChunk.get(marketIndex);
|
|
229
|
+
if (!feedIds) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return await this.getLatestPriceMessage(feedIds);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Gets the array of price feed IDs associated with a market index.
|
|
236
|
+
* @param marketIndex - The market index to look up
|
|
237
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
238
|
+
*/
|
|
239
|
+
getPriceFeedIdsFromMarketIndex(marketIndex) {
|
|
240
|
+
return this.marketIndextoPriceFeedIdChunk.get(marketIndex) || [];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Gets the array of price feed IDs from a subscription hash.
|
|
244
|
+
* @param hash - The subscription hash
|
|
245
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
246
|
+
*/
|
|
247
|
+
getPriceFeedIdsFromHash(hash) {
|
|
248
|
+
return this.feedIdHashToFeedIds.get(hash) || [];
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Gets the current parsed price for a specific market index.
|
|
252
|
+
* @param marketIndex - The market index to get the price for
|
|
253
|
+
* @returns The price as a number, or undefined if not available
|
|
254
|
+
*/
|
|
255
|
+
getPriceFromMarketIndex(marketIndex) {
|
|
256
|
+
const feedId = this.marketIndextoPriceFeedId.get(marketIndex);
|
|
257
|
+
if (feedId === undefined) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return this.feedIdToPrice.get(feedId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.PythLazerSubscriber = PythLazerSubscriber;
|
|
264
|
+
PythLazerSubscriber.SYMBOLS_API_URL = 'https://history.pyth-lazer.dourolabs.app/history/v1/symbols';
|
package/lib/node/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export * from './accounts/types';
|
|
|
33
33
|
export * from './addresses/pda';
|
|
34
34
|
export * from './adminClient';
|
|
35
35
|
export * from './assert/assert';
|
|
36
|
+
export { PythLazerSubscriber, PythLazerPriceFeedArray } from './pyth';
|
|
36
37
|
export * from './testClient';
|
|
37
38
|
export * from './user';
|
|
38
39
|
export * from './userConfig';
|
package/lib/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/node/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.pyth = exports.PublicKey = exports.BN = exports.CustomizedCadenceBulkAccountLoader = exports.WebSocketDriftClientAccountSubscriberV2 = exports.WebSocketProgramAccountsSubscriberV2 = exports.WebSocketProgramUserAccountSubscriber = exports.WebSocketProgramAccountSubscriber = exports.WebSocketAccountSubscriberV2 = void 0;
|
|
20
|
+
exports.pyth = exports.PublicKey = exports.BN = exports.PythLazerSubscriber = exports.CustomizedCadenceBulkAccountLoader = exports.WebSocketDriftClientAccountSubscriberV2 = exports.WebSocketProgramAccountsSubscriberV2 = exports.WebSocketProgramUserAccountSubscriber = exports.WebSocketProgramAccountSubscriber = exports.WebSocketAccountSubscriberV2 = void 0;
|
|
21
21
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
22
22
|
Object.defineProperty(exports, "BN", { enumerable: true, get: function () { return anchor_1.BN; } });
|
|
23
23
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -62,6 +62,8 @@ __exportStar(require("./accounts/types"), exports);
|
|
|
62
62
|
__exportStar(require("./addresses/pda"), exports);
|
|
63
63
|
__exportStar(require("./adminClient"), exports);
|
|
64
64
|
__exportStar(require("./assert/assert"), exports);
|
|
65
|
+
var pyth_1 = require("./pyth");
|
|
66
|
+
Object.defineProperty(exports, "PythLazerSubscriber", { enumerable: true, get: function () { return pyth_1.PythLazerSubscriber; } });
|
|
65
67
|
__exportStar(require("./testClient"), exports);
|
|
66
68
|
__exportStar(require("./user"), exports);
|
|
67
69
|
__exportStar(require("./userConfig"), exports);
|
package/lib/node/pyth/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { WormholeCoreBridgeSolana, WORMHOLE_CORE_BRIDGE_SOLANA_IDL, PythSolanaReceiver, PriceUpdateAccount, } from './types';
|
|
2
2
|
export { DEFAULT_WORMHOLE_PROGRAM_ID, DEFAULT_RECEIVER_PROGRAM_ID, } from './constants';
|
|
3
3
|
export { getGuardianSetPda } from './utils';
|
|
4
|
+
export { PythLazerSubscriber, PythLazerPriceFeedArray, } from './pythLazerSubscriber';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pyth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACN,2BAA2B,EAC3B,2BAA2B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pyth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACN,2BAA2B,EAC3B,2BAA2B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACN,mBAAmB,EACnB,uBAAuB,GACvB,MAAM,uBAAuB,CAAC"}
|
package/lib/node/pyth/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getGuardianSetPda = exports.DEFAULT_RECEIVER_PROGRAM_ID = exports.DEFAULT_WORMHOLE_PROGRAM_ID = exports.WORMHOLE_CORE_BRIDGE_SOLANA_IDL = void 0;
|
|
3
|
+
exports.PythLazerSubscriber = exports.getGuardianSetPda = exports.DEFAULT_RECEIVER_PROGRAM_ID = exports.DEFAULT_WORMHOLE_PROGRAM_ID = exports.WORMHOLE_CORE_BRIDGE_SOLANA_IDL = void 0;
|
|
4
4
|
var types_1 = require("./types");
|
|
5
5
|
Object.defineProperty(exports, "WORMHOLE_CORE_BRIDGE_SOLANA_IDL", { enumerable: true, get: function () { return types_1.WORMHOLE_CORE_BRIDGE_SOLANA_IDL; } });
|
|
6
6
|
var constants_1 = require("./constants");
|
|
@@ -8,3 +8,5 @@ Object.defineProperty(exports, "DEFAULT_WORMHOLE_PROGRAM_ID", { enumerable: true
|
|
|
8
8
|
Object.defineProperty(exports, "DEFAULT_RECEIVER_PROGRAM_ID", { enumerable: true, get: function () { return constants_1.DEFAULT_RECEIVER_PROGRAM_ID; } });
|
|
9
9
|
var utils_1 = require("./utils");
|
|
10
10
|
Object.defineProperty(exports, "getGuardianSetPda", { enumerable: true, get: function () { return utils_1.getGuardianSetPda; } });
|
|
11
|
+
var pythLazerSubscriber_1 = require("./pythLazerSubscriber");
|
|
12
|
+
Object.defineProperty(exports, "PythLazerSubscriber", { enumerable: true, get: function () { return pythLazerSubscriber_1.PythLazerSubscriber; } });
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Channel } from '@pythnetwork/pyth-lazer-sdk';
|
|
3
|
+
import { DriftEnv } from '../config';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for a group of Pyth Lazer price feeds.
|
|
6
|
+
*/
|
|
7
|
+
export type PythLazerPriceFeedArray = {
|
|
8
|
+
/** Optional channel for update frequency (e.g., 'fixed_rate@200ms') */
|
|
9
|
+
channel?: Channel;
|
|
10
|
+
/** Array of Pyth Lazer price feed IDs to subscribe to */
|
|
11
|
+
priceFeedIds: number[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data.
|
|
15
|
+
* Automatically filters out non-stable feeds and handles reconnection logic.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PythLazerSubscriber {
|
|
18
|
+
private endpoints;
|
|
19
|
+
private token;
|
|
20
|
+
private priceFeedArrays;
|
|
21
|
+
private resubTimeoutMs;
|
|
22
|
+
private sdkLogging;
|
|
23
|
+
private static readonly SYMBOLS_API_URL;
|
|
24
|
+
private symbolsCache;
|
|
25
|
+
private pythLazerClient?;
|
|
26
|
+
feedIdChunkToPriceMessage: Map<string, string>;
|
|
27
|
+
feedIdToPrice: Map<number, number>;
|
|
28
|
+
feedIdHashToFeedIds: Map<string, number[]>;
|
|
29
|
+
subscriptionIdsToFeedIdsHash: Map<number, string>;
|
|
30
|
+
allSubscribedIds: number[];
|
|
31
|
+
timeoutId?: NodeJS.Timeout;
|
|
32
|
+
receivingData: boolean;
|
|
33
|
+
isUnsubscribing: boolean;
|
|
34
|
+
marketIndextoPriceFeedIdChunk: Map<number, number[]>;
|
|
35
|
+
marketIndextoPriceFeedId: Map<number, number>;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new PythLazerSubscriber instance.
|
|
38
|
+
* @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer
|
|
39
|
+
* @param token - Authentication token for Pyth Lazer API
|
|
40
|
+
* @param priceFeedArrays - Array of price feed configurations to subscribe to
|
|
41
|
+
* @param env - Drift environment (mainnet-beta, devnet, etc.)
|
|
42
|
+
* @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout
|
|
43
|
+
* @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
44
|
+
*/
|
|
45
|
+
constructor(endpoints: string[], token: string, priceFeedArrays: PythLazerPriceFeedArray[], env?: DriftEnv, resubTimeoutMs?: number, sdkLogging?: boolean);
|
|
46
|
+
private fetchSymbolsIfNeeded;
|
|
47
|
+
private filterStableFeeds;
|
|
48
|
+
/**
|
|
49
|
+
* Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds
|
|
50
|
+
* and establishes WebSocket connections for real-time price updates.
|
|
51
|
+
*/
|
|
52
|
+
subscribe(): Promise<void>;
|
|
53
|
+
protected setTimeout(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Unsubscribes from all Pyth Lazer price feeds and shuts down WebSocket connections.
|
|
56
|
+
*/
|
|
57
|
+
unsubscribe(): Promise<void>;
|
|
58
|
+
hash(arr: number[]): string;
|
|
59
|
+
/**
|
|
60
|
+
* Retrieves the latest Solana-format price message for a group of feed IDs.
|
|
61
|
+
* @param feedIds - Array of price feed IDs
|
|
62
|
+
* @returns Hex-encoded price message data, or undefined if not available
|
|
63
|
+
*/
|
|
64
|
+
getLatestPriceMessage(feedIds: number[]): Promise<string | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the latest Solana-format price message for a specific market.
|
|
67
|
+
* @param marketIndex - The market index to get price data for
|
|
68
|
+
* @returns Hex-encoded price message data, or undefined if not found
|
|
69
|
+
*/
|
|
70
|
+
getLatestPriceMessageForMarketIndex(marketIndex: number): Promise<string | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Gets the array of price feed IDs associated with a market index.
|
|
73
|
+
* @param marketIndex - The market index to look up
|
|
74
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
75
|
+
*/
|
|
76
|
+
getPriceFeedIdsFromMarketIndex(marketIndex: number): number[];
|
|
77
|
+
/**
|
|
78
|
+
* Gets the array of price feed IDs from a subscription hash.
|
|
79
|
+
* @param hash - The subscription hash
|
|
80
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
81
|
+
*/
|
|
82
|
+
getPriceFeedIdsFromHash(hash: string): number[];
|
|
83
|
+
/**
|
|
84
|
+
* Gets the current parsed price for a specific market index.
|
|
85
|
+
* @param marketIndex - The market index to get the price for
|
|
86
|
+
* @returns The price as a number, or undefined if not available
|
|
87
|
+
*/
|
|
88
|
+
getPriceFromMarketIndex(marketIndex: number): number | undefined;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=pythLazerSubscriber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pythLazerSubscriber.d.ts","sourceRoot":"","sources":["../../../src/pyth/pythLazerSubscriber.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAmB,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACrC,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAOF;;;GAGG;AACH,qBAAa,mBAAmB;IA4B9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,eAAe;IAEvB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,UAAU;IAhCnB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CACwB;IAC/D,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,yBAAyB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAC3D,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAC/C,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAa;IACvD,4BAA4B,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAC9D,gBAAgB,EAAE,MAAM,EAAE,CAAM;IAEhC,SAAS,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAC3B,aAAa,UAAS;IACtB,eAAe,UAAS;IAExB,6BAA6B,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAa;IACjE,wBAAwB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAE1D;;;;;;;;OAQG;gBAEM,SAAS,EAAE,MAAM,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,uBAAuB,EAAE,EAClD,GAAG,GAAE,QAAmB,EAChB,cAAc,GAAE,MAAa,EAC7B,UAAU,GAAE,OAAe;YA2BtB,oBAAoB;IAuBlC,OAAO,CAAC,iBAAiB;IAuBzB;;;OAGG;IACG,SAAS;IA0Hf,SAAS,CAAC,UAAU,IAAI,IAAI;IAgB5B;;OAEG;IACG,WAAW;IASjB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM;IAI3B;;;;OAIG;IACG,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI3E;;;;OAIG;IACG,mCAAmC,CACxC,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAQ9B;;;;OAIG;IACH,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE;IAI7D;;;;OAIG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAI/C;;;;OAIG;IACH,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAOhE"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PythLazerSubscriber = void 0;
|
|
4
|
+
const pyth_lazer_sdk_1 = require("@pythnetwork/pyth-lazer-sdk");
|
|
5
|
+
const perpMarkets_1 = require("../constants/perpMarkets");
|
|
6
|
+
/**
|
|
7
|
+
* Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data.
|
|
8
|
+
* Automatically filters out non-stable feeds and handles reconnection logic.
|
|
9
|
+
*/
|
|
10
|
+
class PythLazerSubscriber {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new PythLazerSubscriber instance.
|
|
13
|
+
* @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer
|
|
14
|
+
* @param token - Authentication token for Pyth Lazer API
|
|
15
|
+
* @param priceFeedArrays - Array of price feed configurations to subscribe to
|
|
16
|
+
* @param env - Drift environment (mainnet-beta, devnet, etc.)
|
|
17
|
+
* @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout
|
|
18
|
+
* @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
19
|
+
*/
|
|
20
|
+
constructor(endpoints, token, priceFeedArrays, env = 'devnet', resubTimeoutMs = 2000, sdkLogging = false) {
|
|
21
|
+
this.endpoints = endpoints;
|
|
22
|
+
this.token = token;
|
|
23
|
+
this.priceFeedArrays = priceFeedArrays;
|
|
24
|
+
this.resubTimeoutMs = resubTimeoutMs;
|
|
25
|
+
this.sdkLogging = sdkLogging;
|
|
26
|
+
this.symbolsCache = null;
|
|
27
|
+
this.feedIdChunkToPriceMessage = new Map();
|
|
28
|
+
this.feedIdToPrice = new Map();
|
|
29
|
+
this.feedIdHashToFeedIds = new Map();
|
|
30
|
+
this.subscriptionIdsToFeedIdsHash = new Map();
|
|
31
|
+
this.allSubscribedIds = [];
|
|
32
|
+
this.receivingData = false;
|
|
33
|
+
this.isUnsubscribing = false;
|
|
34
|
+
this.marketIndextoPriceFeedIdChunk = new Map();
|
|
35
|
+
this.marketIndextoPriceFeedId = new Map();
|
|
36
|
+
const markets = perpMarkets_1.PerpMarkets[env].filter((market) => market.pythLazerId !== undefined);
|
|
37
|
+
this.allSubscribedIds = this.priceFeedArrays
|
|
38
|
+
.map((array) => array.priceFeedIds)
|
|
39
|
+
.flat();
|
|
40
|
+
for (const priceFeedIds of priceFeedArrays) {
|
|
41
|
+
const filteredMarkets = markets.filter((market) => priceFeedIds.priceFeedIds.includes(market.pythLazerId));
|
|
42
|
+
for (const market of filteredMarkets) {
|
|
43
|
+
this.marketIndextoPriceFeedIdChunk.set(market.marketIndex, priceFeedIds.priceFeedIds);
|
|
44
|
+
this.marketIndextoPriceFeedId.set(market.marketIndex, market.pythLazerId);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async fetchSymbolsIfNeeded() {
|
|
49
|
+
if (this.symbolsCache !== null)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(PythLazerSubscriber.SYMBOLS_API_URL);
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw new Error(`HTTP ${response.status}`);
|
|
55
|
+
const symbols = await response.json();
|
|
56
|
+
this.symbolsCache = new Map();
|
|
57
|
+
for (const symbol of symbols) {
|
|
58
|
+
this.symbolsCache.set(symbol.pyth_lazer_id, {
|
|
59
|
+
name: symbol.name,
|
|
60
|
+
state: symbol.state,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn(`Failed to fetch Pyth Lazer symbols, proceeding with all feeds: ${error}`);
|
|
66
|
+
this.symbolsCache = new Map(); // Empty map = no filtering
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
filterStableFeeds(feedIds) {
|
|
70
|
+
if (this.symbolsCache === null || this.symbolsCache.size === 0) {
|
|
71
|
+
return feedIds; // No filtering if cache unavailable
|
|
72
|
+
}
|
|
73
|
+
return feedIds.filter((feedId) => {
|
|
74
|
+
const info = this.symbolsCache.get(feedId);
|
|
75
|
+
if (!info) {
|
|
76
|
+
console.warn(`Feed ID ${feedId} not found in symbols API, including anyway`);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (info.state !== 'stable') {
|
|
80
|
+
console.warn(`Removing feed ID ${feedId} (${info.name}) - state is "${info.state}", not "stable"`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds
|
|
88
|
+
* and establishes WebSocket connections for real-time price updates.
|
|
89
|
+
*/
|
|
90
|
+
async subscribe() {
|
|
91
|
+
var _a;
|
|
92
|
+
await this.fetchSymbolsIfNeeded();
|
|
93
|
+
this.pythLazerClient = await pyth_lazer_sdk_1.PythLazerClient.create({
|
|
94
|
+
token: this.token,
|
|
95
|
+
logger: this.sdkLogging ? console : undefined,
|
|
96
|
+
webSocketPoolConfig: {
|
|
97
|
+
urls: this.endpoints,
|
|
98
|
+
numConnections: 4, // Optionally specify number of parallel redundant connections to reduce the chance of dropped messages. The connections will round-robin across the provided URLs. Default is 4.
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
console.error('⛔️ PythLazerClient error:', error.message);
|
|
101
|
+
},
|
|
102
|
+
onWebSocketError: (error) => {
|
|
103
|
+
console.error('⛔️ WebSocket error:', error.message);
|
|
104
|
+
},
|
|
105
|
+
onWebSocketPoolError: (error) => {
|
|
106
|
+
console.error('⛔️ WebSocket pool error:', error.message);
|
|
107
|
+
},
|
|
108
|
+
// Optional configuration for resilient WebSocket connections
|
|
109
|
+
rwsConfig: {
|
|
110
|
+
heartbeatTimeoutDurationMs: 5000, // Optional heartbeat timeout duration in milliseconds
|
|
111
|
+
maxRetryDelayMs: 1000, // Optional maximum retry delay in milliseconds
|
|
112
|
+
logAfterRetryCount: 10, // Optional log after how many retries
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Reset allSubscribedIds to rebuild with only stable feeds
|
|
117
|
+
this.allSubscribedIds = [];
|
|
118
|
+
let subscriptionId = 1;
|
|
119
|
+
for (const priceFeedArray of this.priceFeedArrays) {
|
|
120
|
+
const filteredFeedIds = this.filterStableFeeds(priceFeedArray.priceFeedIds);
|
|
121
|
+
if (filteredFeedIds.length === 0) {
|
|
122
|
+
console.warn(`All feeds filtered out for subscription ${subscriptionId}, skipping`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Update allSubscribedIds with only stable feeds
|
|
126
|
+
this.allSubscribedIds.push(...filteredFeedIds);
|
|
127
|
+
const feedIdsHash = this.hash(filteredFeedIds);
|
|
128
|
+
this.feedIdHashToFeedIds.set(feedIdsHash, filteredFeedIds);
|
|
129
|
+
this.subscriptionIdsToFeedIdsHash.set(subscriptionId, feedIdsHash);
|
|
130
|
+
// Update marketIndextoPriceFeedIdChunk to use filtered feeds
|
|
131
|
+
for (const [marketIndex, chunk,] of this.marketIndextoPriceFeedIdChunk.entries()) {
|
|
132
|
+
if (this.hash(chunk) === this.hash(priceFeedArray.priceFeedIds)) {
|
|
133
|
+
this.marketIndextoPriceFeedIdChunk.set(marketIndex, filteredFeedIds);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Remove entries from marketIndextoPriceFeedId for filtered-out feeds
|
|
137
|
+
for (const [marketIndex, feedId,] of this.marketIndextoPriceFeedId.entries()) {
|
|
138
|
+
if (!filteredFeedIds.includes(feedId) &&
|
|
139
|
+
priceFeedArray.priceFeedIds.includes(feedId)) {
|
|
140
|
+
this.marketIndextoPriceFeedId.delete(marketIndex);
|
|
141
|
+
this.marketIndextoPriceFeedIdChunk.delete(marketIndex);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.pythLazerClient.addMessageListener((message) => {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
this.receivingData = true;
|
|
147
|
+
clearTimeout(this.timeoutId);
|
|
148
|
+
switch (message.type) {
|
|
149
|
+
case 'json': {
|
|
150
|
+
if (message.value.type == 'streamUpdated') {
|
|
151
|
+
if ((_a = message.value.solana) === null || _a === void 0 ? void 0 : _a.data) {
|
|
152
|
+
this.feedIdChunkToPriceMessage.set(this.subscriptionIdsToFeedIdsHash.get(message.value.subscriptionId), message.value.solana.data);
|
|
153
|
+
}
|
|
154
|
+
if ((_b = message.value.parsed) === null || _b === void 0 ? void 0 : _b.priceFeeds) {
|
|
155
|
+
for (const priceFeed of message.value.parsed.priceFeeds) {
|
|
156
|
+
const price = Number(priceFeed.price) *
|
|
157
|
+
Math.pow(10, Number(priceFeed.exponent));
|
|
158
|
+
this.feedIdToPrice.set(priceFeed.priceFeedId, price);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
default: {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.setTimeout();
|
|
169
|
+
});
|
|
170
|
+
this.pythLazerClient.send({
|
|
171
|
+
type: 'subscribe',
|
|
172
|
+
subscriptionId,
|
|
173
|
+
priceFeedIds: filteredFeedIds,
|
|
174
|
+
properties: ['price', 'bestAskPrice', 'bestBidPrice', 'exponent'],
|
|
175
|
+
formats: ['solana'],
|
|
176
|
+
deliveryFormat: 'json',
|
|
177
|
+
channel: (_a = priceFeedArray.channel) !== null && _a !== void 0 ? _a : 'fixed_rate@200ms',
|
|
178
|
+
jsonBinaryEncoding: 'hex',
|
|
179
|
+
});
|
|
180
|
+
subscriptionId++;
|
|
181
|
+
}
|
|
182
|
+
this.receivingData = true;
|
|
183
|
+
this.setTimeout();
|
|
184
|
+
}
|
|
185
|
+
setTimeout() {
|
|
186
|
+
this.timeoutId = setTimeout(async () => {
|
|
187
|
+
if (this.isUnsubscribing) {
|
|
188
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.receivingData) {
|
|
192
|
+
console.log(`No ws data from pyth lazer client resubscribing`);
|
|
193
|
+
await this.unsubscribe();
|
|
194
|
+
this.receivingData = false;
|
|
195
|
+
await this.subscribe();
|
|
196
|
+
}
|
|
197
|
+
}, this.resubTimeoutMs);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Unsubscribes from all Pyth Lazer price feeds and shuts down WebSocket connections.
|
|
201
|
+
*/
|
|
202
|
+
async unsubscribe() {
|
|
203
|
+
var _a;
|
|
204
|
+
this.isUnsubscribing = true;
|
|
205
|
+
(_a = this.pythLazerClient) === null || _a === void 0 ? void 0 : _a.shutdown();
|
|
206
|
+
this.pythLazerClient = undefined;
|
|
207
|
+
clearTimeout(this.timeoutId);
|
|
208
|
+
this.timeoutId = undefined;
|
|
209
|
+
this.isUnsubscribing = false;
|
|
210
|
+
}
|
|
211
|
+
hash(arr) {
|
|
212
|
+
return 'h:' + arr.join('|');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves the latest Solana-format price message for a group of feed IDs.
|
|
216
|
+
* @param feedIds - Array of price feed IDs
|
|
217
|
+
* @returns Hex-encoded price message data, or undefined if not available
|
|
218
|
+
*/
|
|
219
|
+
async getLatestPriceMessage(feedIds) {
|
|
220
|
+
return this.feedIdChunkToPriceMessage.get(this.hash(feedIds));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Retrieves the latest Solana-format price message for a specific market.
|
|
224
|
+
* @param marketIndex - The market index to get price data for
|
|
225
|
+
* @returns Hex-encoded price message data, or undefined if not found
|
|
226
|
+
*/
|
|
227
|
+
async getLatestPriceMessageForMarketIndex(marketIndex) {
|
|
228
|
+
const feedIds = this.marketIndextoPriceFeedIdChunk.get(marketIndex);
|
|
229
|
+
if (!feedIds) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return await this.getLatestPriceMessage(feedIds);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Gets the array of price feed IDs associated with a market index.
|
|
236
|
+
* @param marketIndex - The market index to look up
|
|
237
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
238
|
+
*/
|
|
239
|
+
getPriceFeedIdsFromMarketIndex(marketIndex) {
|
|
240
|
+
return this.marketIndextoPriceFeedIdChunk.get(marketIndex) || [];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Gets the array of price feed IDs from a subscription hash.
|
|
244
|
+
* @param hash - The subscription hash
|
|
245
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
246
|
+
*/
|
|
247
|
+
getPriceFeedIdsFromHash(hash) {
|
|
248
|
+
return this.feedIdHashToFeedIds.get(hash) || [];
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Gets the current parsed price for a specific market index.
|
|
252
|
+
* @param marketIndex - The market index to get the price for
|
|
253
|
+
* @returns The price as a number, or undefined if not available
|
|
254
|
+
*/
|
|
255
|
+
getPriceFromMarketIndex(marketIndex) {
|
|
256
|
+
const feedId = this.marketIndextoPriceFeedId.get(marketIndex);
|
|
257
|
+
if (feedId === undefined) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return this.feedIdToPrice.get(feedId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.PythLazerSubscriber = PythLazerSubscriber;
|
|
264
|
+
PythLazerSubscriber.SYMBOLS_API_URL = 'https://history.pyth-lazer.dourolabs.app/history/v1/symbols';
|
package/package.json
CHANGED
|
@@ -2,8 +2,13 @@ import { Channel, PythLazerClient } from '@pythnetwork/pyth-lazer-sdk';
|
|
|
2
2
|
import { DriftEnv } from '../config';
|
|
3
3
|
import { PerpMarkets } from '../constants/perpMarkets';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for a group of Pyth Lazer price feeds.
|
|
7
|
+
*/
|
|
5
8
|
export type PythLazerPriceFeedArray = {
|
|
9
|
+
/** Optional channel for update frequency (e.g., 'fixed_rate@200ms') */
|
|
6
10
|
channel?: Channel;
|
|
11
|
+
/** Array of Pyth Lazer price feed IDs to subscribe to */
|
|
7
12
|
priceFeedIds: number[];
|
|
8
13
|
};
|
|
9
14
|
|
|
@@ -12,6 +17,10 @@ type FeedSymbolInfo = {
|
|
|
12
17
|
state: string;
|
|
13
18
|
};
|
|
14
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data.
|
|
22
|
+
* Automatically filters out non-stable feeds and handles reconnection logic.
|
|
23
|
+
*/
|
|
15
24
|
export class PythLazerSubscriber {
|
|
16
25
|
private static readonly SYMBOLS_API_URL =
|
|
17
26
|
'https://history.pyth-lazer.dourolabs.app/history/v1/symbols';
|
|
@@ -30,15 +39,21 @@ export class PythLazerSubscriber {
|
|
|
30
39
|
marketIndextoPriceFeedIdChunk: Map<number, number[]> = new Map();
|
|
31
40
|
marketIndextoPriceFeedId: Map<number, number> = new Map();
|
|
32
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new PythLazerSubscriber instance.
|
|
44
|
+
* @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer
|
|
45
|
+
* @param token - Authentication token for Pyth Lazer API
|
|
46
|
+
* @param priceFeedArrays - Array of price feed configurations to subscribe to
|
|
47
|
+
* @param env - Drift environment (mainnet-beta, devnet, etc.)
|
|
48
|
+
* @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout
|
|
49
|
+
* @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
50
|
+
*/
|
|
33
51
|
constructor(
|
|
34
52
|
private endpoints: string[],
|
|
35
53
|
private token: string,
|
|
36
54
|
private priceFeedArrays: PythLazerPriceFeedArray[],
|
|
37
55
|
env: DriftEnv = 'devnet',
|
|
38
56
|
private resubTimeoutMs: number = 2000,
|
|
39
|
-
/**
|
|
40
|
-
* Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging.
|
|
41
|
-
*/
|
|
42
57
|
private sdkLogging: boolean = false
|
|
43
58
|
) {
|
|
44
59
|
const markets = PerpMarkets[env].filter(
|
|
@@ -112,6 +127,10 @@ export class PythLazerSubscriber {
|
|
|
112
127
|
});
|
|
113
128
|
}
|
|
114
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds
|
|
132
|
+
* and establishes WebSocket connections for real-time price updates.
|
|
133
|
+
*/
|
|
115
134
|
async subscribe() {
|
|
116
135
|
await this.fetchSymbolsIfNeeded();
|
|
117
136
|
|
|
@@ -250,6 +269,9 @@ export class PythLazerSubscriber {
|
|
|
250
269
|
}, this.resubTimeoutMs);
|
|
251
270
|
}
|
|
252
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Unsubscribes from all Pyth Lazer price feeds and shuts down WebSocket connections.
|
|
274
|
+
*/
|
|
253
275
|
async unsubscribe() {
|
|
254
276
|
this.isUnsubscribing = true;
|
|
255
277
|
this.pythLazerClient?.shutdown();
|
|
@@ -263,10 +285,20 @@ export class PythLazerSubscriber {
|
|
|
263
285
|
return 'h:' + arr.join('|');
|
|
264
286
|
}
|
|
265
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Retrieves the latest Solana-format price message for a group of feed IDs.
|
|
290
|
+
* @param feedIds - Array of price feed IDs
|
|
291
|
+
* @returns Hex-encoded price message data, or undefined if not available
|
|
292
|
+
*/
|
|
266
293
|
async getLatestPriceMessage(feedIds: number[]): Promise<string | undefined> {
|
|
267
294
|
return this.feedIdChunkToPriceMessage.get(this.hash(feedIds));
|
|
268
295
|
}
|
|
269
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Retrieves the latest Solana-format price message for a specific market.
|
|
299
|
+
* @param marketIndex - The market index to get price data for
|
|
300
|
+
* @returns Hex-encoded price message data, or undefined if not found
|
|
301
|
+
*/
|
|
270
302
|
async getLatestPriceMessageForMarketIndex(
|
|
271
303
|
marketIndex: number
|
|
272
304
|
): Promise<string | undefined> {
|
|
@@ -277,14 +309,29 @@ export class PythLazerSubscriber {
|
|
|
277
309
|
return await this.getLatestPriceMessage(feedIds);
|
|
278
310
|
}
|
|
279
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Gets the array of price feed IDs associated with a market index.
|
|
314
|
+
* @param marketIndex - The market index to look up
|
|
315
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
316
|
+
*/
|
|
280
317
|
getPriceFeedIdsFromMarketIndex(marketIndex: number): number[] {
|
|
281
318
|
return this.marketIndextoPriceFeedIdChunk.get(marketIndex) || [];
|
|
282
319
|
}
|
|
283
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Gets the array of price feed IDs from a subscription hash.
|
|
323
|
+
* @param hash - The subscription hash
|
|
324
|
+
* @returns Array of price feed IDs, or empty array if not found
|
|
325
|
+
*/
|
|
284
326
|
getPriceFeedIdsFromHash(hash: string): number[] {
|
|
285
327
|
return this.feedIdHashToFeedIds.get(hash) || [];
|
|
286
328
|
}
|
|
287
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Gets the current parsed price for a specific market index.
|
|
332
|
+
* @param marketIndex - The market index to get the price for
|
|
333
|
+
* @returns The price as a number, or undefined if not available
|
|
334
|
+
*/
|
|
288
335
|
getPriceFromMarketIndex(marketIndex: number): number | undefined {
|
|
289
336
|
const feedId = this.marketIndextoPriceFeedId.get(marketIndex);
|
|
290
337
|
if (feedId === undefined) {
|