mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-02-02 03:06:35 +00:00
save
This commit is contained in:
parent
5c966fe530
commit
fd1485e42a
7 changed files with 1808 additions and 6 deletions
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
|
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
import 'package:cake_wallet/ionia/ionia_anypay.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||||
|
@ -778,6 +779,11 @@ Future<void> setup({
|
||||||
wallet: getIt.get<AppStore>().wallet!,
|
wallet: getIt.get<AppStore>().wallet!,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
getIt.registerFactory<MoonPayExchangeProvider>(() => MoonPayExchangeProvider(
|
||||||
|
settingsStore: getIt.get<AppStore>().settingsStore,
|
||||||
|
wallet: getIt.get<AppStore>().wallet!,
|
||||||
|
));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
|
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
|
||||||
|
|
||||||
getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
|
getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/entities/buy_provider_types.dart';
|
import 'package:cake_wallet/entities/buy_provider_types.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_provider_types.dart';
|
import 'package:cake_wallet/entities/exchange_provider_types.dart';
|
||||||
|
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
|
@ -102,8 +103,7 @@ class MainActions {
|
||||||
if (!viewModel.isEnabledExchangeAction) return;
|
if (!viewModel.isEnabledExchangeAction) return;
|
||||||
final defaultExchangeProvider = viewModel.defaultExchangeProvider;
|
final defaultExchangeProvider = viewModel.defaultExchangeProvider;
|
||||||
final walletType = viewModel.type;
|
final walletType = viewModel.type;
|
||||||
|
|
||||||
// TODO: CW-492 this is currently all wallets as the ticket didn't specify which wallets should be supported:
|
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
@ -117,7 +117,7 @@ class MainActions {
|
||||||
Navigator.pushNamed(context, Routes.choose_exchange_provider);
|
Navigator.pushNamed(context, Routes.choose_exchange_provider);
|
||||||
break;
|
break;
|
||||||
case ExchangeProviderType.MoonPay:
|
case ExchangeProviderType.MoonPay:
|
||||||
await getIt.get<MoonPayBuyProvider>().launchProvider(context);
|
await getIt.get<MoonPayExchangeProvider>().launchProvider(context);
|
||||||
break;
|
break;
|
||||||
case ExchangeProviderType.Normal:
|
case ExchangeProviderType.Normal:
|
||||||
await Navigator.of(context).pushNamed(Routes.exchange);
|
await Navigator.of(context).pushNamed(Routes.exchange);
|
||||||
|
|
135
lib/exchange/moonpay/moonpay_exchange_provider.dart
Normal file
135
lib/exchange/moonpay/moonpay_exchange_provider.dart
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/palette.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class MoonPayExchangeProvider {
|
||||||
|
MoonPayExchangeProvider(
|
||||||
|
{required SettingsStore settingsStore, required WalletBase wallet, this.isTest = true})
|
||||||
|
: this._settingsStore = settingsStore,
|
||||||
|
this._wallet = wallet,
|
||||||
|
baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
|
||||||
|
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
final WalletBase _wallet;
|
||||||
|
|
||||||
|
static const _baseTestUrl = 'buy-staging.moonpay.com';
|
||||||
|
static const _baseProductUrl = 'buy.moonpay.com';
|
||||||
|
static const _apiUrl = 'https://api.moonpay.com';
|
||||||
|
static const _currenciesSuffix = '/v3/currencies';
|
||||||
|
static const _quoteSuffix = '/buy_quote';
|
||||||
|
static const _transactionsSuffix = '/v1/transactions';
|
||||||
|
static const _ipAddressSuffix = '/v4/ip_address';
|
||||||
|
static const _apiKey = secrets.moonPayApiKey;
|
||||||
|
static const _secretKey = secrets.moonPaySecretKey;
|
||||||
|
|
||||||
|
static String themeToMoonPayTheme(ThemeBase theme) {
|
||||||
|
switch (theme.type) {
|
||||||
|
case ThemeType.bright:
|
||||||
|
return 'light';
|
||||||
|
case ThemeType.light:
|
||||||
|
return 'light';
|
||||||
|
case ThemeType.dark:
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isTest;
|
||||||
|
final String baseUrl;
|
||||||
|
|
||||||
|
Future<Uri> requestUrl(
|
||||||
|
{required CryptoCurrency currency, required String refundWalletAddress}) async {
|
||||||
|
final customParams = {
|
||||||
|
'theme': themeToMoonPayTheme(_settingsStore.currentTheme),
|
||||||
|
'language': _settingsStore.languageCode,
|
||||||
|
'colorCode': _settingsStore.currentTheme.type == ThemeType.dark
|
||||||
|
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
|
||||||
|
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
|
||||||
|
};
|
||||||
|
|
||||||
|
final originalUri = Uri.https(
|
||||||
|
baseUrl,
|
||||||
|
'',
|
||||||
|
<String, dynamic>{
|
||||||
|
// 'apiKey': _apiKey,
|
||||||
|
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
|
||||||
|
// 'refundWalletAddress': refundWalletAddress
|
||||||
|
}..addAll(customParams));
|
||||||
|
final messageBytes = utf8.encode('?${originalUri.query}');
|
||||||
|
final key = utf8.encode(_secretKey);
|
||||||
|
final hmac = Hmac(sha256, key);
|
||||||
|
final digest = hmac.convert(messageBytes);
|
||||||
|
final signature = base64.encode(digest.bytes);
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
return originalUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
final query = Map<String, dynamic>.from(originalUri.queryParameters);
|
||||||
|
query['signature'] = signature;
|
||||||
|
final signedUri = originalUri.replace(queryParameters: query);
|
||||||
|
return signedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.bitcoinCash:
|
||||||
|
// depositCurrency = CryptoCurrency.bch;
|
||||||
|
// receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
// break;
|
||||||
|
// case WalletType.haven:
|
||||||
|
// depositCurrency = CryptoCurrency.xhv;
|
||||||
|
// receiveCurrency = CryptoCurrency.btc;
|
||||||
|
// break;
|
||||||
|
// case WalletType.ethereum:
|
||||||
|
// depositCurrency = CryptoCurrency.eth;
|
||||||
|
// receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
// break;
|
||||||
|
// case WalletType.nano:
|
||||||
|
// depositCurrency = CryptoCurrency.nano;
|
||||||
|
// receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> launchProvider(BuildContext context) async {
|
||||||
|
// primaryColor = getColorStr(Theme.of(context).primaryColor);
|
||||||
|
// secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||||
|
// primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||||
|
// secondaryTextColor =
|
||||||
|
// getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
|
||||||
|
// containerColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||||
|
// cardColor = getColorStr(Theme.of(context).cardColor);
|
||||||
|
|
||||||
|
final uri = await requestUrl(currency: CryptoCurrency.xmr, refundWalletAddress: "");
|
||||||
|
if (DeviceInfo.instance.isMobile) {
|
||||||
|
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).exchange, uri]);
|
||||||
|
} else {
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -391,8 +391,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
|
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
|
||||||
|
|
||||||
case Routes.exchange:
|
case Routes.exchange:
|
||||||
|
final args = settings.arguments as bool;
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
|
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>(param1: args));
|
||||||
|
|
||||||
case Routes.exchangeTemplate:
|
case Routes.exchangeTemplate:
|
||||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
|
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/exchange/moonpay/moonpay_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
@ -40,8 +41,7 @@ class ExchangeOptionsPage extends BasePage {
|
||||||
image: iconRobinhood,
|
image: iconRobinhood,
|
||||||
title: "MoonPay Swaps",
|
title: "MoonPay Swaps",
|
||||||
description: S.of(context).moonpay_exchange_description,
|
description: S.of(context).moonpay_exchange_description,
|
||||||
onPressed: () async =>
|
onPressed: () async => await Navigator.of(context).pushReplacementNamed(Routes.exchange, arguments: ),
|
||||||
await getIt.get<RobinhoodBuyProvider>().launchProvider(context),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
830
lib/view_model/exchange/moonpay_exchange_view_model.dart
Normal file
830
lib/view_model/exchange/moonpay_exchange_view_model.dart
Normal file
|
@ -0,0 +1,830 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||||
|
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/ethereum/ethereum.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_request.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/utils/feature_flag.dart';
|
||||||
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/store/app_store.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 MoonPayExchangeViewModel = MoonPayExchangeViewModelBase with _$MoonPayExchangeViewModel;
|
||||||
|
|
||||||
|
abstract class MoonPayExchangeViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||||
|
@override
|
||||||
|
void onWalletChange(wallet) {
|
||||||
|
receiveCurrency = wallet.currency;
|
||||||
|
depositCurrency = wallet.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoonPayExchangeViewModelBase(
|
||||||
|
AppStore appStore,
|
||||||
|
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 = appStore.wallet!.currency,
|
||||||
|
depositCurrency = appStore.wallet!.currency,
|
||||||
|
providerList = [],
|
||||||
|
selectedProviders = ObservableList<ExchangeProvider>(),
|
||||||
|
super(appStore: appStore) {
|
||||||
|
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
|
||||||
|
_setProviders();
|
||||||
|
const excludeDepositCurrencies = [CryptoCurrency.btt];
|
||||||
|
const excludeReceiveCurrencies = [
|
||||||
|
CryptoCurrency.xlm,
|
||||||
|
CryptoCurrency.xrp,
|
||||||
|
CryptoCurrency.bnb,
|
||||||
|
CryptoCurrency.btt
|
||||||
|
];
|
||||||
|
_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 Box<Trade> trades;
|
||||||
|
final ExchangeTemplateStore _exchangeTemplateStore;
|
||||||
|
final TradesStore tradesStore;
|
||||||
|
final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
List<ExchangeProvider> get _allProviders => [
|
||||||
|
ChangeNowExchangeProvider(settingsStore: _settingsStore),
|
||||||
|
SideShiftExchangeProvider(),
|
||||||
|
SimpleSwapExchangeProvider(),
|
||||||
|
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
|
||||||
|
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||||
|
];
|
||||||
|
|
||||||
|
@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) => 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 ||
|
||||||
|
wallet.type == WalletType.bitcoinCash) &&
|
||||||
|
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();
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return transactionPriority ==
|
||||||
|
ethereum!.getEthereumTransactionPrioritySlow();
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
return transactionPriority ==
|
||||||
|
bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider is ExolixExchangeProvider) {
|
||||||
|
request = ExolixRequest(
|
||||||
|
from: depositCurrency,
|
||||||
|
to: receiveCurrency,
|
||||||
|
fromAmount: depositAmount.replaceAll(',', '.'),
|
||||||
|
toAmount: receiveAmount.replaceAll(',', '.'),
|
||||||
|
refundAddress: depositAddress,
|
||||||
|
address: receiveAddress);
|
||||||
|
amount = isFixedRateMode ? receiveAmount : depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
amount = amount.replaceAll(',', '.');
|
||||||
|
|
||||||
|
if (limitsState is LimitsLoadedSuccessfully) {
|
||||||
|
if (double.tryParse(amount) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 || wallet.type == WalletType.bitcoinCash) {
|
||||||
|
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.bitcoinCash:
|
||||||
|
depositCurrency = CryptoCurrency.bch;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
|
case WalletType.haven:
|
||||||
|
depositCurrency = CryptoCurrency.xhv;
|
||||||
|
receiveCurrency = CryptoCurrency.btc;
|
||||||
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
depositCurrency = CryptoCurrency.eth;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
|
case WalletType.nano:
|
||||||
|
depositCurrency = CryptoCurrency.nano;
|
||||||
|
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;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
_settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
|
||||||
|
break;
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
|
||||||
|
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;
|
||||||
|
}
|
830
lib/view_model/exchange/normal_exchange_view_model.dart
Normal file
830
lib/view_model/exchange/normal_exchange_view_model.dart
Normal file
|
@ -0,0 +1,830 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||||
|
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/ethereum/ethereum.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_request.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/utils/feature_flag.dart';
|
||||||
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cake_wallet/store/app_store.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 NormalExchangeViewModel = NormalExchangeViewModelBase with _$ExchangeViewModel;
|
||||||
|
|
||||||
|
abstract class NormalExchangeViewModelBase extends ExchangeViewModel with Store {
|
||||||
|
@override
|
||||||
|
void onWalletChange(wallet) {
|
||||||
|
receiveCurrency = wallet.currency;
|
||||||
|
depositCurrency = wallet.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalExchangeViewModelBase(
|
||||||
|
AppStore appStore,
|
||||||
|
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 = appStore.wallet!.currency,
|
||||||
|
depositCurrency = appStore.wallet!.currency,
|
||||||
|
providerList = [],
|
||||||
|
selectedProviders = ObservableList<ExchangeProvider>(),
|
||||||
|
super(appStore: appStore) {
|
||||||
|
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
|
||||||
|
_setProviders();
|
||||||
|
const excludeDepositCurrencies = [CryptoCurrency.btt];
|
||||||
|
const excludeReceiveCurrencies = [
|
||||||
|
CryptoCurrency.xlm,
|
||||||
|
CryptoCurrency.xrp,
|
||||||
|
CryptoCurrency.bnb,
|
||||||
|
CryptoCurrency.btt
|
||||||
|
];
|
||||||
|
_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 Box<Trade> trades;
|
||||||
|
final ExchangeTemplateStore _exchangeTemplateStore;
|
||||||
|
final TradesStore tradesStore;
|
||||||
|
final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
|
List<ExchangeProvider> get _allProviders => [
|
||||||
|
ChangeNowExchangeProvider(settingsStore: _settingsStore),
|
||||||
|
SideShiftExchangeProvider(),
|
||||||
|
SimpleSwapExchangeProvider(),
|
||||||
|
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
|
||||||
|
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||||
|
];
|
||||||
|
|
||||||
|
@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) => 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 ||
|
||||||
|
wallet.type == WalletType.bitcoinCash) &&
|
||||||
|
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();
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return transactionPriority ==
|
||||||
|
ethereum!.getEthereumTransactionPrioritySlow();
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
return transactionPriority ==
|
||||||
|
bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider is ExolixExchangeProvider) {
|
||||||
|
request = ExolixRequest(
|
||||||
|
from: depositCurrency,
|
||||||
|
to: receiveCurrency,
|
||||||
|
fromAmount: depositAmount.replaceAll(',', '.'),
|
||||||
|
toAmount: receiveAmount.replaceAll(',', '.'),
|
||||||
|
refundAddress: depositAddress,
|
||||||
|
address: receiveAddress);
|
||||||
|
amount = isFixedRateMode ? receiveAmount : depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
amount = amount.replaceAll(',', '.');
|
||||||
|
|
||||||
|
if (limitsState is LimitsLoadedSuccessfully) {
|
||||||
|
if (double.tryParse(amount) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 || wallet.type == WalletType.bitcoinCash) {
|
||||||
|
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.bitcoinCash:
|
||||||
|
depositCurrency = CryptoCurrency.bch;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
|
case WalletType.haven:
|
||||||
|
depositCurrency = CryptoCurrency.xhv;
|
||||||
|
receiveCurrency = CryptoCurrency.btc;
|
||||||
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
depositCurrency = CryptoCurrency.eth;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
|
case WalletType.nano:
|
||||||
|
depositCurrency = CryptoCurrency.nano;
|
||||||
|
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;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
_settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
|
||||||
|
break;
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Reference in a new issue