@catalyst-team/poly-sdk 0.2.1 → 0.3.0
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 +545 -812
- package/README.zh-CN.md +645 -342
- package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts +12 -0
- package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/arbitrage-service.integration.test.js +267 -0
- package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
- package/dist/__tests__/integration/data-api.integration.test.js +6 -3
- package/dist/__tests__/integration/data-api.integration.test.js.map +1 -1
- package/dist/__tests__/integration/market-service.integration.test.d.ts +10 -0
- package/dist/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/market-service.integration.test.js +173 -0
- package/dist/__tests__/integration/market-service.integration.test.js.map +1 -0
- package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts +10 -0
- package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realtime-service-v2.integration.test.js +307 -0
- package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
- package/dist/__tests__/integration/trading-service.integration.test.d.ts +10 -0
- package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/trading-service.integration.test.js +58 -0
- package/dist/__tests__/integration/trading-service.integration.test.js.map +1 -0
- package/dist/clients/clob-api.d.ts +73 -0
- package/dist/clients/clob-api.d.ts.map +1 -1
- package/dist/clients/clob-api.js +60 -0
- package/dist/clients/clob-api.js.map +1 -1
- package/dist/clients/data-api.d.ts +319 -14
- package/dist/clients/data-api.d.ts.map +1 -1
- package/dist/clients/data-api.js +342 -15
- package/dist/clients/data-api.js.map +1 -1
- package/dist/clients/subgraph.d.ts +196 -0
- package/dist/clients/subgraph.d.ts.map +1 -0
- package/dist/clients/subgraph.js +332 -0
- package/dist/clients/subgraph.js.map +1 -0
- package/dist/clients/websocket-manager.d.ts +3 -0
- package/dist/clients/websocket-manager.d.ts.map +1 -1
- package/dist/clients/websocket-manager.js +10 -3
- package/dist/clients/websocket-manager.js.map +1 -1
- package/dist/core/cache.d.ts +1 -0
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +1 -0
- package/dist/core/cache.js.map +1 -1
- package/dist/core/errors.d.ts +2 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +2 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/rate-limiter.d.ts +2 -1
- package/dist/core/rate-limiter.d.ts.map +1 -1
- package/dist/core/rate-limiter.js +5 -0
- package/dist/core/rate-limiter.js.map +1 -1
- package/dist/core/types.d.ts +100 -12
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/types.test.d.ts +7 -0
- package/dist/core/types.test.d.ts.map +1 -0
- package/dist/core/types.test.js +122 -0
- package/dist/core/types.test.js.map +1 -0
- package/dist/index.d.ts +76 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -132
- package/dist/index.js.map +1 -1
- package/dist/services/arbitrage-service.d.ts +3 -2
- package/dist/services/arbitrage-service.d.ts.map +1 -1
- package/dist/services/arbitrage-service.js +58 -40
- package/dist/services/arbitrage-service.js.map +1 -1
- package/dist/services/market-service.d.ts +108 -8
- package/dist/services/market-service.d.ts.map +1 -1
- package/dist/services/market-service.js +352 -36
- package/dist/services/market-service.js.map +1 -1
- package/dist/services/onchain-service.d.ts +309 -0
- package/dist/services/onchain-service.d.ts.map +1 -0
- package/dist/services/onchain-service.js +417 -0
- package/dist/services/onchain-service.js.map +1 -0
- package/dist/services/realtime-service-v2.d.ts +361 -0
- package/dist/services/realtime-service-v2.d.ts.map +1 -0
- package/dist/services/realtime-service-v2.js +840 -0
- package/dist/services/realtime-service-v2.js.map +1 -0
- package/dist/services/realtime-service.d.ts +17 -17
- package/dist/services/realtime-service.d.ts.map +1 -1
- package/dist/services/realtime-service.js +91 -59
- package/dist/services/realtime-service.js.map +1 -1
- package/dist/services/smart-money-service.d.ts +196 -0
- package/dist/services/smart-money-service.d.ts.map +1 -0
- package/dist/services/smart-money-service.js +358 -0
- package/dist/services/smart-money-service.js.map +1 -0
- package/dist/services/trading-service.d.ts +156 -0
- package/dist/services/trading-service.d.ts.map +1 -0
- package/dist/services/trading-service.js +356 -0
- package/dist/services/trading-service.js.map +1 -0
- package/dist/services/wallet-service.d.ts +183 -2
- package/dist/services/wallet-service.d.ts.map +1 -1
- package/dist/services/wallet-service.js +458 -1
- package/dist/services/wallet-service.js.map +1 -1
- package/dist/utils/price-utils.test.d.ts +5 -0
- package/dist/utils/price-utils.test.d.ts.map +1 -0
- package/dist/utils/price-utils.test.js +192 -0
- package/dist/utils/price-utils.test.js.map +1 -0
- package/package.json +4 -3
- package/README.en.md +0 -502
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealtimeService V2
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive real-time data service using official @polymarket/real-time-data-client.
|
|
5
|
+
*
|
|
6
|
+
* Supports ALL available topics:
|
|
7
|
+
* - clob_market: price_change, agg_orderbook, last_trade_price, tick_size_change, market_created, market_resolved
|
|
8
|
+
* - clob_user: order, trade (requires authentication)
|
|
9
|
+
* - activity: trades, orders_matched
|
|
10
|
+
* - crypto_prices: update (BTC, ETH, etc.)
|
|
11
|
+
* - equity_prices: update (AAPL, etc.)
|
|
12
|
+
* - comments: comment_created, comment_removed, reaction_created, reaction_removed
|
|
13
|
+
* - rfq: request_*, quote_*
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import { RealTimeDataClient, ConnectionStatus, } from '@polymarket/real-time-data-client';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// RealtimeServiceV2 Implementation
|
|
19
|
+
// ============================================================================
|
|
20
|
+
export class RealtimeServiceV2 extends EventEmitter {
|
|
21
|
+
client = null;
|
|
22
|
+
config;
|
|
23
|
+
subscriptions = new Map();
|
|
24
|
+
subscriptionIdCounter = 0;
|
|
25
|
+
connected = false;
|
|
26
|
+
// Caches
|
|
27
|
+
priceCache = new Map();
|
|
28
|
+
bookCache = new Map();
|
|
29
|
+
lastTradeCache = new Map();
|
|
30
|
+
constructor(config = {}) {
|
|
31
|
+
super();
|
|
32
|
+
this.config = {
|
|
33
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
34
|
+
pingInterval: config.pingInterval ?? 5000,
|
|
35
|
+
debug: config.debug ?? false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Connection Management
|
|
40
|
+
// ============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* Connect to WebSocket server
|
|
43
|
+
*/
|
|
44
|
+
connect() {
|
|
45
|
+
if (this.client) {
|
|
46
|
+
this.log('Already connected or connecting');
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
this.client = new RealTimeDataClient({
|
|
50
|
+
onConnect: this.handleConnect.bind(this),
|
|
51
|
+
onMessage: this.handleMessage.bind(this),
|
|
52
|
+
onStatusChange: this.handleStatusChange.bind(this),
|
|
53
|
+
autoReconnect: this.config.autoReconnect,
|
|
54
|
+
pingInterval: this.config.pingInterval,
|
|
55
|
+
});
|
|
56
|
+
this.client.connect();
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Disconnect from WebSocket server
|
|
61
|
+
*/
|
|
62
|
+
disconnect() {
|
|
63
|
+
if (this.client) {
|
|
64
|
+
this.client.disconnect();
|
|
65
|
+
this.client = null;
|
|
66
|
+
this.connected = false;
|
|
67
|
+
this.subscriptions.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if connected
|
|
72
|
+
*/
|
|
73
|
+
isConnected() {
|
|
74
|
+
return this.connected;
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Market Data Subscriptions (clob_market)
|
|
78
|
+
// ============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Subscribe to market data (orderbook, prices, trades)
|
|
81
|
+
* @param tokenIds - Array of token IDs to subscribe to
|
|
82
|
+
* @param handlers - Event handlers
|
|
83
|
+
*/
|
|
84
|
+
subscribeMarkets(tokenIds, handlers = {}) {
|
|
85
|
+
const subId = `market_${++this.subscriptionIdCounter}`;
|
|
86
|
+
const filterStr = JSON.stringify(tokenIds);
|
|
87
|
+
// Subscribe to all market data types
|
|
88
|
+
const subscriptions = [
|
|
89
|
+
{ topic: 'clob_market', type: 'agg_orderbook', filters: filterStr },
|
|
90
|
+
{ topic: 'clob_market', type: 'price_change', filters: filterStr },
|
|
91
|
+
{ topic: 'clob_market', type: 'last_trade_price', filters: filterStr },
|
|
92
|
+
{ topic: 'clob_market', type: 'tick_size_change', filters: filterStr },
|
|
93
|
+
];
|
|
94
|
+
this.sendSubscription({ subscriptions });
|
|
95
|
+
// Register handlers
|
|
96
|
+
const orderbookHandler = (book) => {
|
|
97
|
+
if (tokenIds.includes(book.assetId)) {
|
|
98
|
+
handlers.onOrderbook?.(book);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const priceChangeHandler = (change) => {
|
|
102
|
+
if (tokenIds.includes(change.assetId)) {
|
|
103
|
+
handlers.onPriceChange?.(change);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const lastTradeHandler = (trade) => {
|
|
107
|
+
if (tokenIds.includes(trade.assetId)) {
|
|
108
|
+
handlers.onLastTrade?.(trade);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const tickSizeHandler = (change) => {
|
|
112
|
+
if (tokenIds.includes(change.assetId)) {
|
|
113
|
+
handlers.onTickSizeChange?.(change);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
this.on('orderbook', orderbookHandler);
|
|
117
|
+
this.on('priceChange', priceChangeHandler);
|
|
118
|
+
this.on('lastTrade', lastTradeHandler);
|
|
119
|
+
this.on('tickSizeChange', tickSizeHandler);
|
|
120
|
+
const subscription = {
|
|
121
|
+
id: subId,
|
|
122
|
+
topic: 'clob_market',
|
|
123
|
+
type: '*',
|
|
124
|
+
tokenIds,
|
|
125
|
+
unsubscribe: () => {
|
|
126
|
+
this.off('orderbook', orderbookHandler);
|
|
127
|
+
this.off('priceChange', priceChangeHandler);
|
|
128
|
+
this.off('lastTrade', lastTradeHandler);
|
|
129
|
+
this.off('tickSizeChange', tickSizeHandler);
|
|
130
|
+
this.sendUnsubscription({ subscriptions });
|
|
131
|
+
this.subscriptions.delete(subId);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
this.subscriptions.set(subId, subscription);
|
|
135
|
+
return subscription;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe to a single market (YES + NO tokens)
|
|
139
|
+
* Also emits derived price updates compatible with old API
|
|
140
|
+
*/
|
|
141
|
+
subscribeMarket(yesTokenId, noTokenId, handlers = {}) {
|
|
142
|
+
let lastYesUpdate;
|
|
143
|
+
let lastNoUpdate;
|
|
144
|
+
const checkPairUpdate = () => {
|
|
145
|
+
if (lastYesUpdate && lastNoUpdate && handlers.onPairUpdate) {
|
|
146
|
+
handlers.onPairUpdate({
|
|
147
|
+
yes: lastYesUpdate,
|
|
148
|
+
no: lastNoUpdate,
|
|
149
|
+
spread: lastYesUpdate.price + lastNoUpdate.price,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return this.subscribeMarkets([yesTokenId, noTokenId], {
|
|
154
|
+
onOrderbook: (book) => {
|
|
155
|
+
handlers.onOrderbook?.(book);
|
|
156
|
+
// Convert to BookUpdate for backward compatibility
|
|
157
|
+
if (handlers.onBookUpdate) {
|
|
158
|
+
const bookUpdate = {
|
|
159
|
+
assetId: book.assetId,
|
|
160
|
+
bids: book.bids,
|
|
161
|
+
asks: book.asks,
|
|
162
|
+
timestamp: book.timestamp,
|
|
163
|
+
};
|
|
164
|
+
handlers.onBookUpdate(bookUpdate);
|
|
165
|
+
}
|
|
166
|
+
// Calculate derived price (Polymarket display logic)
|
|
167
|
+
const priceUpdate = this.calculateDerivedPrice(book.assetId, book);
|
|
168
|
+
if (priceUpdate) {
|
|
169
|
+
this.priceCache.set(book.assetId, priceUpdate);
|
|
170
|
+
if (book.assetId === yesTokenId) {
|
|
171
|
+
lastYesUpdate = priceUpdate;
|
|
172
|
+
}
|
|
173
|
+
else if (book.assetId === noTokenId) {
|
|
174
|
+
lastNoUpdate = priceUpdate;
|
|
175
|
+
}
|
|
176
|
+
handlers.onPriceUpdate?.(priceUpdate);
|
|
177
|
+
this.emit('priceUpdate', priceUpdate);
|
|
178
|
+
checkPairUpdate();
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
onLastTrade: (trade) => {
|
|
182
|
+
handlers.onLastTrade?.(trade);
|
|
183
|
+
this.lastTradeCache.set(trade.assetId, trade);
|
|
184
|
+
// Recalculate derived price with new last trade
|
|
185
|
+
const book = this.bookCache.get(trade.assetId);
|
|
186
|
+
if (book) {
|
|
187
|
+
const priceUpdate = this.calculateDerivedPrice(trade.assetId, book);
|
|
188
|
+
if (priceUpdate) {
|
|
189
|
+
this.priceCache.set(trade.assetId, priceUpdate);
|
|
190
|
+
if (trade.assetId === yesTokenId) {
|
|
191
|
+
lastYesUpdate = priceUpdate;
|
|
192
|
+
}
|
|
193
|
+
else if (trade.assetId === noTokenId) {
|
|
194
|
+
lastNoUpdate = priceUpdate;
|
|
195
|
+
}
|
|
196
|
+
handlers.onPriceUpdate?.(priceUpdate);
|
|
197
|
+
this.emit('priceUpdate', priceUpdate);
|
|
198
|
+
checkPairUpdate();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
onPriceChange: handlers.onPriceChange,
|
|
203
|
+
onTickSizeChange: handlers.onTickSizeChange,
|
|
204
|
+
onError: handlers.onError,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Subscribe to market lifecycle events (creation, resolution)
|
|
209
|
+
*/
|
|
210
|
+
subscribeMarketEvents(handlers) {
|
|
211
|
+
const subId = `market_event_${++this.subscriptionIdCounter}`;
|
|
212
|
+
const subscriptions = [
|
|
213
|
+
{ topic: 'clob_market', type: 'market_created' },
|
|
214
|
+
{ topic: 'clob_market', type: 'market_resolved' },
|
|
215
|
+
];
|
|
216
|
+
this.sendSubscription({ subscriptions });
|
|
217
|
+
const handler = (event) => handlers.onMarketEvent?.(event);
|
|
218
|
+
this.on('marketEvent', handler);
|
|
219
|
+
const subscription = {
|
|
220
|
+
id: subId,
|
|
221
|
+
topic: 'clob_market',
|
|
222
|
+
type: 'lifecycle',
|
|
223
|
+
unsubscribe: () => {
|
|
224
|
+
this.off('marketEvent', handler);
|
|
225
|
+
this.sendUnsubscription({ subscriptions });
|
|
226
|
+
this.subscriptions.delete(subId);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
this.subscriptions.set(subId, subscription);
|
|
230
|
+
return subscription;
|
|
231
|
+
}
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// User Data Subscriptions (clob_user) - Requires Authentication
|
|
234
|
+
// ============================================================================
|
|
235
|
+
/**
|
|
236
|
+
* Subscribe to user order and trade events
|
|
237
|
+
* @param credentials - CLOB API credentials
|
|
238
|
+
* @param handlers - Event handlers
|
|
239
|
+
*/
|
|
240
|
+
subscribeUserEvents(credentials, handlers = {}) {
|
|
241
|
+
const subId = `user_${++this.subscriptionIdCounter}`;
|
|
242
|
+
const subscriptions = [
|
|
243
|
+
{ topic: 'clob_user', type: '*', clob_auth: credentials },
|
|
244
|
+
];
|
|
245
|
+
this.sendSubscription({ subscriptions });
|
|
246
|
+
const orderHandler = (order) => handlers.onOrder?.(order);
|
|
247
|
+
const tradeHandler = (trade) => handlers.onTrade?.(trade);
|
|
248
|
+
this.on('userOrder', orderHandler);
|
|
249
|
+
this.on('userTrade', tradeHandler);
|
|
250
|
+
const subscription = {
|
|
251
|
+
id: subId,
|
|
252
|
+
topic: 'clob_user',
|
|
253
|
+
type: '*',
|
|
254
|
+
unsubscribe: () => {
|
|
255
|
+
this.off('userOrder', orderHandler);
|
|
256
|
+
this.off('userTrade', tradeHandler);
|
|
257
|
+
this.sendUnsubscription({ subscriptions });
|
|
258
|
+
this.subscriptions.delete(subId);
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
this.subscriptions.set(subId, subscription);
|
|
262
|
+
return subscription;
|
|
263
|
+
}
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// Activity Subscriptions (trades, orders_matched)
|
|
266
|
+
// ============================================================================
|
|
267
|
+
/**
|
|
268
|
+
* Subscribe to trading activity for a market or event
|
|
269
|
+
* @param filter - Event or market slug (optional - if empty, subscribes to all activity)
|
|
270
|
+
* @param handlers - Event handlers
|
|
271
|
+
*/
|
|
272
|
+
subscribeActivity(filter = {}, handlers = {}) {
|
|
273
|
+
const subId = `activity_${++this.subscriptionIdCounter}`;
|
|
274
|
+
// Build filter object with snake_case keys (as expected by the server)
|
|
275
|
+
// Only include filters if we have actual filter values
|
|
276
|
+
const hasFilter = filter.eventSlug || filter.marketSlug;
|
|
277
|
+
const filterObj = {};
|
|
278
|
+
if (filter.eventSlug)
|
|
279
|
+
filterObj.event_slug = filter.eventSlug;
|
|
280
|
+
if (filter.marketSlug)
|
|
281
|
+
filterObj.market_slug = filter.marketSlug;
|
|
282
|
+
// Create subscription objects - only include filters field if we have filters
|
|
283
|
+
const subscriptions = hasFilter
|
|
284
|
+
? [
|
|
285
|
+
{ topic: 'activity', type: 'trades', filters: JSON.stringify(filterObj) },
|
|
286
|
+
{ topic: 'activity', type: 'orders_matched', filters: JSON.stringify(filterObj) },
|
|
287
|
+
]
|
|
288
|
+
: [
|
|
289
|
+
{ topic: 'activity', type: 'trades' },
|
|
290
|
+
{ topic: 'activity', type: 'orders_matched' },
|
|
291
|
+
];
|
|
292
|
+
this.sendSubscription({ subscriptions });
|
|
293
|
+
const handler = (trade) => handlers.onTrade?.(trade);
|
|
294
|
+
this.on('activityTrade', handler);
|
|
295
|
+
const subscription = {
|
|
296
|
+
id: subId,
|
|
297
|
+
topic: 'activity',
|
|
298
|
+
type: '*',
|
|
299
|
+
unsubscribe: () => {
|
|
300
|
+
this.off('activityTrade', handler);
|
|
301
|
+
this.sendUnsubscription({ subscriptions });
|
|
302
|
+
this.subscriptions.delete(subId);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
this.subscriptions.set(subId, subscription);
|
|
306
|
+
return subscription;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Subscribe to ALL trading activity across all markets (no filtering)
|
|
310
|
+
* This is useful for Copy Trading - monitoring Smart Money across the platform
|
|
311
|
+
* @param handlers - Event handlers
|
|
312
|
+
*/
|
|
313
|
+
subscribeAllActivity(handlers = {}) {
|
|
314
|
+
return this.subscribeActivity({}, handlers);
|
|
315
|
+
}
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Crypto Price Subscriptions
|
|
318
|
+
// ============================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Subscribe to crypto price updates
|
|
321
|
+
* @param symbols - Array of symbols (e.g., ['BTCUSDT', 'ETHUSDT'])
|
|
322
|
+
* @param handlers - Event handlers
|
|
323
|
+
*/
|
|
324
|
+
subscribeCryptoPrices(symbols, handlers = {}) {
|
|
325
|
+
const subId = `crypto_${++this.subscriptionIdCounter}`;
|
|
326
|
+
// Subscribe to each symbol
|
|
327
|
+
const subscriptions = symbols.map(symbol => ({
|
|
328
|
+
topic: 'crypto_prices',
|
|
329
|
+
type: 'update',
|
|
330
|
+
filters: JSON.stringify({ symbol }),
|
|
331
|
+
}));
|
|
332
|
+
this.sendSubscription({ subscriptions });
|
|
333
|
+
const handler = (price) => {
|
|
334
|
+
if (symbols.includes(price.symbol)) {
|
|
335
|
+
handlers.onPrice?.(price);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
this.on('cryptoPrice', handler);
|
|
339
|
+
const subscription = {
|
|
340
|
+
id: subId,
|
|
341
|
+
topic: 'crypto_prices',
|
|
342
|
+
type: 'update',
|
|
343
|
+
unsubscribe: () => {
|
|
344
|
+
this.off('cryptoPrice', handler);
|
|
345
|
+
this.sendUnsubscription({ subscriptions });
|
|
346
|
+
this.subscriptions.delete(subId);
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
this.subscriptions.set(subId, subscription);
|
|
350
|
+
return subscription;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Subscribe to Chainlink crypto prices
|
|
354
|
+
* @param symbols - Array of symbols (e.g., ['ETH/USD', 'BTC/USD'])
|
|
355
|
+
*/
|
|
356
|
+
subscribeCryptoChainlinkPrices(symbols, handlers = {}) {
|
|
357
|
+
const subId = `crypto_chainlink_${++this.subscriptionIdCounter}`;
|
|
358
|
+
const subscriptions = symbols.map(symbol => ({
|
|
359
|
+
topic: 'crypto_prices_chainlink',
|
|
360
|
+
type: 'update',
|
|
361
|
+
filters: JSON.stringify({ symbol }),
|
|
362
|
+
}));
|
|
363
|
+
this.sendSubscription({ subscriptions });
|
|
364
|
+
const handler = (price) => {
|
|
365
|
+
if (symbols.includes(price.symbol)) {
|
|
366
|
+
handlers.onPrice?.(price);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
this.on('cryptoChainlinkPrice', handler);
|
|
370
|
+
const subscription = {
|
|
371
|
+
id: subId,
|
|
372
|
+
topic: 'crypto_prices_chainlink',
|
|
373
|
+
type: 'update',
|
|
374
|
+
unsubscribe: () => {
|
|
375
|
+
this.off('cryptoChainlinkPrice', handler);
|
|
376
|
+
this.sendUnsubscription({ subscriptions });
|
|
377
|
+
this.subscriptions.delete(subId);
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
this.subscriptions.set(subId, subscription);
|
|
381
|
+
return subscription;
|
|
382
|
+
}
|
|
383
|
+
// ============================================================================
|
|
384
|
+
// Equity Price Subscriptions
|
|
385
|
+
// ============================================================================
|
|
386
|
+
/**
|
|
387
|
+
* Subscribe to equity price updates
|
|
388
|
+
* @param symbols - Array of symbols (e.g., ['AAPL', 'GOOGL'])
|
|
389
|
+
* @param handlers - Event handlers
|
|
390
|
+
*/
|
|
391
|
+
subscribeEquityPrices(symbols, handlers = {}) {
|
|
392
|
+
const subId = `equity_${++this.subscriptionIdCounter}`;
|
|
393
|
+
const subscriptions = symbols.map(symbol => ({
|
|
394
|
+
topic: 'equity_prices',
|
|
395
|
+
type: 'update',
|
|
396
|
+
filters: JSON.stringify({ symbol }),
|
|
397
|
+
}));
|
|
398
|
+
this.sendSubscription({ subscriptions });
|
|
399
|
+
const handler = (price) => {
|
|
400
|
+
if (symbols.includes(price.symbol)) {
|
|
401
|
+
handlers.onPrice?.(price);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
this.on('equityPrice', handler);
|
|
405
|
+
const subscription = {
|
|
406
|
+
id: subId,
|
|
407
|
+
topic: 'equity_prices',
|
|
408
|
+
type: 'update',
|
|
409
|
+
unsubscribe: () => {
|
|
410
|
+
this.off('equityPrice', handler);
|
|
411
|
+
this.sendUnsubscription({ subscriptions });
|
|
412
|
+
this.subscriptions.delete(subId);
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
this.subscriptions.set(subId, subscription);
|
|
416
|
+
return subscription;
|
|
417
|
+
}
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Comments Subscriptions
|
|
420
|
+
// ============================================================================
|
|
421
|
+
/**
|
|
422
|
+
* Subscribe to comment and reaction events
|
|
423
|
+
*/
|
|
424
|
+
subscribeComments(filter, handlers = {}) {
|
|
425
|
+
const subId = `comments_${++this.subscriptionIdCounter}`;
|
|
426
|
+
const filterStr = JSON.stringify({
|
|
427
|
+
parentEntityID: filter.parentEntityId,
|
|
428
|
+
parentEntityType: filter.parentEntityType,
|
|
429
|
+
});
|
|
430
|
+
const subscriptions = [
|
|
431
|
+
{ topic: 'comments', type: 'comment_created', filters: filterStr },
|
|
432
|
+
{ topic: 'comments', type: 'comment_removed', filters: filterStr },
|
|
433
|
+
{ topic: 'comments', type: 'reaction_created', filters: filterStr },
|
|
434
|
+
{ topic: 'comments', type: 'reaction_removed', filters: filterStr },
|
|
435
|
+
];
|
|
436
|
+
this.sendSubscription({ subscriptions });
|
|
437
|
+
const commentHandler = (comment) => handlers.onComment?.(comment);
|
|
438
|
+
const reactionHandler = (reaction) => handlers.onReaction?.(reaction);
|
|
439
|
+
this.on('comment', commentHandler);
|
|
440
|
+
this.on('reaction', reactionHandler);
|
|
441
|
+
const subscription = {
|
|
442
|
+
id: subId,
|
|
443
|
+
topic: 'comments',
|
|
444
|
+
type: '*',
|
|
445
|
+
unsubscribe: () => {
|
|
446
|
+
this.off('comment', commentHandler);
|
|
447
|
+
this.off('reaction', reactionHandler);
|
|
448
|
+
this.sendUnsubscription({ subscriptions });
|
|
449
|
+
this.subscriptions.delete(subId);
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
this.subscriptions.set(subId, subscription);
|
|
453
|
+
return subscription;
|
|
454
|
+
}
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// RFQ Subscriptions
|
|
457
|
+
// ============================================================================
|
|
458
|
+
/**
|
|
459
|
+
* Subscribe to RFQ (Request for Quote) events
|
|
460
|
+
*/
|
|
461
|
+
subscribeRFQ(handlers = {}) {
|
|
462
|
+
const subId = `rfq_${++this.subscriptionIdCounter}`;
|
|
463
|
+
const subscriptions = [
|
|
464
|
+
{ topic: 'rfq', type: 'request_created' },
|
|
465
|
+
{ topic: 'rfq', type: 'request_edited' },
|
|
466
|
+
{ topic: 'rfq', type: 'request_canceled' },
|
|
467
|
+
{ topic: 'rfq', type: 'request_expired' },
|
|
468
|
+
{ topic: 'rfq', type: 'quote_created' },
|
|
469
|
+
{ topic: 'rfq', type: 'quote_edited' },
|
|
470
|
+
{ topic: 'rfq', type: 'quote_canceled' },
|
|
471
|
+
{ topic: 'rfq', type: 'quote_expired' },
|
|
472
|
+
];
|
|
473
|
+
this.sendSubscription({ subscriptions });
|
|
474
|
+
const requestHandler = (request) => handlers.onRequest?.(request);
|
|
475
|
+
const quoteHandler = (quote) => handlers.onQuote?.(quote);
|
|
476
|
+
this.on('rfqRequest', requestHandler);
|
|
477
|
+
this.on('rfqQuote', quoteHandler);
|
|
478
|
+
const subscription = {
|
|
479
|
+
id: subId,
|
|
480
|
+
topic: 'rfq',
|
|
481
|
+
type: '*',
|
|
482
|
+
unsubscribe: () => {
|
|
483
|
+
this.off('rfqRequest', requestHandler);
|
|
484
|
+
this.off('rfqQuote', quoteHandler);
|
|
485
|
+
this.sendUnsubscription({ subscriptions });
|
|
486
|
+
this.subscriptions.delete(subId);
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
this.subscriptions.set(subId, subscription);
|
|
490
|
+
return subscription;
|
|
491
|
+
}
|
|
492
|
+
// ============================================================================
|
|
493
|
+
// Cache Access
|
|
494
|
+
// ============================================================================
|
|
495
|
+
/**
|
|
496
|
+
* Get cached derived price for an asset
|
|
497
|
+
*/
|
|
498
|
+
getPrice(assetId) {
|
|
499
|
+
return this.priceCache.get(assetId);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get all cached prices
|
|
503
|
+
*/
|
|
504
|
+
getAllPrices() {
|
|
505
|
+
return new Map(this.priceCache);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Get cached orderbook for an asset
|
|
509
|
+
*/
|
|
510
|
+
getBook(assetId) {
|
|
511
|
+
return this.bookCache.get(assetId);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get cached last trade for an asset
|
|
515
|
+
*/
|
|
516
|
+
getLastTrade(assetId) {
|
|
517
|
+
return this.lastTradeCache.get(assetId);
|
|
518
|
+
}
|
|
519
|
+
// ============================================================================
|
|
520
|
+
// Subscription Management
|
|
521
|
+
// ============================================================================
|
|
522
|
+
/**
|
|
523
|
+
* Get all active subscriptions
|
|
524
|
+
*/
|
|
525
|
+
getActiveSubscriptions() {
|
|
526
|
+
return Array.from(this.subscriptions.values());
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Unsubscribe from all
|
|
530
|
+
*/
|
|
531
|
+
unsubscribeAll() {
|
|
532
|
+
for (const sub of this.subscriptions.values()) {
|
|
533
|
+
sub.unsubscribe();
|
|
534
|
+
}
|
|
535
|
+
this.subscriptions.clear();
|
|
536
|
+
}
|
|
537
|
+
// ============================================================================
|
|
538
|
+
// Private Methods
|
|
539
|
+
// ============================================================================
|
|
540
|
+
handleConnect(client) {
|
|
541
|
+
this.connected = true;
|
|
542
|
+
this.log('Connected to WebSocket server');
|
|
543
|
+
this.emit('connected');
|
|
544
|
+
}
|
|
545
|
+
handleStatusChange(status) {
|
|
546
|
+
this.log(`Connection status: ${status}`);
|
|
547
|
+
if (status === ConnectionStatus.DISCONNECTED) {
|
|
548
|
+
this.connected = false;
|
|
549
|
+
this.emit('disconnected');
|
|
550
|
+
}
|
|
551
|
+
else if (status === ConnectionStatus.CONNECTED) {
|
|
552
|
+
this.connected = true;
|
|
553
|
+
}
|
|
554
|
+
this.emit('statusChange', status);
|
|
555
|
+
}
|
|
556
|
+
handleMessage(client, message) {
|
|
557
|
+
this.log(`Received: ${message.topic}:${message.type}`);
|
|
558
|
+
const payload = message.payload;
|
|
559
|
+
switch (message.topic) {
|
|
560
|
+
case 'clob_market':
|
|
561
|
+
this.handleMarketMessage(message.type, payload, message.timestamp);
|
|
562
|
+
break;
|
|
563
|
+
case 'clob_user':
|
|
564
|
+
this.handleUserMessage(message.type, payload, message.timestamp);
|
|
565
|
+
break;
|
|
566
|
+
case 'activity':
|
|
567
|
+
this.handleActivityMessage(message.type, payload, message.timestamp);
|
|
568
|
+
break;
|
|
569
|
+
case 'crypto_prices':
|
|
570
|
+
this.handleCryptoPriceMessage(payload, message.timestamp);
|
|
571
|
+
break;
|
|
572
|
+
case 'crypto_prices_chainlink':
|
|
573
|
+
this.handleCryptoChainlinkPriceMessage(payload, message.timestamp);
|
|
574
|
+
break;
|
|
575
|
+
case 'equity_prices':
|
|
576
|
+
this.handleEquityPriceMessage(payload, message.timestamp);
|
|
577
|
+
break;
|
|
578
|
+
case 'comments':
|
|
579
|
+
this.handleCommentMessage(message.type, payload, message.timestamp);
|
|
580
|
+
break;
|
|
581
|
+
case 'rfq':
|
|
582
|
+
this.handleRFQMessage(message.type, payload, message.timestamp);
|
|
583
|
+
break;
|
|
584
|
+
default:
|
|
585
|
+
this.log(`Unknown topic: ${message.topic}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
handleMarketMessage(type, payload, timestamp) {
|
|
589
|
+
switch (type) {
|
|
590
|
+
case 'agg_orderbook': {
|
|
591
|
+
const book = this.parseOrderbook(payload, timestamp);
|
|
592
|
+
this.bookCache.set(book.assetId, book);
|
|
593
|
+
this.emit('orderbook', book);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 'price_change': {
|
|
597
|
+
const change = this.parsePriceChange(payload, timestamp);
|
|
598
|
+
this.emit('priceChange', change);
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case 'last_trade_price': {
|
|
602
|
+
const trade = this.parseLastTrade(payload, timestamp);
|
|
603
|
+
this.lastTradeCache.set(trade.assetId, trade);
|
|
604
|
+
this.emit('lastTrade', trade);
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
case 'tick_size_change': {
|
|
608
|
+
const change = this.parseTickSizeChange(payload, timestamp);
|
|
609
|
+
this.emit('tickSizeChange', change);
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
case 'market_created':
|
|
613
|
+
case 'market_resolved': {
|
|
614
|
+
const event = {
|
|
615
|
+
conditionId: payload.condition_id || '',
|
|
616
|
+
type: type === 'market_created' ? 'created' : 'resolved',
|
|
617
|
+
data: payload,
|
|
618
|
+
timestamp,
|
|
619
|
+
};
|
|
620
|
+
this.emit('marketEvent', event);
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
handleUserMessage(type, payload, timestamp) {
|
|
626
|
+
if (type === 'order') {
|
|
627
|
+
const order = {
|
|
628
|
+
orderId: payload.order_id || '',
|
|
629
|
+
market: payload.market || '',
|
|
630
|
+
asset: payload.asset || '',
|
|
631
|
+
side: payload.side,
|
|
632
|
+
price: Number(payload.price) || 0,
|
|
633
|
+
originalSize: Number(payload.original_size) || 0,
|
|
634
|
+
matchedSize: Number(payload.matched_size) || 0,
|
|
635
|
+
eventType: payload.event_type,
|
|
636
|
+
timestamp,
|
|
637
|
+
};
|
|
638
|
+
this.emit('userOrder', order);
|
|
639
|
+
}
|
|
640
|
+
else if (type === 'trade') {
|
|
641
|
+
const trade = {
|
|
642
|
+
tradeId: payload.trade_id || '',
|
|
643
|
+
market: payload.market || '',
|
|
644
|
+
outcome: payload.outcome || '',
|
|
645
|
+
price: Number(payload.price) || 0,
|
|
646
|
+
size: Number(payload.size) || 0,
|
|
647
|
+
side: payload.side,
|
|
648
|
+
status: payload.status,
|
|
649
|
+
timestamp,
|
|
650
|
+
transactionHash: payload.transaction_hash,
|
|
651
|
+
};
|
|
652
|
+
this.emit('userTrade', trade);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
handleActivityMessage(type, payload, timestamp) {
|
|
656
|
+
const trade = {
|
|
657
|
+
asset: payload.asset || '',
|
|
658
|
+
conditionId: payload.conditionId || '',
|
|
659
|
+
eventSlug: payload.eventSlug || '',
|
|
660
|
+
marketSlug: payload.slug || '',
|
|
661
|
+
outcome: payload.outcome || '',
|
|
662
|
+
price: Number(payload.price) || 0,
|
|
663
|
+
side: payload.side,
|
|
664
|
+
size: Number(payload.size) || 0,
|
|
665
|
+
timestamp: Number(payload.timestamp) || timestamp,
|
|
666
|
+
transactionHash: payload.transactionHash || '',
|
|
667
|
+
trader: {
|
|
668
|
+
name: payload.name,
|
|
669
|
+
address: payload.proxyWallet,
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
this.emit('activityTrade', trade);
|
|
673
|
+
}
|
|
674
|
+
handleCryptoPriceMessage(payload, timestamp) {
|
|
675
|
+
const price = {
|
|
676
|
+
symbol: payload.symbol || '',
|
|
677
|
+
price: Number(payload.value) || 0,
|
|
678
|
+
timestamp: Number(payload.timestamp) || timestamp,
|
|
679
|
+
};
|
|
680
|
+
this.emit('cryptoPrice', price);
|
|
681
|
+
}
|
|
682
|
+
handleCryptoChainlinkPriceMessage(payload, timestamp) {
|
|
683
|
+
const price = {
|
|
684
|
+
symbol: payload.symbol || '',
|
|
685
|
+
price: Number(payload.value) || 0,
|
|
686
|
+
timestamp: Number(payload.timestamp) || timestamp,
|
|
687
|
+
};
|
|
688
|
+
this.emit('cryptoChainlinkPrice', price);
|
|
689
|
+
}
|
|
690
|
+
handleEquityPriceMessage(payload, timestamp) {
|
|
691
|
+
const price = {
|
|
692
|
+
symbol: payload.symbol || '',
|
|
693
|
+
price: Number(payload.value) || 0,
|
|
694
|
+
timestamp: Number(payload.timestamp) || timestamp,
|
|
695
|
+
};
|
|
696
|
+
this.emit('equityPrice', price);
|
|
697
|
+
}
|
|
698
|
+
handleCommentMessage(type, payload, timestamp) {
|
|
699
|
+
if (type.includes('comment')) {
|
|
700
|
+
const comment = {
|
|
701
|
+
id: payload.id || '',
|
|
702
|
+
parentEntityId: payload.parentEntityID || 0,
|
|
703
|
+
parentEntityType: payload.parentEntityType,
|
|
704
|
+
content: payload.content,
|
|
705
|
+
author: payload.author,
|
|
706
|
+
timestamp,
|
|
707
|
+
};
|
|
708
|
+
this.emit('comment', comment);
|
|
709
|
+
}
|
|
710
|
+
else if (type.includes('reaction')) {
|
|
711
|
+
const reaction = {
|
|
712
|
+
id: payload.id || '',
|
|
713
|
+
commentId: payload.commentId || '',
|
|
714
|
+
type: payload.type || '',
|
|
715
|
+
author: payload.author,
|
|
716
|
+
timestamp,
|
|
717
|
+
};
|
|
718
|
+
this.emit('reaction', reaction);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
handleRFQMessage(type, payload, timestamp) {
|
|
722
|
+
if (type.startsWith('request_')) {
|
|
723
|
+
const status = type.replace('request_', '');
|
|
724
|
+
const request = {
|
|
725
|
+
id: payload.id || '',
|
|
726
|
+
market: payload.market || '',
|
|
727
|
+
side: payload.side,
|
|
728
|
+
size: Number(payload.size) || 0,
|
|
729
|
+
status,
|
|
730
|
+
timestamp,
|
|
731
|
+
};
|
|
732
|
+
this.emit('rfqRequest', request);
|
|
733
|
+
}
|
|
734
|
+
else if (type.startsWith('quote_')) {
|
|
735
|
+
const status = type.replace('quote_', '');
|
|
736
|
+
const quote = {
|
|
737
|
+
id: payload.id || '',
|
|
738
|
+
requestId: payload.request_id || '',
|
|
739
|
+
price: Number(payload.price) || 0,
|
|
740
|
+
size: Number(payload.size) || 0,
|
|
741
|
+
status,
|
|
742
|
+
timestamp,
|
|
743
|
+
};
|
|
744
|
+
this.emit('rfqQuote', quote);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// Parsers
|
|
748
|
+
parseOrderbook(payload, timestamp) {
|
|
749
|
+
const bidsRaw = payload.bids || [];
|
|
750
|
+
const asksRaw = payload.asks || [];
|
|
751
|
+
// Sort bids descending, asks ascending
|
|
752
|
+
const bids = bidsRaw
|
|
753
|
+
.map(l => ({ price: parseFloat(l.price), size: parseFloat(l.size) }))
|
|
754
|
+
.sort((a, b) => b.price - a.price);
|
|
755
|
+
const asks = asksRaw
|
|
756
|
+
.map(l => ({ price: parseFloat(l.price), size: parseFloat(l.size) }))
|
|
757
|
+
.sort((a, b) => a.price - b.price);
|
|
758
|
+
const tokenId = payload.asset_id || '';
|
|
759
|
+
return {
|
|
760
|
+
tokenId,
|
|
761
|
+
assetId: tokenId, // Backward compatibility
|
|
762
|
+
market: payload.market || '',
|
|
763
|
+
bids,
|
|
764
|
+
asks,
|
|
765
|
+
timestamp: parseInt(payload.timestamp, 10) || timestamp,
|
|
766
|
+
tickSize: payload.tick_size || '0.01',
|
|
767
|
+
minOrderSize: payload.min_order_size || '1',
|
|
768
|
+
hash: payload.hash || '',
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
parsePriceChange(payload, timestamp) {
|
|
772
|
+
const changes = payload.price_changes || [];
|
|
773
|
+
return {
|
|
774
|
+
assetId: payload.asset_id || '',
|
|
775
|
+
changes,
|
|
776
|
+
timestamp,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
parseLastTrade(payload, timestamp) {
|
|
780
|
+
return {
|
|
781
|
+
assetId: payload.asset_id || '',
|
|
782
|
+
price: parseFloat(payload.price) || 0,
|
|
783
|
+
side: payload.side || 'BUY',
|
|
784
|
+
size: parseFloat(payload.size) || 0,
|
|
785
|
+
timestamp: parseInt(payload.timestamp, 10) || timestamp,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
parseTickSizeChange(payload, timestamp) {
|
|
789
|
+
return {
|
|
790
|
+
assetId: payload.asset_id || '',
|
|
791
|
+
oldTickSize: payload.old_tick_size || '',
|
|
792
|
+
newTickSize: payload.new_tick_size || '',
|
|
793
|
+
timestamp,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Calculate derived price using Polymarket's display logic:
|
|
798
|
+
* - If spread <= 0.10: use midpoint
|
|
799
|
+
* - If spread > 0.10: use last trade price
|
|
800
|
+
*/
|
|
801
|
+
calculateDerivedPrice(assetId, book) {
|
|
802
|
+
if (book.bids.length === 0 || book.asks.length === 0) {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
const bestBid = book.bids[0].price;
|
|
806
|
+
const bestAsk = book.asks[0].price;
|
|
807
|
+
const spread = bestAsk - bestBid;
|
|
808
|
+
const midpoint = (bestBid + bestAsk) / 2;
|
|
809
|
+
const lastTrade = this.lastTradeCache.get(assetId);
|
|
810
|
+
const lastTradePrice = lastTrade?.price ?? midpoint;
|
|
811
|
+
// Polymarket display logic
|
|
812
|
+
const displayPrice = spread <= 0.10 ? midpoint : lastTradePrice;
|
|
813
|
+
return {
|
|
814
|
+
assetId,
|
|
815
|
+
price: displayPrice,
|
|
816
|
+
midpoint,
|
|
817
|
+
spread,
|
|
818
|
+
timestamp: book.timestamp,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
sendSubscription(msg) {
|
|
822
|
+
if (this.client && this.connected) {
|
|
823
|
+
this.client.subscribe(msg);
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
this.log('Cannot subscribe: not connected');
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
sendUnsubscription(msg) {
|
|
830
|
+
if (this.client && this.connected) {
|
|
831
|
+
this.client.unsubscribe(msg);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
log(message) {
|
|
835
|
+
if (this.config.debug) {
|
|
836
|
+
console.log(`[RealtimeService] ${message}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
//# sourceMappingURL=realtime-service-v2.js.map
|