cake_wallet/lib/view_model/exchange/exchange_view_model.dart
Omar Hatem 3ce4000dcf
Cw 78 ethereum (#862)
* Add initial flow for ethereum

* Add initial create Eth wallet flow

* Complete Ethereum wallet creation flow

* Fix web3dart versioning issue

* Add primary receive address extracted from private key

* Implement open wallet functionality

* Implement restore wallet from seed functionality

* Fixate web3dart version as higher versions cause some issues

* Add Initial Transaction priorities for eth
Add estimated gas price

* Rename priority value to tip

* Re-order wallet types

* Change ethereum node
Fix connection issues

* Fix estimating gas for priority

* Add case for ethereum to fetch it's seeds

* Add case for ethereum to request node

* Fix Exchange screen initial pairs

* Add initial send transaction flow

* Add missing configure for ethereum class

* Add Eth address initial setup

* Fix Private key for Ethereum wallets

* Change sign/send transaction flow

* - Fix Conflicts with main
- Remove unused function from Haven configure.dart

* Add build command for ethereum package

* Add missing Node list file to pubspec

* - Fix balance display
- Fix parsing of Ethereum amount
- Add more Ethereum Nodes 

* - Fix extracting Ethereum Private key from seeds
- Integrate signing/sending transaction with the send view model

* - Update and Fix Conflicts with main

* Add Balances for ERC20 tokens

* Fix conflicts with main

* Add erc20 abi json

* Add send erc20 tokens initial function

* add missing getHeightByDate in Haven

* Allow contacts and wallets from the same tag

* Add Shiba Inu icon

* Add send ERC-20 tokens initial flow

* Add missing import in generated file

* Add initial approach for transaction sending for ERC-20 tokens

* Refactor signing/sending transactions

* Add initial flow for transactions subscription

* Refactor signing/sending transactions

* Add home settings icon

* Fix conflicts with main

* Initial flow for home settings

* Add logic flow for adding erc20 tokens

* Fix initial UI

* Finalize UI for Tokens

* Integrate UI with Ethereum flow

* Add "Enable/Disable" feature for ERC20 tokens

* Add initial Erc20 tokens

* Add Sorting and Pin Native Token features

* Fix price sorting

* Sort tokens list as well when Sort criteria changes

* - Improve sorting balances flow
- Add initial add token from search bar flow

* Fix Accounts Popup UI

* Fix Pin native token

* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism

* Fix deleting token
Fix renaming tokens

* Fix issue with search

* Add more tokens

* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker

* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically

* Change BNB token initial availability

* Fix Conflicts with main

* Fix Conflicts with main

* Add Verse ERC20 token to the initial tokens list

* Add rename wallet to Ethereum

* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package

* Adjust transactions fiat price for ERC20 tokens

* Free Up GitHub Actions Ubuntu Runner Disk Space

* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)

* Fix Transaction Fee display

* Save transaction history

* Enhance loading time for erc20 tokens transactions

* Minor Fixes and Enhancements

* Fix sending erc20
fix block explorer issue

* Fix int overflow

* Fix transaction amount conversions

* Minor: `slow` -> `Slow` 

* Update build guide

* Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential

* Update transactions on a periodic basis

* For fee, use ETH spot price, not ERC-20 spot price

* Add Etherscan History privacy option to enable/disable Etherscan API

* Show estimated fee amounts in the send screen

* fix send fiat fields parsing issue

* Fix transactions estimated fee less than actual fee

* handle balance sorting when balance is disabled
Handle empty transactions list

* Fix Delete Ethereum wallet
Fix balance < 0.01

* Fix Decimal place for Ethereum amount
Fix sending amount issue

* Change words count

* Remove balance hint and Full balance row from Ethereum wallets

* support changing the asset type in send templates

* Fix Templates for ERC tokens issues

* Fix conflicts in send templates

* Disable batch sending in Ethereum

* Fix Fee calculation with different priorities

* Fix Conflicts with main

* Add offline error to ignored exceptions

---------

Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
2023-08-04 20:01:49 +03:00

812 lines
26 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/changenow/changenow_request.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_trade_request.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_request.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(
this.wallet,
this.trades,
this._exchangeTemplateStore,
this.tradesStore,
this._settingsStore,
this.sharedPreferences,
this.contactListViewModel)
: _cryptoNumberFormat = NumberFormat(),
isFixedRateMode = false,
isReceiveAmountEntered = false,
depositAmount = '',
receiveAmount = '',
receiveAddress = '',
depositAddress = '',
isDepositAddressEnabled = false,
isReceiveAddressEnabled = false,
isReceiveAmountEditable = false,
_useTorOnly = false,
receiveCurrencies = <CryptoCurrency>[],
depositCurrencies = <CryptoCurrency>[],
limits = Limits(min: 0, max: 0),
tradeState = ExchangeTradeStateInitial(),
limitsState = LimitsInitialState(),
receiveCurrency = wallet.currency,
depositCurrency = wallet.currency,
providerList = [],
selectedProviders = ObservableList<ExchangeProvider>() {
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
_setProviders();
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeReceiveCurrencies = [
CryptoCurrency.xlm,
CryptoCurrency.xrp,
CryptoCurrency.bnb,
CryptoCurrency.btt,
CryptoCurrency.nano
];
_initialPairBasedOnWallet();
final Map<String, dynamic> exchangeProvidersSelection = json.decode(
sharedPreferences
.getString(PreferencesKey.exchangeProvidersSelection) ??
"{}") as Map<String, dynamic>;
/// if the provider is not in the user settings (user's first time or newly added provider)
/// then use its default value decided by us
selectedProviders = ObservableList.of(providersForCurrentPair()
.where((element) => exchangeProvidersSelection[element.title] == null
? element.isEnabled
: (exchangeProvidersSelection[element.title] as bool))
.toList());
_setAvailableProviders();
_calculateBestRate();
bestRateSync =
Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate());
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
depositAmount = '';
receiveAmount = '';
receiveAddress = '';
depositAddress = depositCurrency == wallet.currency
? wallet.walletAddresses.address
: '';
provider = providersForCurrentPair().first;
final initialProvider = provider;
provider!.checkIsAvailable().then((bool isAvailable) {
if (!isAvailable && provider == initialProvider) {
provider = providerList.firstWhere(
(provider) => provider is ChangeNowExchangeProvider,
orElse: () => providerList.last);
_onPairChange();
}
});
receiveCurrencies = CryptoCurrency.all
.where((cryptoCurrency) =>
!excludeReceiveCurrencies.contains(cryptoCurrency))
.toList();
depositCurrencies = CryptoCurrency.all
.where((cryptoCurrency) =>
!excludeDepositCurrencies.contains(cryptoCurrency))
.toList();
_defineIsReceiveAmountEditable();
loadLimits();
reaction((_) => isFixedRateMode, (Object _) {
loadLimits();
_bestRate = 0;
_calculateBestRate();
});
}
bool _useTorOnly;
final WalletBase wallet;
final Box<Trade> trades;
final ExchangeTemplateStore _exchangeTemplateStore;
final TradesStore tradesStore;
final SharedPreferences sharedPreferences;
List<ExchangeProvider> get _allProviders => [
ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
];
@observable
ExchangeProvider? provider;
/// Maps in dart are not sorted by default
/// SplayTreeMap is a map sorted by keys
/// will use it to sort available providers
/// based on the rate they yield for the current trade
///
///
/// initialize with descending comparator
/// since we want largest rate first
final SplayTreeMap<double, ExchangeProvider> _sortedAvailableProviders =
SplayTreeMap<double, ExchangeProvider>(
(double a, double b) => b.compareTo(a));
final List<ExchangeProvider> _tradeAvailableProviders = [];
@observable
ObservableList<ExchangeProvider> selectedProviders;
@observable
List<ExchangeProvider> providerList;
@observable
CryptoCurrency depositCurrency;
@observable
CryptoCurrency receiveCurrency;
@observable
LimitsState limitsState;
@observable
ExchangeTradeState tradeState;
@observable
String depositAmount;
@observable
String receiveAmount;
@observable
String depositAddress;
@observable
String receiveAddress;
@observable
bool isDepositAddressEnabled;
@observable
bool isReceiveAddressEnabled;
@observable
bool isReceiveAmountEntered;
@observable
bool isReceiveAmountEditable;
@observable
bool isFixedRateMode;
@observable
Limits limits;
@computed
SyncStatus get status => wallet.syncStatus;
@computed
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
@computed
List<WalletContact> get walletContactsToShow =>
contactListViewModel.walletContacts
.where((element) =>
receiveCurrency == null || element.type == receiveCurrency)
.toList();
@action
bool checkIfWalletIsAnInternalWallet(String address) {
final walletContactList = walletContactsToShow
.where((element) => element.address == address)
.toList();
return walletContactList.isNotEmpty;
}
@computed
bool get shouldDisplayTOTP2FAForExchangesToInternalWallet =>
_settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets;
//* Still open to further optimize these checks
//* It works but can be made better
@action
bool shouldDisplayTOTP() {
final isInternalWallet = checkIfWalletIsAnInternalWallet(receiveAddress);
if (isInternalWallet) {
return shouldDisplayTOTP2FAForExchangesToInternalWallet;
}
return false;
}
@computed
TransactionPriority get transactionPriority {
final priority = _settingsStore.priority[wallet.type];
if (priority == null) {
throw Exception('Unexpected type ${wallet.type.toString()}');
}
return priority;
}
bool get hasAllAmount =>
(wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && depositCurrency == wallet.currency;
bool get isMoneroWallet => wallet.type == WalletType.monero;
bool get isLowFee {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
return transactionPriority ==
monero!.getMoneroTransactionPrioritySlow();
case WalletType.bitcoin:
return transactionPriority ==
bitcoin!.getBitcoinTransactionPrioritySlow();
case WalletType.litecoin:
return transactionPriority ==
bitcoin!.getLitecoinTransactionPrioritySlow();
default:
return false;
}
}
List<CryptoCurrency> receiveCurrencies;
List<CryptoCurrency> depositCurrencies;
final NumberFormat _cryptoNumberFormat;
final SettingsStore _settingsStore;
final ContactListViewModel contactListViewModel;
double _bestRate = 0.0;
late Timer bestRateSync;
@action
void changeDepositCurrency({required CryptoCurrency currency}) {
depositCurrency = currency;
isFixedRateMode = false;
_onPairChange();
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
}
@action
void changeReceiveCurrency({required CryptoCurrency currency}) {
receiveCurrency = currency;
isFixedRateMode = false;
_onPairChange();
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
}
@action
Future<void> changeReceiveAmount({required String amount}) async {
receiveAmount = amount;
if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
if (_bestRate == 0) {
depositAmount = S.current.fetching;
await _calculateBestRate();
}
_cryptoNumberFormat.maximumFractionDigits = depositMaxDigits;
depositAmount = _cryptoNumberFormat
.format(_enteredAmount / _bestRate)
.toString()
.replaceAll(RegExp('\\,'), '');
}
@action
Future<void> changeDepositAmount({required String amount}) async {
depositAmount = amount;
if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
/// in case the best rate was not calculated yet
if (_bestRate == 0) {
receiveAmount = S.current.fetching;
await _calculateBestRate();
}
_cryptoNumberFormat.maximumFractionDigits = receiveMaxDigits;
receiveAmount = _cryptoNumberFormat
.format(_bestRate * _enteredAmount)
.toString()
.replaceAll(RegExp('\\,'), '');
}
bool checkIfInputMeetsMinOrMaxCondition(String input) {
final _enteredAmount = double.tryParse(input.replaceAll(',', '.')) ?? 0;
double minLimit = limits.min ?? 0;
double? maxLimit = limits.max;
if (_enteredAmount < minLimit) {
return false;
}
if (maxLimit != null && _enteredAmount > maxLimit) {
return false;
}
return true;
}
Future<void> _calculateBestRate() async {
final amount =
double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
final _providers = _tradeAvailableProviders
.where((element) => !isFixedRateMode || element.supportsFixedRate)
.toList();
final result = await Future.wait<double>(_providers.map((element) =>
element.fetchRate(
from: depositCurrency,
to: receiveCurrency,
amount: amount,
isFixedRateMode: isFixedRateMode,
isReceiveAmount: isFixedRateMode)));
_sortedAvailableProviders.clear();
for (int i = 0; i < result.length; i++) {
if (result[i] != 0) {
/// add this provider as its valid for this trade
try {
_sortedAvailableProviders[result[i]] = _providers[i];
} catch (e) {
// will throw "Concurrent modification during iteration" error if modified at the same
// time [createTrade] is called, as this is not a normal map, but a sorted map
}
}
}
if (_sortedAvailableProviders.isNotEmpty) {
_bestRate = _sortedAvailableProviders.keys.first;
}
}
@action
Future<void> loadLimits() async {
if (selectedProviders.isEmpty) {
return;
}
limitsState = LimitsIsLoading();
final from = isFixedRateMode ? receiveCurrency : depositCurrency;
final to = isFixedRateMode ? depositCurrency : receiveCurrency;
double? lowestMin = double.maxFinite;
double? highestMax = 0.0;
try {
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
try {
final tempLimits = await provider.fetchLimits(
from: from, to: to, isFixedRateMode: isFixedRateMode);
if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) {
lowestMin = tempLimits.min;
}
if (highestMax != null &&
(tempLimits.max ?? double.maxFinite) > highestMax) {
highestMax = tempLimits.max;
}
} catch (e) {
continue;
}
}
} on ConcurrentModificationError {
/// if user changed the selected providers while fetching limits
/// then delay the fetching limits a bit and try again
///
/// this is because the limitation of collections that
/// you can't modify it while iterating through it
Future.delayed(Duration(milliseconds: 200), loadLimits);
}
if (lowestMin != double.maxFinite) {
limits = Limits(min: lowestMin, max: highestMax);
limitsState = LimitsLoadedSuccessfully(limits: limits);
} else {
limitsState = LimitsLoadedFailure(error: 'Limits loading failed');
}
}
@action
Future<void> createTrade() async {
TradeRequest? request;
String amount = '';
try {
for (var provider in _sortedAvailableProviders.values) {
if (!(await provider.checkIsAvailable())) {
continue;
}
if (provider is SideShiftExchangeProvider) {
request = SideShiftRequest(
depositMethod: depositCurrency,
settleMethod: receiveCurrency,
depositAmount: isFixedRateMode
? receiveAmount.replaceAll(',', '.')
: depositAmount.replaceAll(',', '.'),
settleAddress: receiveAddress,
refundAddress: depositAddress,
);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is SimpleSwapExchangeProvider) {
request = SimpleSwapRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is XMRTOExchangeProvider) {
request = XMRTOTradeRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
receiveAmount: receiveAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
isBTCRequest: isReceiveAmountEntered);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is ChangeNowExchangeProvider) {
request = ChangeNowRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress,
isReverse: isFixedRateMode);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is MorphTokenExchangeProvider) {
request = MorphTokenRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is TrocadorExchangeProvider) {
request = TrocadorRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress,
isReverse: isFixedRateMode);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) {
if (limits.max != null && double.parse(amount) < limits.min!) {
continue;
} else if (limits.max != null && double.parse(amount) > limits.max!) {
continue;
} else {
try {
tradeState = TradeIsCreating();
final trade = await provider.createTrade(
request: request!, isFixedRateMode: isFixedRateMode);
trade.walletId = wallet.id;
tradesStore.setTrade(trade);
await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade);
/// return after the first successful trade
return;
} catch (e) {
continue;
}
}
}
}
/// if the code reached here then none of the providers succeeded
tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created,
error: S.current.none_of_selected_providers_can_exchange);
} on ConcurrentModificationError {
/// if create trade happened at the exact same time of the scheduled rate update
/// then delay the create trade a bit and try again
///
/// this is because the limitation of the SplayTreeMap that
/// you can't modify it while iterating through it
Future.delayed(Duration(milliseconds: 200), createTrade);
}
}
@action
void reset() {
_initialPairBasedOnWallet();
isReceiveAmountEntered = false;
depositAmount = '';
receiveAmount = '';
depositAddress = depositCurrency == wallet.currency
? wallet.walletAddresses.address
: '';
receiveAddress = receiveCurrency == wallet.currency
? wallet.walletAddresses.address
: '';
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
isFixedRateMode = false;
_onPairChange();
}
@action
void calculateDepositAllAmount() {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
final availableBalance = wallet.balance[wallet.currency]!.available;
final priority = _settingsStore.priority[wallet.type]!;
final fee = wallet.calculateEstimatedFee(priority, null);
if (availableBalance < fee || availableBalance == 0) {
return;
}
final amount = availableBalance - fee;
changeDepositAmount(
amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
}
}
void updateTemplate() => _exchangeTemplateStore.update();
void addTemplate(
{required String amount,
required String depositCurrency,
required String receiveCurrency,
required String provider,
required String depositAddress,
required String receiveAddress,
required String depositCurrencyTitle,
required String receiveCurrencyTitle}) =>
_exchangeTemplateStore.addTemplate(
amount: amount,
depositCurrency: depositCurrency,
receiveCurrency: receiveCurrency,
provider: provider,
depositAddress: depositAddress,
receiveAddress: receiveAddress,
depositCurrencyTitle: depositCurrencyTitle,
receiveCurrencyTitle: receiveCurrencyTitle);
void removeTemplate({required ExchangeTemplate template}) =>
_exchangeTemplateStore.remove(template: template);
List<ExchangeProvider> providersForCurrentPair() {
return _providersForPair(from: depositCurrency, to: receiveCurrency);
}
List<ExchangeProvider> _providersForPair(
{required CryptoCurrency from, required CryptoCurrency to}) {
final providers = providerList
.where((provider) => provider.pairList
.where((pair) => pair.from == from && pair.to == to)
.isNotEmpty)
.toList();
return providers;
}
void _onPairChange() {
depositAmount = '';
receiveAmount = '';
loadLimits();
_setAvailableProviders();
_bestRate = 0;
_calculateBestRate();
}
void _initialPairBasedOnWallet() {
switch (wallet.type) {
case WalletType.monero:
depositCurrency = CryptoCurrency.xmr;
receiveCurrency = CryptoCurrency.btc;
break;
case WalletType.bitcoin:
depositCurrency = CryptoCurrency.btc;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.litecoin:
depositCurrency = CryptoCurrency.ltc;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.haven:
depositCurrency = CryptoCurrency.xhv;
receiveCurrency = CryptoCurrency.btc;
break;
case WalletType.ethereum:
depositCurrency = CryptoCurrency.eth;
receiveCurrency = CryptoCurrency.xmr;
break;
default:
break;
}
}
void _defineIsReceiveAmountEditable() {
/*if ((provider is ChangeNowExchangeProvider)
&&(depositCurrency == CryptoCurrency.xmr)
&&(receiveCurrency == CryptoCurrency.btc)) {
isReceiveAmountEditable = true;
} else {
isReceiveAmountEditable = false;
}*/
//isReceiveAmountEditable = false;
// isReceiveAmountEditable = selectedProviders.any((provider) => provider is ChangeNowExchangeProvider);
// isReceiveAmountEditable = provider is ChangeNowExchangeProvider || provider is SimpleSwapExchangeProvider;
isReceiveAmountEditable = true;
}
@action
void addExchangeProvider(ExchangeProvider provider) {
selectedProviders.add(provider);
if (providersForCurrentPair().contains(provider)) {
_tradeAvailableProviders.add(provider);
}
}
@action
void removeExchangeProvider(ExchangeProvider provider) {
selectedProviders.remove(provider);
_tradeAvailableProviders.remove(provider);
}
@action
void saveSelectedProviders() {
depositAmount = '';
receiveAmount = '';
isFixedRateMode = false;
_defineIsReceiveAmountEditable();
loadLimits();
_bestRate = 0;
_calculateBestRate();
final Map<String, dynamic> exchangeProvidersSelection = json.decode(
sharedPreferences
.getString(PreferencesKey.exchangeProvidersSelection) ??
"{}") as Map<String, dynamic>;
for (var provider in providerList) {
exchangeProvidersSelection[provider.title] =
selectedProviders.contains(provider);
}
sharedPreferences.setString(
PreferencesKey.exchangeProvidersSelection,
json.encode(exchangeProvidersSelection),
);
}
bool get isAvailableInSelected {
final providersForPair = providersForCurrentPair();
return selectedProviders.any(
(element) => element.isAvailable && providersForPair.contains(element));
}
void _setAvailableProviders() {
_tradeAvailableProviders.clear();
_tradeAvailableProviders.addAll(selectedProviders
.where((provider) => providersForCurrentPair().contains(provider)));
}
@action
void setDefaultTransactionPriority() {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
_settingsStore.priority[wallet.type] =
monero!.getMoneroTransactionPriorityAutomatic();
break;
case WalletType.bitcoin:
_settingsStore.priority[wallet.type] =
bitcoin!.getBitcoinTransactionPriorityMedium();
break;
case WalletType.litecoin:
_settingsStore.priority[wallet.type] =
bitcoin!.getLitecoinTransactionPriorityMedium();
break;
default:
break;
}
}
void _setProviders() {
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly) {
providerList = _allProviders
.where((provider) => provider.supportsOnionAddress)
.toList();
} else {
providerList = _allProviders;
}
}
int get depositMaxDigits => depositCurrency == CryptoCurrency.btc ? 8 : 12;
int get receiveMaxDigits => receiveCurrency == CryptoCurrency.btc ? 8 : 12;
}