@vesper85/strategy-sdk 0.1.0 → 0.1.2

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,32 @@
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
12
  import { privateKeyToAccount } from 'viem/accounts';
13
13
  import { sepolia, base, optimism, arbitrum, polygon, mainnet } from 'viem/chains';
14
14
 
15
15
  // @osiris-ai/strategy-sdk - Strategy SDK for Trading
16
16
 
17
+ // src/types/event-types.ts
18
+ function isMarketSubscription(sub) {
19
+ return sub.type === "price" || sub.type === "orderbook" || sub.type === "trade" || sub.type === "fill";
20
+ }
21
+ function isWalletSubscription(sub) {
22
+ return sub.type === "wallet";
23
+ }
24
+ function isOpportunitySubscription(sub) {
25
+ return sub.type === "opportunity";
26
+ }
27
+ function isCustomSubscription(sub) {
28
+ return sub.type === "custom";
29
+ }
17
30
  var OsirisSigner = class {
18
31
  hubBaseUrl;
19
32
  accessToken;
@@ -148,13 +161,14 @@ var OsirisSigner = class {
148
161
  return void 0;
149
162
  }
150
163
  };
151
- var MaxUint256BigInt = BigInt(MaxUint256.toString());
164
+ var MaxUint256BigInt = typeof ethers.constants !== "undefined" && ethers.constants.MaxUint256 ? BigInt(ethers.MaxUint256.toString()) : BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
152
165
  var POLYGON_CHAIN_ID = 137;
153
166
  var USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
154
167
  var CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045";
155
168
  var CTF_EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";
156
169
  var DEFAULT_CLOB_HOST = "https://clob.polymarket.com";
157
170
  var POLYGON_RPC_URL = "https://polygon-rpc.com";
171
+ process.env.MCP_GATEWAY_URL ? `${process.env.MCP_GATEWAY_URL}/@osiris/polymarket/mcp` : "https://gateway.backend.osirislabs.xyz/@osiris/polymarket/mcp";
158
172
  var ERC20_ABI = [
159
173
  "function approve(address spender, uint256 amount) external returns (bool)",
160
174
  "function allowance(address owner, address spender) external view returns (uint256)",
@@ -234,7 +248,6 @@ var PolymarketClient = class {
234
248
  this.options.clobHost,
235
249
  this.options.chainId,
236
250
  this.wallet
237
- // Cast to any for CLOB client v5 compatibility
238
251
  );
239
252
  this.logger.info("Deriving API credentials...");
240
253
  const apiCreds = await tempClient.createOrDeriveApiKey();
@@ -243,7 +256,6 @@ var PolymarketClient = class {
243
256
  this.options.clobHost,
244
257
  this.options.chainId,
245
258
  this.wallet,
246
- // Cast to any for CLOB client v5 compatibility
247
259
  apiCreds,
248
260
  signatureType
249
261
  );
@@ -568,7 +580,7 @@ var PolymarketClient = class {
568
580
  const allowanceResult = await this.callMCPTool("check_allowance", {
569
581
  token: tokenType
570
582
  });
571
- this.logger.debug("[MCP] Allowance check result:", allowanceResult);
583
+ console.log("allowanceResult", allowanceResult);
572
584
  const allowanceContent = this.extractMCPContent(allowanceResult);
573
585
  const contractMatches = allowanceContent?.match(/0x[a-fA-F0-9]{40}:\s*(true|false)/gi);
574
586
  let hasFullAllowance = true;
@@ -661,18 +673,6 @@ var PolymarketClient = class {
661
673
  ctfWasAlreadyApproved: false
662
674
  };
663
675
  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
676
  const usdcHasAllowance = await this.checkAllowanceViaMCP("USDC");
677
677
  if (usdcHasAllowance) {
678
678
  result.usdcWasAlreadyApproved = true;
@@ -753,16 +753,13 @@ var PolymarketClient = class {
753
753
  if (!address) {
754
754
  throw new Error("userAddress is required for MCP order placement");
755
755
  }
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}`);
756
+ this.logger.info(`Creating limit order via MCP: ${params.side} ${params.size} @ ${params.price}`);
759
757
  const chooseWalletResult = await this.callMCPTool("choose_wallet", {
760
758
  address
761
759
  });
762
- this.logger.debug(`[MCP] choose_wallet result: ${JSON.stringify(chooseWalletResult, null, 2)}`);
763
760
  const walletError = this.detectMCPError(chooseWalletResult);
764
761
  if (walletError) {
765
- this.logger.error(`[MCP] Choose wallet failed: ${walletError}`);
762
+ this.logger.error(`Choose wallet failed: ${walletError}`);
766
763
  return {
767
764
  success: false,
768
765
  errorMsg: walletError
@@ -775,7 +772,7 @@ var PolymarketClient = class {
775
772
  errorMsg: `Failed to approve ${params.side === "BUY" ? "USDC" : "Conditional Token"}. Please try again.`
776
773
  };
777
774
  }
778
- const orderParams = {
775
+ const orderResult = await this.callMCPTool("create_and_post_order", {
779
776
  userOrder: {
780
777
  tokenID: params.tokenId,
781
778
  side: params.side,
@@ -784,11 +781,7 @@ var PolymarketClient = class {
784
781
  },
785
782
  orderType: params.expiration ? "GTD" : "GTC",
786
783
  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)}`);
784
+ });
792
785
  const orderError = this.detectMCPError(orderResult);
793
786
  if (orderError) {
794
787
  this.logger.error(`Limit order via MCP failed: ${orderError}`);
@@ -799,13 +792,13 @@ var PolymarketClient = class {
799
792
  }
800
793
  const orderDetails = this.extractOrderDetails(orderResult);
801
794
  if (!orderDetails) {
802
- this.logger.error(`Failed to extract order details from MCP response`, orderResult);
795
+ this.logger.error("Failed to extract order details from MCP response");
803
796
  return {
804
797
  success: false,
805
798
  errorMsg: "Failed to extract order details from MCP response"
806
799
  };
807
800
  }
808
- this.logger.info(`[MCP] Limit order created: ${orderDetails.orderID}`);
801
+ this.logger.info(`Limit order via MCP created: ${orderDetails.orderID}`);
809
802
  return {
810
803
  success: true,
811
804
  orderId: orderDetails.orderID,
@@ -866,14 +859,10 @@ var PolymarketClient = class {
866
859
  if (!address) {
867
860
  throw new Error("userAddress is required for MCP order placement");
868
861
  }
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}`);
862
+ this.logger.info(`Creating market order via MCP: ${params.side} ${params.amount}`);
873
863
  const chooseWalletResult = await this.callMCPTool("choose_wallet", {
874
864
  address
875
865
  });
876
- this.logger.debug(`[MCP] choose_wallet result: ${JSON.stringify(chooseWalletResult, null, 2)}`);
877
866
  const walletError = this.detectMCPError(chooseWalletResult);
878
867
  if (walletError) {
879
868
  this.logger.error(`Choose wallet failed: ${walletError}`);
@@ -889,19 +878,16 @@ var PolymarketClient = class {
889
878
  errorMsg: `Failed to approve ${params.side === "BUY" ? "USDC" : "Conditional Token"}. Please try again.`
890
879
  };
891
880
  }
892
- const orderParams = {
881
+ const orderResult = await this.callMCPTool("create_and_post_market_order", {
893
882
  userMarketOrder: {
894
883
  tokenID: params.tokenId,
895
884
  side: params.side,
896
- amount: params.amount
885
+ amount: params.amount,
886
+ orderType: params.slippageTolerance ? "FOK" : "FOK"
887
+ // Fill or Kill
897
888
  },
898
889
  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)}`);
890
+ });
905
891
  const orderError = this.detectMCPError(orderResult);
906
892
  if (orderError) {
907
893
  this.logger.error(`Market order via MCP failed: ${orderError}`);
@@ -912,7 +898,7 @@ var PolymarketClient = class {
912
898
  }
913
899
  const orderDetails = this.extractOrderDetails(orderResult);
914
900
  if (!orderDetails) {
915
- this.logger.error(`Failed to extract order details from MCP response`, orderResult);
901
+ this.logger.error("Failed to extract order details from MCP response");
916
902
  return {
917
903
  success: false,
918
904
  errorMsg: "Failed to extract order details from MCP response"
@@ -1027,9 +1013,8 @@ var PolymarketClient = class {
1027
1013
  // ============================================
1028
1014
  async buy(tokenId, size) {
1029
1015
  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
- );
1016
+ this.logger.warning(`[Dummy] Buying ${size} of ${tokenId} - trading client not initialized`);
1017
+ return;
1033
1018
  }
1034
1019
  const response = await this.createMarketOrder({
1035
1020
  tokenId,
@@ -1042,9 +1027,8 @@ var PolymarketClient = class {
1042
1027
  }
1043
1028
  async sell(tokenId, size) {
1044
1029
  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
- );
1030
+ this.logger.warning(`[Dummy] Selling ${size} of ${tokenId} - trading client not initialized`);
1031
+ return;
1048
1032
  }
1049
1033
  const response = await this.createMarketOrder({
1050
1034
  tokenId,
@@ -1056,73 +1040,6 @@ var PolymarketClient = class {
1056
1040
  }
1057
1041
  }
1058
1042
  // ============================================
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
1043
  // Data Methods
1127
1044
  // ============================================
1128
1045
  async getPrice(ticker) {
@@ -1270,26 +1187,8 @@ var PolymarketClient = class {
1270
1187
  return this.fetchData("/activity", { user, ...params });
1271
1188
  }
1272
1189
  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
- }
1190
+ this.logger.warning("getTopHolders not directly supported by public Data API");
1191
+ return [];
1293
1192
  }
1294
1193
  async getPortfolioValue(user) {
1295
1194
  try {
@@ -1304,18 +1203,8 @@ var PolymarketClient = class {
1304
1203
  }
1305
1204
  }
1306
1205
  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
- }
1206
+ this.logger.warning("getClosedPositions not directly supported, returning empty");
1207
+ return [];
1319
1208
  }
1320
1209
  async getTradedMarketsCount(user) {
1321
1210
  try {
@@ -1338,21 +1227,8 @@ var PolymarketClient = class {
1338
1227
  }
1339
1228
  // Account methods
1340
1229
  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
- }
1230
+ this.logger.warning("getPosition(ticker) not implemented. Use getPositions(user) instead.");
1231
+ return null;
1356
1232
  }
1357
1233
  };
1358
1234
  var HyperliquidClient = class {
@@ -1803,7 +1679,8 @@ function validateStrategy(strategy) {
1803
1679
  );
1804
1680
  }
1805
1681
  }
1806
- var EventRunner = class {
1682
+ var POLYMARKET_RTDS_URL = "wss://ws-live-data.polymarket.com";
1683
+ var PolymarketEventRunner = class {
1807
1684
  constructor(strategy, config, context) {
1808
1685
  this.strategy = strategy;
1809
1686
  this.config = config;
@@ -1817,6 +1694,464 @@ var EventRunner = class {
1817
1694
  this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
1818
1695
  this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
1819
1696
  this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
1697
+ this.pingIntervalMs = config.pingIntervalMs ?? 5e3;
1698
+ }
1699
+ ws = null;
1700
+ isRunning = false;
1701
+ reconnectAttempts = 0;
1702
+ reconnectTimeout = null;
1703
+ pingInterval = null;
1704
+ isIntentionallyClosed = false;
1705
+ // Default reconnection settings
1706
+ maxReconnectAttempts;
1707
+ baseReconnectDelay;
1708
+ maxReconnectDelay;
1709
+ pingIntervalMs;
1710
+ /**
1711
+ * Start listening to events
1712
+ * Connects to Polymarket RTDS and subscribes to events
1713
+ */
1714
+ start() {
1715
+ if (this.isRunning) {
1716
+ this.config.logger.warning("Polymarket event runner is already running");
1717
+ return;
1718
+ }
1719
+ this.isRunning = true;
1720
+ this.isIntentionallyClosed = false;
1721
+ this.reconnectAttempts = 0;
1722
+ const strategyName = this.config.strategyId || "unnamed-strategy";
1723
+ this.config.logger.info(`Starting Polymarket event runner for strategy ${strategyName}`);
1724
+ this.config.logger.info(`Connecting to Polymarket RTDS: ${POLYMARKET_RTDS_URL}`);
1725
+ this.connect();
1726
+ }
1727
+ /**
1728
+ * Stop listening and disconnect from WebSocket
1729
+ */
1730
+ stop() {
1731
+ if (!this.isRunning) {
1732
+ return;
1733
+ }
1734
+ this.isRunning = false;
1735
+ this.isIntentionallyClosed = true;
1736
+ if (this.pingInterval) {
1737
+ clearInterval(this.pingInterval);
1738
+ this.pingInterval = null;
1739
+ }
1740
+ if (this.reconnectTimeout) {
1741
+ clearTimeout(this.reconnectTimeout);
1742
+ this.reconnectTimeout = null;
1743
+ }
1744
+ if (this.ws) {
1745
+ try {
1746
+ this.unsubscribeFromEvents();
1747
+ this.ws.close(1e3, "Intentional close");
1748
+ } catch (error) {
1749
+ }
1750
+ this.ws = null;
1751
+ }
1752
+ this.config.logger.info("Polymarket event runner stopped");
1753
+ }
1754
+ /**
1755
+ * Check if the runner is currently active
1756
+ */
1757
+ isActive() {
1758
+ return this.isRunning;
1759
+ }
1760
+ /**
1761
+ * Get current connection status
1762
+ */
1763
+ getStatus() {
1764
+ return {
1765
+ running: this.isRunning,
1766
+ connected: this.ws?.readyState === WebSocket.OPEN,
1767
+ reconnectAttempts: this.reconnectAttempts,
1768
+ subscriptionCount: this.strategy.subscriptions?.length ?? 0
1769
+ };
1770
+ }
1771
+ /**
1772
+ * Connect to Polymarket RTDS WebSocket
1773
+ */
1774
+ connect() {
1775
+ if (this.isIntentionallyClosed) {
1776
+ return;
1777
+ }
1778
+ try {
1779
+ this.ws = new WebSocket(POLYMARKET_RTDS_URL);
1780
+ this.ws.on("open", () => {
1781
+ this.config.logger.info("Connected to Polymarket RTDS");
1782
+ this.reconnectAttempts = 0;
1783
+ this.startPingInterval();
1784
+ this.subscribeToEvents();
1785
+ });
1786
+ this.ws.on("message", (data) => {
1787
+ this.handleMessage(data);
1788
+ });
1789
+ this.ws.on("error", (error) => {
1790
+ this.config.logger.error(`Polymarket RTDS WebSocket error: ${error.message}`, error);
1791
+ });
1792
+ this.ws.on("close", (code, reason) => {
1793
+ const reasonStr = reason.toString() || "none";
1794
+ this.config.logger.info(`Polymarket RTDS WebSocket closed (code: ${code}, reason: ${reasonStr})`);
1795
+ if (this.pingInterval) {
1796
+ clearInterval(this.pingInterval);
1797
+ this.pingInterval = null;
1798
+ }
1799
+ if (!this.isIntentionallyClosed && this.isRunning) {
1800
+ this.attemptReconnect();
1801
+ }
1802
+ });
1803
+ this.ws.on("pong", () => {
1804
+ this.config.logger.debug("Received PONG from Polymarket RTDS");
1805
+ });
1806
+ } catch (error) {
1807
+ this.config.logger.error(`Failed to connect to Polymarket RTDS: ${error.message}`, error);
1808
+ if (this.isRunning && !this.isIntentionallyClosed) {
1809
+ this.attemptReconnect();
1810
+ }
1811
+ }
1812
+ }
1813
+ /**
1814
+ * Start ping interval to maintain connection (Polymarket recommends 5 seconds)
1815
+ */
1816
+ startPingInterval() {
1817
+ if (this.pingInterval) {
1818
+ clearInterval(this.pingInterval);
1819
+ }
1820
+ this.pingInterval = setInterval(() => {
1821
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1822
+ try {
1823
+ this.ws.send("PING");
1824
+ this.config.logger.debug("Sent PING to Polymarket RTDS");
1825
+ } catch (error) {
1826
+ this.config.logger.warning("Failed to send PING");
1827
+ }
1828
+ }
1829
+ }, this.pingIntervalMs);
1830
+ }
1831
+ /**
1832
+ * Attempt to reconnect with exponential backoff
1833
+ */
1834
+ attemptReconnect() {
1835
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1836
+ this.config.logger.error(`Max reconnection attempts (${this.maxReconnectAttempts}) reached`);
1837
+ this.isRunning = false;
1838
+ return;
1839
+ }
1840
+ this.reconnectAttempts++;
1841
+ const delay = Math.min(
1842
+ this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1e3,
1843
+ this.maxReconnectDelay
1844
+ );
1845
+ this.config.logger.info(
1846
+ `Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
1847
+ );
1848
+ this.reconnectTimeout = setTimeout(() => {
1849
+ this.reconnectTimeout = null;
1850
+ this.connect();
1851
+ }, delay);
1852
+ }
1853
+ /**
1854
+ * Map strategy subscriptions to Polymarket RTDS subscriptions
1855
+ * Only MarketSubscription types are supported by Polymarket RTDS
1856
+ * WalletSubscription and OpportunitySubscription are Osiris-only features
1857
+ */
1858
+ mapToRTDSSubscriptions() {
1859
+ if (!this.strategy.subscriptions) {
1860
+ return [];
1861
+ }
1862
+ const rtdsSubscriptions = [];
1863
+ for (const sub of this.strategy.subscriptions) {
1864
+ let rtdsSub = null;
1865
+ if (isMarketSubscription(sub)) {
1866
+ const topic = this.mapEventTypeToTopic(sub.type);
1867
+ rtdsSub = {
1868
+ topic,
1869
+ type: this.getMessageTypeForTopic(topic)
1870
+ };
1871
+ } else if (isCustomSubscription(sub)) {
1872
+ const validRTDSTopics = [
1873
+ "crypto_prices",
1874
+ "comments",
1875
+ "activity",
1876
+ "orders",
1877
+ "trades",
1878
+ "profile"
1879
+ ];
1880
+ if (validRTDSTopics.includes(sub.topic)) {
1881
+ const topic = sub.topic;
1882
+ rtdsSub = {
1883
+ topic,
1884
+ type: this.getMessageTypeForTopic(topic)
1885
+ };
1886
+ } else {
1887
+ this.config.logger.warning(
1888
+ `Custom subscription topic '${sub.topic}' is not a valid Polymarket RTDS topic. Valid topics: ${validRTDSTopics.join(", ")}`
1889
+ );
1890
+ continue;
1891
+ }
1892
+ } else {
1893
+ this.config.logger.warning(
1894
+ `Subscription type '${sub.type}' is not supported by Polymarket RTDS. Only market subscriptions (price, orderbook, trade, fill) and custom subscriptions with valid RTDS topics are supported.`
1895
+ );
1896
+ continue;
1897
+ }
1898
+ if (!rtdsSub) continue;
1899
+ if (this.config.clobAuth && ["orders", "trades"].includes(rtdsSub.topic)) {
1900
+ rtdsSub.clob_auth = this.config.clobAuth;
1901
+ }
1902
+ if (this.config.walletAddress && ["activity", "profile"].includes(rtdsSub.topic)) {
1903
+ rtdsSub.gamma_auth = {
1904
+ address: this.config.walletAddress
1905
+ };
1906
+ }
1907
+ rtdsSubscriptions.push(rtdsSub);
1908
+ }
1909
+ return rtdsSubscriptions;
1910
+ }
1911
+ /**
1912
+ * Get the message type for a given RTDS topic
1913
+ * Each topic has specific message types as per the Polymarket RTDS documentation
1914
+ */
1915
+ getMessageTypeForTopic(topic) {
1916
+ switch (topic) {
1917
+ case "crypto_prices":
1918
+ return "update";
1919
+ case "comments":
1920
+ return "comment_created";
1921
+ // Subscribes to all comment events
1922
+ case "trades":
1923
+ return "orders_matched";
1924
+ case "orders":
1925
+ return "order_placed";
1926
+ // Can also be 'order_cancelled', 'order_filled'
1927
+ case "activity":
1928
+ return "trade";
1929
+ // Can also be 'offer', etc.
1930
+ case "profile":
1931
+ return "update";
1932
+ default:
1933
+ return "update";
1934
+ }
1935
+ }
1936
+ /**
1937
+ * Map EventType to Polymarket RTDS topic
1938
+ */
1939
+ mapEventTypeToTopic(eventType) {
1940
+ switch (eventType) {
1941
+ case "price":
1942
+ return "crypto_prices";
1943
+ case "trade":
1944
+ return "trades";
1945
+ case "orderbook":
1946
+ case "fill":
1947
+ return "orders";
1948
+ case "custom":
1949
+ return "activity";
1950
+ default:
1951
+ return "activity";
1952
+ }
1953
+ }
1954
+ /**
1955
+ * Subscribe to events based on strategy's subscriptions
1956
+ */
1957
+ subscribeToEvents() {
1958
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
1959
+ this.config.logger.warning("Cannot subscribe - WebSocket not connected");
1960
+ return;
1961
+ }
1962
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
1963
+ this.config.logger.warning("No subscriptions defined for strategy");
1964
+ return;
1965
+ }
1966
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions();
1967
+ const subscribeMessage = {
1968
+ action: "subscribe",
1969
+ subscriptions: rtdsSubscriptions
1970
+ };
1971
+ try {
1972
+ this.ws.send(JSON.stringify(subscribeMessage));
1973
+ this.config.logger.info(
1974
+ `Subscribed to ${rtdsSubscriptions.length} Polymarket RTDS topic(s): ` + rtdsSubscriptions.map((s) => s.topic).join(", ")
1975
+ );
1976
+ } catch (error) {
1977
+ this.config.logger.error(`Failed to send subscription: ${error.message}`, error);
1978
+ }
1979
+ }
1980
+ /**
1981
+ * Unsubscribe from events before disconnecting
1982
+ */
1983
+ unsubscribeFromEvents() {
1984
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
1985
+ return;
1986
+ }
1987
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
1988
+ return;
1989
+ }
1990
+ const rtdsSubscriptions = this.mapToRTDSSubscriptions();
1991
+ const unsubscribeMessage = {
1992
+ action: "unsubscribe",
1993
+ subscriptions: rtdsSubscriptions
1994
+ };
1995
+ try {
1996
+ this.ws.send(JSON.stringify(unsubscribeMessage));
1997
+ } catch (error) {
1998
+ }
1999
+ }
2000
+ /**
2001
+ * Handle incoming WebSocket messages
2002
+ */
2003
+ handleMessage(data) {
2004
+ try {
2005
+ const rawData = typeof data === "string" ? data : data.toString();
2006
+ if (rawData === "PING") {
2007
+ this.ws?.send("PONG");
2008
+ return;
2009
+ }
2010
+ if (rawData === "PONG" || rawData.trim() === "") {
2011
+ return;
2012
+ }
2013
+ let message;
2014
+ try {
2015
+ message = JSON.parse(rawData);
2016
+ } catch (parseError) {
2017
+ this.config.logger.warning(`Received non-JSON message: ${rawData.slice(0, 100)}`);
2018
+ return;
2019
+ }
2020
+ if (!this.isValidRTDSMessage(message)) {
2021
+ this.config.logger.debug(`Received non-event message: ${JSON.stringify(message).slice(0, 200)}`);
2022
+ return;
2023
+ }
2024
+ const strategyEvent = this.convertToStrategyEvent(message);
2025
+ this.dispatchEvent(strategyEvent);
2026
+ } catch (error) {
2027
+ this.config.logger.error(`Error handling message: ${error.message}`, error);
2028
+ }
2029
+ }
2030
+ /**
2031
+ * Validate that a message is a valid Polymarket RTDS message
2032
+ */
2033
+ isValidRTDSMessage(message) {
2034
+ return message && typeof message === "object" && typeof message.topic === "string" && typeof message.type === "string" && typeof message.timestamp === "number" && typeof message.payload === "object";
2035
+ }
2036
+ /**
2037
+ * Convert Polymarket RTDS message to StrategyEvent
2038
+ */
2039
+ convertToStrategyEvent(message) {
2040
+ const eventType = this.mapTopicToEventType(message.topic);
2041
+ const eventData = this.extractEventData(message);
2042
+ return {
2043
+ type: eventType,
2044
+ timestamp: message.timestamp,
2045
+ market: eventData.market,
2046
+ data: eventData
2047
+ };
2048
+ }
2049
+ /**
2050
+ * Map Polymarket RTDS topic to EventType
2051
+ */
2052
+ mapTopicToEventType(topic) {
2053
+ switch (topic) {
2054
+ case "crypto_prices":
2055
+ return "price";
2056
+ case "trades":
2057
+ return "trade";
2058
+ case "orders":
2059
+ return "orderbook";
2060
+ case "activity":
2061
+ case "comments":
2062
+ case "profile":
2063
+ default:
2064
+ return "custom";
2065
+ }
2066
+ }
2067
+ /**
2068
+ * Extract event data from Polymarket RTDS payload
2069
+ */
2070
+ extractEventData(message) {
2071
+ const payload = message.payload;
2072
+ const data = {
2073
+ raw: {
2074
+ ...payload,
2075
+ topic: message.topic,
2076
+ // Include topic for easier filtering
2077
+ type: message.type
2078
+ // Include message type
2079
+ }
2080
+ };
2081
+ switch (message.topic) {
2082
+ case "crypto_prices":
2083
+ data.price = payload.price ?? payload.value;
2084
+ data.previousPrice = payload.previousPrice ?? payload.previous;
2085
+ data.changePercent = payload.changePercent ?? payload.change;
2086
+ break;
2087
+ case "trades":
2088
+ data.price = payload.price;
2089
+ data.size = payload.size ?? payload.amount;
2090
+ data.side = payload.side;
2091
+ data.tradeId = payload.tradeId ?? payload.id;
2092
+ break;
2093
+ case "orders":
2094
+ data.bestBid = payload.bestBid ?? payload.bid;
2095
+ data.bestAsk = payload.bestAsk ?? payload.ask;
2096
+ data.spread = payload.spread;
2097
+ data.orderId = payload.orderId ?? payload.id;
2098
+ break;
2099
+ case "comments":
2100
+ data.commentText = payload.text ?? payload.comment;
2101
+ data.author = payload.author ?? payload.user;
2102
+ data.market = payload.market ?? payload.slug;
2103
+ data.commentType = payload.type;
2104
+ data.reactions = payload.reactions;
2105
+ break;
2106
+ }
2107
+ if (payload.market) data.market = payload.market;
2108
+ if (payload.asset) data.market = payload.asset;
2109
+ if (payload.tokenId) data.market = payload.tokenId;
2110
+ if (payload.conditionId) data.market = payload.conditionId;
2111
+ return data;
2112
+ }
2113
+ /**
2114
+ * Dispatch an event to the strategy's onEvent handler
2115
+ */
2116
+ 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
+ try {
2122
+ if (this.strategy.onEvent) {
2123
+ await this.strategy.onEvent(event, this.context);
2124
+ }
2125
+ } catch (error) {
2126
+ this.config.logger.error(
2127
+ `Error in strategy onEvent handler: ${error.message}`,
2128
+ error
2129
+ );
2130
+ }
2131
+ const eventDuration = Date.now() - eventStart;
2132
+ this.config.logger.debug(`Event handling completed in ${eventDuration}ms`);
2133
+ }
2134
+ };
2135
+ function createPolymarketEventRunner(strategy, config, context) {
2136
+ return new PolymarketEventRunner(strategy, config, context);
2137
+ }
2138
+
2139
+ // src/engine/event-runner.ts
2140
+ var OsirisEventRunner = class {
2141
+ constructor(strategy, config, context) {
2142
+ this.strategy = strategy;
2143
+ this.config = config;
2144
+ this.context = context;
2145
+ if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
2146
+ throw new Error("Strategy must have subscriptions defined for event-based execution");
2147
+ }
2148
+ if (typeof this.strategy.onEvent !== "function") {
2149
+ throw new Error("Strategy must implement onEvent method for event-based execution");
2150
+ }
2151
+ this.maxReconnectAttempts = config.reconnect?.maxAttempts ?? 10;
2152
+ this.baseReconnectDelay = config.reconnect?.delayMs ?? 1e3;
2153
+ this.maxReconnectDelay = config.reconnect?.maxDelayMs ?? 3e4;
2154
+ if (!config.eventSourceUrl) ;
1820
2155
  }
1821
2156
  ws = null;
1822
2157
  isRunning = false;
@@ -1892,6 +2227,9 @@ var EventRunner = class {
1892
2227
  return;
1893
2228
  }
1894
2229
  try {
2230
+ if (!this.config.eventSourceUrl) {
2231
+ throw new Error("eventSourceUrl is required for OsirisEventRunner");
2232
+ }
1895
2233
  this.ws = new WebSocket(this.config.eventSourceUrl);
1896
2234
  this.ws.on("open", () => {
1897
2235
  this.config.logger.info("Connected to event source");
@@ -1943,6 +2281,10 @@ var EventRunner = class {
1943
2281
  /**
1944
2282
  * Subscribe to events based on strategy's subscriptions
1945
2283
  */
2284
+ /**
2285
+ * Subscribe to events based on strategy's subscriptions
2286
+ * Maps SDK subscriptions to Osiris Pub/Sub protocol
2287
+ */
1946
2288
  subscribeToEvents() {
1947
2289
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
1948
2290
  this.config.logger.warning("Cannot subscribe - WebSocket not connected");
@@ -1952,19 +2294,63 @@ var EventRunner = class {
1952
2294
  this.config.logger.warning("No subscriptions defined for strategy");
1953
2295
  return;
1954
2296
  }
2297
+ const topics = [];
2298
+ const filters = {};
2299
+ for (const sub of this.strategy.subscriptions) {
2300
+ const topic = this.mapSubscriptionToTopic(sub);
2301
+ if (topic) {
2302
+ topics.push(topic);
2303
+ if (sub.conditions) {
2304
+ filters[topic] = sub.conditions;
2305
+ }
2306
+ }
2307
+ }
2308
+ if (topics.length === 0) {
2309
+ this.config.logger.warning("No valid topics mapped from subscriptions");
2310
+ return;
2311
+ }
1955
2312
  const subscribeMessage = {
1956
2313
  action: "subscribe",
1957
- subscriptions: this.strategy.subscriptions
2314
+ topics,
2315
+ filters: Object.keys(filters).length > 0 ? filters : void 0
1958
2316
  };
1959
2317
  try {
1960
2318
  this.ws.send(JSON.stringify(subscribeMessage));
1961
2319
  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(", ")
2320
+ `Subscribed to ${topics.length} topics: ${topics.join(", ")}`
1963
2321
  );
1964
2322
  } catch (error) {
1965
2323
  this.config.logger.error(`Failed to send subscription: ${error.message}`, error);
1966
2324
  }
1967
2325
  }
2326
+ /**
2327
+ * Map EventSubscription to Osiris Pub/Sub topic string
2328
+ * Uses discriminated union to properly handle each subscription type
2329
+ */
2330
+ mapSubscriptionToTopic(sub) {
2331
+ switch (sub.type) {
2332
+ // Market subscriptions → market:{market}
2333
+ case "price":
2334
+ case "orderbook":
2335
+ case "trade":
2336
+ case "fill":
2337
+ return sub.market ? `market:${sub.market}` : null;
2338
+ // Wallet subscriptions → wallet:{wallet}
2339
+ case "wallet":
2340
+ return sub.wallet ? `wallet:${sub.wallet}` : null;
2341
+ // Opportunity subscriptions → opps:{filter}
2342
+ case "opportunity":
2343
+ return `opps:${sub.filter || "all"}`;
2344
+ // Custom subscriptions → use topic directly
2345
+ case "custom":
2346
+ return sub.topic || null;
2347
+ default:
2348
+ return null;
2349
+ }
2350
+ }
2351
+ /**
2352
+ * Unsubscribe from events before disconnecting
2353
+ */
1968
2354
  /**
1969
2355
  * Unsubscribe from events before disconnecting
1970
2356
  */
@@ -1975,9 +2361,11 @@ var EventRunner = class {
1975
2361
  if (!this.strategy.subscriptions || this.strategy.subscriptions.length === 0) {
1976
2362
  return;
1977
2363
  }
2364
+ const topics = this.strategy.subscriptions.map((s) => this.mapSubscriptionToTopic(s)).filter((t) => t !== null);
2365
+ if (topics.length === 0) return;
1978
2366
  const unsubscribeMessage = {
1979
2367
  action: "unsubscribe",
1980
- subscriptions: this.strategy.subscriptions
2368
+ topics
1981
2369
  };
1982
2370
  try {
1983
2371
  this.ws.send(JSON.stringify(unsubscribeMessage));
@@ -2000,28 +2388,103 @@ var EventRunner = class {
2000
2388
  this.config.logger.warning(`Received non-JSON message: ${rawData.slice(0, 100)}`);
2001
2389
  return;
2002
2390
  }
2003
- if (!this.isValidEvent(message)) {
2004
- this.config.logger.debug(`Received non-event message: ${JSON.stringify(message).slice(0, 200)}`);
2391
+ if (message.type === "connected") {
2392
+ this.config.logger.info(`Osiris Pub/Sub: ${message.message}`);
2393
+ return;
2394
+ }
2395
+ if (message.type === "subscribe.result") {
2396
+ if (message.success) {
2397
+ this.config.logger.info(`Successfully subscribed to: ${message.subscribed.join(", ")}`);
2398
+ } else {
2399
+ this.config.logger.error(`Subscription failed: ${JSON.stringify(message.errors)}`);
2400
+ }
2005
2401
  return;
2006
2402
  }
2007
- this.dispatchEvent(message);
2403
+ if (message.type === "error") {
2404
+ this.config.logger.error(`Osiris Pub/Sub Error: ${message.error}`);
2405
+ return;
2406
+ }
2407
+ if (message.type === "pong") {
2408
+ return;
2409
+ }
2410
+ const event = this.mapMessageToStrategyEvent(message);
2411
+ if (event) {
2412
+ this.dispatchEvent(event);
2413
+ } else {
2414
+ this.config.logger.debug(`Unhandled message type: ${message.type}`);
2415
+ }
2008
2416
  } catch (error) {
2009
2417
  this.config.logger.error(`Error handling message: ${error.message}`, error);
2010
2418
  }
2011
2419
  }
2420
+ /**
2421
+ * Map incoming Osiris message to StrategyEvent
2422
+ * Uses proper fields for each event type (market vs wallet)
2423
+ */
2424
+ mapMessageToStrategyEvent(message) {
2425
+ const timestamp = message.ts || Date.now();
2426
+ switch (message.type) {
2427
+ case "market.analysis":
2428
+ return {
2429
+ type: "price",
2430
+ // Treating analysis as 'price/market' update
2431
+ market: message.slug,
2432
+ timestamp,
2433
+ data: {
2434
+ ...message.data,
2435
+ raw: message
2436
+ }
2437
+ };
2438
+ case "wallet.analysis":
2439
+ return {
2440
+ type: "wallet",
2441
+ wallet: message.address,
2442
+ // Use wallet field, not market
2443
+ timestamp,
2444
+ data: {
2445
+ ...message.data,
2446
+ raw: message
2447
+ }
2448
+ };
2449
+ case "opportunity":
2450
+ return {
2451
+ type: "opportunity",
2452
+ market: message.slug,
2453
+ timestamp,
2454
+ data: {
2455
+ ...message.data,
2456
+ raw: message
2457
+ }
2458
+ };
2459
+ default:
2460
+ if (message.type && message.data) {
2461
+ return {
2462
+ type: "custom",
2463
+ market: message.slug || message.topic,
2464
+ timestamp,
2465
+ data: {
2466
+ ...message.data,
2467
+ raw: message
2468
+ }
2469
+ };
2470
+ }
2471
+ return null;
2472
+ }
2473
+ }
2012
2474
  /**
2013
2475
  * Validate that a message is a valid StrategyEvent
2014
2476
  */
2015
2477
  isValidEvent(message) {
2016
- return message && typeof message === "object" && typeof message.type === "string" && typeof message.timestamp === "number" && typeof message.data === "object";
2478
+ return message && typeof message === "object" && typeof message.type === "string";
2017
2479
  }
2018
2480
  /**
2019
2481
  * Dispatch an event to the strategy's onEvent handler
2020
2482
  */
2021
2483
  async dispatchEvent(event) {
2022
2484
  const eventStart = Date.now();
2485
+ const target = event.market || event.wallet || "";
2023
2486
  this.config.logger.debug(
2024
- `Dispatching ${event.type} event${event.market ? ` for ${event.market}` : ""}`
2487
+ `Dispatching ${event.type} event${target ? ` for ${target}` : ""}`
2025
2488
  );
2026
2489
  try {
2027
2490
  if (this.strategy.onEvent) {
@@ -2038,7 +2501,29 @@ var EventRunner = class {
2038
2501
  }
2039
2502
  };
2040
2503
  function createEventRunner(strategy, config, context) {
2041
- return new EventRunner(strategy, config, context);
2504
+ const hasPolymarketSubscription = strategy.subscriptions?.some(
2505
+ (sub) => sub.eventSource === "polymarket"
2506
+ );
2507
+ const hasOsirisSubscription = strategy.subscriptions?.some(
2508
+ (sub) => !sub.eventSource || sub.eventSource === "osiris"
2509
+ );
2510
+ if (hasPolymarketSubscription) {
2511
+ if (hasOsirisSubscription) {
2512
+ config.logger.warning(
2513
+ "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."
2514
+ );
2515
+ }
2516
+ return createPolymarketEventRunner(strategy, {
2517
+ logger: config.logger,
2518
+ strategyId: config.strategyId,
2519
+ clobAuth: config.polymarket?.clobAuth,
2520
+ walletAddress: config.polymarket?.walletAddress,
2521
+ pingIntervalMs: config.polymarket?.pingIntervalMs,
2522
+ reconnect: config.reconnect
2523
+ }, context);
2524
+ }
2525
+ if (!config.eventSourceUrl) ;
2526
+ return new OsirisEventRunner(strategy, config, context);
2042
2527
  }
2043
2528
  var CHAIN_MAP = {
2044
2529
  "evm:eip155:1": mainnet,
@@ -2190,6 +2675,6 @@ var Signer = class {
2190
2675
  }
2191
2676
  };
2192
2677
 
2193
- export { EventRunner, HyperliquidClient, MemoryStateManager, OsirisSigner, PolymarketClient, PrivateKeySigner, RedisStateManager, Signer, StrategyEngine, createConsoleLogger, createEventRunner, createOsirisContext, createStrategyEngine, isEventBasedStrategy, isTickBasedStrategy, runStrategy, validateStrategy };
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 };
2194
2679
  //# sourceMappingURL=index.js.map
2195
2680
  //# sourceMappingURL=index.js.map