@vesper85/strategy-sdk 0.1.2 → 0.1.4
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/README.md +153 -12
- package/dist/clients/polymarket-client.d.ts +1 -2
- package/dist/engine/polymarket-event-runner.d.ts +3 -0
- package/dist/engine/strategy-runner.d.ts +3 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1072 -35
- package/dist/rtds/index.d.ts +9 -0
- package/dist/rtds/osiris-rtds.service.d.ts +134 -0
- package/dist/rtds/polymarket-rtds.service.d.ts +117 -0
- package/dist/rtds/unified-rtds.service.d.ts +146 -0
- package/dist/types/event-types.d.ts +173 -3
- package/dist/types/gamma.d.ts +135 -0
- package/dist/types/osiris.d.ts +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createClient } from 'redis';
|
|
|
9
9
|
import superjson from 'superjson';
|
|
10
10
|
import { TechnicalAnalysisService } from '@osiris-ai/technical-indicators';
|
|
11
11
|
import WebSocket from 'ws';
|
|
12
|
+
import { RealTimeDataClient, ConnectionStatus } from '@polymarket/real-time-data-client';
|
|
12
13
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
13
14
|
import { sepolia, base, optimism, arbitrum, polygon, mainnet } from 'viem/chains';
|
|
14
15
|
|
|
@@ -27,6 +28,30 @@ function isOpportunitySubscription(sub) {
|
|
|
27
28
|
function isCustomSubscription(sub) {
|
|
28
29
|
return sub.type === "custom";
|
|
29
30
|
}
|
|
31
|
+
function isClobMarketSubscription(sub) {
|
|
32
|
+
return sub.type === "clob_market";
|
|
33
|
+
}
|
|
34
|
+
function isClobUserSubscription(sub) {
|
|
35
|
+
return sub.type === "clob_user";
|
|
36
|
+
}
|
|
37
|
+
function isActivitySubscription(sub) {
|
|
38
|
+
return sub.type === "activity";
|
|
39
|
+
}
|
|
40
|
+
function isCommentsSubscription(sub) {
|
|
41
|
+
return sub.type === "comments";
|
|
42
|
+
}
|
|
43
|
+
function isCryptoPricesSubscription(sub) {
|
|
44
|
+
return sub.type === "crypto_prices";
|
|
45
|
+
}
|
|
46
|
+
function isRfqSubscription(sub) {
|
|
47
|
+
return sub.type === "rfq";
|
|
48
|
+
}
|
|
49
|
+
function isPolymarketSubscription(sub) {
|
|
50
|
+
return isClobMarketSubscription(sub) || isClobUserSubscription(sub) || isActivitySubscription(sub) || isCommentsSubscription(sub) || isCryptoPricesSubscription(sub) || isRfqSubscription(sub);
|
|
51
|
+
}
|
|
52
|
+
function isOsirisSubscription(sub) {
|
|
53
|
+
return isWalletSubscription(sub) || isOpportunitySubscription(sub);
|
|
54
|
+
}
|
|
30
55
|
var OsirisSigner = class {
|
|
31
56
|
hubBaseUrl;
|
|
32
57
|
accessToken;
|
|
@@ -161,6 +186,80 @@ var OsirisSigner = class {
|
|
|
161
186
|
return void 0;
|
|
162
187
|
}
|
|
163
188
|
};
|
|
189
|
+
|
|
190
|
+
// src/utils/index.ts
|
|
191
|
+
function mapToGammaMarket(raw) {
|
|
192
|
+
const safeParse = (value) => {
|
|
193
|
+
try {
|
|
194
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
195
|
+
} catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
// Identity & Basic Info
|
|
201
|
+
id: raw.id,
|
|
202
|
+
question: raw.question,
|
|
203
|
+
slug: raw.slug,
|
|
204
|
+
condition_id: raw.conditionId,
|
|
205
|
+
question_id: raw.questionId,
|
|
206
|
+
// If present
|
|
207
|
+
description: raw.description,
|
|
208
|
+
// Dates
|
|
209
|
+
end_date: raw.endDate,
|
|
210
|
+
start_date: raw.startDate,
|
|
211
|
+
created_at: raw.createdAt,
|
|
212
|
+
updated_at: raw.updatedAt,
|
|
213
|
+
closed_time: raw.closedTime,
|
|
214
|
+
// Images
|
|
215
|
+
image: raw.image,
|
|
216
|
+
icon: raw.icon,
|
|
217
|
+
twitter_card_image: raw.twitterCardImage,
|
|
218
|
+
// Categorization
|
|
219
|
+
category: raw.category,
|
|
220
|
+
tags: raw.tags,
|
|
221
|
+
market_type: raw.marketType,
|
|
222
|
+
active: raw.active,
|
|
223
|
+
closed: raw.closed,
|
|
224
|
+
archived: raw.archived,
|
|
225
|
+
restricted: raw.restricted,
|
|
226
|
+
// Financials & Numbers (Type Conversions)
|
|
227
|
+
liquidity: raw.liquidity ? parseFloat(raw.liquidity) : 0,
|
|
228
|
+
volume: raw.volume,
|
|
229
|
+
// Interface keeps this as string
|
|
230
|
+
volume_24hr: raw.volume24hr?.toString(),
|
|
231
|
+
// Prices (Number -> String conversion based on your Interface)
|
|
232
|
+
best_bid: raw.bestBid?.toString(),
|
|
233
|
+
best_ask: raw.bestAsk?.toString(),
|
|
234
|
+
last_trade_price: raw.lastTradePrice?.toString(),
|
|
235
|
+
// Arrays (Parsing stringified JSON)
|
|
236
|
+
outcomes: safeParse(raw.outcomes),
|
|
237
|
+
outcome_prices: safeParse(raw.outcomePrices),
|
|
238
|
+
outcomePrices: safeParse(raw.outcomePrices),
|
|
239
|
+
// Populating alias
|
|
240
|
+
clob_token_ids: safeParse(raw.clobTokenIds),
|
|
241
|
+
clobTokenIds: safeParse(raw.clobTokenIds),
|
|
242
|
+
// Populating alias
|
|
243
|
+
uma_resolution_statuses: safeParse(raw.umaResolutionStatuses),
|
|
244
|
+
// Market Maker & Fees
|
|
245
|
+
market_maker_address: raw.marketMakerAddress,
|
|
246
|
+
maker_fee_bps: raw.makerFeeBps,
|
|
247
|
+
taker_fee_bps: raw.takerFeeBps,
|
|
248
|
+
// IDs
|
|
249
|
+
created_by: raw.updatedBy,
|
|
250
|
+
// Note: response has updatedBy, mapping to potentially relevant field
|
|
251
|
+
updated_by: raw.updatedBy,
|
|
252
|
+
// Misc
|
|
253
|
+
events: raw.events,
|
|
254
|
+
// Pass through if structure matches, otherwise map Event type similarly
|
|
255
|
+
new: raw.new,
|
|
256
|
+
featured: raw.featured,
|
|
257
|
+
cyom: raw.cyom,
|
|
258
|
+
competitive: raw.competitive
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/clients/polymarket-client.ts
|
|
164
263
|
var MaxUint256BigInt = typeof ethers.constants !== "undefined" && ethers.constants.MaxUint256 ? BigInt(ethers.MaxUint256.toString()) : BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
165
264
|
var POLYGON_CHAIN_ID = 137;
|
|
166
265
|
var USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
|
|
@@ -178,6 +277,13 @@ var ERC1155_ABI = [
|
|
|
178
277
|
"function setApprovalForAll(address operator, bool approved) external",
|
|
179
278
|
"function isApprovedForAll(address account, address operator) external view returns (bool)"
|
|
180
279
|
];
|
|
280
|
+
function wrapWalletForV5Compatibility(wallet) {
|
|
281
|
+
const wrappedWallet = wallet;
|
|
282
|
+
wrappedWallet._signTypedData = async function(domain, types, value) {
|
|
283
|
+
return wallet.signTypedData(domain, types, value);
|
|
284
|
+
};
|
|
285
|
+
return wrappedWallet;
|
|
286
|
+
}
|
|
181
287
|
var PolymarketClient = class {
|
|
182
288
|
constructor(logger, signer, userAddress, options) {
|
|
183
289
|
this.logger = logger;
|
|
@@ -185,7 +291,7 @@ var PolymarketClient = class {
|
|
|
185
291
|
this.userAddress = userAddress;
|
|
186
292
|
this.gammaClient = new PolymarketGammaClient();
|
|
187
293
|
this.provider = new ethers.JsonRpcProvider(POLYGON_RPC_URL);
|
|
188
|
-
this.isOsirisSigner = signer instanceof OsirisSigner || signer.
|
|
294
|
+
this.isOsirisSigner = signer instanceof OsirisSigner || signer.getPrivateKey() === void 0;
|
|
189
295
|
this.options = {
|
|
190
296
|
chainId: options?.chainId ?? POLYGON_CHAIN_ID,
|
|
191
297
|
autoApprove: options?.autoApprove ?? false,
|
|
@@ -244,10 +350,11 @@ var PolymarketClient = class {
|
|
|
244
350
|
throw new Error("Could not obtain private key for CLOB client initialization. Private key signer is required for trading.");
|
|
245
351
|
}
|
|
246
352
|
this.wallet = new ethers.Wallet(privateKey, this.provider);
|
|
353
|
+
const v5CompatibleWallet = wrapWalletForV5Compatibility(this.wallet);
|
|
247
354
|
const tempClient = new ClobClient(
|
|
248
355
|
this.options.clobHost,
|
|
249
356
|
this.options.chainId,
|
|
250
|
-
|
|
357
|
+
v5CompatibleWallet
|
|
251
358
|
);
|
|
252
359
|
this.logger.info("Deriving API credentials...");
|
|
253
360
|
const apiCreds = await tempClient.createOrDeriveApiKey();
|
|
@@ -255,7 +362,7 @@ var PolymarketClient = class {
|
|
|
255
362
|
this.clobClient = new ClobClient(
|
|
256
363
|
this.options.clobHost,
|
|
257
364
|
this.options.chainId,
|
|
258
|
-
|
|
365
|
+
v5CompatibleWallet,
|
|
259
366
|
apiCreds,
|
|
260
367
|
signatureType
|
|
261
368
|
);
|
|
@@ -269,15 +376,6 @@ var PolymarketClient = class {
|
|
|
269
376
|
isTradingClientInitialized() {
|
|
270
377
|
return this.tradingInitialized;
|
|
271
378
|
}
|
|
272
|
-
async getPrivateKeyFromSigner() {
|
|
273
|
-
if (this.signer && "getPrivateKey" in this.signer) {
|
|
274
|
-
return this.signer.getPrivateKey();
|
|
275
|
-
}
|
|
276
|
-
if (this.signer && this.signer.account?.privateKey) {
|
|
277
|
-
return this.signer.account.privateKey;
|
|
278
|
-
}
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
379
|
ensureTradingClient() {
|
|
282
380
|
if (!this.clobClient || !this.tradingInitialized) {
|
|
283
381
|
throw new Error("Trading client not initialized. Call initializeTradingClient() first.");
|
|
@@ -1099,7 +1197,8 @@ var PolymarketClient = class {
|
|
|
1099
1197
|
throw new Error(`Gamma API error: ${response.status} ${response.statusText}`);
|
|
1100
1198
|
}
|
|
1101
1199
|
const data = await response.json();
|
|
1102
|
-
|
|
1200
|
+
const mappedData = data.map(mapToGammaMarket);
|
|
1201
|
+
return mappedData;
|
|
1103
1202
|
} catch (error) {
|
|
1104
1203
|
this.logger.error("Error fetching markets from Gamma API", error);
|
|
1105
1204
|
throw error;
|
|
@@ -1596,11 +1695,11 @@ async function runStrategy(strategy, config, context) {
|
|
|
1596
1695
|
}
|
|
1597
1696
|
return false;
|
|
1598
1697
|
} catch (error) {
|
|
1599
|
-
config.logger
|
|
1698
|
+
config.logger?.error(`Error executing strategy: ${error.message}`, error);
|
|
1600
1699
|
throw error;
|
|
1601
1700
|
}
|
|
1602
1701
|
}
|
|
1603
|
-
function createStrategyEngine(strategy,
|
|
1702
|
+
function createStrategyEngine(strategy, context, config) {
|
|
1604
1703
|
return new StrategyEngine(strategy, config, context);
|
|
1605
1704
|
}
|
|
1606
1705
|
var StrategyEngine = class {
|
|
@@ -1608,6 +1707,7 @@ var StrategyEngine = class {
|
|
|
1608
1707
|
this.strategy = strategy;
|
|
1609
1708
|
this.config = config;
|
|
1610
1709
|
this.context = context;
|
|
1710
|
+
this.config.logger = this.config.logger || createConsoleLogger();
|
|
1611
1711
|
}
|
|
1612
1712
|
intervalId = null;
|
|
1613
1713
|
isRunning = false;
|
|
@@ -1615,6 +1715,9 @@ var StrategyEngine = class {
|
|
|
1615
1715
|
* Start the engine with the configured tick interval
|
|
1616
1716
|
*/
|
|
1617
1717
|
start() {
|
|
1718
|
+
if (!this.config.logger) {
|
|
1719
|
+
this.config.logger = createConsoleLogger();
|
|
1720
|
+
}
|
|
1618
1721
|
if (this.isRunning) {
|
|
1619
1722
|
this.config.logger.warning("Strategy engine is already running");
|
|
1620
1723
|
return;
|
|
@@ -1631,6 +1734,9 @@ var StrategyEngine = class {
|
|
|
1631
1734
|
* Stop the engine
|
|
1632
1735
|
*/
|
|
1633
1736
|
stop() {
|
|
1737
|
+
if (!this.config.logger) {
|
|
1738
|
+
this.config.logger = createConsoleLogger();
|
|
1739
|
+
}
|
|
1634
1740
|
if (!this.isRunning) {
|
|
1635
1741
|
return;
|
|
1636
1742
|
}
|
|
@@ -1649,6 +1755,9 @@ var StrategyEngine = class {
|
|
|
1649
1755
|
}
|
|
1650
1756
|
async tick() {
|
|
1651
1757
|
const tickStart = Date.now();
|
|
1758
|
+
if (!this.config.logger) {
|
|
1759
|
+
this.config.logger = createConsoleLogger();
|
|
1760
|
+
}
|
|
1652
1761
|
this.config.logger.debug(`Tick start: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1653
1762
|
try {
|
|
1654
1763
|
await runStrategy(this.strategy, this.config, this.context);
|
|
@@ -1862,7 +1971,59 @@ var PolymarketEventRunner = class {
|
|
|
1862
1971
|
const rtdsSubscriptions = [];
|
|
1863
1972
|
for (const sub of this.strategy.subscriptions) {
|
|
1864
1973
|
let rtdsSub = null;
|
|
1865
|
-
if (
|
|
1974
|
+
if (isCryptoPricesSubscription(sub)) {
|
|
1975
|
+
rtdsSub = {
|
|
1976
|
+
topic: "crypto_prices",
|
|
1977
|
+
type: "update"
|
|
1978
|
+
};
|
|
1979
|
+
if (sub.symbol) {
|
|
1980
|
+
rtdsSub.filters = JSON.stringify({ symbol: sub.symbol.toLowerCase() });
|
|
1981
|
+
}
|
|
1982
|
+
} else if (isActivitySubscription(sub)) {
|
|
1983
|
+
rtdsSub = {
|
|
1984
|
+
topic: "activity",
|
|
1985
|
+
type: sub.messageType || "*"
|
|
1986
|
+
};
|
|
1987
|
+
if (sub.eventSlug) {
|
|
1988
|
+
rtdsSub.filters = JSON.stringify({ event_slug: sub.eventSlug });
|
|
1989
|
+
} else if (sub.marketSlug) {
|
|
1990
|
+
rtdsSub.filters = JSON.stringify({ market_slug: sub.marketSlug });
|
|
1991
|
+
}
|
|
1992
|
+
} else if (isClobMarketSubscription(sub)) {
|
|
1993
|
+
rtdsSub = {
|
|
1994
|
+
topic: "clob_market",
|
|
1995
|
+
type: sub.messageType || "*",
|
|
1996
|
+
filters: JSON.stringify([sub.marketId])
|
|
1997
|
+
};
|
|
1998
|
+
} else if (isClobUserSubscription(sub)) {
|
|
1999
|
+
rtdsSub = {
|
|
2000
|
+
topic: "clob_user",
|
|
2001
|
+
type: sub.messageType || "*"
|
|
2002
|
+
};
|
|
2003
|
+
if (this.config.clobAuth) {
|
|
2004
|
+
rtdsSub.clob_auth = this.config.clobAuth;
|
|
2005
|
+
} else {
|
|
2006
|
+
this.config.logger.warning(
|
|
2007
|
+
`clob_user subscription requires clobAuth credentials`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
} else if (isCommentsSubscription(sub)) {
|
|
2011
|
+
rtdsSub = {
|
|
2012
|
+
topic: "comments",
|
|
2013
|
+
type: sub.messageType || "*"
|
|
2014
|
+
};
|
|
2015
|
+
if (sub.parentEntityId && sub.parentEntityType) {
|
|
2016
|
+
rtdsSub.filters = JSON.stringify({
|
|
2017
|
+
parentEntityID: sub.parentEntityId,
|
|
2018
|
+
parentEntityType: sub.parentEntityType
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
} else if (isRfqSubscription(sub)) {
|
|
2022
|
+
rtdsSub = {
|
|
2023
|
+
topic: "rfq",
|
|
2024
|
+
type: sub.messageType || "*"
|
|
2025
|
+
};
|
|
2026
|
+
} else if (isMarketSubscription(sub)) {
|
|
1866
2027
|
const topic = this.mapEventTypeToTopic(sub.type);
|
|
1867
2028
|
rtdsSub = {
|
|
1868
2029
|
topic,
|
|
@@ -1891,7 +2052,7 @@ var PolymarketEventRunner = class {
|
|
|
1891
2052
|
}
|
|
1892
2053
|
} else {
|
|
1893
2054
|
this.config.logger.warning(
|
|
1894
|
-
`Subscription type '${sub.type}' is not supported by Polymarket RTDS.
|
|
2055
|
+
`Subscription type '${sub.type}' is not supported by Polymarket RTDS. Osiris-only subscription types (wallet, opportunity) should use eventSource: 'osiris'.`
|
|
1895
2056
|
);
|
|
1896
2057
|
continue;
|
|
1897
2058
|
}
|
|
@@ -1911,6 +2072,9 @@ var PolymarketEventRunner = class {
|
|
|
1911
2072
|
/**
|
|
1912
2073
|
* Get the message type for a given RTDS topic
|
|
1913
2074
|
* Each topic has specific message types as per the Polymarket RTDS documentation
|
|
2075
|
+
*
|
|
2076
|
+
* Note: Only 'crypto_prices' and 'comments' are documented RTDS topics.
|
|
2077
|
+
* 'trades' and 'orders' subscriptions require the CLOB WebSocket API, not RTDS.
|
|
1914
2078
|
*/
|
|
1915
2079
|
getMessageTypeForTopic(topic) {
|
|
1916
2080
|
switch (topic) {
|
|
@@ -1918,15 +2082,18 @@ var PolymarketEventRunner = class {
|
|
|
1918
2082
|
return "update";
|
|
1919
2083
|
case "comments":
|
|
1920
2084
|
return "comment_created";
|
|
1921
|
-
// Subscribes to all comment events
|
|
1922
2085
|
case "trades":
|
|
1923
|
-
|
|
2086
|
+
this.config.logger.warning(
|
|
2087
|
+
`Topic 'trades' may not be supported by Polymarket RTDS. Consider using the CLOB WebSocket API for order/trade events.`
|
|
2088
|
+
);
|
|
2089
|
+
return "trade";
|
|
1924
2090
|
case "orders":
|
|
2091
|
+
this.config.logger.warning(
|
|
2092
|
+
`Topic 'orders' may not be supported by Polymarket RTDS. Consider using the CLOB WebSocket API for order/trade events.`
|
|
2093
|
+
);
|
|
1925
2094
|
return "order_placed";
|
|
1926
|
-
// Can also be 'order_cancelled', 'order_filled'
|
|
1927
2095
|
case "activity":
|
|
1928
2096
|
return "trade";
|
|
1929
|
-
// Can also be 'offer', etc.
|
|
1930
2097
|
case "profile":
|
|
1931
2098
|
return "update";
|
|
1932
2099
|
default:
|
|
@@ -2114,10 +2281,6 @@ var PolymarketEventRunner = class {
|
|
|
2114
2281
|
* Dispatch an event to the strategy's onEvent handler
|
|
2115
2282
|
*/
|
|
2116
2283
|
async dispatchEvent(event) {
|
|
2117
|
-
const eventStart = Date.now();
|
|
2118
|
-
this.config.logger.debug(
|
|
2119
|
-
`Dispatching ${event.type} event${event.market ? ` for ${event.market}` : ""}`
|
|
2120
|
-
);
|
|
2121
2284
|
try {
|
|
2122
2285
|
if (this.strategy.onEvent) {
|
|
2123
2286
|
await this.strategy.onEvent(event, this.context);
|
|
@@ -2128,8 +2291,6 @@ var PolymarketEventRunner = class {
|
|
|
2128
2291
|
error
|
|
2129
2292
|
);
|
|
2130
2293
|
}
|
|
2131
|
-
const eventDuration = Date.now() - eventStart;
|
|
2132
|
-
this.config.logger.debug(`Event handling completed in ${eventDuration}ms`);
|
|
2133
2294
|
}
|
|
2134
2295
|
};
|
|
2135
2296
|
function createPolymarketEventRunner(strategy, config, context) {
|
|
@@ -2481,11 +2642,7 @@ var OsirisEventRunner = class {
|
|
|
2481
2642
|
* Dispatch an event to the strategy's onEvent handler
|
|
2482
2643
|
*/
|
|
2483
2644
|
async dispatchEvent(event) {
|
|
2484
|
-
|
|
2485
|
-
const target = event.market || event.wallet || "";
|
|
2486
|
-
this.config.logger.debug(
|
|
2487
|
-
`Dispatching ${event.type} event${target ? ` for ${target}` : ""}`
|
|
2488
|
-
);
|
|
2645
|
+
event.market || event.wallet || "";
|
|
2489
2646
|
try {
|
|
2490
2647
|
if (this.strategy.onEvent) {
|
|
2491
2648
|
await this.strategy.onEvent(event, this.context);
|
|
@@ -2496,8 +2653,6 @@ var OsirisEventRunner = class {
|
|
|
2496
2653
|
error
|
|
2497
2654
|
);
|
|
2498
2655
|
}
|
|
2499
|
-
const eventDuration = Date.now() - eventStart;
|
|
2500
|
-
this.config.logger.debug(`Event handling completed in ${eventDuration}ms`);
|
|
2501
2656
|
}
|
|
2502
2657
|
};
|
|
2503
2658
|
function createEventRunner(strategy, config, context) {
|
|
@@ -2525,6 +2680,888 @@ function createEventRunner(strategy, config, context) {
|
|
|
2525
2680
|
if (!config.eventSourceUrl) ;
|
|
2526
2681
|
return new OsirisEventRunner(strategy, config, context);
|
|
2527
2682
|
}
|
|
2683
|
+
var PolymarketRTDSService = class {
|
|
2684
|
+
client = null;
|
|
2685
|
+
config;
|
|
2686
|
+
isConnected = false;
|
|
2687
|
+
activeSubscriptions = [];
|
|
2688
|
+
constructor(config) {
|
|
2689
|
+
this.config = config;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Connect to the Polymarket RTDS WebSocket
|
|
2693
|
+
*/
|
|
2694
|
+
connect() {
|
|
2695
|
+
if (this.isConnected) {
|
|
2696
|
+
this.config.logger.warning("PolymarketRTDS: Already connected");
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
this.config.logger.info("PolymarketRTDS: Connecting...");
|
|
2700
|
+
this.client = new RealTimeDataClient({
|
|
2701
|
+
onConnect: (_client) => {
|
|
2702
|
+
this.isConnected = true;
|
|
2703
|
+
this.config.logger.info("PolymarketRTDS: Connected");
|
|
2704
|
+
this.config.onConnect?.();
|
|
2705
|
+
},
|
|
2706
|
+
onMessage: (_client, message) => {
|
|
2707
|
+
this.handleMessage(message);
|
|
2708
|
+
},
|
|
2709
|
+
onStatusChange: (status) => {
|
|
2710
|
+
if (status === ConnectionStatus.DISCONNECTED) {
|
|
2711
|
+
this.isConnected = false;
|
|
2712
|
+
this.config.logger.info("PolymarketRTDS: Disconnected");
|
|
2713
|
+
this.config.onDisconnect?.();
|
|
2714
|
+
} else if (status === ConnectionStatus.CONNECTING) {
|
|
2715
|
+
this.config.logger.info("PolymarketRTDS: Reconnecting...");
|
|
2716
|
+
}
|
|
2717
|
+
},
|
|
2718
|
+
pingInterval: this.config.pingIntervalMs ?? 5e3,
|
|
2719
|
+
autoReconnect: this.config.autoReconnect ?? true
|
|
2720
|
+
});
|
|
2721
|
+
this.client.connect();
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Disconnect from the Polymarket RTDS WebSocket
|
|
2725
|
+
*/
|
|
2726
|
+
disconnect() {
|
|
2727
|
+
if (!this.client) {
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
this.config.logger.info("PolymarketRTDS: Disconnecting...");
|
|
2731
|
+
this.client.disconnect();
|
|
2732
|
+
this.client = null;
|
|
2733
|
+
this.isConnected = false;
|
|
2734
|
+
this.activeSubscriptions = [];
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Check if connected
|
|
2738
|
+
*/
|
|
2739
|
+
isActive() {
|
|
2740
|
+
return this.isConnected;
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Subscribe to events
|
|
2744
|
+
* Converts SDK EventSubscription types to RTDS Subscription format
|
|
2745
|
+
*/
|
|
2746
|
+
subscribe(subscriptions) {
|
|
2747
|
+
if (!this.client) {
|
|
2748
|
+
this.config.logger.warning("PolymarketRTDS: Cannot subscribe - not connected");
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
const rtdsSubscriptions = this.mapToRTDSSubscriptions(subscriptions);
|
|
2752
|
+
if (rtdsSubscriptions.length === 0) {
|
|
2753
|
+
this.config.logger.warning("PolymarketRTDS: No valid subscriptions to add");
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
const msg = { subscriptions: rtdsSubscriptions };
|
|
2757
|
+
this.client.subscribe(msg);
|
|
2758
|
+
this.activeSubscriptions.push(...rtdsSubscriptions);
|
|
2759
|
+
this.config.logger.info(
|
|
2760
|
+
`PolymarketRTDS: Subscribed to ${rtdsSubscriptions.length} topic(s): ` + rtdsSubscriptions.map((s) => `${s.topic}:${s.type}`).join(", ")
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Unsubscribe from events
|
|
2765
|
+
*/
|
|
2766
|
+
unsubscribe(subscriptions) {
|
|
2767
|
+
if (!this.client) {
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
const rtdsSubscriptions = this.mapToRTDSSubscriptions(subscriptions);
|
|
2771
|
+
if (rtdsSubscriptions.length === 0) {
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
const msg = { subscriptions: rtdsSubscriptions };
|
|
2775
|
+
this.client.unsubscribe(msg);
|
|
2776
|
+
for (const unsub of rtdsSubscriptions) {
|
|
2777
|
+
const idx = this.activeSubscriptions.findIndex(
|
|
2778
|
+
(s) => s.topic === unsub.topic && s.type === unsub.type
|
|
2779
|
+
);
|
|
2780
|
+
if (idx >= 0) {
|
|
2781
|
+
this.activeSubscriptions.splice(idx, 1);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
this.config.logger.info(
|
|
2785
|
+
`PolymarketRTDS: Unsubscribed from ${rtdsSubscriptions.length} topic(s)`
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Map SDK EventSubscription to RTDS Subscription format
|
|
2790
|
+
*/
|
|
2791
|
+
mapToRTDSSubscriptions(subscriptions) {
|
|
2792
|
+
const result = [];
|
|
2793
|
+
for (const sub of subscriptions) {
|
|
2794
|
+
const rtdsSub = this.mapSubscription(sub);
|
|
2795
|
+
if (rtdsSub) {
|
|
2796
|
+
result.push(rtdsSub);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return result;
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Map a single SDK EventSubscription to RTDS Subscription
|
|
2803
|
+
*/
|
|
2804
|
+
mapSubscription(sub) {
|
|
2805
|
+
if (isClobMarketSubscription(sub)) {
|
|
2806
|
+
return this.mapClobMarketSubscription(sub);
|
|
2807
|
+
}
|
|
2808
|
+
if (isClobUserSubscription(sub)) {
|
|
2809
|
+
return this.mapClobUserSubscription(sub);
|
|
2810
|
+
}
|
|
2811
|
+
if (isActivitySubscription(sub)) {
|
|
2812
|
+
return this.mapActivitySubscription(sub);
|
|
2813
|
+
}
|
|
2814
|
+
if (isCommentsSubscription(sub)) {
|
|
2815
|
+
return this.mapCommentsSubscription(sub);
|
|
2816
|
+
}
|
|
2817
|
+
if (isCryptoPricesSubscription(sub)) {
|
|
2818
|
+
return this.mapCryptoPricesSubscription(sub);
|
|
2819
|
+
}
|
|
2820
|
+
if (isRfqSubscription(sub)) {
|
|
2821
|
+
return this.mapRfqSubscription(sub);
|
|
2822
|
+
}
|
|
2823
|
+
if (isMarketSubscription(sub)) {
|
|
2824
|
+
return this.mapMarketSubscription(sub);
|
|
2825
|
+
}
|
|
2826
|
+
this.config.logger.warning(
|
|
2827
|
+
`PolymarketRTDS: Unsupported subscription type '${sub.type}'`
|
|
2828
|
+
);
|
|
2829
|
+
return null;
|
|
2830
|
+
}
|
|
2831
|
+
mapClobMarketSubscription(sub) {
|
|
2832
|
+
return {
|
|
2833
|
+
topic: "clob_market",
|
|
2834
|
+
type: sub.messageType || "*",
|
|
2835
|
+
filters: JSON.stringify([sub.marketId])
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
mapClobUserSubscription(sub) {
|
|
2839
|
+
if (!this.config.clobAuth) {
|
|
2840
|
+
this.config.logger.warning(
|
|
2841
|
+
"PolymarketRTDS: clob_user subscription requires clobAuth credentials"
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
return {
|
|
2845
|
+
topic: "clob_user",
|
|
2846
|
+
type: sub.messageType || "*",
|
|
2847
|
+
clob_auth: this.config.clobAuth
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
mapActivitySubscription(sub) {
|
|
2851
|
+
let filters;
|
|
2852
|
+
if (sub.eventSlug) {
|
|
2853
|
+
filters = JSON.stringify({ event_slug: sub.eventSlug });
|
|
2854
|
+
} else if (sub.marketSlug) {
|
|
2855
|
+
filters = JSON.stringify({ market_slug: sub.marketSlug });
|
|
2856
|
+
}
|
|
2857
|
+
return {
|
|
2858
|
+
topic: "activity",
|
|
2859
|
+
type: sub.messageType || "*",
|
|
2860
|
+
filters
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
mapCommentsSubscription(sub) {
|
|
2864
|
+
let filters;
|
|
2865
|
+
if (sub.parentEntityId && sub.parentEntityType) {
|
|
2866
|
+
filters = JSON.stringify({
|
|
2867
|
+
parentEntityID: sub.parentEntityId,
|
|
2868
|
+
parentEntityType: sub.parentEntityType
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
topic: "comments",
|
|
2873
|
+
type: sub.messageType || "*",
|
|
2874
|
+
filters
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
mapCryptoPricesSubscription(sub) {
|
|
2878
|
+
let filters;
|
|
2879
|
+
if (sub.symbol) {
|
|
2880
|
+
filters = JSON.stringify({ symbol: sub.symbol.toLowerCase() });
|
|
2881
|
+
}
|
|
2882
|
+
return {
|
|
2883
|
+
topic: "crypto_prices",
|
|
2884
|
+
type: "update",
|
|
2885
|
+
filters
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
mapRfqSubscription(sub) {
|
|
2889
|
+
return {
|
|
2890
|
+
topic: "rfq",
|
|
2891
|
+
type: sub.messageType || "*"
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
mapMarketSubscription(sub) {
|
|
2895
|
+
switch (sub.type) {
|
|
2896
|
+
case "price":
|
|
2897
|
+
return {
|
|
2898
|
+
topic: "clob_market",
|
|
2899
|
+
type: "price_change",
|
|
2900
|
+
filters: JSON.stringify([sub.market])
|
|
2901
|
+
};
|
|
2902
|
+
case "orderbook":
|
|
2903
|
+
return {
|
|
2904
|
+
topic: "clob_market",
|
|
2905
|
+
type: "agg_orderbook",
|
|
2906
|
+
filters: JSON.stringify([sub.market])
|
|
2907
|
+
};
|
|
2908
|
+
case "trade":
|
|
2909
|
+
return {
|
|
2910
|
+
topic: "activity",
|
|
2911
|
+
type: "trades",
|
|
2912
|
+
filters: JSON.stringify({ market_slug: sub.market })
|
|
2913
|
+
};
|
|
2914
|
+
case "fill":
|
|
2915
|
+
if (!this.config.clobAuth) {
|
|
2916
|
+
this.config.logger.warning(
|
|
2917
|
+
"PolymarketRTDS: fill subscription requires clobAuth credentials"
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
topic: "clob_user",
|
|
2922
|
+
type: "order",
|
|
2923
|
+
clob_auth: this.config.clobAuth
|
|
2924
|
+
};
|
|
2925
|
+
default:
|
|
2926
|
+
return {
|
|
2927
|
+
topic: "activity",
|
|
2928
|
+
type: "trades",
|
|
2929
|
+
filters: JSON.stringify({ market_slug: sub.market })
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Handle incoming RTDS messages
|
|
2935
|
+
*/
|
|
2936
|
+
handleMessage(message) {
|
|
2937
|
+
try {
|
|
2938
|
+
const event = this.convertToStrategyEvent(message);
|
|
2939
|
+
this.config.onEvent(event);
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
this.config.logger.error(
|
|
2942
|
+
`PolymarketRTDS: Error processing message - ${error.message}`
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Convert RTDS Message to StrategyEvent
|
|
2948
|
+
*/
|
|
2949
|
+
convertToStrategyEvent(message) {
|
|
2950
|
+
const eventType = this.mapTopicToEventType(message.topic, message.type);
|
|
2951
|
+
const eventData = this.extractEventData(message);
|
|
2952
|
+
return {
|
|
2953
|
+
type: eventType,
|
|
2954
|
+
timestamp: message.timestamp || Date.now(),
|
|
2955
|
+
market: eventData.market,
|
|
2956
|
+
data: eventData
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Map RTDS topic/type to SDK EventType
|
|
2961
|
+
*/
|
|
2962
|
+
mapTopicToEventType(topic, type) {
|
|
2963
|
+
switch (topic) {
|
|
2964
|
+
case "crypto_prices":
|
|
2965
|
+
return "price";
|
|
2966
|
+
case "clob_market":
|
|
2967
|
+
switch (type) {
|
|
2968
|
+
case "price_change":
|
|
2969
|
+
case "last_trade_price":
|
|
2970
|
+
return "price";
|
|
2971
|
+
case "agg_orderbook":
|
|
2972
|
+
return "orderbook";
|
|
2973
|
+
default:
|
|
2974
|
+
return "custom";
|
|
2975
|
+
}
|
|
2976
|
+
case "activity":
|
|
2977
|
+
return "trade";
|
|
2978
|
+
case "clob_user":
|
|
2979
|
+
return type === "trade" ? "trade" : "fill";
|
|
2980
|
+
default:
|
|
2981
|
+
return "custom";
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
/**
|
|
2985
|
+
* Extract event data from RTDS message payload
|
|
2986
|
+
*/
|
|
2987
|
+
extractEventData(message) {
|
|
2988
|
+
const payload = message.payload;
|
|
2989
|
+
const data = {
|
|
2990
|
+
raw: {
|
|
2991
|
+
topic: message.topic,
|
|
2992
|
+
type: message.type,
|
|
2993
|
+
...payload
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
switch (message.topic) {
|
|
2997
|
+
case "crypto_prices":
|
|
2998
|
+
data.price = payload.value;
|
|
2999
|
+
data.market = payload.symbol;
|
|
3000
|
+
break;
|
|
3001
|
+
case "clob_market":
|
|
3002
|
+
if (message.type === "price_change") {
|
|
3003
|
+
data.market = payload.market;
|
|
3004
|
+
if (payload.price_changes?.length > 0) {
|
|
3005
|
+
const change = payload.price_changes[0];
|
|
3006
|
+
data.bestBid = parseFloat(change.best_bid);
|
|
3007
|
+
data.bestAsk = parseFloat(change.best_ask);
|
|
3008
|
+
data.price = parseFloat(change.price);
|
|
3009
|
+
}
|
|
3010
|
+
} else if (message.type === "agg_orderbook") {
|
|
3011
|
+
data.market = payload.market;
|
|
3012
|
+
data.bids = payload.bids;
|
|
3013
|
+
data.asks = payload.asks;
|
|
3014
|
+
if (payload.bids?.length > 0) {
|
|
3015
|
+
data.bestBid = parseFloat(payload.bids[0].price);
|
|
3016
|
+
}
|
|
3017
|
+
if (payload.asks?.length > 0) {
|
|
3018
|
+
data.bestAsk = parseFloat(payload.asks[0].price);
|
|
3019
|
+
}
|
|
3020
|
+
} else if (message.type === "last_trade_price") {
|
|
3021
|
+
data.market = payload.market;
|
|
3022
|
+
data.price = parseFloat(payload.price);
|
|
3023
|
+
data.side = payload.side;
|
|
3024
|
+
data.size = parseFloat(payload.size);
|
|
3025
|
+
}
|
|
3026
|
+
break;
|
|
3027
|
+
case "activity":
|
|
3028
|
+
data.market = payload.slug || payload.conditionId;
|
|
3029
|
+
data.price = payload.price;
|
|
3030
|
+
data.size = payload.size;
|
|
3031
|
+
data.side = payload.side?.toLowerCase();
|
|
3032
|
+
data.tradeId = payload.transactionHash;
|
|
3033
|
+
break;
|
|
3034
|
+
case "clob_user":
|
|
3035
|
+
data.market = payload.market;
|
|
3036
|
+
data.orderId = payload.id;
|
|
3037
|
+
data.price = parseFloat(payload.price);
|
|
3038
|
+
data.size = parseFloat(payload.size || payload.original_size);
|
|
3039
|
+
data.side = payload.side?.toLowerCase();
|
|
3040
|
+
break;
|
|
3041
|
+
case "rfq":
|
|
3042
|
+
data.market = payload.market;
|
|
3043
|
+
data.price = payload.price;
|
|
3044
|
+
data.size = payload.sizeIn || payload.sizeOut;
|
|
3045
|
+
data.side = payload.side?.toLowerCase();
|
|
3046
|
+
break;
|
|
3047
|
+
}
|
|
3048
|
+
return data;
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
function createPolymarketRTDSService(config) {
|
|
3052
|
+
return new PolymarketRTDSService(config);
|
|
3053
|
+
}
|
|
3054
|
+
var DEFAULT_OSIRIS_RTDS_URL = "wss://rtds.osirislabs.xyz";
|
|
3055
|
+
var OsirisRTDSService = class {
|
|
3056
|
+
ws = null;
|
|
3057
|
+
config;
|
|
3058
|
+
isConnected = false;
|
|
3059
|
+
reconnectAttempts = 0;
|
|
3060
|
+
reconnectTimeout = null;
|
|
3061
|
+
pingInterval = null;
|
|
3062
|
+
isIntentionallyClosed = false;
|
|
3063
|
+
activeTopics = /* @__PURE__ */ new Set();
|
|
3064
|
+
// Reconnection settings
|
|
3065
|
+
maxReconnectAttempts;
|
|
3066
|
+
baseReconnectDelay;
|
|
3067
|
+
maxReconnectDelay;
|
|
3068
|
+
pingIntervalMs;
|
|
3069
|
+
url;
|
|
3070
|
+
constructor(config) {
|
|
3071
|
+
this.config = config;
|
|
3072
|
+
this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
|
|
3073
|
+
this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
|
|
3074
|
+
this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
|
|
3075
|
+
this.pingIntervalMs = config.pingIntervalMs ?? 3e4;
|
|
3076
|
+
this.url = config.url || DEFAULT_OSIRIS_RTDS_URL;
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Connect to the Osiris Pub/Sub WebSocket
|
|
3080
|
+
*/
|
|
3081
|
+
connect() {
|
|
3082
|
+
if (this.isConnected) {
|
|
3083
|
+
this.config.logger.warning("OsirisRTDS: Already connected");
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
this.isIntentionallyClosed = false;
|
|
3087
|
+
this.config.logger.info(`OsirisRTDS: Connecting to ${this.url}...`);
|
|
3088
|
+
try {
|
|
3089
|
+
this.ws = new WebSocket(this.url);
|
|
3090
|
+
this.ws.on("open", () => {
|
|
3091
|
+
this.isConnected = true;
|
|
3092
|
+
this.reconnectAttempts = 0;
|
|
3093
|
+
this.config.logger.info("OsirisRTDS: Connected");
|
|
3094
|
+
this.startPingInterval();
|
|
3095
|
+
this.config.onConnect?.();
|
|
3096
|
+
});
|
|
3097
|
+
this.ws.on("message", (data) => {
|
|
3098
|
+
this.handleMessage(data);
|
|
3099
|
+
});
|
|
3100
|
+
this.ws.on("error", (error) => {
|
|
3101
|
+
this.config.logger.error(`OsirisRTDS: Error - ${error.message}`);
|
|
3102
|
+
this.config.onError?.(error);
|
|
3103
|
+
});
|
|
3104
|
+
this.ws.on("close", (code, reason) => {
|
|
3105
|
+
this.isConnected = false;
|
|
3106
|
+
this.stopPingInterval();
|
|
3107
|
+
const reasonStr = reason.toString() || "none";
|
|
3108
|
+
this.config.logger.info(`OsirisRTDS: Disconnected (code: ${code}, reason: ${reasonStr})`);
|
|
3109
|
+
this.config.onDisconnect?.();
|
|
3110
|
+
if (!this.isIntentionallyClosed) {
|
|
3111
|
+
this.attemptReconnect();
|
|
3112
|
+
}
|
|
3113
|
+
});
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
this.config.logger.error(`OsirisRTDS: Connection failed - ${error.message}`);
|
|
3116
|
+
if (!this.isIntentionallyClosed) {
|
|
3117
|
+
this.attemptReconnect();
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Disconnect from the Osiris Pub/Sub WebSocket
|
|
3123
|
+
*/
|
|
3124
|
+
disconnect() {
|
|
3125
|
+
this.isIntentionallyClosed = true;
|
|
3126
|
+
this.stopPingInterval();
|
|
3127
|
+
if (this.reconnectTimeout) {
|
|
3128
|
+
clearTimeout(this.reconnectTimeout);
|
|
3129
|
+
this.reconnectTimeout = null;
|
|
3130
|
+
}
|
|
3131
|
+
if (this.ws) {
|
|
3132
|
+
this.config.logger.info("OsirisRTDS: Disconnecting...");
|
|
3133
|
+
try {
|
|
3134
|
+
if (this.activeTopics.size > 0) {
|
|
3135
|
+
this.unsubscribeFromTopics(Array.from(this.activeTopics));
|
|
3136
|
+
}
|
|
3137
|
+
this.ws.close(1e3, "Intentional close");
|
|
3138
|
+
} catch {
|
|
3139
|
+
}
|
|
3140
|
+
this.ws = null;
|
|
3141
|
+
}
|
|
3142
|
+
this.isConnected = false;
|
|
3143
|
+
this.activeTopics.clear();
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Check if connected
|
|
3147
|
+
*/
|
|
3148
|
+
isActive() {
|
|
3149
|
+
return this.isConnected;
|
|
3150
|
+
}
|
|
3151
|
+
/**
|
|
3152
|
+
* Subscribe to events
|
|
3153
|
+
*/
|
|
3154
|
+
subscribe(subscriptions) {
|
|
3155
|
+
if (!this.ws || !this.isConnected) {
|
|
3156
|
+
this.config.logger.warning("OsirisRTDS: Cannot subscribe - not connected");
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const { topics, filters } = this.mapToOsirisSubscriptions(subscriptions);
|
|
3160
|
+
if (topics.length === 0) {
|
|
3161
|
+
this.config.logger.warning("OsirisRTDS: No valid topics to subscribe");
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const message = {
|
|
3165
|
+
action: "subscribe",
|
|
3166
|
+
topics,
|
|
3167
|
+
...Object.keys(filters).length > 0 ? { filters } : {}
|
|
3168
|
+
};
|
|
3169
|
+
try {
|
|
3170
|
+
this.ws.send(JSON.stringify(message));
|
|
3171
|
+
topics.forEach((t) => this.activeTopics.add(t));
|
|
3172
|
+
this.config.logger.info(`OsirisRTDS: Subscribing to ${topics.join(", ")}`);
|
|
3173
|
+
} catch (error) {
|
|
3174
|
+
this.config.logger.error(`OsirisRTDS: Failed to subscribe - ${error.message}`);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Unsubscribe from topics
|
|
3179
|
+
*/
|
|
3180
|
+
unsubscribe(subscriptions) {
|
|
3181
|
+
if (!this.ws || !this.isConnected) {
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
const { topics } = this.mapToOsirisSubscriptions(subscriptions);
|
|
3185
|
+
this.unsubscribeFromTopics(topics);
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* Unsubscribe from topics by name
|
|
3189
|
+
*/
|
|
3190
|
+
unsubscribeFromTopics(topics) {
|
|
3191
|
+
if (topics.length === 0) return;
|
|
3192
|
+
const message = {
|
|
3193
|
+
action: "unsubscribe",
|
|
3194
|
+
topics
|
|
3195
|
+
};
|
|
3196
|
+
try {
|
|
3197
|
+
this.ws?.send(JSON.stringify(message));
|
|
3198
|
+
topics.forEach((t) => this.activeTopics.delete(t));
|
|
3199
|
+
this.config.logger.info(`OsirisRTDS: Unsubscribed from ${topics.join(", ")}`);
|
|
3200
|
+
} catch {
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Send ping to keep connection alive
|
|
3205
|
+
*/
|
|
3206
|
+
ping() {
|
|
3207
|
+
if (this.ws && this.isConnected) {
|
|
3208
|
+
try {
|
|
3209
|
+
this.ws.send(JSON.stringify({ action: "ping" }));
|
|
3210
|
+
} catch {
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* Map SDK subscriptions to Osiris Pub/Sub format
|
|
3216
|
+
*/
|
|
3217
|
+
mapToOsirisSubscriptions(subscriptions) {
|
|
3218
|
+
const topics = [];
|
|
3219
|
+
const filters = {};
|
|
3220
|
+
for (const sub of subscriptions) {
|
|
3221
|
+
const topic = this.mapSubscriptionToTopic(sub);
|
|
3222
|
+
if (topic) {
|
|
3223
|
+
topics.push(topic);
|
|
3224
|
+
if (sub.conditions) {
|
|
3225
|
+
filters[topic] = sub.conditions;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
return { topics, filters };
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Map SDK subscription to Osiris topic string
|
|
3233
|
+
*/
|
|
3234
|
+
mapSubscriptionToTopic(sub) {
|
|
3235
|
+
if (isMarketSubscription(sub)) {
|
|
3236
|
+
return sub.market ? `market:${sub.market}` : null;
|
|
3237
|
+
}
|
|
3238
|
+
if (isWalletSubscription(sub)) {
|
|
3239
|
+
return sub.wallet ? `wallet:${sub.wallet.toLowerCase()}` : null;
|
|
3240
|
+
}
|
|
3241
|
+
if (isOpportunitySubscription(sub)) {
|
|
3242
|
+
return `opps:${sub.filter || "all"}`;
|
|
3243
|
+
}
|
|
3244
|
+
if (isCustomSubscription(sub)) {
|
|
3245
|
+
return sub.topic || null;
|
|
3246
|
+
}
|
|
3247
|
+
return null;
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Start ping interval
|
|
3251
|
+
*/
|
|
3252
|
+
startPingInterval() {
|
|
3253
|
+
this.stopPingInterval();
|
|
3254
|
+
this.pingInterval = setInterval(() => this.ping(), this.pingIntervalMs);
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Stop ping interval
|
|
3258
|
+
*/
|
|
3259
|
+
stopPingInterval() {
|
|
3260
|
+
if (this.pingInterval) {
|
|
3261
|
+
clearInterval(this.pingInterval);
|
|
3262
|
+
this.pingInterval = null;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Attempt reconnection with exponential backoff
|
|
3267
|
+
*/
|
|
3268
|
+
attemptReconnect() {
|
|
3269
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
3270
|
+
this.config.logger.error(
|
|
3271
|
+
`OsirisRTDS: Max reconnection attempts (${this.maxReconnectAttempts}) reached`
|
|
3272
|
+
);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
this.reconnectAttempts++;
|
|
3276
|
+
const delay = Math.min(
|
|
3277
|
+
this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1e3,
|
|
3278
|
+
this.maxReconnectDelay
|
|
3279
|
+
);
|
|
3280
|
+
this.config.logger.info(
|
|
3281
|
+
`OsirisRTDS: Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
3282
|
+
);
|
|
3283
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
3284
|
+
this.reconnectTimeout = null;
|
|
3285
|
+
this.connect();
|
|
3286
|
+
}, delay);
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Handle incoming WebSocket messages
|
|
3290
|
+
*/
|
|
3291
|
+
handleMessage(data) {
|
|
3292
|
+
try {
|
|
3293
|
+
const rawData = typeof data === "string" ? data : data.toString();
|
|
3294
|
+
if (!rawData.trim()) return;
|
|
3295
|
+
let message;
|
|
3296
|
+
try {
|
|
3297
|
+
message = JSON.parse(rawData);
|
|
3298
|
+
} catch {
|
|
3299
|
+
this.config.logger.warning(`OsirisRTDS: Non-JSON message received`);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
switch (message.type) {
|
|
3303
|
+
case "connected":
|
|
3304
|
+
this.config.logger.info(`OsirisRTDS: ${message.message}`);
|
|
3305
|
+
return;
|
|
3306
|
+
case "subscribe.result":
|
|
3307
|
+
if (message.success) {
|
|
3308
|
+
this.config.logger.info(
|
|
3309
|
+
`OsirisRTDS: Subscribed to ${message.subscribed?.join(", ")}`
|
|
3310
|
+
);
|
|
3311
|
+
} else {
|
|
3312
|
+
this.config.logger.error(
|
|
3313
|
+
`OsirisRTDS: Subscription failed - ${message.errors?.join(", ")}`
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
return;
|
|
3317
|
+
case "error":
|
|
3318
|
+
this.config.logger.error(`OsirisRTDS: Server error - ${message.error}`);
|
|
3319
|
+
return;
|
|
3320
|
+
case "pong":
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
const event = this.convertToStrategyEvent(message);
|
|
3324
|
+
if (event) {
|
|
3325
|
+
this.config.onEvent(event);
|
|
3326
|
+
}
|
|
3327
|
+
} catch (error) {
|
|
3328
|
+
this.config.logger.error(`OsirisRTDS: Error processing message - ${error.message}`);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Convert Osiris message to StrategyEvent
|
|
3333
|
+
*/
|
|
3334
|
+
convertToStrategyEvent(message) {
|
|
3335
|
+
const timestamp = message.ts || Date.now();
|
|
3336
|
+
switch (message.type) {
|
|
3337
|
+
case "market.analysis":
|
|
3338
|
+
return {
|
|
3339
|
+
type: "price",
|
|
3340
|
+
// Market analysis treated as price/market update
|
|
3341
|
+
market: message.slug,
|
|
3342
|
+
timestamp,
|
|
3343
|
+
data: {
|
|
3344
|
+
...message.data,
|
|
3345
|
+
raw: message
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
case "wallet.analysis":
|
|
3349
|
+
return {
|
|
3350
|
+
type: "wallet",
|
|
3351
|
+
wallet: message.address,
|
|
3352
|
+
timestamp,
|
|
3353
|
+
data: {
|
|
3354
|
+
...message.data,
|
|
3355
|
+
raw: message
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
case "opportunity":
|
|
3359
|
+
return {
|
|
3360
|
+
type: "opportunity",
|
|
3361
|
+
market: message.slug,
|
|
3362
|
+
timestamp,
|
|
3363
|
+
data: {
|
|
3364
|
+
...message.data,
|
|
3365
|
+
raw: message
|
|
3366
|
+
}
|
|
3367
|
+
};
|
|
3368
|
+
default:
|
|
3369
|
+
return null;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
function createOsirisRTDSService(config) {
|
|
3374
|
+
return new OsirisRTDSService(config);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
// src/rtds/unified-rtds.service.ts
|
|
3378
|
+
var UnifiedRTDSService = class {
|
|
3379
|
+
config;
|
|
3380
|
+
polymarketService = null;
|
|
3381
|
+
osirisService = null;
|
|
3382
|
+
polymarketConnected = false;
|
|
3383
|
+
osirisConnected = false;
|
|
3384
|
+
constructor(config) {
|
|
3385
|
+
this.config = config;
|
|
3386
|
+
this.initializeServices();
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Initialize the underlying services based on configuration
|
|
3390
|
+
*/
|
|
3391
|
+
initializeServices() {
|
|
3392
|
+
if (this.config.polymarket?.enabled !== false) {
|
|
3393
|
+
this.polymarketService = new PolymarketRTDSService({
|
|
3394
|
+
logger: this.config.logger,
|
|
3395
|
+
onEvent: this.config.onEvent,
|
|
3396
|
+
onError: this.config.onError,
|
|
3397
|
+
onConnect: () => {
|
|
3398
|
+
this.polymarketConnected = true;
|
|
3399
|
+
this.checkAllConnected();
|
|
3400
|
+
},
|
|
3401
|
+
onDisconnect: () => {
|
|
3402
|
+
this.polymarketConnected = false;
|
|
3403
|
+
this.checkAnyDisconnected();
|
|
3404
|
+
},
|
|
3405
|
+
clobAuth: this.config.polymarket?.clobAuth
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
if (this.config.osiris?.enabled !== false) {
|
|
3409
|
+
this.osirisService = new OsirisRTDSService({
|
|
3410
|
+
logger: this.config.logger,
|
|
3411
|
+
onEvent: this.config.onEvent,
|
|
3412
|
+
onError: this.config.onError,
|
|
3413
|
+
onConnect: () => {
|
|
3414
|
+
this.osirisConnected = true;
|
|
3415
|
+
this.checkAllConnected();
|
|
3416
|
+
},
|
|
3417
|
+
onDisconnect: () => {
|
|
3418
|
+
this.osirisConnected = false;
|
|
3419
|
+
this.checkAnyDisconnected();
|
|
3420
|
+
},
|
|
3421
|
+
url: this.config.osiris?.url || DEFAULT_OSIRIS_RTDS_URL,
|
|
3422
|
+
pingIntervalMs: this.config.osiris?.pingIntervalMs,
|
|
3423
|
+
reconnect: this.config.reconnect
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Connect to all enabled services
|
|
3429
|
+
*/
|
|
3430
|
+
connect() {
|
|
3431
|
+
this.config.logger.info("UnifiedRTDS: Connecting to enabled services...");
|
|
3432
|
+
if (this.polymarketService) {
|
|
3433
|
+
this.polymarketService.connect();
|
|
3434
|
+
}
|
|
3435
|
+
if (this.osirisService) {
|
|
3436
|
+
this.osirisService.connect();
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Disconnect from all services
|
|
3441
|
+
*/
|
|
3442
|
+
disconnect() {
|
|
3443
|
+
this.config.logger.info("UnifiedRTDS: Disconnecting from all services...");
|
|
3444
|
+
if (this.polymarketService) {
|
|
3445
|
+
this.polymarketService.disconnect();
|
|
3446
|
+
}
|
|
3447
|
+
if (this.osirisService) {
|
|
3448
|
+
this.osirisService.disconnect();
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* Check if connected to at least one service
|
|
3453
|
+
*/
|
|
3454
|
+
isActive() {
|
|
3455
|
+
return (this.polymarketService?.isActive() ?? false) || (this.osirisService?.isActive() ?? false);
|
|
3456
|
+
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Get connection status for each service
|
|
3459
|
+
*/
|
|
3460
|
+
getStatus() {
|
|
3461
|
+
return {
|
|
3462
|
+
polymarket: {
|
|
3463
|
+
enabled: this.polymarketService !== null,
|
|
3464
|
+
connected: this.polymarketConnected
|
|
3465
|
+
},
|
|
3466
|
+
osiris: {
|
|
3467
|
+
enabled: this.osirisService !== null,
|
|
3468
|
+
connected: this.osirisConnected
|
|
3469
|
+
}
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Subscribe to events
|
|
3474
|
+
* Automatically routes subscriptions to the appropriate backend
|
|
3475
|
+
*/
|
|
3476
|
+
subscribe(subscriptions) {
|
|
3477
|
+
const { polymarketSubs, osirisSubs } = this.routeSubscriptions(subscriptions);
|
|
3478
|
+
if (polymarketSubs.length > 0) {
|
|
3479
|
+
if (this.polymarketService) {
|
|
3480
|
+
this.polymarketService.subscribe(polymarketSubs);
|
|
3481
|
+
} else {
|
|
3482
|
+
this.config.logger.warning(
|
|
3483
|
+
"UnifiedRTDS: Polymarket subscriptions requested but service not enabled"
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
if (osirisSubs.length > 0) {
|
|
3488
|
+
if (this.osirisService) {
|
|
3489
|
+
this.osirisService.subscribe(osirisSubs);
|
|
3490
|
+
} else {
|
|
3491
|
+
this.config.logger.warning(
|
|
3492
|
+
"UnifiedRTDS: Osiris subscriptions requested but service not enabled"
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Unsubscribe from events
|
|
3499
|
+
*/
|
|
3500
|
+
unsubscribe(subscriptions) {
|
|
3501
|
+
const { polymarketSubs, osirisSubs } = this.routeSubscriptions(subscriptions);
|
|
3502
|
+
if (polymarketSubs.length > 0 && this.polymarketService) {
|
|
3503
|
+
this.polymarketService.unsubscribe(polymarketSubs);
|
|
3504
|
+
}
|
|
3505
|
+
if (osirisSubs.length > 0 && this.osirisService) {
|
|
3506
|
+
this.osirisService.unsubscribe(osirisSubs);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
/**
|
|
3510
|
+
* Route subscriptions to appropriate backends based on type and eventSource
|
|
3511
|
+
*/
|
|
3512
|
+
routeSubscriptions(subscriptions) {
|
|
3513
|
+
const polymarketSubs = [];
|
|
3514
|
+
const osirisSubs = [];
|
|
3515
|
+
for (const sub of subscriptions) {
|
|
3516
|
+
if (sub.eventSource === "polymarket") {
|
|
3517
|
+
polymarketSubs.push(sub);
|
|
3518
|
+
continue;
|
|
3519
|
+
}
|
|
3520
|
+
if (sub.eventSource === "osiris") {
|
|
3521
|
+
osirisSubs.push(sub);
|
|
3522
|
+
continue;
|
|
3523
|
+
}
|
|
3524
|
+
if (isPolymarketSubscription(sub)) {
|
|
3525
|
+
polymarketSubs.push(sub);
|
|
3526
|
+
} else if (isOsirisSubscription(sub)) {
|
|
3527
|
+
osirisSubs.push(sub);
|
|
3528
|
+
} else if (isMarketSubscription(sub)) {
|
|
3529
|
+
polymarketSubs.push(sub);
|
|
3530
|
+
} else if (isCustomSubscription(sub)) {
|
|
3531
|
+
const topic = sub.topic;
|
|
3532
|
+
if (topic.startsWith("market:") || topic.startsWith("wallet:") || topic.startsWith("opps:")) {
|
|
3533
|
+
osirisSubs.push(sub);
|
|
3534
|
+
} else {
|
|
3535
|
+
polymarketSubs.push(sub);
|
|
3536
|
+
}
|
|
3537
|
+
} else {
|
|
3538
|
+
polymarketSubs.push(sub);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
return { polymarketSubs, osirisSubs };
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* Check if all enabled services are connected
|
|
3545
|
+
*/
|
|
3546
|
+
checkAllConnected() {
|
|
3547
|
+
const polymarketOk = !this.polymarketService || this.polymarketConnected;
|
|
3548
|
+
const osirisOk = !this.osirisService || this.osirisConnected;
|
|
3549
|
+
if (polymarketOk && osirisOk) {
|
|
3550
|
+
this.config.onConnect?.();
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
/**
|
|
3554
|
+
* Check if any service disconnected
|
|
3555
|
+
*/
|
|
3556
|
+
checkAnyDisconnected() {
|
|
3557
|
+
if (!this.polymarketConnected && !this.osirisConnected) {
|
|
3558
|
+
this.config.onDisconnect?.();
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
};
|
|
3562
|
+
function createUnifiedRTDSService(config) {
|
|
3563
|
+
return new UnifiedRTDSService(config);
|
|
3564
|
+
}
|
|
2528
3565
|
var CHAIN_MAP = {
|
|
2529
3566
|
"evm:eip155:1": mainnet,
|
|
2530
3567
|
"evm:eip155:137": polygon,
|
|
@@ -2675,6 +3712,6 @@ var Signer = class {
|
|
|
2675
3712
|
}
|
|
2676
3713
|
};
|
|
2677
3714
|
|
|
2678
|
-
export { HyperliquidClient, MemoryStateManager, OsirisEventRunner, OsirisSigner, PolymarketClient, PolymarketEventRunner, PrivateKeySigner, RedisStateManager, Signer, StrategyEngine, createConsoleLogger, createEventRunner, createOsirisContext, createPolymarketEventRunner, createStrategyEngine, isCustomSubscription, isEventBasedStrategy, isMarketSubscription, isOpportunitySubscription, isTickBasedStrategy, isWalletSubscription, runStrategy, validateStrategy };
|
|
3715
|
+
export { DEFAULT_OSIRIS_RTDS_URL, HyperliquidClient, MemoryStateManager, OsirisEventRunner, OsirisRTDSService, OsirisSigner, PolymarketClient, PolymarketEventRunner, PolymarketRTDSService, PrivateKeySigner, RedisStateManager, Signer, StrategyEngine, UnifiedRTDSService, createConsoleLogger, createEventRunner, createOsirisContext, createOsirisRTDSService, createPolymarketEventRunner, createPolymarketRTDSService, createStrategyEngine, createUnifiedRTDSService, isActivitySubscription, isClobMarketSubscription, isClobUserSubscription, isCommentsSubscription, isCryptoPricesSubscription, isCustomSubscription, isEventBasedStrategy, isMarketSubscription, isOpportunitySubscription, isOsirisSubscription, isPolymarketSubscription, isRfqSubscription, isTickBasedStrategy, isWalletSubscription, runStrategy, validateStrategy };
|
|
2679
3716
|
//# sourceMappingURL=index.js.map
|
|
2680
3717
|
//# sourceMappingURL=index.js.map
|