Skip to content

Commit 51861be

Browse files
committed
refactor: remove string ticker-based coin pair model in favour of quote
1 parent 08ff033 commit 51861be

File tree

11 files changed

+142
-196
lines changed

11 files changed

+142
-196
lines changed

packages/komodo_cex_market_data/lib/src/binance/data/binance_repository.dart

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,17 @@ class BinanceRepository implements CexRepository {
9393

9494
@override
9595
Future<CoinOhlc> getCoinOhlc(
96-
CexCoinPair symbol,
96+
AssetId assetId,
97+
QuoteCurrency quoteCurrency,
9798
GraphInterval interval, {
9899
DateTime? startAt,
99100
DateTime? endAt,
100101
int? limit,
101102
}) async {
102-
if (symbol.baseCoinTicker.toUpperCase() ==
103-
symbol.relCoinTicker.toUpperCase()) {
103+
final baseTicker = resolveTradingSymbol(assetId);
104+
final relTicker = quoteCurrency.binanceId;
105+
106+
if (baseTicker.toUpperCase() == relTicker.toUpperCase()) {
104107
throw ArgumentError('Base and rel coin tickers cannot be the same');
105108
}
106109

@@ -112,8 +115,10 @@ class BinanceRepository implements CexRepository {
112115
Exception? lastException;
113116
for (final baseUrl in binanceApiEndpoint) {
114117
try {
118+
final symbolString =
119+
'${baseTicker.toUpperCase()}${relTicker.toUpperCase()}';
115120
return await _binanceProvider.fetchKlines(
116-
symbol.toString(),
121+
symbolString,
117122
intervalAbbreviation,
118123
startUnixTimestampMilliseconds: startUnixTimestamp,
119124
endUnixTimestampMilliseconds: endUnixTimestamp,
@@ -156,7 +161,8 @@ class BinanceRepository implements CexRepository {
156161
final startAt = endAt.subtract(const Duration(days: 1));
157162

158163
final ohlcData = await getCoinOhlc(
159-
CexCoinPair(baseCoinTicker: trimmedCoinId, relCoinTicker: fiatCurrencyId),
164+
assetId,
165+
fiatCurrency,
160166
GraphInterval.oneDay,
161167
startAt: startAt,
162168
endAt: endAt,
@@ -197,10 +203,8 @@ class BinanceRepository implements CexRepository {
197203
i + 500 > daysDiff ? endDate : startDate.add(Duration(days: i + 500));
198204

199205
final ohlcData = await getCoinOhlc(
200-
CexCoinPair(
201-
baseCoinTicker: trimmedCoinId,
202-
relCoinTicker: fiatCurrencyId,
203-
),
206+
assetId,
207+
fiatCurrency,
204208
GraphInterval.oneDay,
205209
startAt: batchStartDate,
206210
endAt: batchEndDate,

packages/komodo_cex_market_data/lib/src/bootstrap/market_data_bootstrap.dart

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,15 @@ class MarketDataBootstrap {
6565
MarketDataConfig config,
6666
) async {
6767
if (config.enableCoinGecko) {
68-
container.registerSingleton<ICoinGeckoProvider>(CoinGeckoCexProvider());
68+
container.registerSingletonAsync<ICoinGeckoProvider>(
69+
() async => CoinGeckoCexProvider(),
70+
);
6971
}
7072

7173
if (config.enableKomodoPrice) {
72-
container.registerSingleton<IKomodoPriceProvider>(KomodoPriceProvider());
74+
container.registerSingletonAsync<IKomodoPriceProvider>(
75+
() async => KomodoPriceProvider(),
76+
);
7377
}
7478
}
7579

@@ -87,7 +91,7 @@ class MarketDataBootstrap {
8791
if (config.enableCoinGecko) {
8892
container.registerSingletonAsync<CoinGeckoRepository>(
8993
() async => CoinGeckoRepository(
90-
coinGeckoProvider: container<ICoinGeckoProvider>(),
94+
coinGeckoProvider: await container.getAsync<ICoinGeckoProvider>(),
9195
),
9296
dependsOn: [ICoinGeckoProvider],
9397
);
@@ -96,7 +100,7 @@ class MarketDataBootstrap {
96100
if (config.enableKomodoPrice) {
97101
container.registerSingletonAsync<KomodoPriceRepository>(
98102
() async => KomodoPriceRepository(
99-
cexPriceProvider: container<IKomodoPriceProvider>(),
103+
cexPriceProvider: await container.getAsync<IKomodoPriceProvider>(),
100104
),
101105
dependsOn: [IKomodoPriceProvider],
102106
);
@@ -123,15 +127,15 @@ class MarketDataBootstrap {
123127

124128
// Add repositories in priority order
125129
if (config.enableKomodoPrice) {
126-
repositories.add(container<KomodoPriceRepository>());
130+
repositories.add(await container.getAsync<KomodoPriceRepository>());
127131
}
128132

129133
if (config.enableBinance) {
130-
repositories.add(container<BinanceRepository>());
134+
repositories.add(await container.getAsync<BinanceRepository>());
131135
}
132136

133137
if (config.enableCoinGecko) {
134-
repositories.add(container<CoinGeckoRepository>());
138+
repositories.add(await container.getAsync<CoinGeckoRepository>());
135139
}
136140

137141
// Add any custom repositories

packages/komodo_cex_market_data/lib/src/cex_repository.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ abstract class CexRepository {
4646
/// await repo.getCoinOhlc('BTCUSDT', '1d', limit: 100);
4747
/// ```
4848
Future<CoinOhlc> getCoinOhlc(
49-
CexCoinPair symbol,
49+
AssetId assetId,
50+
QuoteCurrency quoteCurrency,
5051
GraphInterval interval, {
5152
DateTime? startAt,
5253
DateTime? endAt,

packages/komodo_cex_market_data/lib/src/coingecko/data/coingecko_repository.dart

Lines changed: 21 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'dart:developer';
2-
31
import 'package:async/async.dart';
42
import 'package:decimal/decimal.dart';
53
import 'package:komodo_cex_market_data/src/cex_repository.dart';
@@ -8,8 +6,6 @@ import 'package:komodo_cex_market_data/src/coingecko/models/coin_historical_data
86
import 'package:komodo_cex_market_data/src/id_resolution_strategy.dart';
97
import 'package:komodo_cex_market_data/src/models/models.dart';
108
import 'package:komodo_cex_market_data/src/repository_selection_strategy.dart';
11-
import 'package:komodo_defi_types/komodo_defi_type_utils.dart'
12-
show BackoffStrategy, ExponentialBackoff, retry;
139
import 'package:komodo_defi_types/komodo_defi_types.dart';
1410

1511
/// The number of seconds in a day.
@@ -23,21 +19,12 @@ class CoinGeckoRepository implements CexRepository {
2319
/// Creates a new instance of [CoinGeckoRepository].
2420
CoinGeckoRepository({
2521
required this.coinGeckoProvider,
26-
BackoffStrategy? defaultBackoffStrategy,
2722
bool enableMemoization = true,
28-
}) : _defaultBackoffStrategy =
29-
defaultBackoffStrategy ??
30-
ExponentialBackoff(
31-
initialDelay: const Duration(milliseconds: 300),
32-
maxDelay: const Duration(seconds: 5),
33-
withJitter: true,
34-
),
35-
_idResolutionStrategy = CoinGeckoIdResolutionStrategy(),
23+
}) : _idResolutionStrategy = CoinGeckoIdResolutionStrategy(),
3624
_enableMemoization = enableMemoization;
3725

3826
/// The CoinGecko provider to use for fetching data.
3927
final ICoinGeckoProvider coinGeckoProvider;
40-
final BackoffStrategy _defaultBackoffStrategy;
4128
final IdResolutionStrategy _idResolutionStrategy;
4229
final bool _enableMemoization;
4330

@@ -61,7 +48,7 @@ class CoinGeckoRepository implements CexRepository {
6148
@override
6249
Future<List<CexCoin>> getCoinList() async {
6350
if (_enableMemoization) {
64-
return _coinListMemoizer.runOnce(() => _fetchCoinListInternal());
51+
return _coinListMemoizer.runOnce(_fetchCoinListInternal);
6552
} else {
6653
// Warning: Direct API calls without memoization can lead to API rate limiting
6754
// and unnecessary network requests. Use this mode sparingly.
@@ -91,7 +78,8 @@ class CoinGeckoRepository implements CexRepository {
9178

9279
@override
9380
Future<CoinOhlc> getCoinOhlc(
94-
CexCoinPair symbol,
81+
AssetId assetId,
82+
QuoteCurrency quoteCurrency,
9583
GraphInterval interval, {
9684
DateTime? startAt,
9785
DateTime? endAt,
@@ -103,11 +91,14 @@ class CoinGeckoRepository implements CexRepository {
10391
days = (timeDelta.inSeconds.toDouble() / secondsInDay).ceil();
10492
}
10593

94+
// Use the same ticker resolution as other methods
95+
final tradingSymbol = resolveTradingSymbol(assetId);
96+
10697
// If the request is within the CoinGecko limit, make a single request
10798
if (days <= maxCoinGeckoDays) {
10899
return coinGeckoProvider.fetchCoinOhlc(
109-
symbol.baseCoinTicker,
110-
symbol.relCoinTicker,
100+
tradingSymbol,
101+
quoteCurrency.coinGeckoId,
111102
days,
112103
);
113104
}
@@ -124,15 +115,17 @@ class CoinGeckoRepository implements CexRepository {
124115
var currentStart = startAt;
125116

126117
while (currentStart.isBefore(endAt)) {
127-
final currentEnd = currentStart.add(Duration(days: maxCoinGeckoDays));
118+
final currentEnd = currentStart.add(
119+
const Duration(days: maxCoinGeckoDays),
120+
);
128121
final batchEndDate = currentEnd.isAfter(endAt) ? endAt : currentEnd;
129122

130123
final batchDays = batchEndDate.difference(currentStart).inDays;
131124
if (batchDays <= 0) break;
132125

133126
final batchOhlc = await coinGeckoProvider.fetchCoinOhlc(
134-
symbol.baseCoinTicker,
135-
symbol.relCoinTicker,
127+
tradingSymbol,
128+
quoteCurrency.coinGeckoId,
136129
batchDays,
137130
);
138131

@@ -153,64 +146,14 @@ class CoinGeckoRepository implements CexRepository {
153146
return _idResolutionStrategy.canResolve(assetId);
154147
}
155148

156-
/// Maps any currency to the appropriate CoinGecko vs_currency
157-
/// Handles stablecoin -> fiat conversion and validates against supported currencies
158-
String _mapFiatCurrencyToCoingecko(QuoteCurrency fiatCurrency) {
159-
// Use the QuoteCurrency's coinGeckoId which handles all mappings
160-
final mappedCurrency = fiatCurrency.coinGeckoId;
161-
162-
// Verify the mapped currency is actually supported by CoinGecko
163-
if (_cachedFiatCurrencies?.contains(mappedCurrency.toUpperCase()) == true) {
164-
return mappedCurrency;
165-
}
166-
167-
// For stablecoins, never fall back to the stablecoin symbol itself
168-
// Always prefer the underlying fiat currency
169-
final isStablecoin = fiatCurrency.maybeWhen(
170-
stablecoin: (_, __, ___) => true,
171-
orElse: () => false,
172-
);
173-
174-
if (!isStablecoin) {
175-
// Fallback: Check if the original currency is directly supported (only for non-stablecoins)
176-
final original = fiatCurrency.symbol.toLowerCase();
177-
if (_cachedFiatCurrencies?.contains(original.toUpperCase()) == true) {
178-
return original;
179-
}
180-
}
181-
182-
// For stablecoins, if the underlying fiat is not supported,
183-
// still return the underlying fiat currency (don't fallback to stablecoin symbol)
184-
// This ensures we never use stablecoin symbols like 'usdt' as vs_currency
185-
if (isStablecoin) {
186-
return mappedCurrency; // This is already the underlying fiat from coinGeckoId
187-
}
188-
189-
// Throw exception instead of silently falling back to USD
190-
// This prevents incorrect price data without warning
191-
throw UnsupportedError(
192-
'Currency ${fiatCurrency.symbol} (mapped to $mappedCurrency) is not supported by CoinGecko. '
193-
'Supported currencies: ${_cachedFiatCurrencies?.join(', ') ?? 'unknown (cache not loaded)'}',
194-
);
195-
}
196-
197-
/// Maps currency for supports() method - returns null instead of throwing for unsupported currencies
198-
String? _mapFiatCurrencyToCoingeckoSafe(QuoteCurrency fiatCurrency) {
199-
try {
200-
return _mapFiatCurrencyToCoingecko(fiatCurrency);
201-
} on UnsupportedError {
202-
return null;
203-
}
204-
}
205-
206149
@override
207150
Future<Decimal> getCoinFiatPrice(
208151
AssetId assetId, {
209152
DateTime? priceDate,
210153
QuoteCurrency fiatCurrency = Stablecoin.usdt,
211154
}) async {
212155
final tradingSymbol = resolveTradingSymbol(assetId);
213-
final mappedFiatId = _mapFiatCurrencyToCoingecko(fiatCurrency);
156+
final mappedFiatId = fiatCurrency.coinGeckoId;
214157

215158
final coinPrice = await coinGeckoProvider.fetchCoinHistoricalMarketData(
216159
id: tradingSymbol,
@@ -247,14 +190,13 @@ class CoinGeckoRepository implements CexRepository {
247190
QuoteCurrency fiatCurrency = Stablecoin.usdt,
248191
}) async {
249192
final tradingSymbol = resolveTradingSymbol(assetId);
250-
final mappedFiatId = _mapFiatCurrencyToCoingecko(fiatCurrency);
193+
final mappedFiatId = fiatCurrency.coinGeckoId;
251194

252195
if (tradingSymbol.toUpperCase() == mappedFiatId.toUpperCase()) {
253196
throw ArgumentError('Coin and fiat coin cannot be the same');
254197
}
255198

256199
dates.sort();
257-
final trimmedCoinId = tradingSymbol.replaceAll(RegExp('-segwit'), '');
258200

259201
if (dates.isEmpty) {
260202
return {};
@@ -275,7 +217,8 @@ class CoinGeckoRepository implements CexRepository {
275217
: startDate.add(Duration(days: i + maxCoinGeckoDays));
276218

277219
final ohlcData = await getCoinOhlc(
278-
CexCoinPair(baseCoinTicker: trimmedCoinId, relCoinTicker: mappedFiatId),
220+
assetId,
221+
fiatCurrency,
279222
GraphInterval.oneDay,
280223
startAt: batchStartDate,
281224
endAt: batchEndDate,
@@ -304,7 +247,7 @@ class CoinGeckoRepository implements CexRepository {
304247
QuoteCurrency fiatCurrency = Stablecoin.usdt,
305248
}) async {
306249
final tradingSymbol = resolveTradingSymbol(assetId);
307-
final mappedFiatId = _mapFiatCurrencyToCoingecko(fiatCurrency);
250+
final mappedFiatId = fiatCurrency.coinGeckoId;
308251

309252
if (tradingSymbol.toUpperCase() == mappedFiatId.toUpperCase()) {
310253
throw ArgumentError('Coin and fiat coin cannot be the same');
@@ -332,19 +275,12 @@ class CoinGeckoRepository implements CexRepository {
332275
PriceRequestType requestType,
333276
) async {
334277
final coins = await getCoinList();
335-
final mappedFiat = _mapFiatCurrencyToCoingeckoSafe(fiatCurrency);
336-
337-
// If currency mapping failed, it's not supported
338-
if (mappedFiat == null) {
339-
return false;
340-
}
278+
final mappedFiat = fiatCurrency.coinGeckoId;
341279

342280
// Use the same logic as resolveTradingSymbol to find the coin
343281
final tradingSymbol = resolveTradingSymbol(assetId);
344282
final supportsAsset = coins.any(
345-
(c) =>
346-
c.id.toLowerCase() == tradingSymbol.toLowerCase() ||
347-
c.symbol.toLowerCase() == tradingSymbol.toLowerCase(),
283+
(c) => c.id.toLowerCase() == tradingSymbol.toLowerCase(),
348284
);
349285
final supportsFiat =
350286
_cachedFiatCurrencies?.contains(mappedFiat.toUpperCase()) ?? false;

0 commit comments

Comments
 (0)