diff --git a/core/src/exchanges/limitless/client.ts b/core/src/exchanges/limitless/client.ts index 2d43fe7b..93488fb1 100644 --- a/core/src/exchanges/limitless/client.ts +++ b/core/src/exchanges/limitless/client.ts @@ -309,14 +309,20 @@ export class LimitlessClient { * Cancel a specific order by ID. */ async cancelOrder(orderId: string) { - return await this.orderClient!.cancel(orderId); + if (!this.orderClient) { + throw new Error('[limitless] Order client not initialized -- trading credentials required'); + } + return await this.orderClient.cancel(orderId); } /** * Cancel all orders for a specific market. */ async cancelAllOrders(marketSlug: string) { - return await this.orderClient!.cancelAll(marketSlug); + if (!this.orderClient) { + throw new Error('[limitless] Order client not initialized -- trading credentials required'); + } + return await this.orderClient.cancelAll(marketSlug); } /** @@ -385,7 +391,10 @@ export class LimitlessClient { }); const contract = new Contract(USDC_ADDRESS, ABI, provider); - const balance = await contract.balanceOf(this.signer!.address); + if (!this.signer) { + throw new Error('[limitless] Signer not initialized -- wallet private key required'); + } + const balance = await contract.balanceOf(this.signer.address); const decimals = await contract.decimals(); // Should be 6 return parseFloat(utils.formatUnits(balance, decimals)); diff --git a/core/src/exchanges/limitless/normalizer.ts b/core/src/exchanges/limitless/normalizer.ts index 43ab6c3c..e08dbc50 100644 --- a/core/src/exchanges/limitless/normalizer.ts +++ b/core/src/exchanges/limitless/normalizer.ts @@ -71,10 +71,12 @@ export class LimitlessNormalizer implements IExchangeNormalizer a.timestamp - b.timestamp); if (params.start) { - candles = candles.filter((c) => c.timestamp >= params.start!.getTime()); + const start = params.start; + candles = candles.filter((c) => c.timestamp >= start.getTime()); } if (params.end) { - candles = candles.filter((c) => c.timestamp <= params.end!.getTime()); + const end = params.end; + candles = candles.filter((c) => c.timestamp <= end.getTime()); } if (params.limit) { candles = candles.slice(0, params.limit); diff --git a/core/src/exchanges/limitless/websocket.ts b/core/src/exchanges/limitless/websocket.ts index 43675759..9d75108f 100644 --- a/core/src/exchanges/limitless/websocket.ts +++ b/core/src/exchanges/limitless/websocket.ts @@ -117,7 +117,8 @@ export class LimitlessWebSocket { // 1. If we have buffered data, return it immediately const buffer = this.orderbookBuffers.get(marketSlug); if (buffer && buffer.length > 0) { - return buffer.shift()!; + const entry = buffer.shift(); + if (entry) return entry; } // 2. Special case: If this is the FIRST call for this market and we have no data, @@ -152,15 +153,31 @@ export class LimitlessWebSocket { // Wait for WebSocket update with timeout try { + const resolverEntry: { resolve: (ob: OrderBook) => void; reject: (err: any) => void } = { + resolve: () => {}, + reject: () => {}, + }; + const wsUpdatePromise = new Promise((resolve, reject) => { + resolverEntry.resolve = resolve; + resolverEntry.reject = reject; if (!this.orderbookResolvers.has(marketSlug)) { this.orderbookResolvers.set(marketSlug, []); } - this.orderbookResolvers.get(marketSlug)!.push({ resolve, reject }); + const resolvers = this.orderbookResolvers.get(marketSlug); + if (resolvers) { + resolvers.push(resolverEntry); + } }); const timeoutPromise = new Promise((resolve) => { setTimeout(async () => { + // Timeout won the race -- remove the stale resolver (#372) + const resolvers = this.orderbookResolvers.get(marketSlug); + if (resolvers) { + const idx = resolvers.indexOf(resolverEntry); + if (idx !== -1) resolvers.splice(idx, 1); + } // Timeout: fetch REST snapshot as fallback try { this.lastOrderbookTimestamps.set(marketSlug, Date.now()); @@ -278,6 +295,14 @@ export class LimitlessWebSocket { async close(): Promise { this.orderbookCallbacks.clear(); this.priceCallbacks.clear(); + // Reject any pending resolvers before clearing (#372) + for (const [, resolvers] of this.orderbookResolvers) { + for (const resolver of resolvers) { + resolver.reject(new Error('WebSocket closed')); + } + } + this.orderbookResolvers.clear(); + this.orderbookBuffers.clear(); await this.client.disconnect(); this.watcher.close(); } @@ -315,17 +340,21 @@ export class LimitlessWebSocket { const resolvers = this.orderbookResolvers.get(marketSlug) || []; if (resolvers.length > 0) { // If someone is waiting, give it to them immediately - const resolver = resolvers.shift()!; - resolver.resolve(pmxtOrderbook); + const resolver = resolvers.shift(); + if (resolver) { + resolver.resolve(pmxtOrderbook); + } } else { // Otherwise, buffer it for the next call if (!this.orderbookBuffers.has(marketSlug)) { this.orderbookBuffers.set(marketSlug, []); } - const buffer = this.orderbookBuffers.get(marketSlug)!; - buffer.push(pmxtOrderbook); - // Keep buffer size reasonable - if (buffer.length > 100) buffer.shift(); + const buffer = this.orderbookBuffers.get(marketSlug); + if (buffer) { + buffer.push(pmxtOrderbook); + // Keep buffer size reasonable + if (buffer.length > 100) buffer.shift(); + } } });