@drift-labs/common 1.0.16 → 1.0.18
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/lib/EnvironmentConstants.js +2 -2
- package/lib/EnvironmentConstants.js.map +1 -1
- package/lib/clients/DlobWebsocketClient.d.ts +6 -0
- package/lib/clients/DlobWebsocketClient.js +24 -4
- package/lib/clients/DlobWebsocketClient.js.map +1 -1
- package/lib/clients/candleClient.js +12 -0
- package/lib/clients/candleClient.js.map +1 -1
- package/lib/clients/swiftClient.d.ts +1 -0
- package/lib/clients/swiftClient.js +79 -25
- package/lib/clients/swiftClient.js.map +1 -1
- package/lib/clients/tvFeed.d.ts +5 -1
- package/lib/clients/tvFeed.js +32 -5
- package/lib/clients/tvFeed.js.map +1 -1
- package/lib/common-ui-utils/commonUiUtils.d.ts +1 -2
- package/lib/common-ui-utils/commonUiUtils.js +38 -3
- package/lib/common-ui-utils/commonUiUtils.js.map +1 -1
- package/lib/common-ui-utils/order.d.ts +1 -2
- package/lib/common-ui-utils/order.js +9 -10
- package/lib/common-ui-utils/order.js.map +1 -1
- package/lib/common-ui-utils/user.js +23 -20
- package/lib/common-ui-utils/user.js.map +1 -1
- package/lib/constants/autogenerated/driftErrors.json +5 -1
- package/lib/drift/Drift/clients/AuthorityDrift/DriftOperations/index.d.ts +1 -1
- package/lib/drift/Drift/clients/AuthorityDrift/DriftOperations/index.js +20 -5
- package/lib/drift/Drift/clients/AuthorityDrift/DriftOperations/index.js.map +1 -1
- package/lib/drift/Drift/clients/AuthorityDrift/DriftOperations/types.d.ts +9 -1
- package/lib/drift/Drift/clients/AuthorityDrift/DriftOperations/types.js.map +1 -1
- package/lib/drift/Drift/clients/AuthorityDrift/index.d.ts +1 -1
- package/lib/drift/Drift/clients/AuthorityDrift/index.js.map +1 -1
- package/lib/drift/Drift/clients/CentralServerDrift/index.d.ts +18 -2
- package/lib/drift/Drift/clients/CentralServerDrift/index.js +54 -19
- package/lib/drift/Drift/clients/CentralServerDrift/index.js.map +1 -1
- package/lib/drift/base/actions/perp/settlePnl.d.ts +4 -3
- package/lib/drift/base/actions/perp/settlePnl.js +6 -3
- package/lib/drift/base/actions/perp/settlePnl.js.map +1 -1
- package/lib/drift/base/actions/spot/deposit.d.ts +10 -3
- package/lib/drift/base/actions/spot/deposit.js +22 -7
- package/lib/drift/base/actions/spot/deposit.js.map +1 -1
- package/lib/drift/base/actions/trade/editOrder.d.ts +2 -0
- package/lib/drift/base/actions/trade/editOrder.js +1 -0
- package/lib/drift/base/actions/trade/editOrder.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/auction.d.ts +4 -1
- package/lib/drift/base/actions/trade/openPerpOrder/auction.js +4 -4
- package/lib/drift/base/actions/trade/openPerpOrder/auction.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/dlobServer/index.d.ts +6 -4
- package/lib/drift/base/actions/trade/openPerpOrder/dlobServer/index.js +6 -5
- package/lib/drift/base/actions/trade/openPerpOrder/dlobServer/index.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.d.ts +13 -6
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.js +26 -16
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.d.ts +1 -4
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.js +12 -15
- package/lib/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.d.ts +48 -11
- package/lib/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.js +54 -18
- package/lib/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/positionMaxLeverage.d.ts +2 -2
- package/lib/drift/base/actions/trade/openPerpOrder/positionMaxLeverage.js +6 -2
- package/lib/drift/base/actions/trade/openPerpOrder/positionMaxLeverage.js.map +1 -1
- package/lib/drift/base/actions/trade/openPerpOrder/types.d.ts +5 -3
- package/lib/drift/base/actions/trade/openPerpOrder/types.js.map +1 -1
- package/lib/drift/base/actions/trade/swap.d.ts +6 -13
- package/lib/drift/base/actions/trade/swap.js +11 -36
- package/lib/drift/base/actions/trade/swap.js.map +1 -1
- package/lib/drift/base/actions/user/create.d.ts +3 -1
- package/lib/drift/base/actions/user/create.js +27 -8
- package/lib/drift/base/actions/user/create.js.map +1 -1
- package/lib/drift/cli.js +16 -2
- package/lib/drift/cli.js.map +1 -1
- package/lib/drift/utils/auctionParamsResponseMapper.d.ts +2 -1
- package/lib/drift/utils/auctionParamsResponseMapper.js +1 -1
- package/lib/drift/utils/auctionParamsResponseMapper.js.map +1 -1
- package/lib/drift/utils/orderParams.js +1 -1
- package/lib/drift/utils/orderParams.js.map +1 -1
- package/lib/serializableTypes.d.ts +1 -0
- package/lib/serializableTypes.js +4 -0
- package/lib/serializableTypes.js.map +1 -1
- package/lib/utils/driftEvents.js +18 -2
- package/lib/utils/driftEvents.js.map +1 -1
- package/lib/utils/logger.js +34 -3
- package/lib/utils/logger.js.map +1 -1
- package/lib/utils/signedMsgs.d.ts +1 -1
- package/lib/utils/signedMsgs.js +2 -2
- package/lib/utils/signedMsgs.js.map +1 -1
- package/lib/utils/token.d.ts +2 -1
- package/lib/utils/token.js +3 -2
- package/lib/utils/token.js.map +1 -1
- package/package.json +16 -4
|
@@ -20,8 +20,8 @@ exports.EnvironmentConstants = {
|
|
|
20
20
|
mainnet: [
|
|
21
21
|
{
|
|
22
22
|
label: 'Triton RPC Pool 1',
|
|
23
|
-
value: 'https://drift-
|
|
24
|
-
wsValue: 'wss://drift-
|
|
23
|
+
value: 'https://drift-drift_ma-39b5.mainnet.rpcpool.com/',
|
|
24
|
+
wsValue: 'wss://drift-drift_ma-39b5.mainnet.rpcpool.com/whirligig',
|
|
25
25
|
allowAdditionalConnection: true,
|
|
26
26
|
},
|
|
27
27
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnvironmentConstants.js","sourceRoot":"","sources":["../src/EnvironmentConstants.ts"],"names":[],"mappings":";;;AAOa,QAAA,oBAAoB,GAAG;IACnC,IAAI,EAAE;QACL,GAAG,EAAE;YACJ;gBACC,KAAK,EAAE,QAAQ;gBACf,KAAK,EAAE,sDAAsD;gBAC7D,OAAO,EAAE,oDAAoD;gBAC7D,yBAAyB,EAAE,IAAI;aAC/B;YACD;gBACC,KAAK,EAAE,UAAU;gBACjB,KAAK,EACJ,kFAAkF;gBACnF,OAAO,EACN,0FAA0F;gBAC3F,yBAAyB,EAAE,KAAK;aAChC;SACgB;QAClB,OAAO,EAAE;YACR;gBACC,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"EnvironmentConstants.js","sourceRoot":"","sources":["../src/EnvironmentConstants.ts"],"names":[],"mappings":";;;AAOa,QAAA,oBAAoB,GAAG;IACnC,IAAI,EAAE;QACL,GAAG,EAAE;YACJ;gBACC,KAAK,EAAE,QAAQ;gBACf,KAAK,EAAE,sDAAsD;gBAC7D,OAAO,EAAE,oDAAoD;gBAC7D,yBAAyB,EAAE,IAAI;aAC/B;YACD;gBACC,KAAK,EAAE,UAAU;gBACjB,KAAK,EACJ,kFAAkF;gBACnF,OAAO,EACN,0FAA0F;gBAC3F,yBAAyB,EAAE,KAAK;aAChC;SACgB;QAClB,OAAO,EAAE;YACR;gBACC,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,kDAAkD;gBACzD,OAAO,EAAE,yDAAyD;gBAClE,yBAAyB,EAAE,IAAI;aAC/B;YACD;gBACC,KAAK,EAAE,UAAU;gBACjB,KAAK,EAAE,kDAAkD;gBACzD,OAAO,EAAE,gDAAgD;gBACzD,yBAAyB,EAAE,IAAI;aAC/B;SACgB;KAClB;IACD,gBAAgB,EAAE;QACjB,GAAG,EAAE,gCAAgC;QACrC,OAAO,EAAE,sCAAsC;QAC/C,OAAO,EAAE,iCAAiC;KAC1C;IACD,aAAa,EAAE;QACd,GAAG,EAAE,qCAAqC;QAC1C,OAAO,EAAE,8BAA8B;QACvC,OAAO,EAAE,sCAAsC;KAC/C;IACD,iBAAiB,EAAE;QAClB,GAAG,EAAE,iCAAiC;QACtC,OAAO,EAAE,0BAA0B;QACnC,OAAO,EAAE,kCAAkC;KAC3C;IACD,eAAe,EAAE;QAChB,GAAG,EAAE,kCAAkC;QACvC,OAAO,EAAE,2BAA2B;QACpC,OAAO,EAAE,mCAAmC;KAC5C;IACD,eAAe,EAAE;QAChB,OAAO,EAAE,6BAA6B;QACtC,OAAO,EAAE,6BAA6B;KACtC;IACD,cAAc,EAAE;QACf,OAAO,EAAE,2BAA2B;QACpC,OAAO,EAAE,kCAAkC;KAC3C;CACD,CAAC","sourcesContent":["export interface RpcEndpoint {\n\tlabel: string;\n\tvalue: string;\n\twsValue?: string;\n\tallowAdditionalConnection: boolean;\n}\n\nexport const EnvironmentConstants = {\n\trpcs: {\n\t\tdev: [\n\t\t\t{\n\t\t\t\tlabel: 'Helius',\n\t\t\t\tvalue: 'https://detailed-sharleen-fast-devnet.helius-rpc.com',\n\t\t\t\twsValue: 'wss://detailed-sharleen-fast-devnet.helius-rpc.com',\n\t\t\t\tallowAdditionalConnection: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'RPC Pool',\n\t\t\t\tvalue:\n\t\t\t\t\t'https://drift-drift-a827.devnet.rpcpool.com/3639271b-6f0e-47c6-a643-1aaa0e498f58',\n\t\t\t\twsValue:\n\t\t\t\t\t'wss://drift-drift-a827.devnet.rpcpool.com/3639271b-6f0e-47c6-a643-1aaa0e498f58/whirligig',\n\t\t\t\tallowAdditionalConnection: false,\n\t\t\t},\n\t\t] as RpcEndpoint[],\n\t\tmainnet: [\n\t\t\t{\n\t\t\t\tlabel: 'Triton RPC Pool 1',\n\t\t\t\tvalue: 'https://drift-drift_ma-39b5.mainnet.rpcpool.com/',\n\t\t\t\twsValue: 'wss://drift-drift_ma-39b5.mainnet.rpcpool.com/whirligig',\n\t\t\t\tallowAdditionalConnection: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Helius 1',\n\t\t\t\tvalue: 'https://kora-8cwrc2-fast-mainnet.helius-rpc.com/',\n\t\t\t\twsValue: 'wss://kora-8cwrc2-fast-mainnet.helius-rpc.com/',\n\t\t\t\tallowAdditionalConnection: true,\n\t\t\t},\n\t\t] as RpcEndpoint[],\n\t},\n\thistoryServerUrl: {\n\t\tdev: 'https://master.api.drift.trade',\n\t\tmainnet: 'https://mainnet-beta.api.drift.trade',\n\t\tstaging: 'https://staging.api.drift.trade',\n\t},\n\tdataServerUrl: {\n\t\tdev: 'https://data-master.api.drift.trade',\n\t\tmainnet: 'https://data.api.drift.trade',\n\t\tstaging: 'https://data-staging.api.drift.trade',\n\t},\n\tdlobServerHttpUrl: {\n\t\tdev: 'https://master.dlob.drift.trade',\n\t\tmainnet: 'https://dlob.drift.trade',\n\t\tstaging: 'https://staging.dlob.drift.trade',\n\t},\n\tdlobServerWsUrl: {\n\t\tdev: 'wss://master.dlob.drift.trade/ws',\n\t\tmainnet: 'wss://dlob.drift.trade/ws',\n\t\tstaging: 'wss://staging.dlob.drift.trade/ws',\n\t},\n\teventsServerUrl: {\n\t\tmainnet: 'wss://events.drift.trade/ws',\n\t\tstaging: 'wss://events.drift.trade/ws',\n\t},\n\tswiftServerUrl: {\n\t\tmainnet: 'https://swift.drift.trade',\n\t\tstaging: 'https://master.swift.drift.trade',\n\t},\n};\n"]}
|
|
@@ -25,6 +25,8 @@ export declare class DlobWebsocketClient {
|
|
|
25
25
|
private subscriptions;
|
|
26
26
|
private resultIncrementer;
|
|
27
27
|
private destroy$;
|
|
28
|
+
private static readonly stringCache;
|
|
29
|
+
private static readonly maxCacheSize;
|
|
28
30
|
private marketSubscriptions$;
|
|
29
31
|
private rawMessages$;
|
|
30
32
|
constructor(config: DlobWebsocketClientConfig);
|
|
@@ -51,6 +53,10 @@ export declare class DlobWebsocketClient {
|
|
|
51
53
|
* Reset slot tracking for clean state on reconnection
|
|
52
54
|
*/
|
|
53
55
|
resetSlotTracking(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Cache frequently used strings to reduce memory allocation
|
|
58
|
+
*/
|
|
59
|
+
private getCachedString;
|
|
54
60
|
/**
|
|
55
61
|
* Destroy the client and clean up all subscriptions
|
|
56
62
|
*/
|
|
@@ -61,10 +61,26 @@ class DlobWebsocketClient {
|
|
|
61
61
|
for (const subscriptionKey of this.subscriptions.keys()) {
|
|
62
62
|
// Extract marketId and channel from subscription key
|
|
63
63
|
const [marketKey, channel] = subscriptionKey.split('_');
|
|
64
|
-
const resultKey = `${channel}_${marketKey}
|
|
64
|
+
const resultKey = this.getCachedString(`${channel}_${marketKey}`, 'result');
|
|
65
65
|
this.resultIncrementer.resetKey(resultKey);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Cache frequently used strings to reduce memory allocation
|
|
70
|
+
*/
|
|
71
|
+
getCachedString(baseString, type) {
|
|
72
|
+
const cacheKey = `${type}:${baseString}`;
|
|
73
|
+
if (DlobWebsocketClient.stringCache.has(cacheKey)) {
|
|
74
|
+
return DlobWebsocketClient.stringCache.get(cacheKey);
|
|
75
|
+
}
|
|
76
|
+
// If cache is getting too large, clear oldest entries
|
|
77
|
+
if (DlobWebsocketClient.stringCache.size >= DlobWebsocketClient.maxCacheSize) {
|
|
78
|
+
const firstKey = DlobWebsocketClient.stringCache.keys().next().value;
|
|
79
|
+
DlobWebsocketClient.stringCache.delete(firstKey);
|
|
80
|
+
}
|
|
81
|
+
DlobWebsocketClient.stringCache.set(cacheKey, baseString);
|
|
82
|
+
return baseString;
|
|
83
|
+
}
|
|
68
84
|
/**
|
|
69
85
|
* Destroy the client and clean up all subscriptions
|
|
70
86
|
*/
|
|
@@ -135,7 +151,7 @@ class DlobWebsocketClient {
|
|
|
135
151
|
const { unsubscribe } = MultiplexWebSocket_1.MultiplexWebSocket.createWebSocketSubscription({
|
|
136
152
|
wsUrl: this.config.websocketUrl,
|
|
137
153
|
enableHeartbeatMonitoring: true,
|
|
138
|
-
subscriptionId: `${this.config.websocketUrl}_dlob_liquidity_${marketId.key}`,
|
|
154
|
+
subscriptionId: this.getCachedString(`${this.config.websocketUrl}_dlob_liquidity_${marketId.key}`, 'subscription'),
|
|
139
155
|
subscribeMessage: JSON.stringify(__1.DLOB_SERVER_WEBSOCKET_UTILS.getSubscriptionProps({
|
|
140
156
|
type: channel,
|
|
141
157
|
market: marketId,
|
|
@@ -183,7 +199,7 @@ class DlobWebsocketClient {
|
|
|
183
199
|
var _a, _b;
|
|
184
200
|
try {
|
|
185
201
|
const parsed = this.tryParse(data);
|
|
186
|
-
const resultKey = `${channel}_${marketId.key}
|
|
202
|
+
const resultKey = this.getCachedString(`${channel}_${marketId.key}`, 'result');
|
|
187
203
|
const messageTimestamp = Date.now(); // Capture when we received this message
|
|
188
204
|
const validResult = this.resultIncrementer.handleResult(resultKey, (_a = parsed.slot) !== null && _a !== void 0 ? _a : 0, messageTimestamp);
|
|
189
205
|
if (!validResult) {
|
|
@@ -218,9 +234,13 @@ class DlobWebsocketClient {
|
|
|
218
234
|
}
|
|
219
235
|
getSubscriptionKey(subscription) {
|
|
220
236
|
const { marketId, channel, grouping } = subscription;
|
|
221
|
-
|
|
237
|
+
const baseKey = `${marketId.key}_${channel}${grouping ? `_${grouping}` : ''}`;
|
|
238
|
+
return this.getCachedString(baseKey, 'subscription_key');
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
exports.DlobWebsocketClient = DlobWebsocketClient;
|
|
242
|
+
// String caches to prevent repeated string concatenation
|
|
243
|
+
DlobWebsocketClient.stringCache = new Map();
|
|
244
|
+
DlobWebsocketClient.maxCacheSize = 1000;
|
|
225
245
|
exports.default = DlobWebsocketClient;
|
|
226
246
|
//# sourceMappingURL=DlobWebsocketClient.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DlobWebsocketClient.js","sourceRoot":"","sources":["../../src/clients/DlobWebsocketClient.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n\tMarketId,\n\tdeserializeL2Response,\n\tOrderbookGrouping,\n\tDLOB_SERVER_WEBSOCKET_UTILS,\n\tDlobServerChannel,\n} from '..';\nimport { Observable, Subject, BehaviorSubject, EMPTY } from 'rxjs';\nimport {\n\tmap,\n\tfilter,\n\tcatchError,\n\ttakeUntil,\n\tshare,\n\tdistinctUntilChanged,\n\tswitchMap,\n} from 'rxjs/operators';\nimport { ResultSlotIncrementer } from '../utils/ResultSlotIncrementer';\nimport { MultiplexWebSocket } from '../utils/MultiplexWebSocket';\nimport { RawL2Output } from '../utils/orderbook/types';\n\nexport type OrderbookChannelTypes = Extract<\n\tDlobServerChannel,\n\t'orderbook' | 'orderbook_indicative'\n>;\n\nexport interface DlobWebsocketClientConfig {\n\twebsocketUrl: string;\n\tenableIndicativeOrderbook?: boolean;\n\tresultSlotIncrementer?: ResultSlotIncrementer;\n\tonFallback?: (marketId: MarketId) => void;\n}\n\nexport interface MarketSubscription {\n\tmarketId: MarketId;\n\tchannel: OrderbookChannelTypes;\n\tgrouping?: OrderbookGrouping;\n}\n\nexport interface ProcessedMarketData {\n\tmarketId: MarketId;\n\trawData: RawL2Output;\n\tdeserializedData: ReturnType<typeof deserializeL2Response>;\n\tslot: number;\n}\n\nexport class DlobWebsocketClient {\n\tprivate config: DlobWebsocketClientConfig;\n\tprivate subscriptions = new Map<string, { unsubscribe: () => void }>();\n\tprivate resultIncrementer: ResultSlotIncrementer;\n\tprivate destroy$ = new Subject<void>();\n\n\t// Subjects for reactive streams\n\tprivate marketSubscriptions$ = new BehaviorSubject<MarketSubscription[]>([]);\n\tprivate rawMessages$ = new Subject<{\n\t\tmarketId: MarketId;\n\t\tchannel: string;\n\t\tdata: string;\n\t}>();\n\n\tconstructor(config: DlobWebsocketClientConfig) {\n\t\tthis.config = config;\n\t\tthis.resultIncrementer =\n\t\t\tconfig.resultSlotIncrementer || new ResultSlotIncrementer();\n\n\t\tthis.setupSubscriptionManagement();\n\t}\n\n\t/**\n\t * Get an observable stream of processed market data for specific markets\n\t */\n\tgetMarketDataStream(marketIds: MarketId[]): Observable<ProcessedMarketData> {\n\t\treturn this.rawMessages$.pipe(\n\t\t\tfilter(({ marketId }) => marketIds.some((id) => id.key === marketId.key)),\n\t\t\tmap(({ marketId, channel, data }) =>\n\t\t\t\tthis.processRawMessage(marketId, channel, data)\n\t\t\t),\n\t\t\tfilter((result): result is ProcessedMarketData => result !== null),\n\t\t\tcatchError((error) => {\n\t\t\t\tconsole.error('Caught error in getMarketDataStream', error);\n\t\t\t\treturn EMPTY;\n\t\t\t}),\n\t\t\ttakeUntil(this.destroy$),\n\t\t\tshare()\n\t\t);\n\t}\n\n\t/**\n\t * Subscribe to market data for given markets\n\t */\n\tsubscribeToMarkets(\n\t\tmarkets: {\n\t\t\tmarketId: MarketId;\n\t\t\tgrouping?: OrderbookGrouping;\n\t\t}[]\n\t): void {\n\t\tconst subscriptions: MarketSubscription[] = markets.map(\n\t\t\t({ marketId, grouping }) => ({\n\t\t\t\tmarketId,\n\t\t\t\tchannel: this.config.enableIndicativeOrderbook\n\t\t\t\t\t? 'orderbook_indicative'\n\t\t\t\t\t: 'orderbook',\n\t\t\t\tgrouping,\n\t\t\t})\n\t\t);\n\n\t\tthis.marketSubscriptions$.next(subscriptions);\n\t}\n\n\t/**\n\t * Unsubscribe from all markets\n\t */\n\tunsubscribeAll(): void {\n\t\tthis.marketSubscriptions$.next([]);\n\t}\n\n\t/**\n\t * Handle tab return to prevent \"speed run\" through queued messages\n\t */\n\thandleTabReturn(): void {\n\t\tthis.resultIncrementer.handleTabReturn();\n\t}\n\n\t/**\n\t * Reset slot tracking for clean state on reconnection\n\t */\n\tresetSlotTracking(): void {\n\t\t// Get all current subscription keys and reset their slot tracking\n\t\tfor (const subscriptionKey of this.subscriptions.keys()) {\n\t\t\t// Extract marketId and channel from subscription key\n\t\t\tconst [marketKey, channel] = subscriptionKey.split('_');\n\t\t\tconst resultKey = `${channel}_${marketKey}`;\n\t\t\tthis.resultIncrementer.resetKey(resultKey);\n\t\t}\n\t}\n\n\t/**\n\t * Destroy the client and clean up all subscriptions\n\t */\n\tdestroy(): void {\n\t\tthis.destroy$.next();\n\t\tthis.destroy$.complete();\n\t\tthis.subscriptions.forEach(({ unsubscribe }) => unsubscribe());\n\t\tthis.subscriptions.clear();\n\t}\n\n\t/**\n\t * Sets up the subscription management pipeline that handles market data subscriptions.\n\t * This pipeline:\n\t * 1. Watches for changes in market subscriptions\n\t * 2. Only processes changes when the subscription list actually changes\n\t * 3. Manages the lifecycle of websocket subscriptions\n\t * 4. Cleans up when the client is destroyed\n\t */\n\tprivate setupSubscriptionManagement(): void {\n\t\tthis.marketSubscriptions$\n\t\t\t.pipe(\n\t\t\t\t// Only emit when the subscription list actually changes\n\t\t\t\t// Uses JSON.stringify for deep comparison of subscription arrays\n\t\t\t\tdistinctUntilChanged(\n\t\t\t\t\t(prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)\n\t\t\t\t),\n\t\t\t\t// Switch to managing the new set of subscriptions\n\t\t\t\t// This will cancel any previous subscription management\n\t\t\t\tswitchMap((subscriptions) =>\n\t\t\t\t\tthis.manageNewSubscriptions(subscriptions)\n\t\t\t\t),\n\t\t\t\t// Clean up when the client is destroyed\n\t\t\t\ttakeUntil(this.destroy$)\n\t\t\t)\n\t\t\t.subscribe();\n\t}\n\n\t/**\n\t * Manages the lifecycle of websocket subscriptions by:\n\t * 1. Comparing current subscriptions with new subscriptions\n\t * 2. Unsubscribing from any subscriptions that are no longer needed\n\t * 3. Creating new subscriptions for markets that weren't previously subscribed\n\t *\n\t * @param newSubscriptions - The new set of market subscriptions to maintain\n\t * @returns An empty observable since this is a side-effect operation\n\t */\n\tprivate manageNewSubscriptions(\n\t\tnewSubscriptions: MarketSubscription[]\n\t): Observable<never> {\n\t\t// Get sets of subscription keys for efficient comparison\n\t\tconst currentKeys = new Set(this.subscriptions.keys());\n\t\tconst newKeys = new Set(\n\t\t\tnewSubscriptions.map((sub) => this.getSubscriptionKey(sub))\n\t\t);\n\n\t\t// Unsubscribe from removed subscriptions\n\t\tfor (const key of currentKeys) {\n\t\t\tif (!newKeys.has(key)) {\n\t\t\t\tconst subscription = this.subscriptions.get(key);\n\t\t\t\tif (subscription) {\n\t\t\t\t\tsubscription.unsubscribe();\n\t\t\t\t\tthis.subscriptions.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Subscribe to new subscriptions\n\t\tfor (const subscription of newSubscriptions) {\n\t\t\tconst key = this.getSubscriptionKey(subscription);\n\t\t\tif (!currentKeys.has(key)) {\n\t\t\t\tthis.createSubscription(subscription);\n\t\t\t}\n\t\t}\n\n\t\treturn EMPTY;\n\t}\n\n\tprivate createSubscription(subscription: MarketSubscription): void {\n\t\tconst { marketId, channel, grouping } = subscription;\n\t\tconst subscriptionKey = this.getSubscriptionKey(subscription);\n\n\t\ttry {\n\t\t\tconst { unsubscribe } = MultiplexWebSocket.createWebSocketSubscription<{\n\t\t\t\tchannel: string;\n\t\t\t\tdata: string;\n\t\t\t}>({\n\t\t\t\twsUrl: this.config.websocketUrl,\n\t\t\t\tenableHeartbeatMonitoring: true,\n\t\t\t\tsubscriptionId: `${this.config.websocketUrl}_dlob_liquidity_${marketId.key}`,\n\t\t\t\tsubscribeMessage: JSON.stringify(\n\t\t\t\t\tDLOB_SERVER_WEBSOCKET_UTILS.getSubscriptionProps({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t\tunsubscribeMessage: JSON.stringify(\n\t\t\t\t\tDLOB_SERVER_WEBSOCKET_UTILS.getUnsubscriptionProps({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t\tonMessage: (message: { channel: string; data: string }) => {\n\t\t\t\t\tthis.rawMessages$.next({\n\t\t\t\t\t\tmarketId,\n\t\t\t\t\t\tchannel: message.channel,\n\t\t\t\t\t\tdata: message.data,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tmessageFilter: (message: { channel: string; data: string }) => {\n\t\t\t\t\treturn DLOB_SERVER_WEBSOCKET_UTILS.getMessageFilter({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})(message);\n\t\t\t\t},\n\t\t\t\tonError: (error?: any) => {\n\t\t\t\t\tconsole.error('Caught error in createSubscription', error);\n\t\t\t\t\tthis.config.onFallback?.(marketId);\n\t\t\t\t},\n\t\t\t\terrorMessageFilter: (message?: any) => {\n\t\t\t\t\tif (message?.error) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.subscriptions.set(subscriptionKey, { unsubscribe });\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to create subscription:', error);\n\t\t\tthis.config.onFallback?.(marketId);\n\t\t}\n\t}\n\n\tprivate processRawMessage(\n\t\tmarketId: MarketId,\n\t\tchannel: string,\n\t\tdata: string\n\t): ProcessedMarketData | null {\n\t\ttry {\n\t\t\tconst parsed = this.tryParse(data) as RawL2Output;\n\t\t\tconst resultKey = `${channel}_${marketId.key}`;\n\t\t\tconst messageTimestamp = Date.now(); // Capture when we received this message\n\n\t\t\tconst validResult = this.resultIncrementer.handleResult(\n\t\t\t\tresultKey,\n\t\t\t\tparsed.slot ?? 0,\n\t\t\t\tmessageTimestamp\n\t\t\t);\n\n\t\t\tif (!validResult) {\n\t\t\t\treturn null; // Skip results which aren't slot-increasing or are filtered due to tab return\n\t\t\t}\n\n\t\t\tconst deserializedData = deserializeL2Response(parsed);\n\n\t\t\treturn {\n\t\t\t\tmarketId,\n\t\t\t\trawData: parsed,\n\t\t\t\tdeserializedData,\n\t\t\t\tslot: parsed.slot ?? 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate tryParse(data: unknown): unknown {\n\t\ttry {\n\t\t\treturn JSON.parse(data as string, (key, value) => {\n\t\t\t\t// If the value is a number and it's too large to be safely represented as a JavaScript number,\n\t\t\t\t// convert it to a string to prevent precision loss\n\t\t\t\tif (typeof value === 'number' && !Number.isSafeInteger(value)) {\n\t\t\t\t\treturn value.toString();\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t\t});\n\t\t} catch (e) {\n\t\t\treturn data;\n\t\t}\n\t}\n\n\tprivate getSubscriptionKey(subscription: MarketSubscription): string {\n\t\tconst { marketId, channel, grouping } = subscription;\n\t\treturn `${marketId.key}_${channel}${grouping ? `_${grouping}` : ''}`;\n\t}\n}\n\nexport default DlobWebsocketClient;\n"]}
|
|
1
|
+
{"version":3,"file":"DlobWebsocketClient.js","sourceRoot":"","sources":["../../src/clients/DlobWebsocketClient.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n\tMarketId,\n\tdeserializeL2Response,\n\tOrderbookGrouping,\n\tDLOB_SERVER_WEBSOCKET_UTILS,\n\tDlobServerChannel,\n} from '..';\nimport { Observable, Subject, BehaviorSubject, EMPTY } from 'rxjs';\nimport {\n\tmap,\n\tfilter,\n\tcatchError,\n\ttakeUntil,\n\tshare,\n\tdistinctUntilChanged,\n\tswitchMap,\n} from 'rxjs/operators';\nimport { ResultSlotIncrementer } from '../utils/ResultSlotIncrementer';\nimport { MultiplexWebSocket } from '../utils/MultiplexWebSocket';\nimport { RawL2Output } from '../utils/orderbook/types';\n\nexport type OrderbookChannelTypes = Extract<\n\tDlobServerChannel,\n\t'orderbook' | 'orderbook_indicative'\n>;\n\nexport interface DlobWebsocketClientConfig {\n\twebsocketUrl: string;\n\tenableIndicativeOrderbook?: boolean;\n\tresultSlotIncrementer?: ResultSlotIncrementer;\n\tonFallback?: (marketId: MarketId) => void;\n}\n\nexport interface MarketSubscription {\n\tmarketId: MarketId;\n\tchannel: OrderbookChannelTypes;\n\tgrouping?: OrderbookGrouping;\n}\n\nexport interface ProcessedMarketData {\n\tmarketId: MarketId;\n\trawData: RawL2Output;\n\tdeserializedData: ReturnType<typeof deserializeL2Response>;\n\tslot: number;\n}\n\nexport class DlobWebsocketClient {\n\tprivate config: DlobWebsocketClientConfig;\n\tprivate subscriptions = new Map<string, { unsubscribe: () => void }>();\n\tprivate resultIncrementer: ResultSlotIncrementer;\n\tprivate destroy$ = new Subject<void>();\n\n\t// String caches to prevent repeated string concatenation\n\tprivate static readonly stringCache = new Map<string, string>();\n\tprivate static readonly maxCacheSize = 1000;\n\n\t// Subjects for reactive streams\n\tprivate marketSubscriptions$ = new BehaviorSubject<MarketSubscription[]>([]);\n\tprivate rawMessages$ = new Subject<{\n\t\tmarketId: MarketId;\n\t\tchannel: string;\n\t\tdata: string;\n\t}>();\n\n\tconstructor(config: DlobWebsocketClientConfig) {\n\t\tthis.config = config;\n\t\tthis.resultIncrementer =\n\t\t\tconfig.resultSlotIncrementer || new ResultSlotIncrementer();\n\n\t\tthis.setupSubscriptionManagement();\n\t}\n\n\t/**\n\t * Get an observable stream of processed market data for specific markets\n\t */\n\tgetMarketDataStream(marketIds: MarketId[]): Observable<ProcessedMarketData> {\n\t\treturn this.rawMessages$.pipe(\n\t\t\tfilter(({ marketId }) => marketIds.some((id) => id.key === marketId.key)),\n\t\t\tmap(({ marketId, channel, data }) =>\n\t\t\t\tthis.processRawMessage(marketId, channel, data)\n\t\t\t),\n\t\t\tfilter((result): result is ProcessedMarketData => result !== null),\n\t\t\tcatchError((error) => {\n\t\t\t\tconsole.error('Caught error in getMarketDataStream', error);\n\t\t\t\treturn EMPTY;\n\t\t\t}),\n\t\t\ttakeUntil(this.destroy$),\n\t\t\tshare()\n\t\t);\n\t}\n\n\t/**\n\t * Subscribe to market data for given markets\n\t */\n\tsubscribeToMarkets(\n\t\tmarkets: {\n\t\t\tmarketId: MarketId;\n\t\t\tgrouping?: OrderbookGrouping;\n\t\t}[]\n\t): void {\n\t\tconst subscriptions: MarketSubscription[] = markets.map(\n\t\t\t({ marketId, grouping }) => ({\n\t\t\t\tmarketId,\n\t\t\t\tchannel: this.config.enableIndicativeOrderbook\n\t\t\t\t\t? 'orderbook_indicative'\n\t\t\t\t\t: 'orderbook',\n\t\t\t\tgrouping,\n\t\t\t})\n\t\t);\n\n\t\tthis.marketSubscriptions$.next(subscriptions);\n\t}\n\n\t/**\n\t * Unsubscribe from all markets\n\t */\n\tunsubscribeAll(): void {\n\t\tthis.marketSubscriptions$.next([]);\n\t}\n\n\t/**\n\t * Handle tab return to prevent \"speed run\" through queued messages\n\t */\n\thandleTabReturn(): void {\n\t\tthis.resultIncrementer.handleTabReturn();\n\t}\n\n\t/**\n\t * Reset slot tracking for clean state on reconnection\n\t */\n\tresetSlotTracking(): void {\n\t\t// Get all current subscription keys and reset their slot tracking\n\t\tfor (const subscriptionKey of this.subscriptions.keys()) {\n\t\t\t// Extract marketId and channel from subscription key\n\t\t\tconst [marketKey, channel] = subscriptionKey.split('_');\n\t\t\tconst resultKey = this.getCachedString(\n\t\t\t\t`${channel}_${marketKey}`,\n\t\t\t\t'result'\n\t\t\t);\n\t\t\tthis.resultIncrementer.resetKey(resultKey);\n\t\t}\n\t}\n\n\t/**\n\t * Cache frequently used strings to reduce memory allocation\n\t */\n\tprivate getCachedString(baseString: string, type: string): string {\n\t\tconst cacheKey = `${type}:${baseString}`;\n\n\t\tif (DlobWebsocketClient.stringCache.has(cacheKey)) {\n\t\t\treturn DlobWebsocketClient.stringCache.get(cacheKey)!;\n\t\t}\n\n\t\t// If cache is getting too large, clear oldest entries\n\t\tif (\n\t\t\tDlobWebsocketClient.stringCache.size >= DlobWebsocketClient.maxCacheSize\n\t\t) {\n\t\t\tconst firstKey = DlobWebsocketClient.stringCache.keys().next().value;\n\t\t\tDlobWebsocketClient.stringCache.delete(firstKey);\n\t\t}\n\n\t\tDlobWebsocketClient.stringCache.set(cacheKey, baseString);\n\t\treturn baseString;\n\t}\n\n\t/**\n\t * Destroy the client and clean up all subscriptions\n\t */\n\tdestroy(): void {\n\t\tthis.destroy$.next();\n\t\tthis.destroy$.complete();\n\t\tthis.subscriptions.forEach(({ unsubscribe }) => unsubscribe());\n\t\tthis.subscriptions.clear();\n\t}\n\n\t/**\n\t * Sets up the subscription management pipeline that handles market data subscriptions.\n\t * This pipeline:\n\t * 1. Watches for changes in market subscriptions\n\t * 2. Only processes changes when the subscription list actually changes\n\t * 3. Manages the lifecycle of websocket subscriptions\n\t * 4. Cleans up when the client is destroyed\n\t */\n\tprivate setupSubscriptionManagement(): void {\n\t\tthis.marketSubscriptions$\n\t\t\t.pipe(\n\t\t\t\t// Only emit when the subscription list actually changes\n\t\t\t\t// Uses JSON.stringify for deep comparison of subscription arrays\n\t\t\t\tdistinctUntilChanged(\n\t\t\t\t\t(prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)\n\t\t\t\t),\n\t\t\t\t// Switch to managing the new set of subscriptions\n\t\t\t\t// This will cancel any previous subscription management\n\t\t\t\tswitchMap((subscriptions) =>\n\t\t\t\t\tthis.manageNewSubscriptions(subscriptions)\n\t\t\t\t),\n\t\t\t\t// Clean up when the client is destroyed\n\t\t\t\ttakeUntil(this.destroy$)\n\t\t\t)\n\t\t\t.subscribe();\n\t}\n\n\t/**\n\t * Manages the lifecycle of websocket subscriptions by:\n\t * 1. Comparing current subscriptions with new subscriptions\n\t * 2. Unsubscribing from any subscriptions that are no longer needed\n\t * 3. Creating new subscriptions for markets that weren't previously subscribed\n\t *\n\t * @param newSubscriptions - The new set of market subscriptions to maintain\n\t * @returns An empty observable since this is a side-effect operation\n\t */\n\tprivate manageNewSubscriptions(\n\t\tnewSubscriptions: MarketSubscription[]\n\t): Observable<never> {\n\t\t// Get sets of subscription keys for efficient comparison\n\t\tconst currentKeys = new Set(this.subscriptions.keys());\n\t\tconst newKeys = new Set(\n\t\t\tnewSubscriptions.map((sub) => this.getSubscriptionKey(sub))\n\t\t);\n\n\t\t// Unsubscribe from removed subscriptions\n\t\tfor (const key of currentKeys) {\n\t\t\tif (!newKeys.has(key)) {\n\t\t\t\tconst subscription = this.subscriptions.get(key);\n\t\t\t\tif (subscription) {\n\t\t\t\t\tsubscription.unsubscribe();\n\t\t\t\t\tthis.subscriptions.delete(key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Subscribe to new subscriptions\n\t\tfor (const subscription of newSubscriptions) {\n\t\t\tconst key = this.getSubscriptionKey(subscription);\n\t\t\tif (!currentKeys.has(key)) {\n\t\t\t\tthis.createSubscription(subscription);\n\t\t\t}\n\t\t}\n\n\t\treturn EMPTY;\n\t}\n\n\tprivate createSubscription(subscription: MarketSubscription): void {\n\t\tconst { marketId, channel, grouping } = subscription;\n\t\tconst subscriptionKey = this.getSubscriptionKey(subscription);\n\n\t\ttry {\n\t\t\tconst { unsubscribe } = MultiplexWebSocket.createWebSocketSubscription<{\n\t\t\t\tchannel: string;\n\t\t\t\tdata: string;\n\t\t\t}>({\n\t\t\t\twsUrl: this.config.websocketUrl,\n\t\t\t\tenableHeartbeatMonitoring: true,\n\t\t\t\tsubscriptionId: this.getCachedString(\n\t\t\t\t\t`${this.config.websocketUrl}_dlob_liquidity_${marketId.key}`,\n\t\t\t\t\t'subscription'\n\t\t\t\t),\n\t\t\t\tsubscribeMessage: JSON.stringify(\n\t\t\t\t\tDLOB_SERVER_WEBSOCKET_UTILS.getSubscriptionProps({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t\tunsubscribeMessage: JSON.stringify(\n\t\t\t\t\tDLOB_SERVER_WEBSOCKET_UTILS.getUnsubscriptionProps({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t\tonMessage: (message: { channel: string; data: string }) => {\n\t\t\t\t\tthis.rawMessages$.next({\n\t\t\t\t\t\tmarketId,\n\t\t\t\t\t\tchannel: message.channel,\n\t\t\t\t\t\tdata: message.data,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tmessageFilter: (message: { channel: string; data: string }) => {\n\t\t\t\t\treturn DLOB_SERVER_WEBSOCKET_UTILS.getMessageFilter({\n\t\t\t\t\t\ttype: channel,\n\t\t\t\t\t\tmarket: marketId,\n\t\t\t\t\t\tgrouping,\n\t\t\t\t\t})(message);\n\t\t\t\t},\n\t\t\t\tonError: (error?: any) => {\n\t\t\t\t\tconsole.error('Caught error in createSubscription', error);\n\t\t\t\t\tthis.config.onFallback?.(marketId);\n\t\t\t\t},\n\t\t\t\terrorMessageFilter: (message?: any) => {\n\t\t\t\t\tif (message?.error) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.subscriptions.set(subscriptionKey, { unsubscribe });\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to create subscription:', error);\n\t\t\tthis.config.onFallback?.(marketId);\n\t\t}\n\t}\n\n\tprivate processRawMessage(\n\t\tmarketId: MarketId,\n\t\tchannel: string,\n\t\tdata: string\n\t): ProcessedMarketData | null {\n\t\ttry {\n\t\t\tconst parsed = this.tryParse(data) as RawL2Output;\n\t\t\tconst resultKey = this.getCachedString(\n\t\t\t\t`${channel}_${marketId.key}`,\n\t\t\t\t'result'\n\t\t\t);\n\t\t\tconst messageTimestamp = Date.now(); // Capture when we received this message\n\n\t\t\tconst validResult = this.resultIncrementer.handleResult(\n\t\t\t\tresultKey,\n\t\t\t\tparsed.slot ?? 0,\n\t\t\t\tmessageTimestamp\n\t\t\t);\n\n\t\t\tif (!validResult) {\n\t\t\t\treturn null; // Skip results which aren't slot-increasing or are filtered due to tab return\n\t\t\t}\n\n\t\t\tconst deserializedData = deserializeL2Response(parsed);\n\n\t\t\treturn {\n\t\t\t\tmarketId,\n\t\t\t\trawData: parsed,\n\t\t\t\tdeserializedData,\n\t\t\t\tslot: parsed.slot ?? 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate tryParse(data: unknown): unknown {\n\t\ttry {\n\t\t\treturn JSON.parse(data as string, (key, value) => {\n\t\t\t\t// If the value is a number and it's too large to be safely represented as a JavaScript number,\n\t\t\t\t// convert it to a string to prevent precision loss\n\t\t\t\tif (typeof value === 'number' && !Number.isSafeInteger(value)) {\n\t\t\t\t\treturn value.toString();\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t\t});\n\t\t} catch (e) {\n\t\t\treturn data;\n\t\t}\n\t}\n\n\tprivate getSubscriptionKey(subscription: MarketSubscription): string {\n\t\tconst { marketId, channel, grouping } = subscription;\n\t\tconst baseKey = `${marketId.key}_${channel}${\n\t\t\tgrouping ? `_${grouping}` : ''\n\t\t}`;\n\t\treturn this.getCachedString(baseKey, 'subscription_key');\n\t}\n}\n\nexport default DlobWebsocketClient;\n"]}
|
|
@@ -28,14 +28,26 @@ const getBaseDataApiUrl = (env) => {
|
|
|
28
28
|
const dataApiUrl = EnvironmentConstants_1.EnvironmentConstants.dataServerUrl[constantEnv];
|
|
29
29
|
return dataApiUrl.replace('https://', '');
|
|
30
30
|
};
|
|
31
|
+
// Cache for URL construction to prevent repeated string concatenation
|
|
32
|
+
const urlCache = new Map();
|
|
33
|
+
const MAX_URL_CACHE_SIZE = 500;
|
|
31
34
|
const getCandleFetchUrl = ({ env, marketId, resolution, startTs, countToFetch, }) => {
|
|
32
35
|
const baseDataApiUrl = getBaseDataApiUrl(env);
|
|
36
|
+
// Cache key for this URL configuration
|
|
37
|
+
const cacheKey = `${marketId.key}-${resolution}-${countToFetch}-${env.key}-${startTs !== null && startTs !== void 0 ? startTs : 'none'}`;
|
|
38
|
+
if (urlCache.has(cacheKey)) {
|
|
39
|
+
return urlCache.get(cacheKey);
|
|
40
|
+
}
|
|
33
41
|
// Base URL without startTs parameter
|
|
34
42
|
let fetchUrl = `https://${baseDataApiUrl}/market/${getMarketSymbolForMarketId(marketId, env)}/candles/${resolution}?limit=${Math.min(countToFetch, CANDLE_FETCH_LIMIT)}`;
|
|
35
43
|
// Only add startTs parameter if it's provided
|
|
36
44
|
if (startTs !== undefined) {
|
|
37
45
|
fetchUrl += `&startTs=${startTs}`;
|
|
38
46
|
}
|
|
47
|
+
// Cache the result if cache isn't too large
|
|
48
|
+
if (urlCache.size < MAX_URL_CACHE_SIZE) {
|
|
49
|
+
urlCache.set(cacheKey, fetchUrl);
|
|
50
|
+
}
|
|
39
51
|
return fetchUrl;
|
|
40
52
|
};
|
|
41
53
|
// Separate event bus for candle events
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"candleClient.js","sourceRoot":"","sources":["../../src/clients/candleClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCandleResolution,\n\tDevnetPerpMarkets,\n\tDevnetSpotMarkets,\n\tMainnetPerpMarkets,\n\tMainnetSpotMarkets,\n} from '@drift-labs/sdk';\nimport { JsonCandle, MarketId, MarketSymbol } from '../types';\nimport { Candle } from '../utils/candles/Candle';\nimport { StrictEventEmitter } from '../utils/StrictEventEmitter';\nimport { EnvironmentConstants } from '../EnvironmentConstants';\nimport { UIEnv } from '../types/UIEnv';\nimport { assert } from '../utils/assert';\nimport { CandleSubscriberSubscription, MarketDataFeed } from './marketDataFeed';\n\n/**\n * # CANDLE CLIENT HIGH LEVEL EXPLANATION:\n * The Candle Client uses the Data API (see https://data.api.drift.trade/playground) to source candles to display.\n *\n * There are two key parts of the client:\n * - Fetching Candles\n * - Subscribing to Candles\n *\n * ## Fetching Candles:\n * - We can fetch candles between any timestamp range.\n * - The maximum number of candles we can fetch in a single request is 1000 (see CANDLE_FETCH_LIMIT).\n * - We define \"recent history\" to be the last 1000 candles .. basically whatever comes back from the infra when we don't use a startTs and use the maximum fetch limit.\n * - We want to avoid using high cardinality parameters in the fetch because otherwise we will miss the cache in the infra.\n * \t- A concrete example of this is that we don't attach a startTs parameter when we are fetching candles within recent history (past 1000 candles)\n * - We cache the recent candles in memory so that any subsequent fetches within recent history after the first one will be served from cache.\n * \t- e.g. moving back to a further timeframe on TradingView - the required candles could potentially be in the cache, so we don't need to refetch them.\n *\n * ## Subscribing to Candles:\n * - We subscribe to a websocket endpoint for a given market and resolution.\n * - We allow the client to support multiple concurrent subscriptions, because the TradingView comopnent will sometimes do this when switching between markets.\n *\n * ## Possible Improvements:\n * - Create a more advanced cache which can store more than the most recent 1000 candles, dynamically growing as more candles are added (for now seems unnecessary, rare for someone to go back further than 1000 candles)\n */\n\n// Used by the subscriber client to fetch candles from the data API\ntype CandleFetchConfig = {\n\tenv: UIEnv;\n\tmarketId: MarketId;\n\tresolution: CandleResolution;\n\tfromTs: number; // Seconds\n\ttoTs: number; // Seconds\n};\n\n// Used by the subscriber client to subscribe to the candles websocket endpoint\ntype CandleSubscriptionConfig = {\n\tresolution: CandleResolution;\n\tmarketId: MarketId;\n\tenv: UIEnv;\n};\n\n// This is what the client subscriber uses internally to fetch the candles from the data API\ntype CandleFetchUrlConfig = {\n\tenv: UIEnv;\n\tmarketId: MarketId;\n\tresolution: CandleResolution;\n\tstartTs?: number; // Seconds - now optional\n\tcountToFetch: number;\n};\n\ntype CandleFetchResponseJson = {\n\tsuccess: boolean;\n\trecords: JsonCandle[];\n};\n\nconst getMarketSymbolForMarketId = (marketId: MarketId, uiEnv: UIEnv) => {\n\tconst isPerp = marketId.isPerp;\n\n\tconst sdkEnv = uiEnv.sdkEnv;\n\n\tif (isPerp) {\n\t\tconst marketConfigs =\n\t\t\tsdkEnv === 'mainnet-beta' ? MainnetPerpMarkets : DevnetPerpMarkets;\n\t\tconst targetMarketConfig = marketConfigs.find(\n\t\t\t(config) => config.marketIndex === marketId.marketIndex\n\t\t);\n\t\treturn targetMarketConfig.symbol as MarketSymbol;\n\t} else {\n\t\tconst marketConfigs =\n\t\t\tsdkEnv === 'mainnet-beta' ? MainnetSpotMarkets : DevnetSpotMarkets;\n\t\tconst targetMarketConfig = marketConfigs.find(\n\t\t\t(config) => config.marketIndex === marketId.marketIndex\n\t\t);\n\t\treturn targetMarketConfig.symbol as MarketSymbol;\n\t}\n};\n\n// This is the maximum number of candles that can be fetched in a single GET request\nconst CANDLE_FETCH_LIMIT = 1000;\n\nconst getBaseDataApiUrl = (env: UIEnv) => {\n\tconst constantEnv: keyof typeof EnvironmentConstants.dataServerUrl =\n\t\tenv.isStaging ? 'staging' : env.isDevnet ? 'dev' : 'mainnet';\n\tconst dataApiUrl = EnvironmentConstants.dataServerUrl[constantEnv];\n\treturn dataApiUrl.replace('https://', '');\n};\n\nconst getCandleFetchUrl = ({\n\tenv,\n\tmarketId,\n\tresolution,\n\tstartTs,\n\tcountToFetch,\n}: CandleFetchUrlConfig) => {\n\tconst baseDataApiUrl = getBaseDataApiUrl(env);\n\n\t// Base URL without startTs parameter\n\tlet fetchUrl = `https://${baseDataApiUrl}/market/${getMarketSymbolForMarketId(\n\t\tmarketId,\n\t\tenv\n\t)}/candles/${resolution}?limit=${Math.min(countToFetch, CANDLE_FETCH_LIMIT)}`;\n\n\t// Only add startTs parameter if it's provided\n\tif (startTs !== undefined) {\n\t\tfetchUrl += `&startTs=${startTs}`;\n\t}\n\n\treturn fetchUrl;\n};\n\ntype CandleSubscriberEvents = {\n\t'candle-update': JsonCandle;\n};\n\n// Separate event bus for candle events\nclass CandleEventBus extends StrictEventEmitter<CandleSubscriberEvents> {\n\tconstructor() {\n\t\tsuper();\n\t}\n}\n\n// This class is reponsible for fetching candles from the data API's GET endpoint\nclass CandleFetcher {\n\tprivate readonly config: CandleFetchConfig;\n\n\t// Cache for storing recent candles by market ID and resolution\n\tpublic static recentCandlesCache: Map<\n\t\tstring, // key: `${marketId.key}-${resolution}`\n\t\t{\n\t\t\tcandles: JsonCandle[];\n\t\t\tearliestTs: number;\n\t\t\tlatestTs: number;\n\t\t\tfetchTime: number;\n\t\t}\n\t> = new Map();\n\n\t// Helper method to generate a cache key from market ID and resolution\n\tpublic static getCacheKey(\n\t\tmarketId: MarketId,\n\t\tresolution: CandleResolution\n\t): string {\n\t\treturn `${marketId.key}-${resolution}`;\n\t}\n\n\t// Public method to clear the entire cache\n\tpublic static clearWholeCache() {\n\t\tCandleFetcher.recentCandlesCache.clear();\n\t}\n\n\tpublic static clearCacheForSubscription(\n\t\tmarketId: MarketId,\n\t\tresolution: CandleResolution\n\t) {\n\t\tCandleFetcher.recentCandlesCache.delete(\n\t\t\tCandleFetcher.getCacheKey(marketId, resolution)\n\t\t);\n\t}\n\n\tconstructor(config: CandleFetchConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Candles are fetched in ascending order of time (index 0 -> oldest to index n -> newest)\n\t */\n\tprivate fetchCandlesFromApi = async (\n\t\tfetchUrl: string\n\t): Promise<JsonCandle[]> => {\n\t\tconst response = await fetch(fetchUrl);\n\t\tconst parsedResponse = (await response.json()) as CandleFetchResponseJson;\n\n\t\tif (!parsedResponse.success) {\n\t\t\tthrow new Error('Failed to fetch candles from data API');\n\t\t}\n\n\t\treturn parsedResponse.records;\n\t};\n\n\tprivate getCountOfCandlesBetweenStartAndEndTs = (\n\t\tstartTs: number,\n\t\tendTs: number\n\t) => {\n\t\tconst diffInSeconds = endTs - startTs;\n\t\tconst resolutionInSeconds =\n\t\t\tCandle.resolutionStringToCandleLengthMs(this.config.resolution) / 1000;\n\t\tconst diffInCandles = diffInSeconds / resolutionInSeconds;\n\t\treturn Math.ceil(diffInCandles);\n\t};\n\n\t/**\n\t * Try to get candles from the cache if they're available.\n\t * Returns null if no cached candles are available for the requested range.\n\t */\n\tprivate getFromCache = (): JsonCandle[] | null => {\n\t\t// Generate cache key for the current request\n\t\tconst cacheKey = CandleFetcher.getCacheKey(\n\t\t\tthis.config.marketId,\n\t\t\tthis.config.resolution\n\t\t);\n\t\tconst cachedCandles = CandleFetcher.recentCandlesCache.get(cacheKey);\n\n\t\t// Check if we have cached candles for this market and resolution\n\t\tif (cachedCandles) {\n\t\t\t// Check if the requested time range is within the bounds of cached candles\n\t\t\tif (\n\t\t\t\tthis.config.fromTs >= cachedCandles.earliestTs &&\n\t\t\t\tthis.config.toTs <= cachedCandles.latestTs\n\t\t\t) {\n\t\t\t\t// Filter cached candles to the requested time range\n\t\t\t\tconst filteredCandles = cachedCandles.candles.filter(\n\t\t\t\t\t(candle) =>\n\t\t\t\t\t\tcandle.ts >= this.config.fromTs && candle.ts <= this.config.toTs\n\t\t\t\t);\n\n\t\t\t\treturn filteredCandles;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t};\n\n\t/**\n\t * Determines if we should use the recent candles approach (without startTs for better caching).\n\t */\n\tprivate isRequestingRecentCandles = (\n\t\tnowSeconds: number,\n\t\tcandleLengthSeconds: number\n\t): boolean => {\n\t\t// Calculate cutoff time for \"recent\" candles (now - 1000 candles worth of time)\n\t\tconst recentCandlesCutoffTs =\n\t\t\tnowSeconds - candleLengthSeconds * CANDLE_FETCH_LIMIT;\n\n\t\t// Check if we're fetching recent candles based on the fromTs\n\t\treturn this.config.fromTs >= recentCandlesCutoffTs;\n\t};\n\n\t/**\n\t * Fetch recent candles without using startTs for better caching.\n\t */\n\tprivate fetchRecentCandles = async (\n\t\tnowSeconds: number\n\t): Promise<JsonCandle[]> => {\n\t\t// Fetch recent candles without specifying startTs\n\t\tconst fetchUrl = getCandleFetchUrl({\n\t\t\tenv: this.config.env,\n\t\t\tmarketId: this.config.marketId,\n\t\t\tresolution: this.config.resolution,\n\t\t\tcountToFetch: CANDLE_FETCH_LIMIT, // Ask for max candles to ensure we get enough\n\t\t});\n\n\t\t// Get the candles and reverse them (into ascending order)\n\t\tconst fetchedCandles = await this.fetchCandlesFromApi(fetchUrl);\n\t\tfetchedCandles.reverse();\n\n\t\tif (fetchedCandles.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Store the full fetchedCandles in cache before filtering\n\t\tthis.updateCandleCache(fetchedCandles, nowSeconds);\n\n\t\t// Filter to only include candles in the requested time range\n\t\tconst filteredCandles = this.filterCandlesByTimeRange(fetchedCandles);\n\n\t\treturn filteredCandles;\n\t};\n\n\t/**\n\t * Filter candles to only include those in the requested time range.\n\t */\n\tprivate filterCandlesByTimeRange = (candles: JsonCandle[]): JsonCandle[] => {\n\t\treturn candles.filter(\n\t\t\t(candle) =>\n\t\t\t\tcandle.ts >= this.config.fromTs && candle.ts <= this.config.toTs\n\t\t);\n\t};\n\n\t/**\n\t * Update the candle cache with the latest fetched candles.\n\t */\n\tprivate updateCandleCache = (\n\t\tfetchedCandles: JsonCandle[],\n\t\tnowSeconds: number\n\t): void => {\n\t\tif (fetchedCandles.length > 0) {\n\t\t\t// Generate cache key\n\t\t\tconst cacheKey = CandleFetcher.getCacheKey(\n\t\t\t\tthis.config.marketId,\n\t\t\t\tthis.config.resolution\n\t\t\t);\n\n\t\t\t// Sort candles by timestamp to find earliest and latest\n\t\t\tconst sortedCandles = [...fetchedCandles].sort((a, b) => a.ts - b.ts);\n\t\t\tconst earliestTs = sortedCandles[0].ts;\n\t\t\tconst latestTs = sortedCandles[sortedCandles.length - 1].ts;\n\n\t\t\t// Update or add to cache\n\t\t\tCandleFetcher.recentCandlesCache.set(cacheKey, {\n\t\t\t\tcandles: sortedCandles,\n\t\t\t\tearliestTs,\n\t\t\t\tlatestTs,\n\t\t\t\tfetchTime: nowSeconds,\n\t\t\t});\n\t\t}\n\t};\n\n\t/**\n\t * Fetch historical candles with pagination using startTs.\n\t */\n\tprivate fetchHistoricalCandles = async (): Promise<JsonCandle[]> => {\n\t\tlet candlesRemainingToFetch = this.getCountOfCandlesBetweenStartAndEndTs(\n\t\t\tthis.config.fromTs,\n\t\t\tthis.config.toTs\n\t\t);\n\n\t\tlet currentStartTs = this.config.toTs; // The data API takes \"startTs\" as the \"first timestamp you want going backwards in time\" e.g. all candles will be returned with descending time backwards from the startTs\n\t\tlet hitEndTsCutoff = false;\n\n\t\tlet candles: JsonCandle[] = [];\n\n\t\twhile (candlesRemainingToFetch > 0) {\n\t\t\tconst result = await this.fetchHistoricalCandlesBatch(\n\t\t\t\tcandlesRemainingToFetch,\n\t\t\t\tcurrentStartTs\n\t\t\t);\n\n\t\t\tif (result.fetchedCandles.length === 0) {\n\t\t\t\tcandlesRemainingToFetch = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// the deeper the loop, the older the result.candlesToAdd will be\n\t\t\tcandles = [...result.candlesToAdd, ...candles];\n\t\t\tcandlesRemainingToFetch -= result.candlesToAdd.length;\n\t\t\thitEndTsCutoff = result.hitEndTsCutoff;\n\n\t\t\tif (result.requiresAnotherFetch) {\n\t\t\t\tcurrentStartTs = result.nextStartTs;\n\t\t\t} else if (candlesRemainingToFetch > 0) {\n\t\t\t\t// This means we have fetched all the candles available for this time range and we can stop fetching\n\t\t\t\tcandlesRemainingToFetch = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (hitEndTsCutoff) {\n\t\t\treturn this.filterCandlesByTimeRange(candles);\n\t\t}\n\n\t\treturn candles;\n\t};\n\n\t/**\n\t * Fetch historical candles with pagination, backwards from the toTs value given in the config.\n\t *\n\t * This method works by looping backwards from the LATEST (toTs) timestamp to the OLDEST (fromTs) timestamp.\n\t *\n\t * Things to note:\n\t * - There is a limit to how many candles can be fetched in a single request (see CANDLE_FETCH_LIMIT)\n\t * - We have implemented this to minimise the cardinality in the API request because that helps with caching\n\t */\n\tprivate fetchHistoricalCandlesBatch = async (\n\t\tcandlesRemainingToFetch: number,\n\t\tcurrentStartTs: number\n\t): Promise<{\n\t\tfetchedCandles: JsonCandle[];\n\t\tcandlesToAdd: JsonCandle[];\n\t\thitEndTsCutoff: boolean;\n\t\trequiresAnotherFetch: boolean;\n\t\tnextStartTs: number;\n\t}> => {\n\t\tconst candlesToFetch = Math.min(\n\t\t\tcandlesRemainingToFetch,\n\t\t\tCANDLE_FETCH_LIMIT\n\t\t);\n\n\t\tconst fetchUrl = getCandleFetchUrl({\n\t\t\tenv: this.config.env,\n\t\t\tmarketId: this.config.marketId,\n\t\t\tresolution: this.config.resolution,\n\t\t\tstartTs: currentStartTs, // Include startTs for historical candles\n\t\t\tcountToFetch: candlesToFetch,\n\t\t});\n\n\t\tconst fetchedCandles = await this.fetchCandlesFromApi(fetchUrl);\n\n\t\t// Reverse candles into ascending order\n\t\tfetchedCandles.reverse();\n\n\t\tif (fetchedCandles.length === 0) {\n\t\t\treturn {\n\t\t\t\tfetchedCandles,\n\t\t\t\tcandlesToAdd: [],\n\t\t\t\thitEndTsCutoff: false,\n\t\t\t\trequiresAnotherFetch: false,\n\t\t\t\tnextStartTs: currentStartTs,\n\t\t\t};\n\t\t}\n\n\t\tconst lastCandle = fetchedCandles[fetchedCandles.length - 1]; // This is the LATEST candle .. (they are sorted ascending by time right now)\n\n\t\tconst hitPageSizeCutoff = fetchedCandles.length === CANDLE_FETCH_LIMIT;\n\t\tconst hitEndTsCutoff = lastCandle.ts < this.config.fromTs;\n\n\t\tconst requiresAnotherFetch = hitPageSizeCutoff && !hitEndTsCutoff; // If the number of candles returned is equal to the maximum number of candles that can be fetched in a single GET request, then we need to fetch more candles\n\n\t\tlet candlesToAdd = fetchedCandles;\n\t\tlet nextStartTs = currentStartTs;\n\n\t\tif (requiresAnotherFetch) {\n\t\t\t// If we need to do another fetch, trim any candles with the same timestamp as the last candle in the previous fetch, because that is the pointer for our next fetch and we don't want to duplicate candles\n\t\t\tcandlesToAdd = candlesToAdd.filter((candle) => {\n\t\t\t\treturn candle.ts < lastCandle.ts;\n\t\t\t});\n\n\t\t\tconst oldestCandle = fetchedCandles[0]; // first candle is the oldest\n\t\t\tnextStartTs = oldestCandle.ts; // If we are doing another loop, then the trimmed candles have all the candles except for ones with the last candle's timestamp. For the next loop we want to fetch from that timestamp;\n\t\t}\n\n\t\treturn {\n\t\t\tfetchedCandles,\n\t\t\tcandlesToAdd,\n\t\t\thitEndTsCutoff,\n\t\t\trequiresAnotherFetch,\n\t\t\tnextStartTs,\n\t\t};\n\t};\n\n\t/**\n\t * This class needs to fetch candles based on the config.\n\t *\n\t * If the number of candles requested exceeds the maximum number of candles that can be fetched in a single GET request, then it needs to loop multiple get requests, using the last candle's timestamp as the offset startTs for each subsequent request. If the number of candles returned is less than the requested number of candles, then we have fetched all the candles available.\n\t *\n\t * For recent candles (ones where fromTs > now - candleLength*1000), we avoid using startTs in the URL to improve caching,\n\t * and instead fetch the most recent 1000 candles and then trim the result.\n\t */\n\tpublic fetchCandles = async () => {\n\t\t// Check cache first\n\t\tconst cachedCandles = this.getFromCache();\n\t\tif (cachedCandles) {\n\t\t\treturn cachedCandles;\n\t\t}\n\n\t\t// Calculate the candle length in seconds for the current resolution\n\t\tconst candleLengthMs = Candle.resolutionStringToCandleLengthMs(\n\t\t\tthis.config.resolution\n\t\t);\n\t\tconst candleLengthSeconds = candleLengthMs / 1000;\n\n\t\t// Get current time in seconds\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000);\n\n\t\t// Check if we're fetching recent candles\n\t\tif (this.isRequestingRecentCandles(nowSeconds, candleLengthSeconds)) {\n\t\t\treturn this.fetchRecentCandles(nowSeconds);\n\t\t}\n\n\t\t// For historical candles (older than the last 1000 candles), use the previous approach\n\t\t// with startTs for pagination\n\t\treturn this.fetchHistoricalCandles();\n\t};\n}\n\nclass CandleSubscriber {\n\tprivate subscription: CandleSubscriberSubscription;\n\n\tconstructor(\n\t\treadonly config: CandleSubscriptionConfig,\n\t\treadonly eventBus: CandleEventBus\n\t) {}\n\n\tsubscribeToCandles = async () => {\n\t\tthis.subscription = MarketDataFeed.subscribe({\n\t\t\ttype: 'candles',\n\t\t\tresolution: this.config.resolution,\n\t\t\tenv: this.config.env,\n\t\t\tmarketSymbol: getMarketSymbolForMarketId(\n\t\t\t\tthis.config.marketId,\n\t\t\t\tthis.config.env\n\t\t\t),\n\t\t});\n\n\t\tthis.subscription.observable.subscribe((candle) => {\n\t\t\tthis.eventBus.emit('candle-update', candle);\n\t\t});\n\t};\n\n\tunsubscribe = () => {\n\t\tMarketDataFeed.unsubscribe(this.subscription.id);\n\t};\n}\n\n/**\n * This class will subscribe to candles from the Drift Data API.\n *\n * Note: If you are using TradingView you probably want to just use the DriftTvFeed class instead.\n */\nexport class CandleClient {\n\tprivate activeSubscriptions: Map<\n\t\tstring,\n\t\t{\n\t\t\tsubscriber: CandleSubscriber;\n\t\t\teventBus: CandleEventBus;\n\t\t}\n\t> = new Map();\n\n\tconstructor() {}\n\n\tpublic subscribe = async (\n\t\tconfig: CandleSubscriptionConfig,\n\t\tsubscriptionKey: string\n\t) => {\n\t\t// Kill any existing subscription with the same key before creating a new one\n\t\tif (this.activeSubscriptions.has(subscriptionKey)) {\n\t\t\tthis.unsubscribe(subscriptionKey);\n\t\t}\n\n\t\tconst eventBus = new CandleEventBus();\n\t\tconst subscriber = new CandleSubscriber(config, eventBus);\n\t\tawait subscriber.subscribeToCandles();\n\n\t\tthis.activeSubscriptions.set(subscriptionKey, {\n\t\t\tsubscriber,\n\t\t\teventBus,\n\t\t});\n\n\t\treturn;\n\t};\n\n\t/**\n\t *\n\t * @param config {\n\t *\n\t * env: UIEnv;\n\t *\n\t * marketId: MarketId;\n\t *\n\t * resolution: CandleResolution;\n\t *\n\t * fromTs: number; // Seconds :: This should be the START (oldest) timestamp of the candles to fetch\n\t *\n\t * toTs: number; // Seconds :: This should be the END (newest) timestamp of the candles to fetch\n\t *\n\t * }\n\t * @returns\n\t */\n\tpublic fetch = async (config: CandleFetchConfig): Promise<JsonCandle[]> => {\n\t\tassert(config.fromTs < config.toTs, 'fromTs must be less than toTs');\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000);\n\t\tassert(\n\t\t\tconfig.fromTs <= nowSeconds && config.toTs <= nowSeconds,\n\t\t\t`fromTs and toTs cannot be in the future (Requested fromTs: ${new Date(\n\t\t\t\tconfig.fromTs * 1000\n\t\t\t).toISOString()} and toTs: ${new Date(\n\t\t\t\tconfig.toTs * 1000\n\t\t\t).toISOString()}, Current time: ${new Date(\n\t\t\t\tnowSeconds * 1000\n\t\t\t).toISOString()})`\n\t\t);\n\t\tconst candleFetcher = new CandleFetcher(config);\n\t\tconst candles = await candleFetcher.fetchCandles();\n\t\treturn candles;\n\t};\n\n\tpublic unsubscribe = (subscriptionKey: string) => {\n\t\tconst subscription = this.activeSubscriptions.get(subscriptionKey);\n\t\tif (subscription) {\n\t\t\tCandleFetcher.clearCacheForSubscription(\n\t\t\t\tsubscription.subscriber.config.marketId,\n\t\t\t\tsubscription.subscriber.config.resolution\n\t\t\t);\n\t\t\tsubscription.subscriber.unsubscribe();\n\t\t\tsubscription.eventBus.removeAllListeners();\n\t\t\tthis.activeSubscriptions.delete(subscriptionKey);\n\t\t}\n\t};\n\n\tpublic unsubscribeAll = () => {\n\t\tfor (const subscriptionKey of this.activeSubscriptions.keys()) {\n\t\t\tthis.unsubscribe(subscriptionKey);\n\t\t}\n\t};\n\n\tpublic on(\n\t\tsubscriptionKey: string,\n\t\tevent: keyof CandleSubscriberEvents,\n\t\tlistener: (candle: JsonCandle) => void\n\t) {\n\t\tconst subscription = this.activeSubscriptions.get(subscriptionKey);\n\t\tif (subscription) {\n\t\t\tsubscription.eventBus.on(event, listener);\n\t\t} else {\n\t\t\tconsole.warn(`No active subscription found for key: ${subscriptionKey}`);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"candleClient.js","sourceRoot":"","sources":["../../src/clients/candleClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCandleResolution,\n\tDevnetPerpMarkets,\n\tDevnetSpotMarkets,\n\tMainnetPerpMarkets,\n\tMainnetSpotMarkets,\n} from '@drift-labs/sdk';\nimport { JsonCandle, MarketId, MarketSymbol } from '../types';\nimport { Candle } from '../utils/candles/Candle';\nimport { StrictEventEmitter } from '../utils/StrictEventEmitter';\nimport { EnvironmentConstants } from '../EnvironmentConstants';\nimport { UIEnv } from '../types/UIEnv';\nimport { assert } from '../utils/assert';\nimport { CandleSubscriberSubscription, MarketDataFeed } from './marketDataFeed';\n\n/**\n * # CANDLE CLIENT HIGH LEVEL EXPLANATION:\n * The Candle Client uses the Data API (see https://data.api.drift.trade/playground) to source candles to display.\n *\n * There are two key parts of the client:\n * - Fetching Candles\n * - Subscribing to Candles\n *\n * ## Fetching Candles:\n * - We can fetch candles between any timestamp range.\n * - The maximum number of candles we can fetch in a single request is 1000 (see CANDLE_FETCH_LIMIT).\n * - We define \"recent history\" to be the last 1000 candles .. basically whatever comes back from the infra when we don't use a startTs and use the maximum fetch limit.\n * - We want to avoid using high cardinality parameters in the fetch because otherwise we will miss the cache in the infra.\n * \t- A concrete example of this is that we don't attach a startTs parameter when we are fetching candles within recent history (past 1000 candles)\n * - We cache the recent candles in memory so that any subsequent fetches within recent history after the first one will be served from cache.\n * \t- e.g. moving back to a further timeframe on TradingView - the required candles could potentially be in the cache, so we don't need to refetch them.\n *\n * ## Subscribing to Candles:\n * - We subscribe to a websocket endpoint for a given market and resolution.\n * - We allow the client to support multiple concurrent subscriptions, because the TradingView comopnent will sometimes do this when switching between markets.\n *\n * ## Possible Improvements:\n * - Create a more advanced cache which can store more than the most recent 1000 candles, dynamically growing as more candles are added (for now seems unnecessary, rare for someone to go back further than 1000 candles)\n */\n\n// Used by the subscriber client to fetch candles from the data API\ntype CandleFetchConfig = {\n\tenv: UIEnv;\n\tmarketId: MarketId;\n\tresolution: CandleResolution;\n\tfromTs: number; // Seconds\n\ttoTs: number; // Seconds\n};\n\n// Used by the subscriber client to subscribe to the candles websocket endpoint\ntype CandleSubscriptionConfig = {\n\tresolution: CandleResolution;\n\tmarketId: MarketId;\n\tenv: UIEnv;\n};\n\n// This is what the client subscriber uses internally to fetch the candles from the data API\ntype CandleFetchUrlConfig = {\n\tenv: UIEnv;\n\tmarketId: MarketId;\n\tresolution: CandleResolution;\n\tstartTs?: number; // Seconds - now optional\n\tcountToFetch: number;\n};\n\ntype CandleFetchResponseJson = {\n\tsuccess: boolean;\n\trecords: JsonCandle[];\n};\n\nconst getMarketSymbolForMarketId = (marketId: MarketId, uiEnv: UIEnv) => {\n\tconst isPerp = marketId.isPerp;\n\n\tconst sdkEnv = uiEnv.sdkEnv;\n\n\tif (isPerp) {\n\t\tconst marketConfigs =\n\t\t\tsdkEnv === 'mainnet-beta' ? MainnetPerpMarkets : DevnetPerpMarkets;\n\t\tconst targetMarketConfig = marketConfigs.find(\n\t\t\t(config) => config.marketIndex === marketId.marketIndex\n\t\t);\n\t\treturn targetMarketConfig.symbol as MarketSymbol;\n\t} else {\n\t\tconst marketConfigs =\n\t\t\tsdkEnv === 'mainnet-beta' ? MainnetSpotMarkets : DevnetSpotMarkets;\n\t\tconst targetMarketConfig = marketConfigs.find(\n\t\t\t(config) => config.marketIndex === marketId.marketIndex\n\t\t);\n\t\treturn targetMarketConfig.symbol as MarketSymbol;\n\t}\n};\n\n// This is the maximum number of candles that can be fetched in a single GET request\nconst CANDLE_FETCH_LIMIT = 1000;\n\nconst getBaseDataApiUrl = (env: UIEnv) => {\n\tconst constantEnv: keyof typeof EnvironmentConstants.dataServerUrl =\n\t\tenv.isStaging ? 'staging' : env.isDevnet ? 'dev' : 'mainnet';\n\tconst dataApiUrl = EnvironmentConstants.dataServerUrl[constantEnv];\n\treturn dataApiUrl.replace('https://', '');\n};\n\n// Cache for URL construction to prevent repeated string concatenation\nconst urlCache = new Map<string, string>();\nconst MAX_URL_CACHE_SIZE = 500;\n\nconst getCandleFetchUrl = ({\n\tenv,\n\tmarketId,\n\tresolution,\n\tstartTs,\n\tcountToFetch,\n}: CandleFetchUrlConfig) => {\n\tconst baseDataApiUrl = getBaseDataApiUrl(env);\n\n\t// Cache key for this URL configuration\n\tconst cacheKey = `${marketId.key}-${resolution}-${countToFetch}-${env.key}-${\n\t\tstartTs ?? 'none'\n\t}`;\n\n\tif (urlCache.has(cacheKey)) {\n\t\treturn urlCache.get(cacheKey)!;\n\t}\n\n\t// Base URL without startTs parameter\n\tlet fetchUrl = `https://${baseDataApiUrl}/market/${getMarketSymbolForMarketId(\n\t\tmarketId,\n\t\tenv\n\t)}/candles/${resolution}?limit=${Math.min(countToFetch, CANDLE_FETCH_LIMIT)}`;\n\n\t// Only add startTs parameter if it's provided\n\tif (startTs !== undefined) {\n\t\tfetchUrl += `&startTs=${startTs}`;\n\t}\n\n\t// Cache the result if cache isn't too large\n\tif (urlCache.size < MAX_URL_CACHE_SIZE) {\n\t\turlCache.set(cacheKey, fetchUrl);\n\t}\n\n\treturn fetchUrl;\n};\n\ntype CandleSubscriberEvents = {\n\t'candle-update': JsonCandle;\n};\n\n// Separate event bus for candle events\nclass CandleEventBus extends StrictEventEmitter<CandleSubscriberEvents> {\n\tconstructor() {\n\t\tsuper();\n\t}\n}\n\n// This class is reponsible for fetching candles from the data API's GET endpoint\nclass CandleFetcher {\n\tprivate readonly config: CandleFetchConfig;\n\n\t// Cache for storing recent candles by market ID and resolution\n\tpublic static recentCandlesCache: Map<\n\t\tstring, // key: `${marketId.key}-${resolution}`\n\t\t{\n\t\t\tcandles: JsonCandle[];\n\t\t\tearliestTs: number;\n\t\t\tlatestTs: number;\n\t\t\tfetchTime: number;\n\t\t}\n\t> = new Map();\n\n\t// Helper method to generate a cache key from market ID and resolution\n\tpublic static getCacheKey(\n\t\tmarketId: MarketId,\n\t\tresolution: CandleResolution\n\t): string {\n\t\treturn `${marketId.key}-${resolution}`;\n\t}\n\n\t// Public method to clear the entire cache\n\tpublic static clearWholeCache() {\n\t\tCandleFetcher.recentCandlesCache.clear();\n\t}\n\n\tpublic static clearCacheForSubscription(\n\t\tmarketId: MarketId,\n\t\tresolution: CandleResolution\n\t) {\n\t\tCandleFetcher.recentCandlesCache.delete(\n\t\t\tCandleFetcher.getCacheKey(marketId, resolution)\n\t\t);\n\t}\n\n\tconstructor(config: CandleFetchConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Candles are fetched in ascending order of time (index 0 -> oldest to index n -> newest)\n\t */\n\tprivate fetchCandlesFromApi = async (\n\t\tfetchUrl: string\n\t): Promise<JsonCandle[]> => {\n\t\tconst response = await fetch(fetchUrl);\n\t\tconst parsedResponse = (await response.json()) as CandleFetchResponseJson;\n\n\t\tif (!parsedResponse.success) {\n\t\t\tthrow new Error('Failed to fetch candles from data API');\n\t\t}\n\n\t\treturn parsedResponse.records;\n\t};\n\n\tprivate getCountOfCandlesBetweenStartAndEndTs = (\n\t\tstartTs: number,\n\t\tendTs: number\n\t) => {\n\t\tconst diffInSeconds = endTs - startTs;\n\t\tconst resolutionInSeconds =\n\t\t\tCandle.resolutionStringToCandleLengthMs(this.config.resolution) / 1000;\n\t\tconst diffInCandles = diffInSeconds / resolutionInSeconds;\n\t\treturn Math.ceil(diffInCandles);\n\t};\n\n\t/**\n\t * Try to get candles from the cache if they're available.\n\t * Returns null if no cached candles are available for the requested range.\n\t */\n\tprivate getFromCache = (): JsonCandle[] | null => {\n\t\t// Generate cache key for the current request\n\t\tconst cacheKey = CandleFetcher.getCacheKey(\n\t\t\tthis.config.marketId,\n\t\t\tthis.config.resolution\n\t\t);\n\t\tconst cachedCandles = CandleFetcher.recentCandlesCache.get(cacheKey);\n\n\t\t// Check if we have cached candles for this market and resolution\n\t\tif (cachedCandles) {\n\t\t\t// Check if the requested time range is within the bounds of cached candles\n\t\t\tif (\n\t\t\t\tthis.config.fromTs >= cachedCandles.earliestTs &&\n\t\t\t\tthis.config.toTs <= cachedCandles.latestTs\n\t\t\t) {\n\t\t\t\t// Filter cached candles to the requested time range\n\t\t\t\tconst filteredCandles = cachedCandles.candles.filter(\n\t\t\t\t\t(candle) =>\n\t\t\t\t\t\tcandle.ts >= this.config.fromTs && candle.ts <= this.config.toTs\n\t\t\t\t);\n\n\t\t\t\treturn filteredCandles;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t};\n\n\t/**\n\t * Determines if we should use the recent candles approach (without startTs for better caching).\n\t */\n\tprivate isRequestingRecentCandles = (\n\t\tnowSeconds: number,\n\t\tcandleLengthSeconds: number\n\t): boolean => {\n\t\t// Calculate cutoff time for \"recent\" candles (now - 1000 candles worth of time)\n\t\tconst recentCandlesCutoffTs =\n\t\t\tnowSeconds - candleLengthSeconds * CANDLE_FETCH_LIMIT;\n\n\t\t// Check if we're fetching recent candles based on the fromTs\n\t\treturn this.config.fromTs >= recentCandlesCutoffTs;\n\t};\n\n\t/**\n\t * Fetch recent candles without using startTs for better caching.\n\t */\n\tprivate fetchRecentCandles = async (\n\t\tnowSeconds: number\n\t): Promise<JsonCandle[]> => {\n\t\t// Fetch recent candles without specifying startTs\n\t\tconst fetchUrl = getCandleFetchUrl({\n\t\t\tenv: this.config.env,\n\t\t\tmarketId: this.config.marketId,\n\t\t\tresolution: this.config.resolution,\n\t\t\tcountToFetch: CANDLE_FETCH_LIMIT, // Ask for max candles to ensure we get enough\n\t\t});\n\n\t\t// Get the candles and reverse them (into ascending order)\n\t\tconst fetchedCandles = await this.fetchCandlesFromApi(fetchUrl);\n\t\tfetchedCandles.reverse();\n\n\t\tif (fetchedCandles.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Store the full fetchedCandles in cache before filtering\n\t\tthis.updateCandleCache(fetchedCandles, nowSeconds);\n\n\t\t// Filter to only include candles in the requested time range\n\t\tconst filteredCandles = this.filterCandlesByTimeRange(fetchedCandles);\n\n\t\treturn filteredCandles;\n\t};\n\n\t/**\n\t * Filter candles to only include those in the requested time range.\n\t */\n\tprivate filterCandlesByTimeRange = (candles: JsonCandle[]): JsonCandle[] => {\n\t\treturn candles.filter(\n\t\t\t(candle) =>\n\t\t\t\tcandle.ts >= this.config.fromTs && candle.ts <= this.config.toTs\n\t\t);\n\t};\n\n\t/**\n\t * Update the candle cache with the latest fetched candles.\n\t */\n\tprivate updateCandleCache = (\n\t\tfetchedCandles: JsonCandle[],\n\t\tnowSeconds: number\n\t): void => {\n\t\tif (fetchedCandles.length > 0) {\n\t\t\t// Generate cache key\n\t\t\tconst cacheKey = CandleFetcher.getCacheKey(\n\t\t\t\tthis.config.marketId,\n\t\t\t\tthis.config.resolution\n\t\t\t);\n\n\t\t\t// Sort candles by timestamp to find earliest and latest\n\t\t\tconst sortedCandles = [...fetchedCandles].sort((a, b) => a.ts - b.ts);\n\t\t\tconst earliestTs = sortedCandles[0].ts;\n\t\t\tconst latestTs = sortedCandles[sortedCandles.length - 1].ts;\n\n\t\t\t// Update or add to cache\n\t\t\tCandleFetcher.recentCandlesCache.set(cacheKey, {\n\t\t\t\tcandles: sortedCandles,\n\t\t\t\tearliestTs,\n\t\t\t\tlatestTs,\n\t\t\t\tfetchTime: nowSeconds,\n\t\t\t});\n\t\t}\n\t};\n\n\t/**\n\t * Fetch historical candles with pagination using startTs.\n\t */\n\tprivate fetchHistoricalCandles = async (): Promise<JsonCandle[]> => {\n\t\tlet candlesRemainingToFetch = this.getCountOfCandlesBetweenStartAndEndTs(\n\t\t\tthis.config.fromTs,\n\t\t\tthis.config.toTs\n\t\t);\n\n\t\tlet currentStartTs = this.config.toTs; // The data API takes \"startTs\" as the \"first timestamp you want going backwards in time\" e.g. all candles will be returned with descending time backwards from the startTs\n\t\tlet hitEndTsCutoff = false;\n\n\t\tlet candles: JsonCandle[] = [];\n\n\t\twhile (candlesRemainingToFetch > 0) {\n\t\t\tconst result = await this.fetchHistoricalCandlesBatch(\n\t\t\t\tcandlesRemainingToFetch,\n\t\t\t\tcurrentStartTs\n\t\t\t);\n\n\t\t\tif (result.fetchedCandles.length === 0) {\n\t\t\t\tcandlesRemainingToFetch = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// the deeper the loop, the older the result.candlesToAdd will be\n\t\t\tcandles = [...result.candlesToAdd, ...candles];\n\t\t\tcandlesRemainingToFetch -= result.candlesToAdd.length;\n\t\t\thitEndTsCutoff = result.hitEndTsCutoff;\n\n\t\t\tif (result.requiresAnotherFetch) {\n\t\t\t\tcurrentStartTs = result.nextStartTs;\n\t\t\t} else if (candlesRemainingToFetch > 0) {\n\t\t\t\t// This means we have fetched all the candles available for this time range and we can stop fetching\n\t\t\t\tcandlesRemainingToFetch = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (hitEndTsCutoff) {\n\t\t\treturn this.filterCandlesByTimeRange(candles);\n\t\t}\n\n\t\treturn candles;\n\t};\n\n\t/**\n\t * Fetch historical candles with pagination, backwards from the toTs value given in the config.\n\t *\n\t * This method works by looping backwards from the LATEST (toTs) timestamp to the OLDEST (fromTs) timestamp.\n\t *\n\t * Things to note:\n\t * - There is a limit to how many candles can be fetched in a single request (see CANDLE_FETCH_LIMIT)\n\t * - We have implemented this to minimise the cardinality in the API request because that helps with caching\n\t */\n\tprivate fetchHistoricalCandlesBatch = async (\n\t\tcandlesRemainingToFetch: number,\n\t\tcurrentStartTs: number\n\t): Promise<{\n\t\tfetchedCandles: JsonCandle[];\n\t\tcandlesToAdd: JsonCandle[];\n\t\thitEndTsCutoff: boolean;\n\t\trequiresAnotherFetch: boolean;\n\t\tnextStartTs: number;\n\t}> => {\n\t\tconst candlesToFetch = Math.min(\n\t\t\tcandlesRemainingToFetch,\n\t\t\tCANDLE_FETCH_LIMIT\n\t\t);\n\n\t\tconst fetchUrl = getCandleFetchUrl({\n\t\t\tenv: this.config.env,\n\t\t\tmarketId: this.config.marketId,\n\t\t\tresolution: this.config.resolution,\n\t\t\tstartTs: currentStartTs, // Include startTs for historical candles\n\t\t\tcountToFetch: candlesToFetch,\n\t\t});\n\n\t\tconst fetchedCandles = await this.fetchCandlesFromApi(fetchUrl);\n\n\t\t// Reverse candles into ascending order\n\t\tfetchedCandles.reverse();\n\n\t\tif (fetchedCandles.length === 0) {\n\t\t\treturn {\n\t\t\t\tfetchedCandles,\n\t\t\t\tcandlesToAdd: [],\n\t\t\t\thitEndTsCutoff: false,\n\t\t\t\trequiresAnotherFetch: false,\n\t\t\t\tnextStartTs: currentStartTs,\n\t\t\t};\n\t\t}\n\n\t\tconst lastCandle = fetchedCandles[fetchedCandles.length - 1]; // This is the LATEST candle .. (they are sorted ascending by time right now)\n\n\t\tconst hitPageSizeCutoff = fetchedCandles.length === CANDLE_FETCH_LIMIT;\n\t\tconst hitEndTsCutoff = lastCandle.ts < this.config.fromTs;\n\n\t\tconst requiresAnotherFetch = hitPageSizeCutoff && !hitEndTsCutoff; // If the number of candles returned is equal to the maximum number of candles that can be fetched in a single GET request, then we need to fetch more candles\n\n\t\tlet candlesToAdd = fetchedCandles;\n\t\tlet nextStartTs = currentStartTs;\n\n\t\tif (requiresAnotherFetch) {\n\t\t\t// If we need to do another fetch, trim any candles with the same timestamp as the last candle in the previous fetch, because that is the pointer for our next fetch and we don't want to duplicate candles\n\t\t\tcandlesToAdd = candlesToAdd.filter((candle) => {\n\t\t\t\treturn candle.ts < lastCandle.ts;\n\t\t\t});\n\n\t\t\tconst oldestCandle = fetchedCandles[0]; // first candle is the oldest\n\t\t\tnextStartTs = oldestCandle.ts; // If we are doing another loop, then the trimmed candles have all the candles except for ones with the last candle's timestamp. For the next loop we want to fetch from that timestamp;\n\t\t}\n\n\t\treturn {\n\t\t\tfetchedCandles,\n\t\t\tcandlesToAdd,\n\t\t\thitEndTsCutoff,\n\t\t\trequiresAnotherFetch,\n\t\t\tnextStartTs,\n\t\t};\n\t};\n\n\t/**\n\t * This class needs to fetch candles based on the config.\n\t *\n\t * If the number of candles requested exceeds the maximum number of candles that can be fetched in a single GET request, then it needs to loop multiple get requests, using the last candle's timestamp as the offset startTs for each subsequent request. If the number of candles returned is less than the requested number of candles, then we have fetched all the candles available.\n\t *\n\t * For recent candles (ones where fromTs > now - candleLength*1000), we avoid using startTs in the URL to improve caching,\n\t * and instead fetch the most recent 1000 candles and then trim the result.\n\t */\n\tpublic fetchCandles = async () => {\n\t\t// Check cache first\n\t\tconst cachedCandles = this.getFromCache();\n\t\tif (cachedCandles) {\n\t\t\treturn cachedCandles;\n\t\t}\n\n\t\t// Calculate the candle length in seconds for the current resolution\n\t\tconst candleLengthMs = Candle.resolutionStringToCandleLengthMs(\n\t\t\tthis.config.resolution\n\t\t);\n\t\tconst candleLengthSeconds = candleLengthMs / 1000;\n\n\t\t// Get current time in seconds\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000);\n\n\t\t// Check if we're fetching recent candles\n\t\tif (this.isRequestingRecentCandles(nowSeconds, candleLengthSeconds)) {\n\t\t\treturn this.fetchRecentCandles(nowSeconds);\n\t\t}\n\n\t\t// For historical candles (older than the last 1000 candles), use the previous approach\n\t\t// with startTs for pagination\n\t\treturn this.fetchHistoricalCandles();\n\t};\n}\n\nclass CandleSubscriber {\n\tprivate subscription: CandleSubscriberSubscription;\n\n\tconstructor(\n\t\treadonly config: CandleSubscriptionConfig,\n\t\treadonly eventBus: CandleEventBus\n\t) {}\n\n\tsubscribeToCandles = async () => {\n\t\tthis.subscription = MarketDataFeed.subscribe({\n\t\t\ttype: 'candles',\n\t\t\tresolution: this.config.resolution,\n\t\t\tenv: this.config.env,\n\t\t\tmarketSymbol: getMarketSymbolForMarketId(\n\t\t\t\tthis.config.marketId,\n\t\t\t\tthis.config.env\n\t\t\t),\n\t\t});\n\n\t\tthis.subscription.observable.subscribe((candle) => {\n\t\t\tthis.eventBus.emit('candle-update', candle);\n\t\t});\n\t};\n\n\tunsubscribe = () => {\n\t\tMarketDataFeed.unsubscribe(this.subscription.id);\n\t};\n}\n\n/**\n * This class will subscribe to candles from the Drift Data API.\n *\n * Note: If you are using TradingView you probably want to just use the DriftTvFeed class instead.\n */\nexport class CandleClient {\n\tprivate activeSubscriptions: Map<\n\t\tstring,\n\t\t{\n\t\t\tsubscriber: CandleSubscriber;\n\t\t\teventBus: CandleEventBus;\n\t\t}\n\t> = new Map();\n\n\tconstructor() {}\n\n\tpublic subscribe = async (\n\t\tconfig: CandleSubscriptionConfig,\n\t\tsubscriptionKey: string\n\t) => {\n\t\t// Kill any existing subscription with the same key before creating a new one\n\t\tif (this.activeSubscriptions.has(subscriptionKey)) {\n\t\t\tthis.unsubscribe(subscriptionKey);\n\t\t}\n\n\t\tconst eventBus = new CandleEventBus();\n\t\tconst subscriber = new CandleSubscriber(config, eventBus);\n\t\tawait subscriber.subscribeToCandles();\n\n\t\tthis.activeSubscriptions.set(subscriptionKey, {\n\t\t\tsubscriber,\n\t\t\teventBus,\n\t\t});\n\n\t\treturn;\n\t};\n\n\t/**\n\t *\n\t * @param config {\n\t *\n\t * env: UIEnv;\n\t *\n\t * marketId: MarketId;\n\t *\n\t * resolution: CandleResolution;\n\t *\n\t * fromTs: number; // Seconds :: This should be the START (oldest) timestamp of the candles to fetch\n\t *\n\t * toTs: number; // Seconds :: This should be the END (newest) timestamp of the candles to fetch\n\t *\n\t * }\n\t * @returns\n\t */\n\tpublic fetch = async (config: CandleFetchConfig): Promise<JsonCandle[]> => {\n\t\tassert(config.fromTs < config.toTs, 'fromTs must be less than toTs');\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000);\n\t\tassert(\n\t\t\tconfig.fromTs <= nowSeconds && config.toTs <= nowSeconds,\n\t\t\t`fromTs and toTs cannot be in the future (Requested fromTs: ${new Date(\n\t\t\t\tconfig.fromTs * 1000\n\t\t\t).toISOString()} and toTs: ${new Date(\n\t\t\t\tconfig.toTs * 1000\n\t\t\t).toISOString()}, Current time: ${new Date(\n\t\t\t\tnowSeconds * 1000\n\t\t\t).toISOString()})`\n\t\t);\n\t\tconst candleFetcher = new CandleFetcher(config);\n\t\tconst candles = await candleFetcher.fetchCandles();\n\t\treturn candles;\n\t};\n\n\tpublic unsubscribe = (subscriptionKey: string) => {\n\t\tconst subscription = this.activeSubscriptions.get(subscriptionKey);\n\t\tif (subscription) {\n\t\t\tCandleFetcher.clearCacheForSubscription(\n\t\t\t\tsubscription.subscriber.config.marketId,\n\t\t\t\tsubscription.subscriber.config.resolution\n\t\t\t);\n\t\t\tsubscription.subscriber.unsubscribe();\n\t\t\tsubscription.eventBus.removeAllListeners();\n\t\t\tthis.activeSubscriptions.delete(subscriptionKey);\n\t\t}\n\t};\n\n\tpublic unsubscribeAll = () => {\n\t\tfor (const subscriptionKey of this.activeSubscriptions.keys()) {\n\t\t\tthis.unsubscribe(subscriptionKey);\n\t\t}\n\t};\n\n\tpublic on(\n\t\tsubscriptionKey: string,\n\t\tevent: keyof CandleSubscriberEvents,\n\t\tlistener: (candle: JsonCandle) => void\n\t) {\n\t\tconst subscription = this.activeSubscriptions.get(subscriptionKey);\n\t\tif (subscription) {\n\t\t\tsubscription.eventBus.on(event, listener);\n\t\t} else {\n\t\t\tconsole.warn(`No active subscription found for key: ${subscriptionKey}`);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -43,6 +43,7 @@ export declare class SwiftClient {
|
|
|
43
43
|
hash: string;
|
|
44
44
|
}>;
|
|
45
45
|
static confirmSwiftOrderWS(connection: Connection, client: DriftClient, signedMsgUserOrdersAccount: PublicKey, signedMsgOrderUuid: Uint8Array, confirmDuration: number): Promise<number | undefined>;
|
|
46
|
+
private static confirmSwiftOrderRPCFetch;
|
|
46
47
|
static findOrderInSignedMsgUserOrdersAccount(client: DriftClient, ordersAccount: AccountInfo<Buffer>, signedMsgOrderUuid: Uint8Array): import("@drift-labs/sdk").SignedMsgOrderId;
|
|
47
48
|
static confirmSwiftOrder(hash: string, confirmDuration: number): Promise<ClientResponse<{
|
|
48
49
|
orderId: string;
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SwiftClient = void 0;
|
|
4
4
|
const sdk_1 = require("@drift-labs/sdk");
|
|
5
|
+
// Cache for URL construction to prevent repeated string concatenation
|
|
6
|
+
const swiftUrlCache = new Map();
|
|
7
|
+
const MAX_SWIFT_URL_CACHE_SIZE = 200;
|
|
8
|
+
function getCachedUrl(baseUrl, endpoint) {
|
|
9
|
+
const cacheKey = `${baseUrl}:${endpoint}`;
|
|
10
|
+
if (swiftUrlCache.has(cacheKey)) {
|
|
11
|
+
return swiftUrlCache.get(cacheKey);
|
|
12
|
+
}
|
|
13
|
+
const result = `${baseUrl}${endpoint}`;
|
|
14
|
+
if (swiftUrlCache.size < MAX_SWIFT_URL_CACHE_SIZE) {
|
|
15
|
+
swiftUrlCache.set(cacheKey, result);
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
5
19
|
const rxjs_1 = require("rxjs");
|
|
6
20
|
const logger_1 = require("../utils/logger");
|
|
7
21
|
class SwiftClient {
|
|
@@ -17,7 +31,7 @@ class SwiftClient {
|
|
|
17
31
|
const headers = new Headers({
|
|
18
32
|
...this.getSwiftHeaders(),
|
|
19
33
|
});
|
|
20
|
-
fetch(
|
|
34
|
+
fetch(getCachedUrl(this.baseUrl, url), {
|
|
21
35
|
headers,
|
|
22
36
|
})
|
|
23
37
|
.then(async (response) => {
|
|
@@ -50,7 +64,7 @@ class SwiftClient {
|
|
|
50
64
|
body: JSON.stringify(bodyObject),
|
|
51
65
|
};
|
|
52
66
|
return new Promise((res) => {
|
|
53
|
-
const postRequest = new Request(
|
|
67
|
+
const postRequest = new Request(getCachedUrl(this.baseUrl, url), requestOptions);
|
|
54
68
|
fetch(postRequest)
|
|
55
69
|
.then(async (response) => {
|
|
56
70
|
let resBody = null;
|
|
@@ -109,36 +123,76 @@ class SwiftClient {
|
|
|
109
123
|
};
|
|
110
124
|
}
|
|
111
125
|
static async confirmSwiftOrderWS(connection, client, signedMsgUserOrdersAccount, signedMsgOrderUuid, confirmDuration) {
|
|
112
|
-
(0, logger_1.allEnvDlog)('swiftClient', 'confirmSwiftOrderWS - confirmation duration', confirmDuration);
|
|
113
126
|
return new Promise((resolve, reject) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.then((accountInfo) => {
|
|
120
|
-
if (!accountInfo) {
|
|
121
|
-
reject(new Error('Swift message account not found'));
|
|
127
|
+
let settled = false;
|
|
128
|
+
let subId = undefined;
|
|
129
|
+
let pollInterval = undefined;
|
|
130
|
+
const finalizeResolve = (orderId) => {
|
|
131
|
+
if (settled)
|
|
122
132
|
return;
|
|
133
|
+
settled = true;
|
|
134
|
+
if (subId !== undefined) {
|
|
135
|
+
connection.removeAccountChangeListener(subId).catch(() => { });
|
|
123
136
|
}
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
(
|
|
127
|
-
clearTimeout(timeout);
|
|
128
|
-
resolve(order.orderId);
|
|
137
|
+
clearTimeout(timeout);
|
|
138
|
+
if (pollInterval) {
|
|
139
|
+
clearInterval(pollInterval);
|
|
129
140
|
}
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
resolve(orderId);
|
|
142
|
+
};
|
|
143
|
+
const finalizeReject = (error) => {
|
|
144
|
+
if (settled)
|
|
145
|
+
return;
|
|
146
|
+
settled = true;
|
|
147
|
+
if (subId !== undefined) {
|
|
148
|
+
connection.removeAccountChangeListener(subId).catch(() => { });
|
|
149
|
+
}
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
if (pollInterval) {
|
|
152
|
+
clearInterval(pollInterval);
|
|
153
|
+
}
|
|
154
|
+
reject(error);
|
|
155
|
+
};
|
|
156
|
+
const checkOrder = async (confirmType) => {
|
|
157
|
+
try {
|
|
158
|
+
const lastOrderId = await this.confirmSwiftOrderRPCFetch(connection, client, signedMsgUserOrdersAccount, signedMsgOrderUuid);
|
|
159
|
+
if (lastOrderId !== undefined) {
|
|
160
|
+
(0, logger_1.allEnvDlog)('swiftClient', `confirmed in ${confirmType} RPC fetch orderID\n`, lastOrderId);
|
|
161
|
+
finalizeResolve(lastOrderId);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
(0, logger_1.allEnvDlog)('swiftClient', `${confirmType} RPC fetch error`, err);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const timeout = setTimeout(async () => {
|
|
169
|
+
await checkOrder('timeout');
|
|
170
|
+
if (!settled) {
|
|
171
|
+
finalizeReject(new Error('Order not found'));
|
|
172
|
+
}
|
|
173
|
+
}, confirmDuration);
|
|
174
|
+
// Perform an initial check.
|
|
175
|
+
checkOrder('initial');
|
|
176
|
+
// Poll every 2 seconds as backup for unreliable websocket.
|
|
177
|
+
pollInterval = setInterval(() => checkOrder('poll'), 2000);
|
|
178
|
+
// Subscribe for account change confirmations via WS.
|
|
179
|
+
subId = connection.onAccountChange(signedMsgUserOrdersAccount, (accountInfo) => {
|
|
132
180
|
const order = this.findOrderInSignedMsgUserOrdersAccount(client, accountInfo, signedMsgOrderUuid);
|
|
133
181
|
if (order) {
|
|
134
182
|
(0, logger_1.allEnvDlog)('swiftClient', 'confirmed in onAccountChange orderID\n', order.orderId);
|
|
135
|
-
|
|
136
|
-
clearTimeout(timeout);
|
|
137
|
-
resolve(order.orderId);
|
|
183
|
+
finalizeResolve(order.orderId);
|
|
138
184
|
}
|
|
139
|
-
}
|
|
185
|
+
});
|
|
140
186
|
});
|
|
141
187
|
}
|
|
188
|
+
static async confirmSwiftOrderRPCFetch(connection, client, signedMsgUserOrdersAccount, signedMsgOrderUuid) {
|
|
189
|
+
const accountInfo = await connection.getAccountInfo(signedMsgUserOrdersAccount);
|
|
190
|
+
if (!accountInfo) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const order = this.findOrderInSignedMsgUserOrdersAccount(client, accountInfo, signedMsgOrderUuid);
|
|
194
|
+
return order === null || order === void 0 ? void 0 : order.orderId;
|
|
195
|
+
}
|
|
142
196
|
static findOrderInSignedMsgUserOrdersAccount(client, ordersAccount, signedMsgOrderUuid) {
|
|
143
197
|
const accountDecoder = client.program.account.signedMsgUserOrders.coder.accounts.decodeUnchecked.bind(client.program.account.signedMsgUserOrders.coder.accounts);
|
|
144
198
|
const decodedAccount = accountDecoder('SignedMsgUserOrders', ordersAccount.data);
|
|
@@ -156,7 +210,7 @@ class SwiftClient {
|
|
|
156
210
|
return {
|
|
157
211
|
success: true,
|
|
158
212
|
status: 200,
|
|
159
|
-
message:
|
|
213
|
+
message: `Confirmed hash: ${hash}`,
|
|
160
214
|
body: {
|
|
161
215
|
orderId: confirmResponse.body,
|
|
162
216
|
status: 'confirmed',
|
|
@@ -173,7 +227,7 @@ class SwiftClient {
|
|
|
173
227
|
return {
|
|
174
228
|
success: false,
|
|
175
229
|
status: 408,
|
|
176
|
-
message:
|
|
230
|
+
message: `Failed to confirm hash: ${hash}`,
|
|
177
231
|
body: {
|
|
178
232
|
status: 'expired',
|
|
179
233
|
},
|
|
@@ -227,7 +281,7 @@ class SwiftClient {
|
|
|
227
281
|
subscriber.next({
|
|
228
282
|
type: 'errored',
|
|
229
283
|
hash: '',
|
|
230
|
-
message:
|
|
284
|
+
message: `Error from swift node: ${sendResponse.message}`,
|
|
231
285
|
status: sendResponse.status,
|
|
232
286
|
});
|
|
233
287
|
subscriber.error();
|