Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ import 'package:komodo_defi_types/komodo_defi_types.dart';

// Declaring constants here to make this easier to copy & move around
/// The base URL for the Binance API.
List<String> get binanceApiEndpoint =>
['https://api.binance.com/api/v3', 'https://api.binance.us/api/v3'];

BinanceRepository binanceRepository = BinanceRepository(
binanceProvider: const BinanceProvider(),
);
List<String> get binanceApiEndpoint => [
'https://api.binance.com/api/v3',
'https://api.binance.us/api/v3',
];

/// A repository class for interacting with the Binance API.
/// This class provides methods to fetch legacy tickers and OHLC candle data.
Expand All @@ -27,12 +25,11 @@ class BinanceRepository implements CexRepository {
BinanceRepository({
required IBinanceProvider binanceProvider,
BackoffStrategy? defaultBackoffStrategy,
}) : _binanceProvider = binanceProvider,
_defaultBackoffStrategy = defaultBackoffStrategy ??
ExponentialBackoff(
maxDelay: const Duration(seconds: 5),
),
_idResolutionStrategy = BinanceIdResolutionStrategy();
}) : _binanceProvider = binanceProvider,
_defaultBackoffStrategy =
defaultBackoffStrategy ??
ExponentialBackoff(maxDelay: const Duration(seconds: 5)),
_idResolutionStrategy = BinanceIdResolutionStrategy();

final IBinanceProvider _binanceProvider;
final BackoffStrategy _defaultBackoffStrategy;
Expand Down Expand Up @@ -213,11 +210,11 @@ class BinanceRepository implements CexRepository {
backoffStrategy: backoffStrategy,
);

final batchResult =
ohlcData.ohlc.fold<Map<DateTime, double>>({}, (map, ohlc) {
final date = DateTime.fromMillisecondsSinceEpoch(
ohlc.closeTime,
);
final batchResult = ohlcData.ohlc.fold<Map<DateTime, double>>({}, (
map,
ohlc,
) {
final date = DateTime.fromMillisecondsSinceEpoch(ohlc.closeTime);
map[DateTime(date.year, date.month, date.day)] = ohlc.close;
return map;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,55 @@ import 'package:http/http.dart' as http;
import 'package:komodo_cex_market_data/komodo_cex_market_data.dart';
import 'package:komodo_cex_market_data/src/coingecko/models/coin_historical_data/coin_historical_data.dart';

/// Interface for fetching data from CoinGecko API.
abstract class ICoinGeckoProvider {
Future<List<CexCoin>> fetchCoinList({bool includePlatforms = false});

Future<List<String>> fetchSupportedVsCurrencies();

Future<List<CoinMarketData>> fetchCoinMarketData({
String vsCurrency = 'usd',
List<String>? ids,
String? category,
String order = 'market_cap_asc',
int perPage = 100,
int page = 1,
bool sparkline = false,
String? priceChangePercentage,
String locale = 'en',
String? precision,
});

Future<CoinMarketChart> fetchCoinMarketChart({
required String id,
required String vsCurrency,
required int fromUnixTimestamp,
required int toUnixTimestamp,
String? precision,
});

Future<CoinOhlc> fetchCoinOhlc(
String id,
String vsCurrency,
int days, {
int? precision,
});

Future<CoinHistoricalData> fetchCoinHistoricalMarketData({
required String id,
required DateTime date,
String vsCurrency = 'usd',
bool localization = false,
});

Future<Map<String, CexPrice>> fetchCoinPrices(
List<String> coinGeckoIds, {
List<String> vsCurrencies = const <String>['usd'],
});
}

/// A class for fetching data from CoinGecko API.
class CoinGeckoCexProvider {
class CoinGeckoCexProvider implements ICoinGeckoProvider {
/// Creates a new instance of [CoinGeckoCexProvider].
CoinGeckoCexProvider({
this.baseUrl = 'api.coingecko.com',
Expand Down Expand Up @@ -45,8 +92,10 @@ class CoinGeckoCexProvider {

/// Fetches the list of supported vs currencies.
Future<List<String>> fetchSupportedVsCurrencies() async {
final uri =
Uri.https(baseUrl, '$apiVersion/simple/supported_vs_currencies');
final uri = Uri.https(
baseUrl,
'$apiVersion/simple/supported_vs_currencies',
);

final response = await http.get(uri);
if (response.statusCode == 200) {
Expand Down Expand Up @@ -96,8 +145,11 @@ class CoinGeckoCexProvider {
'locale': locale,
if (precision != null) 'price_change_percentage': precision,
};
final uri =
Uri.https(baseUrl, '$apiVersion/coins/markets', queryParameters);
final uri = Uri.https(
baseUrl,
'$apiVersion/coins/markets',
queryParameters,
);

return http.get(uri).then((http.Response response) {
if (response.statusCode == 200) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CoinGeckoRepository implements CexRepository {
_idResolutionStrategy = CoinGeckoIdResolutionStrategy();

/// The CoinGecko provider to use for fetching data.
final CoinGeckoCexProvider coinGeckoProvider;
final ICoinGeckoProvider coinGeckoProvider;
final BackoffStrategy _defaultBackoffStrategy;
final IdResolutionStrategy _idResolutionStrategy;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:komodo_cex_market_data/src/models/models.dart';

/// Interface for fetching prices from Komodo API.
abstract class IKomodoPriceProvider {
Future<Map<String, CexPrice>> getKomodoPrices();
}

/// A class for fetching prices from Komodo API.
class KomodoPriceProvider {
class KomodoPriceProvider implements IKomodoPriceProvider {
/// Creates a new instance of [KomodoPriceProvider].
KomodoPriceProvider({
this.mainTickersUrl =
Expand Down Expand Up @@ -42,8 +47,10 @@ class KomodoPriceProvider {

final prices = <String, CexPrice>{};
json.forEach((String priceTicker, dynamic pricesData) {
prices[priceTicker] =
CexPrice.fromJson(priceTicker, pricesData as Map<String, dynamic>);
prices[priceTicker] = CexPrice.fromJson(
priceTicker,
pricesData as Map<String, dynamic>,
);
});
return prices;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import 'package:komodo_cex_market_data/src/models/models.dart';
import 'package:komodo_cex_market_data/src/repository_selection_strategy.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Interface for Komodo price repository.
abstract class IKomodoPriceRepository implements CexRepository {
Future<Map<String, CexPrice>> getKomodoPrices();
}

/// A repository for fetching the prices of coins from the Komodo Defi API.
class KomodoPriceRepository extends CexRepository {
Copy link

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class should implement IKomodoPriceRepository interface in addition to extending CexRepository to maintain the contract defined by the interface.

Suggested change
class KomodoPriceRepository extends CexRepository {
class KomodoPriceRepository extends CexRepository implements IKomodoPriceRepository {

Copilot uses AI. Check for mistakes.
/// Creates a new instance of [KomodoPriceRepository].
KomodoPriceRepository({
required KomodoPriceProvider cexPriceProvider,
}) : _cexPriceProvider = cexPriceProvider;
KomodoPriceRepository({required IKomodoPriceProvider cexPriceProvider})
: _cexPriceProvider = cexPriceProvider;

/// The price provider to fetch the prices from.
final KomodoPriceProvider _cexPriceProvider;
final IKomodoPriceProvider _cexPriceProvider;

// Supported coins and vs currencies are not expected to change regularly,
// so this in-memory cache is acceptable for now until a more complete and
Expand Down Expand Up @@ -89,9 +93,9 @@ class KomodoPriceRepository extends CexRepository {
List<String> timestamps, {
String vsCurrency = 'usd',
}) async {
return (await _cexPriceProvider.getKomodoPrices())
.values
.firstWhere((CexPrice element) {
return (await _cexPriceProvider.getKomodoPrices()).values.firstWhere((
CexPrice element,
) {
if (element.ticker != coinId) {
return false;
}
Expand All @@ -104,6 +108,7 @@ class KomodoPriceRepository extends CexRepository {
/// Fetches the prices of the provided coin IDs.
///
/// Returns a map of coin IDs to their prices.
@override
Future<Map<String, CexPrice>> getKomodoPrices() async {
return _cexPriceProvider.getKomodoPrices();
}
Expand All @@ -114,17 +119,18 @@ class KomodoPriceRepository extends CexRepository {
return _cachedCoinsList!;
}
final prices = await getKomodoPrices();
_cachedCoinsList = prices.values
.map(
(e) => CexCoin(
id: e.ticker,
symbol: e.ticker,
name: e.ticker,
currencies: <String>{'USD', 'USDT'},
source: 'komodo',
),
)
.toList();
_cachedCoinsList =
prices.values
.map(
(e) => CexCoin(
id: e.ticker,
symbol: e.ticker,
name: e.ticker,
currencies: <String>{'USD', 'USDT'},
source: 'komodo',
),
)
.toList();
_cachedFiatCurrencies = {'USD', 'USDT'};
return _cachedCoinsList!;
}
Expand All @@ -145,4 +151,51 @@ class KomodoPriceRepository extends CexRepository {
requestType == PriceRequestType.priceChange;
return supportsAsset && supportsFiat && supportsRequestType;
}

@override
Future<CoinOhlc> getCoinOhlc(
CexCoinPair symbol,
GraphInterval interval, {
DateTime? startAt,
DateTime? endAt,
int? limit,
}) {
throw UnimplementedError('KomodoPriceRepository does not support OHLC');
}

@override
String resolveTradingSymbol(AssetId assetId) {
return assetId.symbol.configSymbol;
}

@override
bool canHandleAsset(AssetId assetId) {
final symbol = assetId.symbol.configSymbol.toUpperCase();
return _cachedCoinsList?.any((c) => c.id.toUpperCase() == symbol) ?? false;
}

@override
Future<double> getCoinFiatPrice(
AssetId assetId, {
DateTime? priceDate,
String fiatCoinId = 'usdt',
}) async {
final prices = await getKomodoPrices();
final symbol = assetId.symbol.configSymbol;
final price = prices[symbol]?.price;
if (price == null) {
throw StateError('Price not found for ${assetId.symbol.configSymbol}');
}
return price;
}

@override
Future<Map<DateTime, double>> getCoinFiatPrices(
AssetId assetId,
List<DateTime> dates, {
String fiatCoinId = 'usdt',
}) async {
final price = await getCoinFiatPrice(assetId, fiatCoinId: fiatCoinId);
return {for (final date in dates) date: price};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@ import 'package:komodo_cex_market_data/src/models/cex_coin.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Enum for the type of price request
enum PriceRequestType {
currentPrice,
priceChange,
priceHistory,
enum PriceRequestType { currentPrice, priceChange, priceHistory }

/// Strategy interface for selecting repositories
abstract class RepositorySelectionStrategy {
Future<void> ensureCacheInitialized(List<CexRepository> repositories);

Future<CexRepository?> selectRepository({
required AssetId assetId,
required AssetId fiatAssetId,
required PriceRequestType requestType,
required List<CexRepository> availableRepositories,
});
}

/// Strategy for selecting the best repository for a given asset and operation
class RepositorySelectionStrategy {
/// Default strategy for selecting the best repository for a given asset
class DefaultRepositorySelectionStrategy
implements RepositorySelectionStrategy {
final Map<CexRepository, _RepositorySupportCache> _supportCache = {};

@override
Future<void> ensureCacheInitialized(List<CexRepository> repositories) async {
for (final repo in repositories) {
if (!_supportCache.containsKey(repo)) {
Expand All @@ -29,6 +39,7 @@ class RepositorySelectionStrategy {
}

/// Selects the best repository for a given asset, fiat, and request type
@override
Future<CexRepository?> selectRepository({
required AssetId assetId,
required AssetId fiatAssetId,
Expand All @@ -37,15 +48,17 @@ class RepositorySelectionStrategy {
}) async {
await ensureCacheInitialized(availableRepositories);
final fiatSymbol = fiatAssetId.symbol.configSymbol.toUpperCase();
final candidates = availableRepositories.where((repo) {
final cache = _supportCache[repo];
if (cache == null) return false;
final supportsAsset = cache.coins.any(
(c) => c.id.toUpperCase() == assetId.symbol.configSymbol.toUpperCase(),
);
final supportsFiat = cache.fiatCurrencies.contains(fiatSymbol);
return supportsAsset && supportsFiat;
}).toList();
final candidates =
availableRepositories.where((repo) {
final cache = _supportCache[repo];
if (cache == null) return false;
final supportsAsset = cache.coins.any(
(c) =>
c.id.toUpperCase() == assetId.symbol.configSymbol.toUpperCase(),
);
final supportsFiat = cache.fiatCurrencies.contains(fiatSymbol);
return supportsAsset && supportsFiat;
}).toList();
candidates.sort(
(a, b) => RepositoryPriorityManager.getPriority(a)
.compareTo(RepositoryPriorityManager.getPriority(b)),
Expand Down
1 change: 1 addition & 0 deletions packages/komodo_cex_market_data/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ dependencies:

dev_dependencies:
flutter_lints: ^6.0.0 # flutter.dev
mocktail: ^1.0.4
test: ^1.25.7
Loading
Loading