1- import 'dart:developer' ;
2-
31import 'package:async/async.dart' ;
42import 'package:decimal/decimal.dart' ;
53import '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
86import 'package:komodo_cex_market_data/src/id_resolution_strategy.dart' ;
97import 'package:komodo_cex_market_data/src/models/models.dart' ;
108import '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;
139import '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