@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/README.md +339 -209
- package/dist/clients/polymarket-client.d.ts +1 -18
- package/dist/engine/event-runner.d.ts +33 -3
- package/dist/engine/polymarket-event-runner.d.ts +205 -0
- package/dist/engine/strategy-runner.d.ts +3 -4
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1738 -204
- package/dist/rtds/index.d.ts +9 -0
- package/dist/rtds/osiris-rtds.service.d.ts +134 -0
- package/dist/rtds/polymarket-rtds.service.d.ts +117 -0
- package/dist/rtds/unified-rtds.service.d.ts +146 -0
- package/dist/types/event-types.d.ts +281 -11
- package/dist/types/gamma.d.ts +135 -0
- package/dist/types/osiris.d.ts +3 -40
- package/dist/utils/index.d.ts +3 -0
- package/package.json +6 -4
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 {
|
|
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 '@
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
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(
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
1342
|
-
|
|
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
|
|
1698
|
+
config.logger?.error(`Error executing strategy: ${error.message}`, error);
|
|
1724
1699
|
throw error;
|
|
1725
1700
|
}
|
|
1726
1701
|
}
|
|
1727
|
-
function createStrategyEngine(strategy,
|
|
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
|
|
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
|
|
1821
|
+
* Connects to Polymarket RTDS and subscribes to events
|
|
1833
1822
|
*/
|
|
1834
1823
|
start() {
|
|
1835
1824
|
if (this.isRunning) {
|
|
1836
|
-
this.config.logger.warning("
|
|
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
|
|
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("
|
|
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
|
|
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(
|
|
1888
|
+
this.ws = new WebSocket(POLYMARKET_RTDS_URL);
|
|
1896
1889
|
this.ws.on("open", () => {
|
|
1897
|
-
this.config.logger.info("Connected to
|
|
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
|
|
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:
|
|
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 ${
|
|
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:
|
|
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"
|
|
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.
|
|
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.
|
|
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
|
|
2198
|
+
* Validate that a message is a valid Polymarket RTDS message
|
|
2014
2199
|
*/
|
|
2015
|
-
|
|
2016
|
-
return message && typeof message === "object" && typeof message.type === "string" && typeof message.timestamp === "number" && typeof message.
|
|
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
|
|
2041
|
-
return new
|
|
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 {
|
|
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
|