From 4fbc05367d3ea08a87aed4c9191247faf7a0e474 Mon Sep 17 00:00:00 2001 From: Anikesh Thakur Date: Tue, 22 Sep 2026 17:10:04 +0000 Subject: [PATCH] fix: normalize Binance feed symbol case --- core/src/feeds/binance/binance-feed.ts | 7 ++-- core/test/unit/dataFeeds.core.test.ts | 44 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/core/src/feeds/binance/binance-feed.ts b/core/src/feeds/binance/binance-feed.ts index a9020cb6..f6548fd1 100644 --- a/core/src/feeds/binance/binance-feed.ts +++ b/core/src/feeds/binance/binance-feed.ts @@ -100,7 +100,8 @@ export class BinanceFeed extends BaseDataFeed { } protected async fetchTickerImpl(symbol: string): Promise { - const cached = this.latestTickers.get(symbol); + const normalizedSymbol = symbol.toUpperCase(); + const cached = this.latestTickers.get(normalizedSymbol); if (cached) return cached; await this.ensureConnected(); @@ -110,7 +111,7 @@ export class BinanceFeed extends BaseDataFeed { reject(new Error(`BinanceFeed: timed out waiting for trade on ${symbol} (10s)`)); }, 10_000); - const cleanup = this.watchTickerImpl(symbol, (ticker) => { + const cleanup = this.watchTickerImpl(normalizedSymbol, (ticker) => { clearTimeout(timeout); cleanup(); resolve(ticker); @@ -130,7 +131,7 @@ export class BinanceFeed extends BaseDataFeed { } protected watchTickerImpl(symbol: string, callback: (ticker: Ticker) => void): () => void { - const sub: Subscription = { symbol, callback }; + const sub: Subscription = { symbol: symbol.toUpperCase(), callback }; this.subscriptions.push(sub); this.ensureConnected().catch((err: unknown) => { logger.error('[BinanceFeed] initial connect failed in watchTickerImpl', { diff --git a/core/test/unit/dataFeeds.core.test.ts b/core/test/unit/dataFeeds.core.test.ts index aa30892c..d45bdf62 100644 --- a/core/test/unit/dataFeeds.core.test.ts +++ b/core/test/unit/dataFeeds.core.test.ts @@ -1,6 +1,23 @@ import { ExchangeNotAvailable, NotSupported } from '../../src/errors'; import { BinanceFeed } from '../../src/feeds/binance'; +import { normalizeTradeToTicker } from '../../src/feeds/binance/normalizer'; +import { BinanceRelayTradeEvent } from '../../src/feeds/binance/types'; import { ChainlinkFeed } from '../../src/feeds/chainlink/chainlink-feed'; +import { Ticker } from '../../src/feeds/types'; + +class TestBinanceFeed extends BinanceFeed { + async connect(): Promise {} + + cacheTicker(ticker: Ticker): void { + const internals = this as unknown as { latestTickers: Map }; + internals.latestTickers.set(ticker.symbol, ticker); + } + + emitTrade(event: BinanceRelayTradeEvent): void { + const internals = this as unknown as { handleMessage(data: string): void }; + internals.handleMessage(JSON.stringify(event)); + } +} describe('Data feed backend errors', () => { test('Binance fetchTicker names the missing relay URL setting', async () => { @@ -22,6 +39,33 @@ describe('Data feed backend errors', () => { } satisfies Partial); }); + test('Binance normalizes symbol case for cached and watched tickers', async () => { + const feed = new TestBinanceFeed({ wsUrl: '', apiKey: '' }); + const event: BinanceRelayTradeEvent = { + op: 'event', + source: 'binance', + symbol: 'BTCUSDT', + trade_id: 1, + price: '65000', + quantity: '0.01', + event_time_ms: 1_700_000_000_000, + trade_time_ms: 1_700_000_000_000, + is_buyer_maker: false, + timestamp_received_ms: 1_700_000_000_001, + }; + const ticker = normalizeTradeToTicker(event); + feed.cacheTicker(ticker); + + await expect(feed.fetchTicker('btc/usdt')).resolves.toBe(ticker); + + const callback = jest.fn(); + const unsubscribe = feed.watchTicker('btc/usdt', callback); + feed.emitTrade(event); + + expect(callback).toHaveBeenCalledWith(expect.objectContaining({ symbol: 'BTC/USDT' })); + unsubscribe(); + }); + test('Chainlink oracle calls name the missing REST API URL setting', async () => { const feed = new ChainlinkFeed({ baseUrl: '', apiKey: '', wsUrl: '' });