@vesper85/strategy-sdk 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,19 +1,57 @@
1
1
  import { PolymarketGammaClient } from 'polymarket-gamma';
2
2
  import { ClobClient, Side } from '@polymarket/clob-client';
3
- import { MaxUint256, ethers } from 'ethers';
3
+ import { ethers } from 'ethers';
4
4
  import axios from 'axios';
5
5
  import { serializeSignature, createWalletClient, http } from 'viem';
6
6
  import { connectMCPServer, removeConnection } from '@osiris-ai/agent-sdk';
7
7
  import { Hyperliquid } from 'hyperliquid';
8
8
  import { createClient } from 'redis';
9
9
  import superjson from 'superjson';
10
- import { TechnicalAnalysisService } from '@vesper85/technical-indicators';
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
 
15
16
  // @osiris-ai/strategy-sdk - Strategy SDK for Trading
16
17
 
18
+ // src/types/event-types.ts
19
+ function isMarketSubscription(sub) {
20
+ return sub.type === "price" || sub.type === "orderbook" || sub.type === "trade" || sub.type === "fill";
21
+ }
22
+ function isWalletSubscription(sub) {
23
+ return sub.type === "wallet";
24
+ }
25
+ function isOpportunitySubscription(sub) {
26
+ return sub.type === "opportunity";
27
+ }
28
+ function isCustomSubscription(sub) {
29
+ return sub.type === "custom";
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
+ }
17
55
  var OsirisSigner = class {
18
56
  hubBaseUrl;
19
57
  accessToken;
@@ -148,13 +186,88 @@ var OsirisSigner = class {
148
186
  return void 0;
149
187
  }
150
188
  };
151
- var MaxUint256BigInt = BigInt(MaxUint256.toString());
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
263
+ var MaxUint256BigInt = typeof ethers.constants !== "undefined" && ethers.constants.MaxUint256 ? BigInt(ethers.MaxUint256.toString()) : BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
152
264
  var POLYGON_CHAIN_ID = 137;
153
265
  var USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
154
266
  var CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045";
155
267
  var CTF_EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";
156
268
  var DEFAULT_CLOB_HOST = "https://clob.polymarket.com";
157
269
  var POLYGON_RPC_URL = "https://polygon-rpc.com";
270
+ process.env.MCP_GATEWAY_URL ? `${process.env.MCP_GATEWAY_URL}/@osiris/polymarket/mcp` : "https://gateway.backend.osirislabs.xyz/@osiris/polymarket/mcp";
158
271
  var ERC20_ABI = [
159
272
  "function approve(address spender, uint256 amount) external returns (bool)",
160
273
  "function allowance(address owner, address spender) external view returns (uint256)",
@@ -164,6 +277,13 @@ var ERC1155_ABI = [
164
277
  "function setApprovalForAll(address operator, bool approved) external",
165
278
  "function isApprovedForAll(address account, address operator) external view returns (bool)"
166
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
+ }
167
287
  var PolymarketClient = class {
168
288
  constructor(logger, signer, userAddress, options) {
169
289
  this.logger = logger;
@@ -171,7 +291,7 @@ var PolymarketClient = class {
171
291
  this.userAddress = userAddress;
172
292
  this.gammaClient = new PolymarketGammaClient();
173
293
  this.provider = new ethers.JsonRpcProvider(POLYGON_RPC_URL);
174
- this.isOsirisSigner = signer instanceof OsirisSigner || signer.impl instanceof OsirisSigner || signer.getPrivateKey() === void 0;
294
+ this.isOsirisSigner = signer instanceof OsirisSigner || signer.getPrivateKey() === void 0;
175
295
  this.options = {
176
296
  chainId: options?.chainId ?? POLYGON_CHAIN_ID,
177
297
  autoApprove: options?.autoApprove ?? false,
@@ -230,11 +350,11 @@ var PolymarketClient = class {
230
350
  throw new Error("Could not obtain private key for CLOB client initialization. Private key signer is required for trading.");
231
351
  }
232
352
  this.wallet = new ethers.Wallet(privateKey, this.provider);
353
+ const v5CompatibleWallet = wrapWalletForV5Compatibility(this.wallet);
233
354
  const tempClient = new ClobClient(
234
355
  this.options.clobHost,
235
356
  this.options.chainId,
236
- this.wallet
237
- // Cast to any for CLOB client v5 compatibility
357
+ v5CompatibleWallet
238
358
  );
239
359
  this.logger.info("Deriving API credentials...");
240
360
  const apiCreds = await tempClient.createOrDeriveApiKey();
@@ -242,8 +362,7 @@ var PolymarketClient = class {
242
362
  this.clobClient = new ClobClient(
243
363
  this.options.clobHost,
244
364
  this.options.chainId,
245
- this.wallet,
246
- // Cast to any for CLOB client v5 compatibility
365
+ v5CompatibleWallet,
247
366
  apiCreds,
248
367
  signatureType
249
368
  );
@@ -257,15 +376,6 @@ var PolymarketClient = class {
257
376
  isTradingClientInitialized() {
258
377
  return this.tradingInitialized;
259
378
  }
260
- async getPrivateKeyFromSigner() {
261
- if (this.signer && "getPrivateKey" in this.signer) {
262
- return this.signer.getPrivateKey();
263
- }
264
- if (this.signer && this.signer.account?.privateKey) {
265
- return this.signer.account.privateKey;
266
- }
267
- return null;
268
- }
269
379
  ensureTradingClient() {
270
380
  if (!this.clobClient || !this.tradingInitialized) {
271
381
  throw new Error("Trading client not initialized. Call initializeTradingClient() first.");
@@ -568,7 +678,7 @@ var PolymarketClient = class {
568
678
  const allowanceResult = await this.callMCPTool("check_allowance", {
569
679
  token: tokenType
570
680
  });
571
- this.logger.debug("[MCP] Allowance check result:", allowanceResult);
681
+ console.log("allowanceResult", allowanceResult);
572
682
  const allowanceContent = this.extractMCPContent(allowanceResult);
573
683
  const contractMatches = allowanceContent?.match(/0x[a-fA-F0-9]{40}:\s*(true|false)/gi);
574
684
  let hasFullAllowance = true;
@@ -661,18 +771,6 @@ var PolymarketClient = class {
661
771
  ctfWasAlreadyApproved: false
662
772
  };
663
773
  try {
664
- const address = this.userAddress;
665
- if (!address) {
666
- throw new Error("userAddress is required for MCP approvals");
667
- }
668
- const chooseWalletResult = await this.callMCPTool("choose_wallet", {
669
- address
670
- });
671
- const walletError = this.detectMCPError(chooseWalletResult);
672
- if (walletError) {
673
- this.logger.error(`[MCP] Choose wallet failed: ${walletError}`);
674
- throw new Error(`Choose wallet failed: ${walletError}`);
675
- }
676
774
  const usdcHasAllowance = await this.checkAllowanceViaMCP("USDC");
677
775
  if (usdcHasAllowance) {
678
776
  result.usdcWasAlreadyApproved = true;
@@ -753,16 +851,13 @@ var PolymarketClient = class {
753
851
  if (!address) {
754
852
  throw new Error("userAddress is required for MCP order placement");
755
853
  }
756
- this.logger.info(`[MCP] Creating limit order: ${params.side} ${params.size} @ ${params.price}`);
757
- this.logger.debug(`[MCP] Using wallet address: ${address}`);
758
- this.logger.debug(`[MCP] Calling choose_wallet for address: ${address}`);
854
+ this.logger.info(`Creating limit order via MCP: ${params.side} ${params.size} @ ${params.price}`);
759
855
  const chooseWalletResult = await this.callMCPTool("choose_wallet", {
760
856
  address
761
857
  });
762
- this.logger.debug(`[MCP] choose_wallet result: ${JSON.stringify(chooseWalletResult, null, 2)}`);
763
858
  const walletError = this.detectMCPError(chooseWalletResult);
764
859
  if (walletError) {
765
- this.logger.error(`[MCP] Choose wallet failed: ${walletError}`);
860
+ this.logger.error(`Choose wallet failed: ${walletError}`);
766
861
  return {
767
862
  success: false,
768
863
  errorMsg: walletError
@@ -775,7 +870,7 @@ var PolymarketClient = class {
775
870
  errorMsg: `Failed to approve ${params.side === "BUY" ? "USDC" : "Conditional Token"}. Please try again.`
776
871
  };
777
872
  }
778
- const orderParams = {
873
+ const orderResult = await this.callMCPTool("create_and_post_order", {
779
874
  userOrder: {
780
875
  tokenID: params.tokenId,
781
876
  side: params.side,
@@ -784,11 +879,7 @@ var PolymarketClient = class {
784
879
  },
785
880
  orderType: params.expiration ? "GTD" : "GTC",
786
881
  deferExec: false
787
- };
788
- this.logger.debug(`[MCP] Limit order params:`, orderParams);
789
- this.logger.info(`[MCP] Submitting limit order: ${params.side} ${params.size} @ ${params.price} for token ${params.tokenId.substring(0, 20)}...`);
790
- const orderResult = await this.callMCPTool("create_and_post_order", orderParams);
791
- this.logger.debug(`[MCP] Raw limit order result: ${JSON.stringify(orderResult, null, 2)}`);
882
+ });
792
883
  const orderError = this.detectMCPError(orderResult);
793
884
  if (orderError) {
794
885
  this.logger.error(`Limit order via MCP failed: ${orderError}`);
@@ -799,13 +890,13 @@ var PolymarketClient = class {
799
890
  }
800
891
  const orderDetails = this.extractOrderDetails(orderResult);
801
892
  if (!orderDetails) {
802
- this.logger.error(`Failed to extract order details from MCP response`, orderResult);
893
+ this.logger.error("Failed to extract order details from MCP response");
803
894
  return {
804
895
  success: false,
805
896
  errorMsg: "Failed to extract order details from MCP response"
806
897
  };
807
898
  }
808
- this.logger.info(`[MCP] Limit order created: ${orderDetails.orderID}`);
899
+ this.logger.info(`Limit order via MCP created: ${orderDetails.orderID}`);
809
900
  return {
810
901
  success: true,
811
902
  orderId: orderDetails.orderID,
@@ -866,14 +957,10 @@ var PolymarketClient = class {
866
957
  if (!address) {
867
958
  throw new Error("userAddress is required for MCP order placement");
868
959
  }
869
- this.logger.info(`[MCP] Creating market order: ${params.side} ${params.amount} USDC`);
870
- this.logger.debug(`[MCP] Using wallet address: ${address}`);
871
- this.logger.debug(`[MCP] Order details: tokenId=${params.tokenId}, side=${params.side}, amount=${params.amount}`);
872
- this.logger.debug(`[MCP] Calling choose_wallet for address: ${address}`);
960
+ this.logger.info(`Creating market order via MCP: ${params.side} ${params.amount}`);
873
961
  const chooseWalletResult = await this.callMCPTool("choose_wallet", {
874
962
  address
875
963
  });
876
- this.logger.debug(`[MCP] choose_wallet result: ${JSON.stringify(chooseWalletResult, null, 2)}`);
877
964
  const walletError = this.detectMCPError(chooseWalletResult);
878
965
  if (walletError) {
879
966
  this.logger.error(`Choose wallet failed: ${walletError}`);
@@ -889,19 +976,16 @@ var PolymarketClient = class {
889
976
  errorMsg: `Failed to approve ${params.side === "BUY" ? "USDC" : "Conditional Token"}. Please try again.`
890
977
  };
891
978
  }
892
- const orderParams = {
979
+ const orderResult = await this.callMCPTool("create_and_post_market_order", {
893
980
  userMarketOrder: {
894
981
  tokenID: params.tokenId,
895
982
  side: params.side,
896
- amount: params.amount
983
+ amount: params.amount,
984
+ orderType: params.slippageTolerance ? "FOK" : "FOK"
985
+ // Fill or Kill
897
986
  },
898
987
  orderType: "FOK"
899
- // Fill or Kill
900
- };
901
- this.logger.debug(`[MCP] Market order params:`, orderParams);
902
- this.logger.info(`[MCP] Submitting market order: ${params.side} ${params.amount} USDC for token ${params.tokenId.substring(0, 20)}...`);
903
- const orderResult = await this.callMCPTool("create_and_post_market_order", orderParams);
904
- this.logger.debug(`[MCP] Raw order result: ${JSON.stringify(orderResult, null, 2)}`);
988
+ });
905
989
  const orderError = this.detectMCPError(orderResult);
906
990
  if (orderError) {
907
991
  this.logger.error(`Market order via MCP failed: ${orderError}`);
@@ -912,7 +996,7 @@ var PolymarketClient = class {
912
996
  }
913
997
  const orderDetails = this.extractOrderDetails(orderResult);
914
998
  if (!orderDetails) {
915
- this.logger.error(`Failed to extract order details from MCP response`, orderResult);
999
+ this.logger.error("Failed to extract order details from MCP response");
916
1000
  return {
917
1001
  success: false,
918
1002
  errorMsg: "Failed to extract order details from MCP response"
@@ -1027,9 +1111,8 @@ var PolymarketClient = class {
1027
1111
  // ============================================
1028
1112
  async buy(tokenId, size) {
1029
1113
  if (!this.tradingInitialized) {
1030
- throw new Error(
1031
- `Trading client not initialized. Call initializeTradingClient() before placing orders. Attempted to buy ${size} USDC of token ${tokenId}.`
1032
- );
1114
+ this.logger.warning(`[Dummy] Buying ${size} of ${tokenId} - trading client not initialized`);
1115
+ return;
1033
1116
  }
1034
1117
  const response = await this.createMarketOrder({
1035
1118
  tokenId,
@@ -1042,9 +1125,8 @@ var PolymarketClient = class {
1042
1125
  }
1043
1126
  async sell(tokenId, size) {
1044
1127
  if (!this.tradingInitialized) {
1045
- throw new Error(
1046
- `Trading client not initialized. Call initializeTradingClient() before placing orders. Attempted to sell ${size} shares of token ${tokenId}.`
1047
- );
1128
+ this.logger.warning(`[Dummy] Selling ${size} of ${tokenId} - trading client not initialized`);
1129
+ return;
1048
1130
  }
1049
1131
  const response = await this.createMarketOrder({
1050
1132
  tokenId,
@@ -1056,73 +1138,6 @@ var PolymarketClient = class {
1056
1138
  }
1057
1139
  }
1058
1140
  // ============================================
1059
- // Convenience Trading Methods
1060
- // ============================================
1061
- /**
1062
- * Place a limit buy order
1063
- */
1064
- async buyLimit(tokenId, price, size) {
1065
- if (!this.tradingInitialized) {
1066
- throw new Error(
1067
- `Trading client not initialized. Call initializeTradingClient() before placing orders.`
1068
- );
1069
- }
1070
- return this.createLimitOrder({
1071
- tokenId,
1072
- price,
1073
- size,
1074
- side: "BUY"
1075
- });
1076
- }
1077
- /**
1078
- * Place a limit sell order
1079
- */
1080
- async sellLimit(tokenId, price, size) {
1081
- if (!this.tradingInitialized) {
1082
- throw new Error(
1083
- `Trading client not initialized. Call initializeTradingClient() before placing orders.`
1084
- );
1085
- }
1086
- return this.createLimitOrder({
1087
- tokenId,
1088
- price,
1089
- size,
1090
- side: "SELL"
1091
- });
1092
- }
1093
- /**
1094
- * Place a market buy order
1095
- */
1096
- async buyMarket(tokenId, amount, slippage = 0.05) {
1097
- if (!this.tradingInitialized) {
1098
- throw new Error(
1099
- `Trading client not initialized. Call initializeTradingClient() before placing orders.`
1100
- );
1101
- }
1102
- return this.createMarketOrder({
1103
- tokenId,
1104
- amount,
1105
- side: "BUY",
1106
- slippageTolerance: slippage
1107
- });
1108
- }
1109
- /**
1110
- * Place a market sell order
1111
- */
1112
- async sellMarket(tokenId, size, slippage = 0.05) {
1113
- if (!this.tradingInitialized) {
1114
- throw new Error(
1115
- `Trading client not initialized. Call initializeTradingClient() before placing orders.`
1116
- );
1117
- }
1118
- return this.createMarketOrder({
1119
- tokenId,
1120
- amount: size,
1121
- side: "SELL",
1122
- slippageTolerance: slippage
1123
- });
1124
- }
1125
- // ============================================
1126
1141
  // Data Methods
1127
1142
  // ============================================
1128
1143
  async getPrice(ticker) {
@@ -1182,7 +1197,8 @@ var PolymarketClient = class {
1182
1197
  throw new Error(`Gamma API error: ${response.status} ${response.statusText}`);
1183
1198
  }
1184
1199
  const data = await response.json();
1185
- return data;
1200
+ const mappedData = data.map(mapToGammaMarket);
1201
+ return mappedData;
1186
1202
  } catch (error) {
1187
1203
  this.logger.error("Error fetching markets from Gamma API", error);
1188
1204
  throw error;
@@ -1270,26 +1286,8 @@ var PolymarketClient = class {
1270
1286
  return this.fetchData("/activity", { user, ...params });
1271
1287
  }
1272
1288
  async getTopHolders(marketId) {
1273
- try {
1274
- if (this.clobClient) {
1275
- try {
1276
- const market = await this.getMarket(marketId);
1277
- if (market && market.tokens) {
1278
- return market.tokens.map((token) => ({
1279
- tokenId: token.token_id,
1280
- outcome: token.outcome,
1281
- holders: token.holders || 0
1282
- }));
1283
- }
1284
- } catch (e) {
1285
- }
1286
- }
1287
- this.logger.debug("getTopHolders: No direct API available, returning market token info if available");
1288
- return [];
1289
- } catch (error) {
1290
- this.logger.error(`Error fetching top holders for ${marketId}`, error);
1291
- return [];
1292
- }
1289
+ this.logger.warning("getTopHolders not directly supported by public Data API");
1290
+ return [];
1293
1291
  }
1294
1292
  async getPortfolioValue(user) {
1295
1293
  try {
@@ -1304,18 +1302,8 @@ var PolymarketClient = class {
1304
1302
  }
1305
1303
  }
1306
1304
  async getClosedPositions(user, params) {
1307
- try {
1308
- const allPositions = await this.getPositions(user, params);
1309
- const closedPositions = allPositions.filter((pos) => {
1310
- const size = parseFloat(pos.size || pos.amount || "0");
1311
- const isClosed = size === 0 || pos.closed === true || pos.status === "closed";
1312
- return isClosed;
1313
- });
1314
- return closedPositions;
1315
- } catch (error) {
1316
- this.logger.error("Error fetching closed positions", error);
1317
- return [];
1318
- }
1305
+ this.logger.warning("getClosedPositions not directly supported, returning empty");
1306
+ return [];
1319
1307
  }
1320
1308
  async getTradedMarketsCount(user) {
1321
1309
  try {
@@ -1338,21 +1326,8 @@ var PolymarketClient = class {
1338
1326
  }
1339
1327
  // Account methods
1340
1328
  async getPosition(ticker) {
1341
- try {
1342
- if (!this.userAddress) {
1343
- this.logger.error("getPosition requires userAddress to be set");
1344
- return null;
1345
- }
1346
- const positions = await this.getPositions(this.userAddress);
1347
- const matchingPosition = positions.find((pos) => {
1348
- const marketMatch = pos.market === ticker || pos.slug === ticker || pos.condition_id === ticker || pos.asset_id === ticker || pos.token_id === ticker;
1349
- return marketMatch && parseFloat(pos.size || pos.amount || "0") > 0;
1350
- });
1351
- return matchingPosition || null;
1352
- } catch (error) {
1353
- this.logger.error(`Error fetching position for ${ticker}`, error);
1354
- return null;
1355
- }
1329
+ this.logger.warning("getPosition(ticker) not implemented. Use getPositions(user) instead.");
1330
+ return null;
1356
1331
  }
1357
1332
  };
1358
1333
  var HyperliquidClient = class {
@@ -1720,11 +1695,11 @@ async function runStrategy(strategy, config, context) {
1720
1695
  }
1721
1696
  return false;
1722
1697
  } catch (error) {
1723
- config.logger.error(`Error executing strategy: ${error.message}`, error);
1698
+ config.logger?.error(`Error executing strategy: ${error.message}`, error);
1724
1699
  throw error;
1725
1700
  }
1726
1701
  }
1727
- function createStrategyEngine(strategy, config, context) {
1702
+ function createStrategyEngine(strategy, context, config) {
1728
1703
  return new StrategyEngine(strategy, config, context);
1729
1704
  }
1730
1705
  var StrategyEngine = class {
@@ -1732,6 +1707,7 @@ var StrategyEngine = class {
1732
1707
  this.strategy = strategy;
1733
1708
  this.config = config;
1734
1709
  this.context = context;
1710
+ this.config.logger = this.config.logger || createConsoleLogger();
1735
1711
  }
1736
1712
  intervalId = null;
1737
1713
  isRunning = false;
@@ -1739,6 +1715,9 @@ var StrategyEngine = class {
1739
1715
  * Start the engine with the configured tick interval
1740
1716
  */
1741
1717
  start() {
1718
+ if (!this.config.logger) {
1719
+ this.config.logger = createConsoleLogger();
1720
+ }
1742
1721
  if (this.isRunning) {
1743
1722
  this.config.logger.warning("Strategy engine is already running");
1744
1723
  return;
@@ -1755,6 +1734,9 @@ var StrategyEngine = class {
1755
1734
  * Stop the engine
1756
1735
  */
1757
1736
  stop() {
1737
+ if (!this.config.logger) {
1738
+ this.config.logger = createConsoleLogger();
1739
+ }
1758
1740
  if (!this.isRunning) {
1759
1741
  return;
1760
1742
  }
@@ -1773,6 +1755,9 @@ var StrategyEngine = class {
1773
1755
  }
1774
1756
  async tick() {
1775
1757
  const tickStart = Date.now();
1758
+ if (!this.config.logger) {
1759
+ this.config.logger = createConsoleLogger();
1760
+ }
1776
1761
  this.config.logger.debug(`Tick start: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1777
1762
  try {
1778
1763
  await runStrategy(this.strategy, this.config, this.context);
@@ -1803,7 +1788,8 @@ function validateStrategy(strategy) {
1803
1788
  );
1804
1789
  }
1805
1790
  }
1806
- var EventRunner = class {
1791
+ var POLYMARKET_RTDS_URL = "wss://ws-live-data.polymarket.com";
1792
+ var PolymarketEventRunner = class {
1807
1793
  constructor(strategy, config, context) {
1808
1794
  this.strategy = strategy;
1809
1795
  this.config = config;
@@ -1817,31 +1803,34 @@ var EventRunner = class {
1817
1803
  this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
1818
1804
  this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
1819
1805
  this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
1806
+ this.pingIntervalMs = config.pingIntervalMs ?? 5e3;
1820
1807
  }
1821
1808
  ws = null;
1822
1809
  isRunning = false;
1823
1810
  reconnectAttempts = 0;
1824
1811
  reconnectTimeout = null;
1812
+ pingInterval = null;
1825
1813
  isIntentionallyClosed = false;
1826
1814
  // Default reconnection settings
1827
1815
  maxReconnectAttempts;
1828
1816
  baseReconnectDelay;
1829
1817
  maxReconnectDelay;
1818
+ pingIntervalMs;
1830
1819
  /**
1831
1820
  * Start listening to events
1832
- * Connects to the WebSocket and subscribes to events
1821
+ * Connects to Polymarket RTDS and subscribes to events
1833
1822
  */
1834
1823
  start() {
1835
1824
  if (this.isRunning) {
1836
- this.config.logger.warning("Event runner is already running");
1825
+ this.config.logger.warning("Polymarket event runner is already running");
1837
1826
  return;
1838
1827
  }
1839
1828
  this.isRunning = true;
1840
1829
  this.isIntentionallyClosed = false;
1841
1830
  this.reconnectAttempts = 0;
1842
1831
  const strategyName = this.config.strategyId || "unnamed-strategy";
1843
- this.config.logger.info(`Starting event runner for strategy ${strategyName}`);
1844
- this.config.logger.info(`Connecting to event source: ${this.config.eventSourceUrl}`);
1832
+ this.config.logger.info(`Starting Polymarket event runner for strategy ${strategyName}`);
1833
+ this.config.logger.info(`Connecting to Polymarket RTDS: ${POLYMARKET_RTDS_URL}`);
1845
1834
  this.connect();
1846
1835
  }
1847
1836
  /**
@@ -1853,6 +1842,10 @@ var EventRunner = class {
1853
1842
  }
1854
1843
  this.isRunning = false;
1855
1844
  this.isIntentionallyClosed = true;
1845
+ if (this.pingInterval) {
1846
+ clearInterval(this.pingInterval);
1847
+ this.pingInterval = null;
1848
+ }
1856
1849
  if (this.reconnectTimeout) {
1857
1850
  clearTimeout(this.reconnectTimeout);
1858
1851
  this.reconnectTimeout = null;
@@ -1865,7 +1858,7 @@ var EventRunner = class {
1865
1858
  }
1866
1859
  this.ws = null;
1867
1860
  }
1868
- this.config.logger.info("Event runner stopped");
1861
+ this.config.logger.info("Polymarket event runner stopped");
1869
1862
  }
1870
1863
  /**
1871
1864
  * Check if the runner is currently active
@@ -1885,39 +1878,65 @@ var EventRunner = class {
1885
1878
  };
1886
1879
  }
1887
1880
  /**
1888
- * Connect to the WebSocket event source
1881
+ * Connect to Polymarket RTDS WebSocket
1889
1882
  */
1890
1883
  connect() {
1891
1884
  if (this.isIntentionallyClosed) {
1892
1885
  return;
1893
1886
  }
1894
1887
  try {
1895
- this.ws = new WebSocket(this.config.eventSourceUrl);
1888
+ this.ws = new WebSocket(POLYMARKET_RTDS_URL);
1896
1889
  this.ws.on("open", () => {
1897
- this.config.logger.info("Connected to event source");
1890
+ this.config.logger.info("Connected to Polymarket RTDS");
1898
1891
  this.reconnectAttempts = 0;
1892
+ this.startPingInterval();
1899
1893
  this.subscribeToEvents();
1900
1894
  });
1901
1895
  this.ws.on("message", (data) => {
1902
1896
  this.handleMessage(data);
1903
1897
  });
1904
1898
  this.ws.on("error", (error) => {
1905
- this.config.logger.error(`WebSocket error: ${error.message}`, error);
1899
+ this.config.logger.error(`Polymarket RTDS WebSocket error: ${error.message}`, error);
1906
1900
  });
1907
1901
  this.ws.on("close", (code, reason) => {
1908
1902
  const reasonStr = reason.toString() || "none";
1909
- this.config.logger.info(`WebSocket closed (code: ${code}, reason: ${reasonStr})`);
1903
+ this.config.logger.info(`Polymarket RTDS WebSocket closed (code: ${code}, reason: ${reasonStr})`);
1904
+ if (this.pingInterval) {
1905
+ clearInterval(this.pingInterval);
1906
+ this.pingInterval = null;
1907
+ }
1910
1908
  if (!this.isIntentionallyClosed && this.isRunning) {
1911
1909
  this.attemptReconnect();
1912
1910
  }
1913
1911
  });
1912
+ this.ws.on("pong", () => {
1913
+ this.config.logger.debug("Received PONG from Polymarket RTDS");
1914
+ });
1914
1915
  } catch (error) {
1915
- this.config.logger.error(`Failed to connect to WebSocket: ${error.message}`, error);
1916
+ this.config.logger.error(`Failed to connect to Polymarket RTDS: ${error.message}`, error);
1916
1917
  if (this.isRunning && !this.isIntentionallyClosed) {
1917
1918
  this.attemptReconnect();
1918
1919
  }
1919
1920
  }
1920
1921
  }
1922
+ /**
1923
+ * Start ping interval to maintain connection (Polymarket recommends 5 seconds)
1924
+ */
1925
+ startPingInterval() {
1926
+ if (this.pingInterval) {
1927
+ clearInterval(this.pingInterval);
1928
+ }
1929
+ this.pingInterval = setInterval(() => {
1930
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1931
+ try {
1932
+ this.ws.send("PING");
1933
+ this.config.logger.debug("Sent PING to Polymarket RTDS");
1934
+ } catch (error) {
1935
+ this.config.logger.warning("Failed to send PING");
1936
+ }
1937
+ }
1938
+ }, this.pingIntervalMs);
1939
+ }
1921
1940
  /**
1922
1941
  * Attempt to reconnect with exponential backoff
1923
1942
  */
@@ -1940,6 +1959,165 @@ var EventRunner = class {
1940
1959
  this.connect();
1941
1960
  }, delay);
1942
1961
  }
1962
+ /**
1963
+ * Map strategy subscriptions to Polymarket RTDS subscriptions
1964
+ * Only MarketSubscription types are supported by Polymarket RTDS
1965
+ * WalletSubscription and OpportunitySubscription are Osiris-only features
1966
+ */
1967
+ mapToRTDSSubscriptions() {
1968
+ if (!this.strategy.subscriptions) {
1969
+ return [];
1970
+ }
1971
+ const rtdsSubscriptions = [];
1972
+ for (const sub of this.strategy.subscriptions) {
1973
+ let rtdsSub = null;
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)) {
2027
+ const topic = this.mapEventTypeToTopic(sub.type);
2028
+ rtdsSub = {
2029
+ topic,
2030
+ type: this.getMessageTypeForTopic(topic)
2031
+ };
2032
+ } else if (isCustomSubscription(sub)) {
2033
+ const validRTDSTopics = [
2034
+ "crypto_prices",
2035
+ "comments",
2036
+ "activity",
2037
+ "orders",
2038
+ "trades",
2039
+ "profile"
2040
+ ];
2041
+ if (validRTDSTopics.includes(sub.topic)) {
2042
+ const topic = sub.topic;
2043
+ rtdsSub = {
2044
+ topic,
2045
+ type: this.getMessageTypeForTopic(topic)
2046
+ };
2047
+ } else {
2048
+ this.config.logger.warning(
2049
+ `Custom subscription topic '${sub.topic}' is not a valid Polymarket RTDS topic. Valid topics: ${validRTDSTopics.join(", ")}`
2050
+ );
2051
+ continue;
2052
+ }
2053
+ } else {
2054
+ this.config.logger.warning(
2055
+ `Subscription type '${sub.type}' is not supported by Polymarket RTDS. Osiris-only subscription types (wallet, opportunity) should use eventSource: 'osiris'.`
2056
+ );
2057
+ continue;
2058
+ }
2059
+ if (!rtdsSub) continue;
2060
+ if (this.config.clobAuth && ["orders", "trades"].includes(rtdsSub.topic)) {
2061
+ rtdsSub.clob_auth = this.config.clobAuth;
2062
+ }
2063
+ if (this.config.walletAddress && ["activity", "profile"].includes(rtdsSub.topic)) {
2064
+ rtdsSub.gamma_auth = {
2065
+ address: this.config.walletAddress
2066
+ };
2067
+ }
2068
+ rtdsSubscriptions.push(rtdsSub);
2069
+ }
2070
+ return rtdsSubscriptions;
2071
+ }
2072
+ /**
2073
+ * Get the message type for a given RTDS topic
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.
2078
+ */
2079
+ getMessageTypeForTopic(topic) {
2080
+ switch (topic) {
2081
+ case "crypto_prices":
2082
+ return "update";
2083
+ case "comments":
2084
+ return "comment_created";
2085
+ case "trades":
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";
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
+ );
2094
+ return "order_placed";
2095
+ case "activity":
2096
+ return "trade";
2097
+ case "profile":
2098
+ return "update";
2099
+ default:
2100
+ return "update";
2101
+ }
2102
+ }
2103
+ /**
2104
+ * Map EventType to Polymarket RTDS topic
2105
+ */
2106
+ mapEventTypeToTopic(eventType) {
2107
+ switch (eventType) {
2108
+ case "price":
2109
+ return "crypto_prices";
2110
+ case "trade":
2111
+ return "trades";
2112
+ case "orderbook":
2113
+ case "fill":
2114
+ return "orders";
2115
+ case "custom":
2116
+ return "activity";
2117
+ default:
2118
+ return "activity";
2119
+ }
2120
+ }
1943
2121
  /**
1944
2122
  * Subscribe to events based on strategy's subscriptions
1945
2123
  */
@@ -1952,14 +2130,15 @@ var EventRunner = class {
1952
2130
  this.config.logger.warning("No subscriptions defined for strategy");
1953
2131
  return;
1954
2132
  }
2133
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions();
1955
2134
  const subscribeMessage = {
1956
2135
  action: "subscribe",
1957
- subscriptions: this.strategy.subscriptions
2136
+ subscriptions: rtdsSubscriptions
1958
2137
  };
1959
2138
  try {
1960
2139
  this.ws.send(JSON.stringify(subscribeMessage));
1961
2140
  this.config.logger.info(
1962
- `Subscribed to ${this.strategy.subscriptions.length} event(s): ` + this.strategy.subscriptions.map((s) => `${s.type}${s.market ? `@${s.market}` : ""}`).join(", ")
2141
+ `Subscribed to ${rtdsSubscriptions.length} Polymarket RTDS topic(s): ` + rtdsSubscriptions.map((s) => s.topic).join(", ")
1963
2142
  );
1964
2143
  } catch (error) {
1965
2144
  this.config.logger.error(`Failed to send subscription: ${error.message}`, error);
@@ -1975,9 +2154,10 @@ var EventRunner = class {
1975
2154
  if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
1976
2155
  return;
1977
2156
  }
2157
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions();
1978
2158
  const unsubscribeMessage = {
1979
2159
  action: "unsubscribe",
1980
- subscriptions: this.strategy.subscriptions
2160
+ subscriptions: rtdsSubscriptions
1981
2161
  };
1982
2162
  try {
1983
2163
  this.ws.send(JSON.stringify(unsubscribeMessage));
@@ -1990,7 +2170,11 @@ var EventRunner = class {
1990
2170
  handleMessage(data) {
1991
2171
  try {
1992
2172
  const rawData = typeof data === "string" ? data : data.toString();
1993
- if (rawData === "PING" || rawData === "PONG" || rawData.trim() === "") {
2173
+ if (rawData === "PING") {
2174
+ this.ws?.send("PONG");
2175
+ return;
2176
+ }
2177
+ if (rawData === "PONG" || rawData.trim() === "") {
1994
2178
  return;
1995
2179
  }
1996
2180
  let message;
@@ -2000,20 +2184,98 @@ var EventRunner = class {
2000
2184
  this.config.logger.warning(`Received non-JSON message: ${rawData.slice(0, 100)}`);
2001
2185
  return;
2002
2186
  }
2003
- if (!this.isValidEvent(message)) {
2187
+ if (!this.isValidRTDSMessage(message)) {
2004
2188
  this.config.logger.debug(`Received non-event message: ${JSON.stringify(message).slice(0, 200)}`);
2005
2189
  return;
2006
2190
  }
2007
- this.dispatchEvent(message);
2191
+ const strategyEvent = this.convertToStrategyEvent(message);
2192
+ this.dispatchEvent(strategyEvent);
2008
2193
  } catch (error) {
2009
2194
  this.config.logger.error(`Error handling message: ${error.message}`, error);
2010
2195
  }
2011
2196
  }
2012
2197
  /**
2013
- * Validate that a message is a valid StrategyEvent
2198
+ * Validate that a message is a valid Polymarket RTDS message
2014
2199
  */
2015
- isValidEvent(message) {
2016
- return message && typeof message === "object" && typeof message.type === "string" && typeof message.timestamp === "number" && typeof message.data === "object";
2200
+ isValidRTDSMessage(message) {
2201
+ return message && typeof message === "object" && typeof message.topic === "string" && typeof message.type === "string" && typeof message.timestamp === "number" && typeof message.payload === "object";
2202
+ }
2203
+ /**
2204
+ * Convert Polymarket RTDS message to StrategyEvent
2205
+ */
2206
+ convertToStrategyEvent(message) {
2207
+ const eventType = this.mapTopicToEventType(message.topic);
2208
+ const eventData = this.extractEventData(message);
2209
+ return {
2210
+ type: eventType,
2211
+ timestamp: message.timestamp,
2212
+ market: eventData.market,
2213
+ data: eventData
2214
+ };
2215
+ }
2216
+ /**
2217
+ * Map Polymarket RTDS topic to EventType
2218
+ */
2219
+ mapTopicToEventType(topic) {
2220
+ switch (topic) {
2221
+ case "crypto_prices":
2222
+ return "price";
2223
+ case "trades":
2224
+ return "trade";
2225
+ case "orders":
2226
+ return "orderbook";
2227
+ case "activity":
2228
+ case "comments":
2229
+ case "profile":
2230
+ default:
2231
+ return "custom";
2232
+ }
2233
+ }
2234
+ /**
2235
+ * Extract event data from Polymarket RTDS payload
2236
+ */
2237
+ extractEventData(message) {
2238
+ const payload = message.payload;
2239
+ const data = {
2240
+ raw: {
2241
+ ...payload,
2242
+ topic: message.topic,
2243
+ // Include topic for easier filtering
2244
+ type: message.type
2245
+ // Include message type
2246
+ }
2247
+ };
2248
+ switch (message.topic) {
2249
+ case "crypto_prices":
2250
+ data.price = payload.price ?? payload.value;
2251
+ data.previousPrice = payload.previousPrice ?? payload.previous;
2252
+ data.changePercent = payload.changePercent ?? payload.change;
2253
+ break;
2254
+ case "trades":
2255
+ data.price = payload.price;
2256
+ data.size = payload.size ?? payload.amount;
2257
+ data.side = payload.side;
2258
+ data.tradeId = payload.tradeId ?? payload.id;
2259
+ break;
2260
+ case "orders":
2261
+ data.bestBid = payload.bestBid ?? payload.bid;
2262
+ data.bestAsk = payload.bestAsk ?? payload.ask;
2263
+ data.spread = payload.spread;
2264
+ data.orderId = payload.orderId ?? payload.id;
2265
+ break;
2266
+ case "comments":
2267
+ data.commentText = payload.text ?? payload.comment;
2268
+ data.author = payload.author ?? payload.user;
2269
+ data.market = payload.market ?? payload.slug;
2270
+ data.commentType = payload.type;
2271
+ data.reactions = payload.reactions;
2272
+ break;
2273
+ }
2274
+ if (payload.market) data.market = payload.market;
2275
+ if (payload.asset) data.market = payload.asset;
2276
+ if (payload.tokenId) data.market = payload.tokenId;
2277
+ if (payload.conditionId) data.market = payload.conditionId;
2278
+ return data;
2017
2279
  }
2018
2280
  /**
2019
2281
  * Dispatch an event to the strategy's onEvent handler
@@ -2037,8 +2299,1280 @@ var EventRunner = class {
2037
2299
  this.config.logger.debug(`Event handling completed in ${eventDuration}ms`);
2038
2300
  }
2039
2301
  };
2040
- function createEventRunner(strategy, config, context) {
2041
- return new EventRunner(strategy, config, context);
2302
+ function createPolymarketEventRunner(strategy, config, context) {
2303
+ return new PolymarketEventRunner(strategy, config, context);
2304
+ }
2305
+
2306
+ // src/engine/event-runner.ts
2307
+ var OsirisEventRunner = class {
2308
+ constructor(strategy, config, context) {
2309
+ this.strategy = strategy;
2310
+ this.config = config;
2311
+ this.context = context;
2312
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
2313
+ throw new Error("Strategy must have subscriptions defined for event-based execution");
2314
+ }
2315
+ if (typeof this.strategy.onEvent !== "function") {
2316
+ throw new Error("Strategy must implement onEvent method for event-based execution");
2317
+ }
2318
+ this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
2319
+ this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
2320
+ this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
2321
+ if (!config.eventSourceUrl) ;
2322
+ }
2323
+ ws = null;
2324
+ isRunning = false;
2325
+ reconnectAttempts = 0;
2326
+ reconnectTimeout = null;
2327
+ isIntentionallyClosed = false;
2328
+ // Default reconnection settings
2329
+ maxReconnectAttempts;
2330
+ baseReconnectDelay;
2331
+ maxReconnectDelay;
2332
+ /**
2333
+ * Start listening to events
2334
+ * Connects to the WebSocket and subscribes to events
2335
+ */
2336
+ start() {
2337
+ if (this.isRunning) {
2338
+ this.config.logger.warning("Event runner is already running");
2339
+ return;
2340
+ }
2341
+ this.isRunning = true;
2342
+ this.isIntentionallyClosed = false;
2343
+ this.reconnectAttempts = 0;
2344
+ const strategyName = this.config.strategyId || "unnamed-strategy";
2345
+ this.config.logger.info(`Starting event runner for strategy ${strategyName}`);
2346
+ this.config.logger.info(`Connecting to event source: ${this.config.eventSourceUrl}`);
2347
+ this.connect();
2348
+ }
2349
+ /**
2350
+ * Stop listening and disconnect from WebSocket
2351
+ */
2352
+ stop() {
2353
+ if (!this.isRunning) {
2354
+ return;
2355
+ }
2356
+ this.isRunning = false;
2357
+ this.isIntentionallyClosed = true;
2358
+ if (this.reconnectTimeout) {
2359
+ clearTimeout(this.reconnectTimeout);
2360
+ this.reconnectTimeout = null;
2361
+ }
2362
+ if (this.ws) {
2363
+ try {
2364
+ this.unsubscribeFromEvents();
2365
+ this.ws.close(1e3, "Intentional close");
2366
+ } catch (error) {
2367
+ }
2368
+ this.ws = null;
2369
+ }
2370
+ this.config.logger.info("Event runner stopped");
2371
+ }
2372
+ /**
2373
+ * Check if the runner is currently active
2374
+ */
2375
+ isActive() {
2376
+ return this.isRunning;
2377
+ }
2378
+ /**
2379
+ * Get current connection status
2380
+ */
2381
+ getStatus() {
2382
+ return {
2383
+ running: this.isRunning,
2384
+ connected: this.ws?.readyState === WebSocket.OPEN,
2385
+ reconnectAttempts: this.reconnectAttempts,
2386
+ subscriptionCount: this.strategy.subscriptions?.length ?? 0
2387
+ };
2388
+ }
2389
+ /**
2390
+ * Connect to the WebSocket event source
2391
+ */
2392
+ connect() {
2393
+ if (this.isIntentionallyClosed) {
2394
+ return;
2395
+ }
2396
+ try {
2397
+ if (!this.config.eventSourceUrl) {
2398
+ throw new Error("eventSourceUrl is required for OsirisEventRunner");
2399
+ }
2400
+ this.ws = new WebSocket(this.config.eventSourceUrl);
2401
+ this.ws.on("open", () => {
2402
+ this.config.logger.info("Connected to event source");
2403
+ this.reconnectAttempts = 0;
2404
+ this.subscribeToEvents();
2405
+ });
2406
+ this.ws.on("message", (data) => {
2407
+ this.handleMessage(data);
2408
+ });
2409
+ this.ws.on("error", (error) => {
2410
+ this.config.logger.error(`WebSocket error: ${error.message}`, error);
2411
+ });
2412
+ this.ws.on("close", (code, reason) => {
2413
+ const reasonStr = reason.toString() || "none";
2414
+ this.config.logger.info(`WebSocket closed (code: ${code}, reason: ${reasonStr})`);
2415
+ if (!this.isIntentionallyClosed && this.isRunning) {
2416
+ this.attemptReconnect();
2417
+ }
2418
+ });
2419
+ } catch (error) {
2420
+ this.config.logger.error(`Failed to connect to WebSocket: ${error.message}`, error);
2421
+ if (this.isRunning && !this.isIntentionallyClosed) {
2422
+ this.attemptReconnect();
2423
+ }
2424
+ }
2425
+ }
2426
+ /**
2427
+ * Attempt to reconnect with exponential backoff
2428
+ */
2429
+ attemptReconnect() {
2430
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2431
+ this.config.logger.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached`);
2432
+ this.isRunning = false;
2433
+ return;
2434
+ }
2435
+ this.reconnectAttempts++;
2436
+ const delay = Math.min(
2437
+ this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1e3,
2438
+ this.maxReconnectDelay
2439
+ );
2440
+ this.config.logger.info(
2441
+ `Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
2442
+ );
2443
+ this.reconnectTimeout = setTimeout(() => {
2444
+ this.reconnectTimeout = null;
2445
+ this.connect();
2446
+ }, delay);
2447
+ }
2448
+ /**
2449
+ * Subscribe to events based on strategy's subscriptions
2450
+ */
2451
+ /**
2452
+ * Subscribe to events based on strategy's subscriptions
2453
+ * Maps SDK subscriptions to Osiris Pub/Sub protocol
2454
+ */
2455
+ subscribeToEvents() {
2456
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
2457
+ this.config.logger.warning("Cannot subscribe - WebSocket not connected");
2458
+ return;
2459
+ }
2460
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
2461
+ this.config.logger.warning("No subscriptions defined for strategy");
2462
+ return;
2463
+ }
2464
+ const topics = [];
2465
+ const filters = {};
2466
+ for (const sub of this.strategy.subscriptions) {
2467
+ const topic = this.mapSubscriptionToTopic(sub);
2468
+ if (topic) {
2469
+ topics.push(topic);
2470
+ if (sub.conditions) {
2471
+ filters[topic] = sub.conditions;
2472
+ }
2473
+ }
2474
+ }
2475
+ if (topics.length === 0) {
2476
+ this.config.logger.warning("No valid topics mapped from subscriptions");
2477
+ return;
2478
+ }
2479
+ const subscribeMessage = {
2480
+ action: "subscribe",
2481
+ topics,
2482
+ filters: Object.keys(filters).length > 0 ? filters : void 0
2483
+ };
2484
+ try {
2485
+ this.ws.send(JSON.stringify(subscribeMessage));
2486
+ this.config.logger.info(
2487
+ `Subscribed to ${topics.length} topics: ${topics.join(", ")}`
2488
+ );
2489
+ } catch (error) {
2490
+ this.config.logger.error(`Failed to send subscription: ${error.message}`, error);
2491
+ }
2492
+ }
2493
+ /**
2494
+ * Map EventSubscription to Osiris Pub/Sub topic string
2495
+ * Uses discriminated union to properly handle each subscription type
2496
+ */
2497
+ mapSubscriptionToTopic(sub) {
2498
+ switch (sub.type) {
2499
+ // Market subscriptions → market:{market}
2500
+ case "price":
2501
+ case "orderbook":
2502
+ case "trade":
2503
+ case "fill":
2504
+ return sub.market ? `market:${sub.market}` : null;
2505
+ // Wallet subscriptions → wallet:{wallet}
2506
+ case "wallet":
2507
+ return sub.wallet ? `wallet:${sub.wallet}` : null;
2508
+ // Opportunity subscriptions → opps:{filter}
2509
+ case "opportunity":
2510
+ return `opps:${sub.filter || "all"}`;
2511
+ // Custom subscriptions → use topic directly
2512
+ case "custom":
2513
+ return sub.topic || null;
2514
+ default:
2515
+ return null;
2516
+ }
2517
+ }
2518
+ /**
2519
+ * Unsubscribe from events before disconnecting
2520
+ */
2521
+ /**
2522
+ * Unsubscribe from events before disconnecting
2523
+ */
2524
+ unsubscribeFromEvents() {
2525
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
2526
+ return;
2527
+ }
2528
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
2529
+ return;
2530
+ }
2531
+ const topics = this.strategy.subscriptions.map((s) => this.mapSubscriptionToTopic(s)).filter((t) => t !== null);
2532
+ if (topics.length === 0) return;
2533
+ const unsubscribeMessage = {
2534
+ action: "unsubscribe",
2535
+ topics
2536
+ };
2537
+ try {
2538
+ this.ws.send(JSON.stringify(unsubscribeMessage));
2539
+ } catch (error) {
2540
+ }
2541
+ }
2542
+ /**
2543
+ * Handle incoming WebSocket messages
2544
+ */
2545
+ handleMessage(data) {
2546
+ try {
2547
+ const rawData = typeof data === "string" ? data : data.toString();
2548
+ if (rawData === "PING" || rawData === "PONG" || rawData.trim() === "") {
2549
+ return;
2550
+ }
2551
+ let message;
2552
+ try {
2553
+ message = JSON.parse(rawData);
2554
+ } catch (parseError) {
2555
+ this.config.logger.warning(`Received non-JSON message: ${rawData.slice(0, 100)}`);
2556
+ return;
2557
+ }
2558
+ if (message.type === "connected") {
2559
+ this.config.logger.info(`Osiris Pub/Sub: ${message.message}`);
2560
+ return;
2561
+ }
2562
+ if (message.type === "subscribe.result") {
2563
+ if (message.success) {
2564
+ this.config.logger.info(`Successfully subscribed to: ${message.subscribed.join(", ")}`);
2565
+ } else {
2566
+ this.config.logger.error(`Subscription failed: ${JSON.stringify(message.errors)}`);
2567
+ }
2568
+ return;
2569
+ }
2570
+ if (message.type === "error") {
2571
+ this.config.logger.error(`Osiris Pub/Sub Error: ${message.error}`);
2572
+ return;
2573
+ }
2574
+ if (message.type === "pong") {
2575
+ return;
2576
+ }
2577
+ const event = this.mapMessageToStrategyEvent(message);
2578
+ if (event) {
2579
+ this.dispatchEvent(event);
2580
+ } else {
2581
+ this.config.logger.debug(`Unhandled message type: ${message.type}`);
2582
+ }
2583
+ } catch (error) {
2584
+ this.config.logger.error(`Error handling message: ${error.message}`, error);
2585
+ }
2586
+ }
2587
+ /**
2588
+ * Map incoming Osiris message to StrategyEvent
2589
+ * Uses proper fields for each event type (market vs wallet)
2590
+ */
2591
+ mapMessageToStrategyEvent(message) {
2592
+ const timestamp = message.ts || Date.now();
2593
+ switch (message.type) {
2594
+ case "market.analysis":
2595
+ return {
2596
+ type: "price",
2597
+ // Treating analysis as 'price/market' update
2598
+ market: message.slug,
2599
+ timestamp,
2600
+ data: {
2601
+ ...message.data,
2602
+ raw: message
2603
+ }
2604
+ };
2605
+ case "wallet.analysis":
2606
+ return {
2607
+ type: "wallet",
2608
+ wallet: message.address,
2609
+ // Use wallet field, not market
2610
+ timestamp,
2611
+ data: {
2612
+ ...message.data,
2613
+ raw: message
2614
+ }
2615
+ };
2616
+ case "opportunity":
2617
+ return {
2618
+ type: "opportunity",
2619
+ market: message.slug,
2620
+ timestamp,
2621
+ data: {
2622
+ ...message.data,
2623
+ raw: message
2624
+ }
2625
+ };
2626
+ default:
2627
+ if (message.type && message.data) {
2628
+ return {
2629
+ type: "custom",
2630
+ market: message.slug || message.topic,
2631
+ timestamp,
2632
+ data: {
2633
+ ...message.data,
2634
+ raw: message
2635
+ }
2636
+ };
2637
+ }
2638
+ return null;
2639
+ }
2640
+ }
2641
+ /**
2642
+ * Validate that a message is a valid StrategyEvent
2643
+ */
2644
+ isValidEvent(message) {
2645
+ return message && typeof message === "object" && typeof message.type === "string";
2646
+ }
2647
+ /**
2648
+ * Dispatch an event to the strategy's onEvent handler
2649
+ */
2650
+ async dispatchEvent(event) {
2651
+ const eventStart = Date.now();
2652
+ const target = event.market || event.wallet || "";
2653
+ this.config.logger.debug(
2654
+ `Dispatching ${event.type} event${target ? ` for ${target}` : ""}`
2655
+ );
2656
+ try {
2657
+ if (this.strategy.onEvent) {
2658
+ await this.strategy.onEvent(event, this.context);
2659
+ }
2660
+ } catch (error) {
2661
+ this.config.logger.error(
2662
+ `Error in strategy onEvent handler: ${error.message}`,
2663
+ error
2664
+ );
2665
+ }
2666
+ const eventDuration = Date.now() - eventStart;
2667
+ this.config.logger.debug(`Event handling completed in ${eventDuration}ms`);
2668
+ }
2669
+ };
2670
+ function createEventRunner(strategy, config, context) {
2671
+ const hasPolymarketSubscription = strategy.subscriptions?.some(
2672
+ (sub) => sub.eventSource === "polymarket"
2673
+ );
2674
+ const hasOsirisSubscription = strategy.subscriptions?.some(
2675
+ (sub) => !sub.eventSource || sub.eventSource === "osiris"
2676
+ );
2677
+ if (hasPolymarketSubscription) {
2678
+ if (hasOsirisSubscription) {
2679
+ config.logger.warning(
2680
+ "Strategy has both Polymarket and generic Osiris subscriptions. Only Polymarket events will be processed by this runner. Mixed sources are not currently supported in a single runner."
2681
+ );
2682
+ }
2683
+ return createPolymarketEventRunner(strategy, {
2684
+ logger: config.logger,
2685
+ strategyId: config.strategyId,
2686
+ clobAuth: config.polymarket?.clobAuth,
2687
+ walletAddress: config.polymarket?.walletAddress,
2688
+ pingIntervalMs: config.polymarket?.pingIntervalMs,
2689
+ reconnect: config.reconnect
2690
+ }, context);
2691
+ }
2692
+ if (!config.eventSourceUrl) ;
2693
+ return new OsirisEventRunner(strategy, config, context);
2694
+ }
2695
+ var PolymarketRTDSService = class {
2696
+ client = null;
2697
+ config;
2698
+ isConnected = false;
2699
+ activeSubscriptions = [];
2700
+ constructor(config) {
2701
+ this.config = config;
2702
+ }
2703
+ /**
2704
+ * Connect to the Polymarket RTDS WebSocket
2705
+ */
2706
+ connect() {
2707
+ if (this.isConnected) {
2708
+ this.config.logger.warning("PolymarketRTDS: Already connected");
2709
+ return;
2710
+ }
2711
+ this.config.logger.info("PolymarketRTDS: Connecting...");
2712
+ this.client = new RealTimeDataClient({
2713
+ onConnect: (_client) => {
2714
+ this.isConnected = true;
2715
+ this.config.logger.info("PolymarketRTDS: Connected");
2716
+ this.config.onConnect?.();
2717
+ },
2718
+ onMessage: (_client, message) => {
2719
+ this.handleMessage(message);
2720
+ },
2721
+ onStatusChange: (status) => {
2722
+ if (status === ConnectionStatus.DISCONNECTED) {
2723
+ this.isConnected = false;
2724
+ this.config.logger.info("PolymarketRTDS: Disconnected");
2725
+ this.config.onDisconnect?.();
2726
+ } else if (status === ConnectionStatus.CONNECTING) {
2727
+ this.config.logger.info("PolymarketRTDS: Reconnecting...");
2728
+ }
2729
+ },
2730
+ pingInterval: this.config.pingIntervalMs ?? 5e3,
2731
+ autoReconnect: this.config.autoReconnect ?? true
2732
+ });
2733
+ this.client.connect();
2734
+ }
2735
+ /**
2736
+ * Disconnect from the Polymarket RTDS WebSocket
2737
+ */
2738
+ disconnect() {
2739
+ if (!this.client) {
2740
+ return;
2741
+ }
2742
+ this.config.logger.info("PolymarketRTDS: Disconnecting...");
2743
+ this.client.disconnect();
2744
+ this.client = null;
2745
+ this.isConnected = false;
2746
+ this.activeSubscriptions = [];
2747
+ }
2748
+ /**
2749
+ * Check if connected
2750
+ */
2751
+ isActive() {
2752
+ return this.isConnected;
2753
+ }
2754
+ /**
2755
+ * Subscribe to events
2756
+ * Converts SDK EventSubscription types to RTDS Subscription format
2757
+ */
2758
+ subscribe(subscriptions) {
2759
+ if (!this.client) {
2760
+ this.config.logger.warning("PolymarketRTDS: Cannot subscribe - not connected");
2761
+ return;
2762
+ }
2763
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions(subscriptions);
2764
+ if (rtdsSubscriptions.length === 0) {
2765
+ this.config.logger.warning("PolymarketRTDS: No valid subscriptions to add");
2766
+ return;
2767
+ }
2768
+ const msg = { subscriptions: rtdsSubscriptions };
2769
+ this.client.subscribe(msg);
2770
+ this.activeSubscriptions.push(...rtdsSubscriptions);
2771
+ this.config.logger.info(
2772
+ `PolymarketRTDS: Subscribed to ${rtdsSubscriptions.length} topic(s): ` + rtdsSubscriptions.map((s) => `${s.topic}:${s.type}`).join(", ")
2773
+ );
2774
+ }
2775
+ /**
2776
+ * Unsubscribe from events
2777
+ */
2778
+ unsubscribe(subscriptions) {
2779
+ if (!this.client) {
2780
+ return;
2781
+ }
2782
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions(subscriptions);
2783
+ if (rtdsSubscriptions.length === 0) {
2784
+ return;
2785
+ }
2786
+ const msg = { subscriptions: rtdsSubscriptions };
2787
+ this.client.unsubscribe(msg);
2788
+ for (const unsub of rtdsSubscriptions) {
2789
+ const idx = this.activeSubscriptions.findIndex(
2790
+ (s) => s.topic === unsub.topic && s.type === unsub.type
2791
+ );
2792
+ if (idx >= 0) {
2793
+ this.activeSubscriptions.splice(idx, 1);
2794
+ }
2795
+ }
2796
+ this.config.logger.info(
2797
+ `PolymarketRTDS: Unsubscribed from ${rtdsSubscriptions.length} topic(s)`
2798
+ );
2799
+ }
2800
+ /**
2801
+ * Map SDK EventSubscription to RTDS Subscription format
2802
+ */
2803
+ mapToRTDSSubscriptions(subscriptions) {
2804
+ const result = [];
2805
+ for (const sub of subscriptions) {
2806
+ const rtdsSub = this.mapSubscription(sub);
2807
+ if (rtdsSub) {
2808
+ result.push(rtdsSub);
2809
+ }
2810
+ }
2811
+ return result;
2812
+ }
2813
+ /**
2814
+ * Map a single SDK EventSubscription to RTDS Subscription
2815
+ */
2816
+ mapSubscription(sub) {
2817
+ if (isClobMarketSubscription(sub)) {
2818
+ return this.mapClobMarketSubscription(sub);
2819
+ }
2820
+ if (isClobUserSubscription(sub)) {
2821
+ return this.mapClobUserSubscription(sub);
2822
+ }
2823
+ if (isActivitySubscription(sub)) {
2824
+ return this.mapActivitySubscription(sub);
2825
+ }
2826
+ if (isCommentsSubscription(sub)) {
2827
+ return this.mapCommentsSubscription(sub);
2828
+ }
2829
+ if (isCryptoPricesSubscription(sub)) {
2830
+ return this.mapCryptoPricesSubscription(sub);
2831
+ }
2832
+ if (isRfqSubscription(sub)) {
2833
+ return this.mapRfqSubscription(sub);
2834
+ }
2835
+ if (isMarketSubscription(sub)) {
2836
+ return this.mapMarketSubscription(sub);
2837
+ }
2838
+ this.config.logger.warning(
2839
+ `PolymarketRTDS: Unsupported subscription type '${sub.type}'`
2840
+ );
2841
+ return null;
2842
+ }
2843
+ mapClobMarketSubscription(sub) {
2844
+ return {
2845
+ topic: "clob_market",
2846
+ type: sub.messageType || "*",
2847
+ filters: JSON.stringify([sub.marketId])
2848
+ };
2849
+ }
2850
+ mapClobUserSubscription(sub) {
2851
+ if (!this.config.clobAuth) {
2852
+ this.config.logger.warning(
2853
+ "PolymarketRTDS: clob_user subscription requires clobAuth credentials"
2854
+ );
2855
+ }
2856
+ return {
2857
+ topic: "clob_user",
2858
+ type: sub.messageType || "*",
2859
+ clob_auth: this.config.clobAuth
2860
+ };
2861
+ }
2862
+ mapActivitySubscription(sub) {
2863
+ let filters;
2864
+ if (sub.eventSlug) {
2865
+ filters = JSON.stringify({ event_slug: sub.eventSlug });
2866
+ } else if (sub.marketSlug) {
2867
+ filters = JSON.stringify({ market_slug: sub.marketSlug });
2868
+ }
2869
+ return {
2870
+ topic: "activity",
2871
+ type: sub.messageType || "*",
2872
+ filters
2873
+ };
2874
+ }
2875
+ mapCommentsSubscription(sub) {
2876
+ let filters;
2877
+ if (sub.parentEntityId && sub.parentEntityType) {
2878
+ filters = JSON.stringify({
2879
+ parentEntityID: sub.parentEntityId,
2880
+ parentEntityType: sub.parentEntityType
2881
+ });
2882
+ }
2883
+ return {
2884
+ topic: "comments",
2885
+ type: sub.messageType || "*",
2886
+ filters
2887
+ };
2888
+ }
2889
+ mapCryptoPricesSubscription(sub) {
2890
+ let filters;
2891
+ if (sub.symbol) {
2892
+ filters = JSON.stringify({ symbol: sub.symbol.toLowerCase() });
2893
+ }
2894
+ return {
2895
+ topic: "crypto_prices",
2896
+ type: "update",
2897
+ filters
2898
+ };
2899
+ }
2900
+ mapRfqSubscription(sub) {
2901
+ return {
2902
+ topic: "rfq",
2903
+ type: sub.messageType || "*"
2904
+ };
2905
+ }
2906
+ mapMarketSubscription(sub) {
2907
+ switch (sub.type) {
2908
+ case "price":
2909
+ return {
2910
+ topic: "clob_market",
2911
+ type: "price_change",
2912
+ filters: JSON.stringify([sub.market])
2913
+ };
2914
+ case "orderbook":
2915
+ return {
2916
+ topic: "clob_market",
2917
+ type: "agg_orderbook",
2918
+ filters: JSON.stringify([sub.market])
2919
+ };
2920
+ case "trade":
2921
+ return {
2922
+ topic: "activity",
2923
+ type: "trades",
2924
+ filters: JSON.stringify({ market_slug: sub.market })
2925
+ };
2926
+ case "fill":
2927
+ if (!this.config.clobAuth) {
2928
+ this.config.logger.warning(
2929
+ "PolymarketRTDS: fill subscription requires clobAuth credentials"
2930
+ );
2931
+ }
2932
+ return {
2933
+ topic: "clob_user",
2934
+ type: "order",
2935
+ clob_auth: this.config.clobAuth
2936
+ };
2937
+ default:
2938
+ return {
2939
+ topic: "activity",
2940
+ type: "trades",
2941
+ filters: JSON.stringify({ market_slug: sub.market })
2942
+ };
2943
+ }
2944
+ }
2945
+ /**
2946
+ * Handle incoming RTDS messages
2947
+ */
2948
+ handleMessage(message) {
2949
+ try {
2950
+ const event = this.convertToStrategyEvent(message);
2951
+ this.config.onEvent(event);
2952
+ } catch (error) {
2953
+ this.config.logger.error(
2954
+ `PolymarketRTDS: Error processing message - ${error.message}`
2955
+ );
2956
+ }
2957
+ }
2958
+ /**
2959
+ * Convert RTDS Message to StrategyEvent
2960
+ */
2961
+ convertToStrategyEvent(message) {
2962
+ const eventType = this.mapTopicToEventType(message.topic, message.type);
2963
+ const eventData = this.extractEventData(message);
2964
+ return {
2965
+ type: eventType,
2966
+ timestamp: message.timestamp || Date.now(),
2967
+ market: eventData.market,
2968
+ data: eventData
2969
+ };
2970
+ }
2971
+ /**
2972
+ * Map RTDS topic/type to SDK EventType
2973
+ */
2974
+ mapTopicToEventType(topic, type) {
2975
+ switch (topic) {
2976
+ case "crypto_prices":
2977
+ return "price";
2978
+ case "clob_market":
2979
+ switch (type) {
2980
+ case "price_change":
2981
+ case "last_trade_price":
2982
+ return "price";
2983
+ case "agg_orderbook":
2984
+ return "orderbook";
2985
+ default:
2986
+ return "custom";
2987
+ }
2988
+ case "activity":
2989
+ return "trade";
2990
+ case "clob_user":
2991
+ return type === "trade" ? "trade" : "fill";
2992
+ default:
2993
+ return "custom";
2994
+ }
2995
+ }
2996
+ /**
2997
+ * Extract event data from RTDS message payload
2998
+ */
2999
+ extractEventData(message) {
3000
+ const payload = message.payload;
3001
+ const data = {
3002
+ raw: {
3003
+ topic: message.topic,
3004
+ type: message.type,
3005
+ ...payload
3006
+ }
3007
+ };
3008
+ switch (message.topic) {
3009
+ case "crypto_prices":
3010
+ data.price = payload.value;
3011
+ data.market = payload.symbol;
3012
+ break;
3013
+ case "clob_market":
3014
+ if (message.type === "price_change") {
3015
+ data.market = payload.market;
3016
+ if (payload.price_changes?.length > 0) {
3017
+ const change = payload.price_changes[0];
3018
+ data.bestBid = parseFloat(change.best_bid);
3019
+ data.bestAsk = parseFloat(change.best_ask);
3020
+ data.price = parseFloat(change.price);
3021
+ }
3022
+ } else if (message.type === "agg_orderbook") {
3023
+ data.market = payload.market;
3024
+ data.bids = payload.bids;
3025
+ data.asks = payload.asks;
3026
+ if (payload.bids?.length > 0) {
3027
+ data.bestBid = parseFloat(payload.bids[0].price);
3028
+ }
3029
+ if (payload.asks?.length > 0) {
3030
+ data.bestAsk = parseFloat(payload.asks[0].price);
3031
+ }
3032
+ } else if (message.type === "last_trade_price") {
3033
+ data.market = payload.market;
3034
+ data.price = parseFloat(payload.price);
3035
+ data.side = payload.side;
3036
+ data.size = parseFloat(payload.size);
3037
+ }
3038
+ break;
3039
+ case "activity":
3040
+ data.market = payload.slug || payload.conditionId;
3041
+ data.price = payload.price;
3042
+ data.size = payload.size;
3043
+ data.side = payload.side?.toLowerCase();
3044
+ data.tradeId = payload.transactionHash;
3045
+ break;
3046
+ case "clob_user":
3047
+ data.market = payload.market;
3048
+ data.orderId = payload.id;
3049
+ data.price = parseFloat(payload.price);
3050
+ data.size = parseFloat(payload.size || payload.original_size);
3051
+ data.side = payload.side?.toLowerCase();
3052
+ break;
3053
+ case "rfq":
3054
+ data.market = payload.market;
3055
+ data.price = payload.price;
3056
+ data.size = payload.sizeIn || payload.sizeOut;
3057
+ data.side = payload.side?.toLowerCase();
3058
+ break;
3059
+ }
3060
+ return data;
3061
+ }
3062
+ };
3063
+ function createPolymarketRTDSService(config) {
3064
+ return new PolymarketRTDSService(config);
3065
+ }
3066
+ var DEFAULT_OSIRIS_RTDS_URL = "wss://rtds.osirislabs.xyz";
3067
+ var OsirisRTDSService = class {
3068
+ ws = null;
3069
+ config;
3070
+ isConnected = false;
3071
+ reconnectAttempts = 0;
3072
+ reconnectTimeout = null;
3073
+ pingInterval = null;
3074
+ isIntentionallyClosed = false;
3075
+ activeTopics = /* @__PURE__ */ new Set();
3076
+ // Reconnection settings
3077
+ maxReconnectAttempts;
3078
+ baseReconnectDelay;
3079
+ maxReconnectDelay;
3080
+ pingIntervalMs;
3081
+ url;
3082
+ constructor(config) {
3083
+ this.config = config;
3084
+ this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
3085
+ this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
3086
+ this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
3087
+ this.pingIntervalMs = config.pingIntervalMs ?? 3e4;
3088
+ this.url = config.url || DEFAULT_OSIRIS_RTDS_URL;
3089
+ }
3090
+ /**
3091
+ * Connect to the Osiris Pub/Sub WebSocket
3092
+ */
3093
+ connect() {
3094
+ if (this.isConnected) {
3095
+ this.config.logger.warning("OsirisRTDS: Already connected");
3096
+ return;
3097
+ }
3098
+ this.isIntentionallyClosed = false;
3099
+ this.config.logger.info(`OsirisRTDS: Connecting to ${this.url}...`);
3100
+ try {
3101
+ this.ws = new WebSocket(this.url);
3102
+ this.ws.on("open", () => {
3103
+ this.isConnected = true;
3104
+ this.reconnectAttempts = 0;
3105
+ this.config.logger.info("OsirisRTDS: Connected");
3106
+ this.startPingInterval();
3107
+ this.config.onConnect?.();
3108
+ });
3109
+ this.ws.on("message", (data) => {
3110
+ this.handleMessage(data);
3111
+ });
3112
+ this.ws.on("error", (error) => {
3113
+ this.config.logger.error(`OsirisRTDS: Error - ${error.message}`);
3114
+ this.config.onError?.(error);
3115
+ });
3116
+ this.ws.on("close", (code, reason) => {
3117
+ this.isConnected = false;
3118
+ this.stopPingInterval();
3119
+ const reasonStr = reason.toString() || "none";
3120
+ this.config.logger.info(`OsirisRTDS: Disconnected (code: ${code}, reason: ${reasonStr})`);
3121
+ this.config.onDisconnect?.();
3122
+ if (!this.isIntentionallyClosed) {
3123
+ this.attemptReconnect();
3124
+ }
3125
+ });
3126
+ } catch (error) {
3127
+ this.config.logger.error(`OsirisRTDS: Connection failed - ${error.message}`);
3128
+ if (!this.isIntentionallyClosed) {
3129
+ this.attemptReconnect();
3130
+ }
3131
+ }
3132
+ }
3133
+ /**
3134
+ * Disconnect from the Osiris Pub/Sub WebSocket
3135
+ */
3136
+ disconnect() {
3137
+ this.isIntentionallyClosed = true;
3138
+ this.stopPingInterval();
3139
+ if (this.reconnectTimeout) {
3140
+ clearTimeout(this.reconnectTimeout);
3141
+ this.reconnectTimeout = null;
3142
+ }
3143
+ if (this.ws) {
3144
+ this.config.logger.info("OsirisRTDS: Disconnecting...");
3145
+ try {
3146
+ if (this.activeTopics.size > 0) {
3147
+ this.unsubscribeFromTopics(Array.from(this.activeTopics));
3148
+ }
3149
+ this.ws.close(1e3, "Intentional close");
3150
+ } catch {
3151
+ }
3152
+ this.ws = null;
3153
+ }
3154
+ this.isConnected = false;
3155
+ this.activeTopics.clear();
3156
+ }
3157
+ /**
3158
+ * Check if connected
3159
+ */
3160
+ isActive() {
3161
+ return this.isConnected;
3162
+ }
3163
+ /**
3164
+ * Subscribe to events
3165
+ */
3166
+ subscribe(subscriptions) {
3167
+ if (!this.ws || !this.isConnected) {
3168
+ this.config.logger.warning("OsirisRTDS: Cannot subscribe - not connected");
3169
+ return;
3170
+ }
3171
+ const { topics, filters } = this.mapToOsirisSubscriptions(subscriptions);
3172
+ if (topics.length === 0) {
3173
+ this.config.logger.warning("OsirisRTDS: No valid topics to subscribe");
3174
+ return;
3175
+ }
3176
+ const message = {
3177
+ action: "subscribe",
3178
+ topics,
3179
+ ...Object.keys(filters).length > 0 ? { filters } : {}
3180
+ };
3181
+ try {
3182
+ this.ws.send(JSON.stringify(message));
3183
+ topics.forEach((t) => this.activeTopics.add(t));
3184
+ this.config.logger.info(`OsirisRTDS: Subscribing to ${topics.join(", ")}`);
3185
+ } catch (error) {
3186
+ this.config.logger.error(`OsirisRTDS: Failed to subscribe - ${error.message}`);
3187
+ }
3188
+ }
3189
+ /**
3190
+ * Unsubscribe from topics
3191
+ */
3192
+ unsubscribe(subscriptions) {
3193
+ if (!this.ws || !this.isConnected) {
3194
+ return;
3195
+ }
3196
+ const { topics } = this.mapToOsirisSubscriptions(subscriptions);
3197
+ this.unsubscribeFromTopics(topics);
3198
+ }
3199
+ /**
3200
+ * Unsubscribe from topics by name
3201
+ */
3202
+ unsubscribeFromTopics(topics) {
3203
+ if (topics.length === 0) return;
3204
+ const message = {
3205
+ action: "unsubscribe",
3206
+ topics
3207
+ };
3208
+ try {
3209
+ this.ws?.send(JSON.stringify(message));
3210
+ topics.forEach((t) => this.activeTopics.delete(t));
3211
+ this.config.logger.info(`OsirisRTDS: Unsubscribed from ${topics.join(", ")}`);
3212
+ } catch {
3213
+ }
3214
+ }
3215
+ /**
3216
+ * Send ping to keep connection alive
3217
+ */
3218
+ ping() {
3219
+ if (this.ws && this.isConnected) {
3220
+ try {
3221
+ this.ws.send(JSON.stringify({ action: "ping" }));
3222
+ } catch {
3223
+ }
3224
+ }
3225
+ }
3226
+ /**
3227
+ * Map SDK subscriptions to Osiris Pub/Sub format
3228
+ */
3229
+ mapToOsirisSubscriptions(subscriptions) {
3230
+ const topics = [];
3231
+ const filters = {};
3232
+ for (const sub of subscriptions) {
3233
+ const topic = this.mapSubscriptionToTopic(sub);
3234
+ if (topic) {
3235
+ topics.push(topic);
3236
+ if (sub.conditions) {
3237
+ filters[topic] = sub.conditions;
3238
+ }
3239
+ }
3240
+ }
3241
+ return { topics, filters };
3242
+ }
3243
+ /**
3244
+ * Map SDK subscription to Osiris topic string
3245
+ */
3246
+ mapSubscriptionToTopic(sub) {
3247
+ if (isMarketSubscription(sub)) {
3248
+ return sub.market ? `market:${sub.market}` : null;
3249
+ }
3250
+ if (isWalletSubscription(sub)) {
3251
+ return sub.wallet ? `wallet:${sub.wallet.toLowerCase()}` : null;
3252
+ }
3253
+ if (isOpportunitySubscription(sub)) {
3254
+ return `opps:${sub.filter || "all"}`;
3255
+ }
3256
+ if (isCustomSubscription(sub)) {
3257
+ return sub.topic || null;
3258
+ }
3259
+ return null;
3260
+ }
3261
+ /**
3262
+ * Start ping interval
3263
+ */
3264
+ startPingInterval() {
3265
+ this.stopPingInterval();
3266
+ this.pingInterval = setInterval(() => this.ping(), this.pingIntervalMs);
3267
+ }
3268
+ /**
3269
+ * Stop ping interval
3270
+ */
3271
+ stopPingInterval() {
3272
+ if (this.pingInterval) {
3273
+ clearInterval(this.pingInterval);
3274
+ this.pingInterval = null;
3275
+ }
3276
+ }
3277
+ /**
3278
+ * Attempt reconnection with exponential backoff
3279
+ */
3280
+ attemptReconnect() {
3281
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
3282
+ this.config.logger.error(
3283
+ `OsirisRTDS: Max reconnection attempts (${this.maxReconnectAttempts}) reached`
3284
+ );
3285
+ return;
3286
+ }
3287
+ this.reconnectAttempts++;
3288
+ const delay = Math.min(
3289
+ this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1e3,
3290
+ this.maxReconnectDelay
3291
+ );
3292
+ this.config.logger.info(
3293
+ `OsirisRTDS: Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
3294
+ );
3295
+ this.reconnectTimeout = setTimeout(() => {
3296
+ this.reconnectTimeout = null;
3297
+ this.connect();
3298
+ }, delay);
3299
+ }
3300
+ /**
3301
+ * Handle incoming WebSocket messages
3302
+ */
3303
+ handleMessage(data) {
3304
+ try {
3305
+ const rawData = typeof data === "string" ? data : data.toString();
3306
+ if (!rawData.trim()) return;
3307
+ let message;
3308
+ try {
3309
+ message = JSON.parse(rawData);
3310
+ } catch {
3311
+ this.config.logger.warning(`OsirisRTDS: Non-JSON message received`);
3312
+ return;
3313
+ }
3314
+ switch (message.type) {
3315
+ case "connected":
3316
+ this.config.logger.info(`OsirisRTDS: ${message.message}`);
3317
+ return;
3318
+ case "subscribe.result":
3319
+ if (message.success) {
3320
+ this.config.logger.info(
3321
+ `OsirisRTDS: Subscribed to ${message.subscribed?.join(", ")}`
3322
+ );
3323
+ } else {
3324
+ this.config.logger.error(
3325
+ `OsirisRTDS: Subscription failed - ${message.errors?.join(", ")}`
3326
+ );
3327
+ }
3328
+ return;
3329
+ case "error":
3330
+ this.config.logger.error(`OsirisRTDS: Server error - ${message.error}`);
3331
+ return;
3332
+ case "pong":
3333
+ return;
3334
+ }
3335
+ const event = this.convertToStrategyEvent(message);
3336
+ if (event) {
3337
+ this.config.onEvent(event);
3338
+ }
3339
+ } catch (error) {
3340
+ this.config.logger.error(`OsirisRTDS: Error processing message - ${error.message}`);
3341
+ }
3342
+ }
3343
+ /**
3344
+ * Convert Osiris message to StrategyEvent
3345
+ */
3346
+ convertToStrategyEvent(message) {
3347
+ const timestamp = message.ts || Date.now();
3348
+ switch (message.type) {
3349
+ case "market.analysis":
3350
+ return {
3351
+ type: "price",
3352
+ // Market analysis treated as price/market update
3353
+ market: message.slug,
3354
+ timestamp,
3355
+ data: {
3356
+ ...message.data,
3357
+ raw: message
3358
+ }
3359
+ };
3360
+ case "wallet.analysis":
3361
+ return {
3362
+ type: "wallet",
3363
+ wallet: message.address,
3364
+ timestamp,
3365
+ data: {
3366
+ ...message.data,
3367
+ raw: message
3368
+ }
3369
+ };
3370
+ case "opportunity":
3371
+ return {
3372
+ type: "opportunity",
3373
+ market: message.slug,
3374
+ timestamp,
3375
+ data: {
3376
+ ...message.data,
3377
+ raw: message
3378
+ }
3379
+ };
3380
+ default:
3381
+ return null;
3382
+ }
3383
+ }
3384
+ };
3385
+ function createOsirisRTDSService(config) {
3386
+ return new OsirisRTDSService(config);
3387
+ }
3388
+
3389
+ // src/rtds/unified-rtds.service.ts
3390
+ var UnifiedRTDSService = class {
3391
+ config;
3392
+ polymarketService = null;
3393
+ osirisService = null;
3394
+ polymarketConnected = false;
3395
+ osirisConnected = false;
3396
+ constructor(config) {
3397
+ this.config = config;
3398
+ this.initializeServices();
3399
+ }
3400
+ /**
3401
+ * Initialize the underlying services based on configuration
3402
+ */
3403
+ initializeServices() {
3404
+ if (this.config.polymarket?.enabled !== false) {
3405
+ this.polymarketService = new PolymarketRTDSService({
3406
+ logger: this.config.logger,
3407
+ onEvent: this.config.onEvent,
3408
+ onError: this.config.onError,
3409
+ onConnect: () => {
3410
+ this.polymarketConnected = true;
3411
+ this.checkAllConnected();
3412
+ },
3413
+ onDisconnect: () => {
3414
+ this.polymarketConnected = false;
3415
+ this.checkAnyDisconnected();
3416
+ },
3417
+ clobAuth: this.config.polymarket?.clobAuth
3418
+ });
3419
+ }
3420
+ if (this.config.osiris?.enabled !== false) {
3421
+ this.osirisService = new OsirisRTDSService({
3422
+ logger: this.config.logger,
3423
+ onEvent: this.config.onEvent,
3424
+ onError: this.config.onError,
3425
+ onConnect: () => {
3426
+ this.osirisConnected = true;
3427
+ this.checkAllConnected();
3428
+ },
3429
+ onDisconnect: () => {
3430
+ this.osirisConnected = false;
3431
+ this.checkAnyDisconnected();
3432
+ },
3433
+ url: this.config.osiris?.url || DEFAULT_OSIRIS_RTDS_URL,
3434
+ pingIntervalMs: this.config.osiris?.pingIntervalMs,
3435
+ reconnect: this.config.reconnect
3436
+ });
3437
+ }
3438
+ }
3439
+ /**
3440
+ * Connect to all enabled services
3441
+ */
3442
+ connect() {
3443
+ this.config.logger.info("UnifiedRTDS: Connecting to enabled services...");
3444
+ if (this.polymarketService) {
3445
+ this.polymarketService.connect();
3446
+ }
3447
+ if (this.osirisService) {
3448
+ this.osirisService.connect();
3449
+ }
3450
+ }
3451
+ /**
3452
+ * Disconnect from all services
3453
+ */
3454
+ disconnect() {
3455
+ this.config.logger.info("UnifiedRTDS: Disconnecting from all services...");
3456
+ if (this.polymarketService) {
3457
+ this.polymarketService.disconnect();
3458
+ }
3459
+ if (this.osirisService) {
3460
+ this.osirisService.disconnect();
3461
+ }
3462
+ }
3463
+ /**
3464
+ * Check if connected to at least one service
3465
+ */
3466
+ isActive() {
3467
+ return (this.polymarketService?.isActive() ?? false) || (this.osirisService?.isActive() ?? false);
3468
+ }
3469
+ /**
3470
+ * Get connection status for each service
3471
+ */
3472
+ getStatus() {
3473
+ return {
3474
+ polymarket: {
3475
+ enabled: this.polymarketService !== null,
3476
+ connected: this.polymarketConnected
3477
+ },
3478
+ osiris: {
3479
+ enabled: this.osirisService !== null,
3480
+ connected: this.osirisConnected
3481
+ }
3482
+ };
3483
+ }
3484
+ /**
3485
+ * Subscribe to events
3486
+ * Automatically routes subscriptions to the appropriate backend
3487
+ */
3488
+ subscribe(subscriptions) {
3489
+ const { polymarketSubs, osirisSubs } = this.routeSubscriptions(subscriptions);
3490
+ if (polymarketSubs.length > 0) {
3491
+ if (this.polymarketService) {
3492
+ this.polymarketService.subscribe(polymarketSubs);
3493
+ } else {
3494
+ this.config.logger.warning(
3495
+ "UnifiedRTDS: Polymarket subscriptions requested but service not enabled"
3496
+ );
3497
+ }
3498
+ }
3499
+ if (osirisSubs.length > 0) {
3500
+ if (this.osirisService) {
3501
+ this.osirisService.subscribe(osirisSubs);
3502
+ } else {
3503
+ this.config.logger.warning(
3504
+ "UnifiedRTDS: Osiris subscriptions requested but service not enabled"
3505
+ );
3506
+ }
3507
+ }
3508
+ }
3509
+ /**
3510
+ * Unsubscribe from events
3511
+ */
3512
+ unsubscribe(subscriptions) {
3513
+ const { polymarketSubs, osirisSubs } = this.routeSubscriptions(subscriptions);
3514
+ if (polymarketSubs.length > 0 && this.polymarketService) {
3515
+ this.polymarketService.unsubscribe(polymarketSubs);
3516
+ }
3517
+ if (osirisSubs.length > 0 && this.osirisService) {
3518
+ this.osirisService.unsubscribe(osirisSubs);
3519
+ }
3520
+ }
3521
+ /**
3522
+ * Route subscriptions to appropriate backends based on type and eventSource
3523
+ */
3524
+ routeSubscriptions(subscriptions) {
3525
+ const polymarketSubs = [];
3526
+ const osirisSubs = [];
3527
+ for (const sub of subscriptions) {
3528
+ if (sub.eventSource === "polymarket") {
3529
+ polymarketSubs.push(sub);
3530
+ continue;
3531
+ }
3532
+ if (sub.eventSource === "osiris") {
3533
+ osirisSubs.push(sub);
3534
+ continue;
3535
+ }
3536
+ if (isPolymarketSubscription(sub)) {
3537
+ polymarketSubs.push(sub);
3538
+ } else if (isOsirisSubscription(sub)) {
3539
+ osirisSubs.push(sub);
3540
+ } else if (isMarketSubscription(sub)) {
3541
+ polymarketSubs.push(sub);
3542
+ } else if (isCustomSubscription(sub)) {
3543
+ const topic = sub.topic;
3544
+ if (topic.startsWith("market:") || topic.startsWith("wallet:") || topic.startsWith("opps:")) {
3545
+ osirisSubs.push(sub);
3546
+ } else {
3547
+ polymarketSubs.push(sub);
3548
+ }
3549
+ } else {
3550
+ polymarketSubs.push(sub);
3551
+ }
3552
+ }
3553
+ return { polymarketSubs, osirisSubs };
3554
+ }
3555
+ /**
3556
+ * Check if all enabled services are connected
3557
+ */
3558
+ checkAllConnected() {
3559
+ const polymarketOk = !this.polymarketService || this.polymarketConnected;
3560
+ const osirisOk = !this.osirisService || this.osirisConnected;
3561
+ if (polymarketOk && osirisOk) {
3562
+ this.config.onConnect?.();
3563
+ }
3564
+ }
3565
+ /**
3566
+ * Check if any service disconnected
3567
+ */
3568
+ checkAnyDisconnected() {
3569
+ if (!this.polymarketConnected && !this.osirisConnected) {
3570
+ this.config.onDisconnect?.();
3571
+ }
3572
+ }
3573
+ };
3574
+ function createUnifiedRTDSService(config) {
3575
+ return new UnifiedRTDSService(config);
2042
3576
  }
2043
3577
  var CHAIN_MAP = {
2044
3578
  "evm:eip155:1": mainnet,
@@ -2190,6 +3724,6 @@ var Signer = class {
2190
3724
  }
2191
3725
  };
2192
3726
 
2193
- export { EventRunner, HyperliquidClient, MemoryStateManager, OsirisSigner, PolymarketClient, PrivateKeySigner, RedisStateManager, Signer, StrategyEngine, createConsoleLogger, createEventRunner, createOsirisContext, createStrategyEngine, isEventBasedStrategy, isTickBasedStrategy, runStrategy, validateStrategy };
3727
+ 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 };
2194
3728
  //# sourceMappingURL=index.js.map
2195
3729
  //# sourceMappingURL=index.js.map