From b07e8ed3a93da685efb989e1a72957485c77098f Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 3 Sep 2024 22:59:26 +0200 Subject: [PATCH 1/3] Return an empty coins list for 451 status code response --- .../src/binance/data/binance_provider.dart | 11 +++-- .../src/binance/data/binance_repository.dart | 46 +++++++++++-------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart b/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart index fdea631b39..e81935d918 100644 --- a/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart +++ b/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart @@ -106,14 +106,19 @@ class BinanceProvider { /// object. /// Throws an [Exception] if the request fails. Future fetchExchangeInfoReduced() async { - final response = await http.get( - Uri.parse('$apiUrl/exchangeInfo'), - ); + final response = await http.get(Uri.parse('$apiUrl/exchangeInfo')); if (response.statusCode == 200) { return BinanceExchangeInfoResponseReduced.fromJson( jsonDecode(response.body) as Map, ); + } else if (response.statusCode == 451) { + // service unavailable for legal reasons + return BinanceExchangeInfoResponseReduced( + timezone: '', + serverTime: 0, + symbols: List.empty(), + ); } else { throw http.ClientException( 'Failed to load exchange info: ${response.statusCode} ${response.body}', diff --git a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart index d62ee104cc..6288adf8c2 100644 --- a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart +++ b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart @@ -29,30 +29,36 @@ class BinanceRepository implements CexRepository { return _cachedCoinsList!; } - final exchangeInfo = await _binanceProvider.fetchExchangeInfoReduced(); - - final coins = {}; - for (final symbol in exchangeInfo.symbols) { - final baseAsset = symbol.baseAsset; - final quoteAsset = symbol.quoteAsset; - - // TODO(Anon): Decide if this belongs at the repository level considering - // that the repository should provide and transform data as required - // without implementing business logic (or make it an optional parameter). - if (!symbol.isSpotTradingAllowed) { - continue; + // Temporary solution to bypass the exception thrown for 451 responses + try { + final exchangeInfo = await _binanceProvider.fetchExchangeInfoReduced(); + + final coins = {}; + for (final symbol in exchangeInfo.symbols) { + final baseAsset = symbol.baseAsset; + final quoteAsset = symbol.quoteAsset; + + // TODO(Anon): Decide if this belongs at the repository level considering + // that the repository should provide and transform data as required + // without implementing business logic (or make it an optional parameter). + if (!symbol.isSpotTradingAllowed) { + continue; + } + + if (!coins.containsKey(baseAsset)) { + coins[baseAsset] = _binanceCoin(baseAsset, quoteAsset); + } else { + coins[baseAsset] = coins[baseAsset]!.copyWith( + currencies: {...coins[baseAsset]!.currencies, quoteAsset}, + ); + } } - if (!coins.containsKey(baseAsset)) { - coins[baseAsset] = _binanceCoin(baseAsset, quoteAsset); - } else { - coins[baseAsset] = coins[baseAsset]!.copyWith( - currencies: {...coins[baseAsset]!.currencies, quoteAsset}, - ); - } + _cachedCoinsList = coins.values.toList(); + } catch (e) { + _cachedCoinsList = List.empty(); } - _cachedCoinsList = coins.values.toList(); return _cachedCoinsList!; } From 1a27c81b56e5f9bf0c46c0a9bf758ffc55ff34a0 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 5 Sep 2024 14:38:07 +0200 Subject: [PATCH 2/3] Add support for multiple URLs in Binance repository US has it's own url to comply with legal obligations, and other regions might follow --- .../src/binance/data/binance_provider.dart | 22 +++-- .../src/binance/data/binance_repository.dart | 88 ++++++++++++------- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart b/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart index e81935d918..946769c3c9 100644 --- a/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart +++ b/packages/komodo_cex_market_data/lib/src/binance/data/binance_provider.dart @@ -54,6 +54,7 @@ class BinanceProvider { int? startUnixTimestampMilliseconds, int? endUnixTimestampMilliseconds, int? limit, + String? baseUrl, }) async { final queryParameters = { 'symbol': symbol, @@ -65,8 +66,9 @@ class BinanceProvider { if (limit != null) 'limit': limit.toString(), }; - final uri = - Uri.parse('$apiUrl/klines').replace(queryParameters: queryParameters); + final baseRequestUrl = baseUrl ?? apiUrl; + final uri = Uri.parse('$baseRequestUrl/klines') + .replace(queryParameters: queryParameters); final response = await http.get(uri); if (response.statusCode == 200) { @@ -84,10 +86,11 @@ class BinanceProvider { /// /// Returns a [Future] that resolves to a [BinanceExchangeInfoResponse] object /// Throws an [Exception] if the request fails. - Future fetchExchangeInfo() async { - final response = await http.get( - Uri.parse('$apiUrl/exchangeInfo'), - ); + Future fetchExchangeInfo({ + String? baseUrl, + }) async { + final requestUrl = baseUrl ?? apiUrl; + final response = await http.get(Uri.parse('$requestUrl/exchangeInfo')); if (response.statusCode == 200) { return BinanceExchangeInfoResponse.fromJson( @@ -105,8 +108,11 @@ class BinanceProvider { /// Returns a [Future] that resolves to a [BinanceExchangeInfoResponseReduced] /// object. /// Throws an [Exception] if the request fails. - Future fetchExchangeInfoReduced() async { - final response = await http.get(Uri.parse('$apiUrl/exchangeInfo')); + Future fetchExchangeInfoReduced({ + String? baseUrl, + }) async { + final requestUrl = baseUrl ?? apiUrl; + final response = await http.get(Uri.parse('$requestUrl/exchangeInfo')); if (response.statusCode == 200) { return BinanceExchangeInfoResponseReduced.fromJson( diff --git a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart index 6288adf8c2..eadf6f7121 100644 --- a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart +++ b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart @@ -1,12 +1,14 @@ // Using relative imports in this "package" to make it easier to track external // dependencies when moving or copying this "package" to another project. import 'package:komodo_cex_market_data/src/binance/data/binance_provider.dart'; +import 'package:komodo_cex_market_data/src/binance/models/binance_exchange_info_reduced.dart'; import 'package:komodo_cex_market_data/src/cex_repository.dart'; import 'package:komodo_cex_market_data/src/models/models.dart'; // Declaring constants here to make this easier to copy & move around /// The base URL for the Binance API. -String get binanceApiEndpoint => 'https://api.binance.com/api/v3'; +List get binanceApiEndpoint => + ['https://api.binance.com/api/v3', 'https://api.binance.us/api/v3']; BinanceRepository binanceRepository = BinanceRepository( binanceProvider: const BinanceProvider(), @@ -29,37 +31,26 @@ class BinanceRepository implements CexRepository { return _cachedCoinsList!; } - // Temporary solution to bypass the exception thrown for 451 responses - try { - final exchangeInfo = await _binanceProvider.fetchExchangeInfoReduced(); - - final coins = {}; - for (final symbol in exchangeInfo.symbols) { - final baseAsset = symbol.baseAsset; - final quoteAsset = symbol.quoteAsset; - - // TODO(Anon): Decide if this belongs at the repository level considering - // that the repository should provide and transform data as required - // without implementing business logic (or make it an optional parameter). - if (!symbol.isSpotTradingAllowed) { - continue; - } + return await _executeWithRetry((String baseUrl) async { + final exchangeInfo = + await _binanceProvider.fetchExchangeInfoReduced(baseUrl: baseUrl); + _cachedCoinsList = _convertSymbolsToCoins(exchangeInfo); + return _cachedCoinsList!; + }); + } - if (!coins.containsKey(baseAsset)) { - coins[baseAsset] = _binanceCoin(baseAsset, quoteAsset); - } else { - coins[baseAsset] = coins[baseAsset]!.copyWith( - currencies: {...coins[baseAsset]!.currencies, quoteAsset}, - ); + Future _executeWithRetry(Future Function(String) callback) async { + for (int i = 0; i < binanceApiEndpoint.length; i++) { + try { + return await callback(binanceApiEndpoint.elementAt(i)); + } catch (e) { + if (i >= binanceApiEndpoint.length) { + rethrow; } } - - _cachedCoinsList = coins.values.toList(); - } catch (e) { - _cachedCoinsList = List.empty(); } - return _cachedCoinsList!; + throw Exception('Invalid state'); } CexCoin _binanceCoin(String baseCoinAbbr, String quoteCoinAbbr) { @@ -89,13 +80,16 @@ class BinanceRepository implements CexRepository { final endUnixTimestamp = endAt?.millisecondsSinceEpoch; final intervalAbbreviation = interval.toAbbreviation(); - return _binanceProvider.fetchKlines( - symbol.toString(), - intervalAbbreviation, - startUnixTimestampMilliseconds: startUnixTimestamp, - endUnixTimestampMilliseconds: endUnixTimestamp, - limit: limit, - ); + return await _executeWithRetry((String baseUrl) async { + return await _binanceProvider.fetchKlines( + symbol.toString(), + intervalAbbreviation, + startUnixTimestampMilliseconds: startUnixTimestamp, + endUnixTimestampMilliseconds: endUnixTimestamp, + limit: limit, + baseUrl: baseUrl, + ); + }); } @override @@ -172,4 +166,30 @@ class BinanceRepository implements CexRepository { return result; } + + List _convertSymbolsToCoins( + BinanceExchangeInfoResponseReduced exchangeInfo, + ) { + final coins = {}; + for (final symbol in exchangeInfo.symbols) { + final baseAsset = symbol.baseAsset; + final quoteAsset = symbol.quoteAsset; + + // TODO(Anon): Decide if this belongs at the repository level considering + // that the repository should provide and transform data as required + // without implementing business logic (or make it an optional parameter). + if (!symbol.isSpotTradingAllowed) { + continue; + } + + if (!coins.containsKey(baseAsset)) { + coins[baseAsset] = _binanceCoin(baseAsset, quoteAsset); + } else { + coins[baseAsset] = coins[baseAsset]!.copyWith( + currencies: {...coins[baseAsset]!.currencies, quoteAsset}, + ); + } + } + return coins.values.toList(); + } } From 67e4e07cf42ebdbf4277f226d8ba7138253bf77f Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 5 Sep 2024 15:14:47 +0200 Subject: [PATCH 3/3] Return an empty supported coins list if requests fail --- .../src/binance/data/binance_repository.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart index eadf6f7121..5e302316ca 100644 --- a/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart +++ b/packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart @@ -31,12 +31,18 @@ class BinanceRepository implements CexRepository { return _cachedCoinsList!; } - return await _executeWithRetry((String baseUrl) async { - final exchangeInfo = - await _binanceProvider.fetchExchangeInfoReduced(baseUrl: baseUrl); - _cachedCoinsList = _convertSymbolsToCoins(exchangeInfo); - return _cachedCoinsList!; - }); + try { + return await _executeWithRetry((String baseUrl) async { + final exchangeInfo = + await _binanceProvider.fetchExchangeInfoReduced(baseUrl: baseUrl); + _cachedCoinsList = _convertSymbolsToCoins(exchangeInfo); + return _cachedCoinsList!; + }); + } catch (e) { + _cachedCoinsList = List.empty(); + } + + return _cachedCoinsList!; } Future _executeWithRetry(Future Function(String) callback) async {