stack_wallet/lib/models/exchange/exchange_form_state.dart

553 lines
16 KiB
Dart
Raw Normal View History

2023-05-02 16:22:35 +00:00
import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
2023-02-08 19:06:26 +00:00
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
2023-04-28 20:31:26 +00:00
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
class ExchangeFormState extends ChangeNotifier {
Exchange? _exchange;
Exchange get exchange => _exchange ??= Exchange.defaultExchange;
String? _providerName;
// default to exchange name that isn't trocador
String get providerName => _providerName ??= Exchange.defaultExchange.name;
String get combinedExchangeId => "${exchange.name} ($providerName)";
ExchangeRateType _exchangeRateType = ExchangeRateType.estimated;
ExchangeRateType get exchangeRateType => _exchangeRateType;
set exchangeRateType(ExchangeRateType exchangeRateType) {
_exchangeRateType = exchangeRateType;
//
}
List<Estimate> _estimates = [];
List<Estimate> get estimates => _estimates;
bool _reversed = false;
bool get reversed => _reversed;
set reversed(bool reversed) {
_reversed = reversed;
//
}
Decimal? _rate;
Decimal? get rate => _rate;
// set rate(Decimal? rate) {
// _rate = rate;
// //
// }
Decimal? _sendAmount;
Decimal? get sendAmount => _sendAmount;
// set sendAmount(Decimal? sendAmount) {
// _sendAmount = sendAmount;
// //
// }
Decimal? _receiveAmount;
Decimal? get receiveAmount => _receiveAmount;
set receiveAmount(Decimal? receiveAmount) {
_receiveAmount = receiveAmount;
//
}
2023-02-08 19:06:26 +00:00
AggregateCurrency? _sendCurrency;
AggregateCurrency? get sendCurrency => _sendCurrency;
// set sendCurrency(Currency? sendCurrency) {
// _sendCurrency = sendCurrency;
// //
// }
2023-02-08 19:06:26 +00:00
AggregateCurrency? _receiveCurrency;
AggregateCurrency? get receiveCurrency => _receiveCurrency;
// set receiveCurrency(Currency? receiveCurrency) {
// _receiveCurrency = receiveCurrency;
// //
// }
Decimal? _minSendAmount;
Decimal? get minSendAmount => _minSendAmount;
// set minSendAmount(Decimal? minSendAmount) {
// _minSendAmount = minSendAmount;
// //
// }
Decimal? _minReceiveAmount;
Decimal? get minReceiveAmount => _minReceiveAmount;
// set minReceiveAmount(Decimal? minReceiveAmount) {
// _minReceiveAmount = minReceiveAmount;
// //
// }
Decimal? _maxSendAmount;
Decimal? get maxSendAmount => _maxSendAmount;
// set maxSendAmount(Decimal? maxSendAmount) {
// _maxSendAmount = maxSendAmount;
// //
// }
Decimal? _maxReceiveAmount;
Decimal? get maxReceiveAmount => _maxReceiveAmount;
// set maxReceiveAmount(Decimal? maxReceiveAmount) {
// _maxReceiveAmount = maxReceiveAmount;
// //
// }
//============================================================================
// computed properties
//============================================================================
String? get fromTicker => _sendCurrency?.ticker;
String? get toTicker => _receiveCurrency?.ticker;
String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? "";
String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? "";
bool get canExchange {
return sendCurrency != null &&
receiveCurrency != null &&
sendAmount != null &&
sendAmount! >= Decimal.zero &&
receiveAmount != null &&
rate != null &&
rate! >= Decimal.zero &&
2023-02-08 19:06:26 +00:00
sendCurrency!.forExchange(exchange.name) != null &&
receiveCurrency!.forExchange(exchange.name) != null &&
warning.isEmpty;
}
String get warning {
if (reversed) {
if (_receiveCurrency != null && _receiveAmount != null) {
if (_minReceiveAmount != null &&
_receiveAmount! < _minReceiveAmount! &&
_receiveAmount! > Decimal.zero) {
return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
} else if (_maxReceiveAmount != null &&
_receiveAmount! > _maxReceiveAmount!) {
return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
}
}
} else {
if (_sendCurrency != null && _sendAmount != null) {
if (_minSendAmount != null &&
_sendAmount! < _minSendAmount! &&
_sendAmount! > Decimal.zero) {
return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
} else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
}
}
}
return "";
}
//============================================================================
// public state updaters
//============================================================================
2023-02-05 23:50:40 +00:00
Future<void> updateExchange({
required Exchange exchange,
required String providerName,
2023-02-05 23:50:40 +00:00
required bool shouldUpdateData,
required bool shouldNotifyListeners,
2023-05-02 16:22:35 +00:00
bool shouldAwait = true,
2023-02-05 23:50:40 +00:00
}) async {
_exchange = exchange;
_providerName = providerName;
2023-02-05 23:50:40 +00:00
if (shouldUpdateData) {
2023-05-02 16:22:35 +00:00
if (shouldAwait) {
await _updateRangesAndEstimate(
shouldNotifyListeners: shouldNotifyListeners,
);
} else {
unawaited(
_updateRangesAndEstimate(
shouldNotifyListeners: shouldNotifyListeners,
),
);
}
} else if (shouldNotifyListeners) {
_notify();
2023-02-05 23:50:40 +00:00
}
}
void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) {
_sendCurrency = from;
_receiveCurrency = to;
}
void reset({
required bool shouldNotifyListeners,
}) {
_exchange = null;
_providerName = null;
_reversed = false;
_rate = null;
_sendAmount = null;
_receiveAmount = null;
_sendCurrency = null;
_receiveCurrency = null;
_minSendAmount = null;
_minReceiveAmount = null;
_maxSendAmount = null;
_maxReceiveAmount = null;
if (shouldNotifyListeners) {
_notify();
}
}
Future<void> setSendAmountAndCalculateReceiveAmount(
Decimal? newSendAmount,
bool shouldNotifyListeners,
) async {
if (newSendAmount == null) {
// todo: check if this breaks things and stuff
_receiveAmount = null;
_sendAmount = null;
} else {
if (newSendAmount <= Decimal.zero) {
_receiveAmount = Decimal.zero;
}
_sendAmount = newSendAmount;
_reversed = false;
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
}
if (shouldNotifyListeners) {
_notify();
}
}
Future<void> setReceivingAmountAndCalculateSendAmount(
Decimal? newReceiveAmount,
bool shouldNotifyListeners,
) async {
if (newReceiveAmount == null) {
// todo: check if this breaks things and stuff
_receiveAmount = null;
_sendAmount = null;
} else {
if (newReceiveAmount <= Decimal.zero) {
_sendAmount = Decimal.zero;
}
_receiveAmount = newReceiveAmount;
_reversed = true;
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
}
if (shouldNotifyListeners) {
_notify();
}
}
2023-02-05 21:25:35 +00:00
Future<void> updateSendCurrency(
2023-02-08 19:06:26 +00:00
AggregateCurrency sendCurrency,
bool shouldNotifyListeners,
) async {
try {
_sendCurrency = sendCurrency;
2023-02-05 23:50:40 +00:00
_minSendAmount = null;
_maxSendAmount = null;
if (_receiveCurrency == null) {
_rate = null;
} else {
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
}
2023-02-05 21:25:35 +00:00
if (shouldNotifyListeners) {
_notify();
2023-02-05 21:25:35 +00:00
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
}
2023-02-05 21:25:35 +00:00
Future<void> updateReceivingCurrency(
2023-02-08 19:06:26 +00:00
AggregateCurrency receiveCurrency,
bool shouldNotifyListeners,
) async {
try {
_receiveCurrency = receiveCurrency;
2023-02-05 23:50:40 +00:00
_minReceiveAmount = null;
_maxReceiveAmount = null;
if (_sendCurrency == null) {
_rate = null;
} else {
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
}
2023-02-05 21:25:35 +00:00
if (shouldNotifyListeners) {
_notify();
2023-02-05 21:25:35 +00:00
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
}
Future<void> swap({
required bool shouldNotifyListeners,
}) async {
final Decimal? temp = sendAmount;
_sendAmount = receiveAmount;
_receiveAmount = temp;
_minSendAmount = null;
_maxSendAmount = null;
_minReceiveAmount = null;
_maxReceiveAmount = null;
2023-02-08 19:06:26 +00:00
final AggregateCurrency? tmp = sendCurrency;
_sendCurrency = receiveCurrency;
_receiveCurrency = tmp;
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
2022-10-20 15:03:16 +00:00
);
if (shouldNotifyListeners) {
_notify();
}
}
Future<void> refresh() => _updateRangesAndEstimate(
shouldNotifyListeners: true,
);
//============================================================================
// private state updaters
//============================================================================
Future<void> _updateRangesAndEstimate({
required bool shouldNotifyListeners,
}) async {
try {
switch (exchange.name) {
case ChangeNowExchange.exchangeName:
if (!_exchangeSupported(
exchangeName: exchange.name,
sendCurrency: sendCurrency,
receiveCurrency: receiveCurrency,
exchangeRateType: exchangeRateType,
)) {
_exchange = MajesticBankExchange.instance;
}
break;
case MajesticBankExchange.exchangeName:
if (!_exchangeSupported(
2023-04-28 20:31:26 +00:00
exchangeName: exchange.name,
sendCurrency: sendCurrency,
receiveCurrency: receiveCurrency,
exchangeRateType: exchangeRateType,
)) {
_exchange = ChangeNowExchange.instance;
}
break;
case TrocadorExchange.exchangeName:
if (!_exchangeSupported(
exchangeName: exchange.name,
sendCurrency: sendCurrency,
receiveCurrency: receiveCurrency,
exchangeRateType: exchangeRateType,
)) {
_exchange = ChangeNowExchange.instance;
}
break;
}
await _updateRanges(shouldNotifyListeners: false);
await _updateEstimate(shouldNotifyListeners: false);
if (shouldNotifyListeners) {
_notify();
}
} catch (_) {
//
}
}
Future<void> _updateRanges({
required bool shouldNotifyListeners,
}) async {
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
// reversed = false;
// }
final _send = sendCurrency;
final _receive = receiveCurrency;
if (_send == null || _receive == null) {
Logging.instance.log(
"Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
level: LogLevel.Info,
);
return;
}
final response = await exchange.getRange(
_send.ticker,
_receive.ticker,
exchangeRateType == ExchangeRateType.fixed,
);
if (response.value == null) {
Logging.instance.log(
2022-10-04 19:10:44 +00:00
"Tried to $runtimeType.updateRanges for: $exchange where response: $response",
level: LogLevel.Info,
);
return;
}
final responseReversed = await exchange.getRange(
_receive.ticker,
_send.ticker,
exchangeRateType == ExchangeRateType.fixed,
);
if (responseReversed.value == null) {
Logging.instance.log(
"Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
level: LogLevel.Info,
);
return;
}
final range = response.value!;
final rangeReversed = responseReversed.value!;
_minSendAmount = range.min;
_maxSendAmount = range.max;
_minReceiveAmount = rangeReversed.min;
_maxReceiveAmount = rangeReversed.max;
2022-12-13 00:17:02 +00:00
//todo: check if print needed
// debugPrint(
// "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
if (shouldNotifyListeners) {
_notify();
}
}
Future<void> _updateEstimate({
required bool shouldNotifyListeners,
}) async {
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
// reversed = false;
// }
final amount = reversed ? receiveAmount : sendAmount;
if (sendCurrency == null ||
receiveCurrency == null ||
amount == null ||
amount <= Decimal.zero) {
Logging.instance.log(
"Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
level: LogLevel.Info,
);
return;
}
final response = await exchange.getEstimates(
sendCurrency!.ticker,
receiveCurrency!.ticker,
amount,
exchangeRateType == ExchangeRateType.fixed,
reversed,
);
if (response.value == null) {
Logging.instance.log(
2022-10-04 19:10:44 +00:00
"Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
level: LogLevel.Info,
);
return;
}
_estimates = response.value!;
if (reversed) {
_sendAmount = _estimates
.firstWhere((e) => e.exchangeProvider == providerName)
.estimatedAmount;
} else {
_receiveAmount = _estimates
.firstWhere((e) => e.exchangeProvider == providerName)
.estimatedAmount;
}
_rate =
(receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
2022-12-13 00:17:02 +00:00
//todo: check if print needed
// debugPrint(
// "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
if (shouldNotifyListeners) {
_notify();
}
}
//============================================================================
void _notify() {
debugPrint("ExFState NOTIFY: ${toString()}");
notifyListeners();
}
bool _exchangeSupported({
required String exchangeName,
required AggregateCurrency? sendCurrency,
required AggregateCurrency? receiveCurrency,
required ExchangeRateType exchangeRateType,
}) {
final send = sendCurrency?.forExchange(exchangeName);
if (send == null) return false;
final rcv = receiveCurrency?.forExchange(exchangeName);
if (rcv == null) return false;
if (exchangeRateType == ExchangeRateType.fixed) {
return send.supportsFixedRate && rcv.supportsFixedRate;
} else {
return send.supportsEstimatedRate && rcv.supportsEstimatedRate;
}
}
@override
String toString() {
return "{"
"\n\t exchange: $exchange,"
"\n\t exchangeRateType: $exchangeRateType,"
"\n\t sendCurrency: $sendCurrency,"
"\n\t receiveCurrency: $receiveCurrency,"
"\n\t rate: $rate,"
"\n\t reversed: $reversed,"
"\n\t sendAmount: $sendAmount,"
"\n\t receiveAmount: $receiveAmount,"
"\n\t estimates: $estimates,"
"\n\t minSendAmount: $minSendAmount,"
"\n\t maxSendAmount: $maxSendAmount,"
"\n\t minReceiveAmount: $minReceiveAmount,"
"\n\t maxReceiveAmount: $maxReceiveAmount,"
"\n\t canExchange: $canExchange,"
"\n\t warning: $warning,"
"\n}";
}
}