| 1 | import 'package:cake_wallet/buy/buy_provider.dart'; |
| 2 | import 'package:cake_wallet/buy/payment_method.dart'; |
| 3 | import 'package:cake_wallet/core/selectable_option.dart'; |
| 4 | import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; |
| 5 | import 'package:cake_wallet/entities/fiat_currency.dart'; |
| 6 | import 'package:cake_wallet/entities/provider_types.dart'; |
| 7 | import 'package:cake_wallet/exchange/limits.dart'; |
| 8 | import 'package:cw_core/crypto_currency.dart'; |
| 9 | |
| 10 | enum ProviderRecommendation { bestRate, lowKyc, successRate } |
| 11 | |
| 12 | extension RecommendationTitle on ProviderRecommendation { |
| 13 | String get title { |
| 14 | switch (this) { |
| 15 | case ProviderRecommendation.bestRate: |
| 16 | return 'BEST RATE'; |
| 17 | case ProviderRecommendation.lowKyc: |
| 18 | return 'LOW KYC'; |
| 19 | case ProviderRecommendation.successRate: |
| 20 | return 'HIGHEST SUCCESS RATE'; |
| 21 | } |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | ProviderRecommendation? getRecommendationFromString(String title) { |
| 26 | switch (title) { |
| 27 | case 'BEST RATE': |
| 28 | return ProviderRecommendation.bestRate; |
| 29 | case 'LowKyc': |
| 30 | return ProviderRecommendation.lowKyc; |
| 31 | case 'SuccessRate': |
| 32 | return ProviderRecommendation.successRate; |
| 33 | default: |
| 34 | return null; |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | class Quote extends SelectableOption { |
| 39 | Quote({ |
| 40 | required this.rate, |
| 41 | required this.feeAmount, |
| 42 | required this.networkFee, |
| 43 | required this.transactionFee, |
| 44 | required this.payout, |
| 45 | required this.provider, |
| 46 | required this.paymentType, |
| 47 | required this.recommendations, |
| 48 | this.isBuyAction = true, |
| 49 | this.quoteId, |
| 50 | this.rampId, |
| 51 | this.rampName, |
| 52 | this.rampIconPath, |
| 53 | this.limits, |
| 54 | this.customPaymentMethodType, |
| 55 | }) : super(title: provider.isAggregator ? rampName ?? '' : provider.title); |
| 56 | |
| 57 | final double rate; |
| 58 | final double feeAmount; |
| 59 | final double networkFee; |
| 60 | final double transactionFee; |
| 61 | final double payout; |
| 62 | final PaymentType paymentType; |
| 63 | final BuyProvider provider; |
| 64 | final String? quoteId; |
| 65 | final List<ProviderRecommendation> recommendations; |
| 66 | String? rampId; |
| 67 | String? rampName; |
| 68 | String? rampIconPath; |
| 69 | bool _isSelected = false; |
| 70 | bool _isBestRate = false; |
| 71 | bool isBuyAction; |
| 72 | Limits? limits; |
| 73 | String? customPaymentMethodType; |
| 74 | |
| 75 | late FiatCurrency _fiatCurrency; |
| 76 | late CryptoCurrency _cryptoCurrency; |
| 77 | |
| 78 | bool get isSelected => _isSelected; |
| 79 | |
| 80 | bool get isBestRate => _isBestRate; |
| 81 | |
| 82 | FiatCurrency get fiatCurrency => _fiatCurrency; |
| 83 | |
| 84 | CryptoCurrency get cryptoCurrency => _cryptoCurrency; |
| 85 | |
| 86 | @override |
| 87 | bool get isOptionSelected => this._isSelected; |
| 88 | |
| 89 | @override |
| 90 | String get lightIconPath => |
| 91 | provider.isAggregator ? rampIconPath ?? provider.lightIcon : provider.lightIcon; |
| 92 | |
| 93 | @override |
| 94 | String get darkIconPath => |
| 95 | provider.isAggregator ? rampIconPath ?? provider.darkIcon : provider.darkIcon; |
| 96 | |
| 97 | @override |
| 98 | List<String> get badges => recommendations.map((e) => e.title).toList(); |
| 99 | |
| 100 | @override |
| 101 | String get topLeftSubTitle => this.rate > 0 |
| 102 | ? '1 ${cryptoCurrency.toString()} = ${formatWithCommas(rate.toStringAsFixed(2))} ${fiatCurrency.toString()}' |
| 103 | : ''; |
| 104 | |
| 105 | @override |
| 106 | String get bottomLeftSubTitle { |
| 107 | if (limits != null) { |
| 108 | final min = limits!.min; |
| 109 | final max = limits!.max; |
| 110 | return 'min: ${formatWithCommas(min?.toString())} ${fiatCurrency.toString()} | max: ${max == double.infinity ? '' : '${formatWithCommas(max?.toString())} ${fiatCurrency.toString()}'}'; |
| 111 | } |
| 112 | return ''; |
| 113 | } |
| 114 | |
| 115 | @override |
| 116 | String? get topRightSubTitle => ''; |
| 117 | |
| 118 | @override |
| 119 | String get topRightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : ''; |
| 120 | |
| 121 | @override |
| 122 | String get topRightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : ''; |
| 123 | |
| 124 | String get quoteTitle => '${provider.title} - ${paymentType.name}'; |
| 125 | |
| 126 | String get formatedFee => '$feeAmount ${isBuyAction ? fiatCurrency : cryptoCurrency}'; |
| 127 | |
| 128 | set setIsSelected(bool isSelected) => _isSelected = isSelected; |
| 129 | |
| 130 | set setIsBestRate(bool isBestRate) => _isBestRate = isBestRate; |
| 131 | |
| 132 | set setFiatCurrency(FiatCurrency fiatCurrency) => _fiatCurrency = fiatCurrency; |
| 133 | |
| 134 | set setCryptoCurrency(CryptoCurrency cryptoCurrency) => _cryptoCurrency = cryptoCurrency; |
| 135 | |
| 136 | set setLimits(Limits limits) => this.limits = limits; |
| 137 | |
| 138 | factory Quote.fromOnramperJson(Map<String, dynamic> json, bool isBuyAction, |
| 139 | Map<String, dynamic> metaData, PaymentType paymentType, String? customPaymentMethodType) { |
| 140 | final rate = _toDouble(json['rate']) ?? 0.0; |
| 141 | final networkFee = _toDouble(json['networkFee']) ?? 0.0; |
| 142 | final transactionFee = _toDouble(json['transactionFee']) ?? 0.0; |
| 143 | final feeAmount = double.parse((networkFee + transactionFee).toStringAsFixed(2)); |
| 144 | |
| 145 | final rampId = json['ramp'] as String? ?? ''; |
| 146 | final rampData = metaData[rampId] ?? {}; |
| 147 | final rampName = rampData['displayName'] as String? ?? ''; |
| 148 | final rampIconPath = rampData['svg'] as String? ?? ''; |
| 149 | |
| 150 | final recommendations = json['recommendations'] != null |
| 151 | ? List<String>.from(json['recommendations'] as List<dynamic>) |
| 152 | : <String>[]; |
| 153 | |
| 154 | final enumRecommendations = recommendations |
| 155 | .map((e) => getRecommendationFromString(e)) |
| 156 | .whereType<ProviderRecommendation>() |
| 157 | .toList(); |
| 158 | |
| 159 | final availablePaymentMethods = json['availablePaymentMethods'] as List<dynamic>? ?? []; |
| 160 | double minLimit = 0.0; |
| 161 | double maxLimit = double.infinity; |
| 162 | |
| 163 | for (var paymentMethod in availablePaymentMethods) { |
| 164 | if (paymentMethod is Map<String, dynamic>) { |
| 165 | final details = paymentMethod['details'] as Map<String, dynamic>?; |
| 166 | |
| 167 | if (details != null) { |
| 168 | final limits = details['limits'] as Map<String, dynamic>?; |
| 169 | |
| 170 | if (limits != null && limits.isNotEmpty) { |
| 171 | final firstLimitEntry = limits.values.first as Map<String, dynamic>?; |
| 172 | if (firstLimitEntry != null) { |
| 173 | minLimit = _toDouble(firstLimitEntry['min'])?.roundToDouble() ?? 0.0; |
| 174 | maxLimit = _toDouble(firstLimitEntry['max'])?.roundToDouble() ?? double.infinity; |
| 175 | break; |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | return Quote( |
| 183 | rate: rate, |
| 184 | feeAmount: feeAmount, |
| 185 | networkFee: networkFee, |
| 186 | transactionFee: transactionFee, |
| 187 | payout: json['payout'] as double? ?? 0.0, |
| 188 | rampId: rampId, |
| 189 | rampName: rampName, |
| 190 | rampIconPath: rampIconPath, |
| 191 | paymentType: paymentType, |
| 192 | customPaymentMethodType: customPaymentMethodType, |
| 193 | quoteId: json['quoteId'] as String? ?? '', |
| 194 | recommendations: enumRecommendations, |
| 195 | provider: ProvidersHelper.getProviderByType(ProviderType.onramper), |
| 196 | isBuyAction: isBuyAction, |
| 197 | limits: Limits(min: minLimit, max: maxLimit), |
| 198 | ); |
| 199 | } |
| 200 | |
| 201 | factory Quote.fromMoonPayJson( |
| 202 | Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) { |
| 203 | final rate = isBuyAction |
| 204 | ? json['quoteCurrencyPrice'] as double? ?? 0.0 |
| 205 | : json['baseCurrencyPrice'] as double? ?? 0.0; |
| 206 | final fee = _toDouble(json['feeAmount']) ?? 0.0; |
| 207 | final networkFee = _toDouble(json['networkFeeAmount']) ?? 0.0; |
| 208 | final transactionFee = _toDouble(json['extraFeeAmount']) ?? 0.0; |
| 209 | final feeAmount = double.parse((fee + networkFee + transactionFee).toStringAsFixed(2)); |
| 210 | |
| 211 | final baseCurrency = json['baseCurrency'] as Map<String, dynamic>?; |
| 212 | |
| 213 | double minLimit = 0.0; |
| 214 | double maxLimit = double.infinity; |
| 215 | |
| 216 | if (baseCurrency != null) { |
| 217 | minLimit = _toDouble(baseCurrency['minAmount']) ?? minLimit; |
| 218 | maxLimit = _toDouble(baseCurrency['maxAmount']) ?? maxLimit; |
| 219 | } |
| 220 | |
| 221 | return Quote( |
| 222 | rate: rate, |
| 223 | feeAmount: feeAmount, |
| 224 | networkFee: networkFee, |
| 225 | transactionFee: transactionFee, |
| 226 | payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0, |
| 227 | paymentType: paymentType, |
| 228 | recommendations: [], |
| 229 | quoteId: json['signature'] as String? ?? '', |
| 230 | provider: ProvidersHelper.getProviderByType(ProviderType.moonpay), |
| 231 | isBuyAction: isBuyAction, |
| 232 | limits: Limits(min: minLimit, max: maxLimit), |
| 233 | ); |
| 234 | } |
| 235 | |
| 236 | factory Quote.fromDFXJson( |
| 237 | Map<String, dynamic> json, |
| 238 | bool isBuyAction, |
| 239 | PaymentType paymentType, |
| 240 | ) { |
| 241 | final rate = _toDouble(json['exchangeRate']) ?? 0.0; |
| 242 | final fees = json['fees'] as Map<String, dynamic>; |
| 243 | |
| 244 | final minVolume = _toDouble(json['minVolume']) ?? 0.0; |
| 245 | final maxVolume = _toDouble(json['maxVolume']) ?? double.infinity; |
| 246 | |
| 247 | return Quote( |
| 248 | rate: isBuyAction ? rate : 1 / rate, |
| 249 | feeAmount: _toDouble(json['feeAmount']) ?? 0.0, |
| 250 | networkFee: _toDouble(fees['network']) ?? 0.0, |
| 251 | transactionFee: _toDouble(fees['rate']) ?? 0.0, |
| 252 | payout: _toDouble(json['payout']) ?? 0.0, |
| 253 | paymentType: paymentType, |
| 254 | recommendations: [ProviderRecommendation.lowKyc], |
| 255 | provider: ProvidersHelper.getProviderByType(ProviderType.dfx), |
| 256 | isBuyAction: isBuyAction, |
| 257 | limits: Limits(min: minVolume, max: maxVolume), |
| 258 | ); |
| 259 | } |
| 260 | |
| 261 | factory Quote.fromRobinhoodJson( |
| 262 | Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) { |
| 263 | final networkFee = json['networkFee'] as Map<String, dynamic>; |
| 264 | final processingFee = json['processingFee'] as Map<String, dynamic>; |
| 265 | final networkFeeAmount = _toDouble(networkFee['fiatAmount']) ?? 0.0; |
| 266 | final transactionFeeAmount = _toDouble(processingFee['fiatAmount']) ?? 0.0; |
| 267 | final feeAmount = double.parse((networkFeeAmount + transactionFeeAmount).toStringAsFixed(2)); |
| 268 | |
| 269 | return Quote( |
| 270 | rate: _toDouble(json['price']) ?? 0.0, |
| 271 | feeAmount: feeAmount, |
| 272 | networkFee: _toDouble(networkFee['fiatAmount']) ?? 0.0, |
| 273 | transactionFee: _toDouble(processingFee['fiatAmount']) ?? 0.0, |
| 274 | payout: _toDouble(json['cryptoAmount']) ?? 0.0, |
| 275 | paymentType: paymentType, |
| 276 | recommendations: [], |
| 277 | provider: ProvidersHelper.getProviderByType(ProviderType.robinhood), |
| 278 | isBuyAction: isBuyAction, |
| 279 | limits: Limits(min: 0.0, max: double.infinity), |
| 280 | ); |
| 281 | } |
| 282 | |
| 283 | factory Quote.fromMeldJson(Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) { |
| 284 | final quotes = json['quotes'][0] as Map<String, dynamic>; |
| 285 | return Quote( |
| 286 | rate: quotes['exchangeRate'] as double? ?? 0.0, |
| 287 | feeAmount: quotes['totalFee'] as double? ?? 0.0, |
| 288 | networkFee: quotes['networkFee'] as double? ?? 0.0, |
| 289 | transactionFee: quotes['transactionFee'] as double? ?? 0.0, |
| 290 | payout: quotes['payout'] as double? ?? 0.0, |
| 291 | paymentType: paymentType, |
| 292 | recommendations: [], |
| 293 | provider: ProvidersHelper.getProviderByType(ProviderType.meld), |
| 294 | isBuyAction: isBuyAction, |
| 295 | limits: Limits(min: 0.0, max: double.infinity), |
| 296 | ); |
| 297 | } |
| 298 | |
| 299 | factory Quote.fromKryptonimJson( |
| 300 | Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) { |
| 301 | final fees = json['fees'] as Map<String, dynamic>; |
| 302 | // final rate = _toDouble(json['rate']) ?? 0.0; |
| 303 | final limits = json['limits'] as Map<String, dynamic>; |
| 304 | final minLimit = _toDouble(limits['min_amount']) ?? 0.0; |
| 305 | final maxLimit = _toDouble(limits['max_amount']) ?? double.infinity; |
| 306 | final convertedAmount = _toDouble(json['converted_amount']) ?? 0.0; |
| 307 | final amount = _toDouble(json['amount']) ?? 0.0; |
| 308 | final calculatedRate = amount / convertedAmount; |
| 309 | return Quote( |
| 310 | rate: calculatedRate, |
| 311 | feeAmount: _toDouble(fees['totalFee']) ?? 0.0, |
| 312 | networkFee: _toDouble(fees['network_fee']) ?? 0.0, |
| 313 | transactionFee: _toDouble(fees['operation_fee']) ?? 0.0, |
| 314 | payout: _toDouble(json['amount']) ?? 0.0, |
| 315 | paymentType: paymentType, |
| 316 | recommendations: [], |
| 317 | provider: ProvidersHelper.getProviderByType(ProviderType.kriptonim), |
| 318 | isBuyAction: isBuyAction, |
| 319 | limits: Limits(min: minLimit, max: maxLimit)); |
| 320 | } |
| 321 | |
| 322 | static double? _toDouble(dynamic value) { |
| 323 | if (value is int) { |
| 324 | return value.toDouble(); |
| 325 | } else if (value is double) { |
| 326 | return value; |
| 327 | } else if (value is String) { |
| 328 | return double.tryParse(value); |
| 329 | } |
| 330 | return null; |
| 331 | } |
| 332 | |
| 333 | @override |
| 334 | String toString() => |
| 335 | 'Quote: rate: $rate, feeAmount: $feeAmount, networkFee: $networkFee, transactionFee: $transactionFee, payout: $payout, paymentType: $paymentType, provider: $provider, quoteId: $quoteId, recommendations: $recommendations, isBuyAction: $isBuyAction, rampId: $rampId, rampName: $rampName, rampIconPath: $rampIconPath, [limits: min: ${limits?.min}, max: ${limits?.max}]'; |
| 336 | } |