Merge branch 'dev' into cw-1497-transaction-history-improved-layout

# Conflicts:
#	lib/new-ui/widgets/coins_page/assets_history/history_section.dart
#	lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
This commit is contained in:
Robert Malikowski
2026-07-01 16:53:59 +02:00
263 changed files with 6371 additions and 4762 deletions
+22 -22
View File
@@ -17,6 +17,8 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/bridge_transfers_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
@@ -130,18 +132,18 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
}
@computed
String get tokenBalanceFormatted {
if (selectedToken == null) return "0.00";
return amountParsingProxy.getDisplayCryptoStringFromBigInt(
selectedTokenBalance, selectedToken!,
);
}
String get tokenBalanceFormatted {
if (selectedToken == null) return "0.00";
return amountParsingProxy.asDisplayString(
Money(selectedTokenBalance, selectedToken!),
);
}
@computed
String get amountDisplayFormatted {
if (selectedToken == null) return "0.00";
return amountParsingProxy.getDisplayCryptoAmount(
amount.replaceAll(',', '.'),
selectedToken!,
@@ -174,10 +176,8 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
String get quoteNativeFee {
if (quote == null) return '—';
return amountParsingProxy.getDisplayCryptoStringFromBigInt(
quote!.nativeFee,
wallet.currency,
);
return amountParsingProxy.asDisplayString(
Money(quote!.nativeFee, wallet.currency));
}
@computed
@@ -218,10 +218,10 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
if (amountError != null) return false;
final validAmount = amountParsingProxy.tryParseCryptoString(
amount.replaceAll(',', '.'),
amount.sanitized(),
selectedToken!,
);
return validAmount != null && validAmount > BigInt.zero;
return validAmount != null && validAmount > Money.zero(selectedToken!);
}
@computed
@@ -231,7 +231,7 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
try {
final bal = wallet.balance[selectedToken!];
return bal?.fullAvailableBalance ?? BigInt.zero;
return bal?.available.amount ?? BigInt.zero;
} catch (e) {
return BigInt.zero;
}
@@ -247,7 +247,7 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
);
if (amountBigInt == null || amountBigInt == BigInt.zero) return null;
if (amountBigInt > selectedTokenBalance) {
if (amountBigInt.amount > selectedTokenBalance) {
return 'Insufficient balance for ${selectedToken!.title} token.';
}
@@ -281,9 +281,9 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
return;
}
setAmount(
amountParsingProxy.getDisplayCryptoStringFromBigInt(
amountParsingProxy.asDisplayString(Money(
selectedTokenBalance,
token,
token)
),
);
}
@@ -382,23 +382,23 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
}
({String? error, BigInt? parsedAmount}) _parseAndValidateAmount(Erc20Token token) {
final amountBigInt = amountParsingProxy.tryParseCryptoString(
final parsedAmount = amountParsingProxy.tryParseCryptoString(
amount.replaceAll(',', '.'),
token,
);
if (amountBigInt == null || amountBigInt == BigInt.zero) {
if (parsedAmount == null || parsedAmount == Money(BigInt.zero, token)) {
return (error: 'Invalid amount', parsedAmount: null);
}
if (amountBigInt > selectedTokenBalance) {
if (parsedAmount.amount > selectedTokenBalance) {
return (
error: 'Insufficient balance for ${token.title} token.',
parsedAmount: null,
);
}
return (error: null, parsedAmount: amountBigInt);
return (error: null, parsedAmount: parsedAmount.amount);
}
@action
+114 -90
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
@@ -10,8 +11,10 @@ import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -28,47 +31,74 @@ part 'balance_view_model.g.dart';
class BalanceRecord {
const BalanceRecord({
required this.raw,
required this.availableBalance,
required this.additionalBalance,
required this.secondAvailableBalance,
required this.secondAdditionalBalance,
required this.frozenBalance,
required this.fiatAvailableBalance,
required this.fiatAdditionalBalance,
required this.fiatFrozenBalance,
required this.fiatSecondAvailableBalance,
required this.fiatSecondAdditionalBalance,
required this.fiatAvailableBalanceRaw,
required this.fiatAdditionalBalanceRaw,
required this.fiatFrozenBalanceRaw,
required this.fiatSecondAvailableBalanceRaw,
required this.fiatSecondAdditionalBalanceRaw,
required this.asset,
required this.secondAsset,
required this.fiatCurrency,
required this.formattedAssetTitle,
this.languageCode,
});
final String fiatAdditionalBalance;
final String fiatAvailableBalance;
final String fiatFrozenBalance;
final Balance raw;
final String fiatAvailableBalanceRaw;
final String fiatAdditionalBalanceRaw;
final String fiatFrozenBalanceRaw;
final String fiatSecondAvailableBalanceRaw;
final String fiatSecondAdditionalBalanceRaw;
final String additionalBalance;
final String availableBalance;
final String frozenBalance;
final String secondAvailableBalance;
final String secondAdditionalBalance;
final String fiatSecondAdditionalBalance;
final String fiatSecondAvailableBalance;
final CryptoCurrency asset;
final CryptoCurrency secondAsset;
final FiatCurrency? fiatCurrency;
final String formattedAssetTitle;
final String? languageCode;
String get combinedAvailableBalance =>
((double.tryParse(availableBalance) ?? 0) + (double.tryParse(secondAvailableBalance) ?? 0))
.toString().withMaxDecimals(8);
(raw.available + (raw.secondAvailable ?? Money.zero(raw.available.currency)))
.toString()
.withMaxDecimals(8);
String get combinedFiatAvailableBalance =>
((double.tryParse(fiatAvailableBalanceRaw) ?? 0) +
(double.tryParse(fiatSecondAvailableBalanceRaw) ?? 0))
.toStringAsFixed(2);
String get combinedFiatAvailableBalance => fiatCurrency != null
? "$fiatCurrency " +
_withLocalSeperator(((double.tryParse(fiatAvailableBalanceRaw) ?? 0) +
(double.tryParse(fiatSecondAvailableBalanceRaw) ?? 0))
.toStringAsFixed(2))
: "";
String get fiatAvailableBalanceRaw => fiatAvailableBalance.split(" ").last;
String get fiatSecondAvailableBalanceRaw => fiatSecondAvailableBalance.split(" ").last;
String get fiatCurrencyTicker => fiatAvailableBalance.split(" ").first;
String get fiatAvailableBalance =>
fiatCurrency != null ? "$fiatCurrency ${_withLocalSeperator(fiatAvailableBalanceRaw)}" : "";
String get fiatAdditionalBalance =>
fiatCurrency != null ? "$fiatCurrency ${_withLocalSeperator(fiatAdditionalBalanceRaw)}" : "";
String get fiatFrozenBalance =>
fiatCurrency != null ? "$fiatCurrency ${_withLocalSeperator(fiatFrozenBalanceRaw)}" : "";
String get fiatSecondAvailableBalance => fiatCurrency != null
? "$fiatCurrency ${_withLocalSeperator(fiatSecondAvailableBalanceRaw)}"
: "";
String get fiatSecondAdditionalBalance => fiatCurrency != null
? "$fiatCurrency ${_withLocalSeperator(fiatSecondAdditionalBalanceRaw)}"
: "";
String _withLocalSeperator(String rawAmount) =>
languageCode == null ? rawAmount : rawAmount.withLocalSeperator(languageCode);
}
class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel;
@@ -221,12 +251,13 @@ abstract class BalanceViewModelBase with Store {
}
}
String additionalBalance(CryptoCurrency cryptoCurrency) {
Money additionalBalance(CryptoCurrency cryptoCurrency) {
final balance = _currencyBalance(cryptoCurrency);
if (displayMode == BalanceDisplayMode.hiddenBalance || balance.additional == BigInt.zero) return '0.0';
if (displayMode == BalanceDisplayMode.hiddenBalance || balance.unavailable.isZero)
return Money.zero(cryptoCurrency);
return cryptoCurrency.formatAmount(balance.additional);
return balance.unavailable;
}
@computed
@@ -237,25 +268,26 @@ abstract class BalanceViewModelBase with Store {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
final fiatCurrency = settingsStore.fiatCurrency;
return MapEntry(
key,
BalanceRecord(
availableBalance: '●●●●●●',
additionalBalance: additionalBalance(key),
frozenBalance: '',
secondAvailableBalance: '●●●●●●',
secondAdditionalBalance: '●●●●●●',
fiatAdditionalBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatAvailableBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatFrozenBalance: isFiatDisabled ? '' : '',
fiatSecondAvailableBalance:
isFiatDisabled ? '' : '${fiatCurrency.toString()}',
fiatSecondAdditionalBalance:
isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
asset: key,
secondAsset: secondAsset,
formattedAssetTitle: _formatterAsset(key)));
key,
BalanceRecord(
raw: value,
availableBalance: '●●●●●●',
additionalBalance: '',
frozenBalance: '',
secondAvailableBalance: '●●●●●●',
secondAdditionalBalance: '●●●●●●',
fiatAdditionalBalanceRaw: '●●●●●',
fiatAvailableBalanceRaw: '●●●●●',
fiatFrozenBalanceRaw: '',
fiatSecondAvailableBalanceRaw: '●●●●●',
fiatSecondAdditionalBalanceRaw: '●●●●●',
asset: key,
secondAsset: secondAsset,
fiatCurrency: isFiatDisabled ? null : fiatCurrency,
formattedAssetTitle: _formatterAsset(key),
),
);
}
final fiatCurrency = settingsStore.fiatCurrency;
final price = key.isPotentialScam
? 0.0
: fiatConversionStore.prices[key == CryptoCurrency.btcln ? CryptoCurrency.btc : key] ?? 0;
@@ -264,48 +296,41 @@ abstract class BalanceViewModelBase with Store {
// throw Exception('Price is null for: $key');
// }
final available = evm?.getERC20AvailableBalance(value) ??
(value.fullAvailableBalance - (value.secondAvailable ?? BigInt.zero));
final additional = evm?.getERC20AvailableBalance(value) ??
(value.additional - (value.secondAdditional ?? BigInt.zero));
final availableFiatBalance =
isFiatDisabled ? '' : _getFiatBalance(price: price, cryptoAmount: value.available);
final availableFiatBalance = isFiatDisabled
? ''
: '$fiatCurrency ${_getFiatBalance(price: price, cryptoAmount: key.formatAmount(available))}';
final additionalFiatBalance =
isFiatDisabled ? '' : _getFiatBalance(price: price, cryptoAmount: value.unavailable);
final additionalFiatBalance = isFiatDisabled
? ''
: '$fiatCurrency ${_getFiatBalance(price: price, cryptoAmount: key.formatAmount(additional))}';
final frozenFiatBalance =
isFiatDisabled ? '' : _getFiatBalance(price: price, cryptoAmount: value.frozen);
final frozenFiatBalance = isFiatDisabled
? ''
: '$fiatCurrency ${_getFiatBalance(price: price, cryptoAmount: value.frozen != null ? key.formatAmount(value.frozen!) : null)}';
final secondAvailableFiatBalance = isFiatDisabled
? ''
: '$fiatCurrency ${_getFiatBalance(price: price, cryptoAmount: value.secondAvailable != null ? key.formatAmount(value.secondAvailable!) : null)}';
final secondAvailableFiatBalance =
isFiatDisabled ? '' : _getFiatBalance(price: price, cryptoAmount: value.secondAvailable);
final secondAdditionalFiatBalance = isFiatDisabled
? ''
: '$fiatCurrency ${_getFiatBalance(price: price, cryptoAmount: value.secondAdditional != null ? key.formatAmount(value.secondAdditional!) : null)}';
: _getFiatBalance(price: price, cryptoAmount: value.secondUnavailable);
return MapEntry(
key,
BalanceRecord(
availableBalance: _getFormattedCryptoAmount(key, available),
fiatAvailableBalance: availableFiatBalance,
additionalBalance: _getFormattedCryptoAmount(key, additional),
fiatAdditionalBalance: additionalFiatBalance,
frozenBalance:
(value.frozen ?? BigInt.zero) > BigInt.zero ? _getFormattedCryptoAmount(key, value.frozen??BigInt.zero) : '',
fiatFrozenBalance: frozenFiatBalance,
secondAvailableBalance: _getFormattedCryptoAmount(secondAsset, value.secondAvailable??BigInt.zero),
fiatSecondAvailableBalance: secondAvailableFiatBalance,
secondAdditionalBalance: _getFormattedCryptoAmount(secondAsset, value.secondAdditional??BigInt.zero),
fiatSecondAdditionalBalance: secondAdditionalFiatBalance,
raw: value,
availableBalance: _getFormattedCryptoAmount(value.available),
fiatAvailableBalanceRaw: availableFiatBalance,
additionalBalance: _getFormattedCryptoAmount(value.unavailable),
fiatAdditionalBalanceRaw: additionalFiatBalance,
frozenBalance: _getFormattedCryptoAmount(value.frozen),
fiatFrozenBalanceRaw: frozenFiatBalance,
secondAvailableBalance: _getFormattedCryptoAmount(value.secondAvailable),
fiatSecondAvailableBalanceRaw: secondAvailableFiatBalance,
secondAdditionalBalance: _getFormattedCryptoAmount(value.secondUnavailable),
fiatSecondAdditionalBalanceRaw: secondAdditionalFiatBalance,
asset: key,
secondAsset: secondAsset,
fiatCurrency: isFiatDisabled ? null : settingsStore.fiatCurrency,
formattedAssetTitle: _formatterAsset(key),
languageCode: settingsStore.languageCode,
),
);
});
@@ -316,7 +341,7 @@ abstract class BalanceViewModelBase with Store {
bool hasAdditionalBalance(CryptoCurrency currency) {
final isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
final isNotZeroAmount = additionalBalance(currency) != "0.0";
final isNotZeroAmount = !additionalBalance(currency).isZero;
return isWalletTypeActivated && isNotZeroAmount;
}
@@ -324,9 +349,9 @@ abstract class BalanceViewModelBase with Store {
@computed
bool get hasSecondAdditionalBalance {
if (wallet.type == WalletType.litecoin && mwebEnabled) {
return (wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) != 0;
return (wallet.balance[CryptoCurrency.ltc]?.secondUnavailable ?? 0) != 0;
} else if (wallet.type == WalletType.bitcoin) {
return (wallet.balance[CryptoCurrency.btc]?.secondAdditional ?? 0) != 0;
return (wallet.balance[CryptoCurrency.btc]?.secondUnavailable ?? 0) != 0;
}
return false;
}
@@ -351,12 +376,12 @@ abstract class BalanceViewModelBase with Store {
WalletType.zcash
].contains(type);
String _getFormattedCryptoAmount(CryptoCurrency cryptoCurrency, BigInt? amount) {
String _getFormattedCryptoAmount(Money? amount) {
if (amount == null) return "";
return appStore.amountParsingProxy
.getDisplayCryptoStringFromBigInt(amount, cryptoCurrency)
.withMaxDecimals(8).withLocalSeperator(settingsStore.languageCode);
.asDisplayString(amount)
.withLocalSeperator(settingsStore.languageCode);
}
@computed
@@ -402,9 +427,9 @@ abstract class BalanceViewModelBase with Store {
switch (sortBalanceBy) {
case SortBalanceBy.FiatBalance:
final aFiatBalance = _getFiatBalance(
price: fiatConversionStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
price: fiatConversionStore.prices[a.asset] ?? 0, cryptoAmount: a.raw.available);
final bFiatBalance = _getFiatBalance(
price: fiatConversionStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
price: fiatConversionStore.prices[b.asset] ?? 0, cryptoAmount: b.raw.available);
return (double.tryParse(bFiatBalance) ?? 0)
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
@@ -425,7 +450,8 @@ abstract class BalanceViewModelBase with Store {
}
if (wallet.walletInfo.favoriteTokenAddress != null) {
return formattedBalances.firstWhereOrNull((item) => (getTokenAddressBasedOnWallet(item.asset) ==
return formattedBalances.firstWhereOrNull((item) =>
(getTokenAddressBasedOnWallet(item.asset) ==
wallet.walletInfo.favoriteTokenAddress)) ??
formattedBalances.elementAtOrNull(0);
}
@@ -455,8 +481,8 @@ abstract class BalanceViewModelBase with Store {
@computed
bool get showCombinedBalance {
if(wallet.type == WalletType.bitcoin) return false;
if(balances.values.length == 1) return false;
if (wallet.type == WalletType.bitcoin) return false;
if (balances.values.length == 1) return false;
return wallet.walletInfo.showCombinedBalance;
}
@@ -470,15 +496,14 @@ abstract class BalanceViewModelBase with Store {
double ret = 0.0;
for (final curr in wallet.balance.keys) {
final record = wallet.balance[curr]!;
final available = evm?.getERC20AvailableBalance(record) ??
(record.fullAvailableBalance - (record.secondAvailable ?? BigInt.zero));
final available = record.available - (record.secondAvailable ?? Money.zero(curr));
final price = fiatConversionStore.prices[curr] ?? 0;
ret += double.tryParse(calculateFiatAmount(
price: price, cryptoAmount: curr.formatAmount(available).replaceAll(",", ""))
printV(record.available);
ret += double.tryParse(calculateFiatAmount(price: price, cryptoAmount: available.toString())
.replaceAll(",", "")) ??
0;
}
return ret.toStringAsFixed(2);
return ret.toStringAsFixed(2).withLocalSeperator(settingsStore.languageCode);
}
Balance _currencyBalance(CryptoCurrency cryptoCurrency) {
@@ -522,13 +547,12 @@ abstract class BalanceViewModelBase with Store {
}
}
String _getFiatBalance({required double price, String? cryptoAmount}) {
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
return '0.00'.withLocalSeperator(settingsStore.languageCode);
String _getFiatBalance({required double price, Money? cryptoAmount}) {
if (cryptoAmount == null) {
return '0.00';
}
return calculateFiatAmount(price: price, cryptoAmount: cryptoAmount)
.withLocalSeperator(settingsStore.languageCode);
return calculateFiatAmount(price: price, cryptoAmount: cryptoAmount.toString(), raw: true);
}
String _formatterAsset(CryptoCurrency asset) {
@@ -1,23 +1,17 @@
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/keyable.dart';
@@ -52,17 +46,12 @@ class TransactionListItem extends ActionListItem with Keyable {
String get formattedCryptoAmount {
if (displayMode == BalanceDisplayMode.hiddenBalance) return '---';
if (balanceViewModel.wallet.type == WalletType.bitcoin) {
final isLightning = (transaction.additionalInfo["isLightning"] as bool?) ?? false;
final crypto = isLightning ? CryptoCurrency.btcln : CryptoCurrency.btc;
final amount = _appStore.amountParsingProxy
.getDisplayCryptoString(transaction.amount, crypto)
.withMaxDecimals(8)
return _appStore.amountParsingProxy
.asDisplayStringWithSymbol(transaction.amount)
.withLocalSeperator(_appStore.settingsStore.languageCode);
return '$amount ${_appStore.amountParsingProxy.getCryptoSymbol(crypto)}';
}
return transaction.amountFormatted();
return transaction.amount.toStringWithSymbol();
}
String get formattedTitle {
@@ -193,26 +182,18 @@ class TransactionListItem extends ActionListItem with Keyable {
switch (balanceViewModel.wallet.type) {
case WalletType.monero:
amount = calculateFiatAmountRaw(
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.wownero:
amount = calculateFiatAmountRaw(
cryptoAmount: wownero!.formatterWowneroAmountToDouble(amount: transaction.amount),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.dogecoin:
case WalletType.nano:
case WalletType.decred:
case WalletType.zcash:
amount = calculateFiatAmountRaw(
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
@@ -221,14 +202,7 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = assetOfTransaction;
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: evm!.formatterEVMAmountToDouble(transaction: transaction),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
@@ -236,16 +210,15 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: solana!.getTransactionAmountRaw(transaction),
cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.tron:
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConversionStore.prices[asset];
final cryptoAmount = tron!.getTransactionAmountRaw(transaction);
amount = calculateFiatAmountRaw(
cryptoAmount: cryptoAmount,
cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
@@ -257,24 +230,10 @@ class TransactionListItem extends ActionListItem with Keyable {
}
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: zano!.formatterIntAmountToDouble(
amount: transaction.amount, currency: asset, forFee: false),
cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.decred:
amount = calculateFiatAmountRaw(
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.zcash:
amount = calculateFiatAmountRaw(
cryptoAmount:
zcash!.formatterZcashAmountToDouble(amount: BigInt.from(transaction.amount)),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
case WalletType.none:
case WalletType.banano:
case WalletType.haven:
+136 -93
View File
@@ -2,10 +2,12 @@ import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:developer';
import 'dart:math' show min;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
import 'package:cake_wallet/core/create_trade_result.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
@@ -41,6 +43,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/amount/money.dart';
import "package:cw_core/wallet_info.dart";
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
@@ -103,8 +107,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
) : isSendAllEnabled = false,
isFixedRateMode = false,
isReceiveAmountEntered = false,
_depositAmount = '',
_receiveAmount = '',
_depositAmount = null,
_receiveAmount = null,
receiveAddress = '',
depositAddress = '',
isDepositAddressEnabled = false,
@@ -163,8 +167,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
});
isDepositAddressEnabled = !useSameWalletAddress(depositCurrency);
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
receiveAddress = '';
depositAddress =
useSameWalletAddress(depositCurrency) ? wallet.walletAddresses.addressForExchange : '';
@@ -237,12 +241,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
final balance = wallet.balance[depositCurrency];
if (balance != null) {
depositAvailableAmount = _appStore.amountParsingProxy
.getDisplayCryptoStringFromBigInt(balance.fullAvailableBalance, depositCurrency);
depositAvailableAmount = _appStore.amountParsingProxy.asDisplayString(balance.available);
return false;
}
return true;
});
}).timeout(Duration(seconds: 20));
} else if (isEVMCompatibleChain(wallet.type)) {
final currency = depositCurrency;
final balanceCurrency = wallet.balance.keys.firstWhereOrNull(
@@ -253,15 +256,15 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final balanceForCurrency =
balanceCurrency != null ? wallet.balance[balanceCurrency] : null;
if (depositCurrency == currency && balanceForCurrency != null) {
depositAvailableAmount = _appStore.amountParsingProxy
.getDisplayCryptoStringFromBigInt(
balanceForCurrency.fullAvailableBalance, balanceCurrency!);
depositAvailableAmount =
_appStore.amountParsingProxy.asDisplayStringWithSymbol(balanceForCurrency.available);
}
} else {
final currency = depositCurrency;
final amount = _appStore.amountParsingProxy.getDisplayCryptoString(
(await unspentCoinsListViewModel.getSendingBalance(UnspentCoinType.any)), depositCurrency);
if(depositCurrency == currency) {
final sendingBalance = Money.fromInt(
await unspentCoinsListViewModel.getSendingBalance(UnspentCoinType.any), currency);
final amount = _appStore.amountParsingProxy.asDisplayStringWithSymbol(sendingBalance);
if (depositCurrency == currency) {
depositAvailableAmount = amount;
}
}
@@ -289,7 +292,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
].contains(wallet.type);
bool get hideAddressAfterExchange =>
[WalletType.monero, WalletType.wownero, WalletType.zcash].contains(wallet.type);
[WalletType.monero, WalletType.wownero, WalletType.zcash].contains(wallet.type) ||
isElectrumWallet;
bool _useTorOnly;
final ExchangeTemplateStore _exchangeTemplateStore;
@@ -352,25 +356,29 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
ExchangeTradeState tradeState;
@observable
String _depositAmount;
Money? _depositAmount;
@computed
bool get hasDepositAmount => _depositAmount != null;
@computed
String get depositAmount =>
_depositAmount == "..."? _depositAmount:amountParsingProxy.getDisplayCryptoAmount(_depositAmount, depositCurrency);
_depositAmount == null ? "" : amountParsingProxy.asDisplayString(_depositAmount!);
@computed
String get depositAmountCanonical => _depositAmount;
String get depositAmountCanonical => _depositAmount == null ? "0.0" : _depositAmount.toString();
@observable
String _receiveAmount;
Money? _receiveAmount;
@computed
String get receiveAmount =>
_receiveAmount == "..." ? _receiveAmount:amountParsingProxy.getDisplayCryptoAmount(_receiveAmount, receiveCurrency);
_receiveAmount == null ? "" : amountParsingProxy.asDisplayString(_receiveAmount!);
@action
// only set canonical formated amounts here;
void setCanonicalReceiveAmount(String value) => _receiveAmount = value;
void setCanonicalReceiveAmount(String value) =>
_receiveAmount = Money.tryParse(value, receiveCurrency);
@observable
String depositAddress;
@@ -486,7 +494,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@computed
String? get balanceDisplay {
CryptoCurrency? balanceCurrency;
if (isEVMCompatibleChain(wallet.type)) {
if (isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana || wallet.type == WalletType.tron) {
balanceCurrency = wallet.balance.keys.firstWhereOrNull(
(c) =>
c.title == depositCurrency.title &&
@@ -495,10 +503,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
} else {
balanceCurrency = depositCurrency;
}
final bal = balanceCurrency != null ? wallet.balance[balanceCurrency]?.fullAvailableBalance : null;
final bal = balanceCurrency != null ? wallet.balance[balanceCurrency]?.available : null;
if (bal == null) return null;
return amountParsingProxy.getDisplayCryptoStringFromBigInt(
bal, balanceCurrency ?? depositCurrency);
return amountParsingProxy.asDisplayString(bal);
}
//* Still open to further optimize these checks
@@ -572,10 +579,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
double bestRate = 0.0;
@computed
bool get useSatoshiDeposit => _appStore.amountParsingProxy.useSatoshi(depositCurrency);
bool get useDepositBaseUnit => _appStore.amountParsingProxy.useSatoshi(depositCurrency);
@computed
bool get useSatoshisReceive => _appStore.amountParsingProxy.useSatoshi(receiveCurrency);
bool get useReceiveBaseUnit => _appStore.amountParsingProxy.useSatoshi(receiveCurrency);
@computed
AmountParsingProxy get amountParsingProxy => _appStore.amountParsingProxy;
@@ -624,12 +631,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
String get receiveAmountFiatFormatted {
var amount = '0.00';
try {
if (_receiveAmount.isNotEmpty) {
if (_receiveAmount != null) {
if (fiatConversionStore.prices[receiveCurrency] == null) return '';
amount = calculateFiatAmount(
price: fiatConversionStore.prices[receiveCurrency]!,
cryptoAmount: _receiveAmount,
cryptoAmount: _receiveAmount.toString(),
);
}
} catch (_) {
@@ -642,12 +649,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
String get depositAmountFiatFormatted {
var amount = '0.00';
try {
if (_depositAmount.isNotEmpty) {
if (_depositAmount != null) {
if (fiatConversionStore.prices[depositCurrency] == null) return '';
amount = calculateFiatAmount(
price: fiatConversionStore.prices[depositCurrency]!,
cryptoAmount: _depositAmount,
cryptoAmount: _depositAmount.toString(),
);
}
} catch (_) {
@@ -660,12 +667,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
String get receiveAmountFiat {
var amount = '';
try {
if (_receiveAmount.isNotEmpty) {
if (_receiveAmount != null) {
if (fiatConversionStore.prices[receiveCurrency] == null) return '';
amount = calculateFiatAmount(
price: fiatConversionStore.prices[receiveCurrency]!,
cryptoAmount: _receiveAmount,
cryptoAmount: _receiveAmount.toString(),
raw: true);
}
} catch (_) {
@@ -678,13 +685,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
String get depositAmountFiat {
var amount = '';
try {
if (_depositAmount.isNotEmpty) {
if (_depositAmount != null) {
if (fiatConversionStore.prices[depositCurrency] == null) return '';
amount = calculateFiatAmount(
price: fiatConversionStore.prices[depositCurrency]!,
cryptoAmount: _depositAmount,
raw: true);
price: fiatConversionStore.prices[depositCurrency]!,
cryptoAmount: _depositAmount.toString(),
raw: true,
);
}
} catch (_) {
log('Error calculating deposit amount fiat formatted: $_');
@@ -771,32 +779,37 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
@action
Future<void> changeReceiveAmount({required String amount}) async {
amount = amountParsingProxy.getCanonicalCryptoAmount(amount, receiveCurrency);
_receiveAmount = amount;
Future<void> changeReceiveAmount({required String amount, bool isCanonical = false}) async {
if (amount.isEmpty) {
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
return;
}
final _enteredAmount = double.tryParse(_receiveAmount.replaceAll(',', '.')) ?? 0;
_receiveAmount = isCanonical
? Money.tryParse(amount.sanitized(), receiveCurrency)
: _appStore.amountParsingProxy.tryParseCryptoString(amount.sanitized(), receiveCurrency);
if (_receiveAmount == null) {
_depositAmount = null;
return;
}
final _enteredAmount = double.tryParse(_receiveAmount.toString()) ?? 0;
if (bestRate == 0) {
_depositAmount = "...";
_depositAmount = null;
await calculateBestRate();
}
final amount_ = _enteredAmount / (forcedProvider == null ? bestRate : forcedProviderRate);
_depositAmount = amount_.toString().withMaxDecimals(depositCurrency.decimals);
_depositAmount = Money.tryParse(amount_.toStringAsFixed(depositCurrency.decimals), depositCurrency);
}
@action
void setReceiveAmountFromFiat({required String fiatAmount}) {
final _enteredAmount = double.tryParse(fiatAmount.replaceAll(',', '.')) ?? 0.0;
final _enteredAmount = double.tryParse(fiatAmount.sanitized()) ?? 0.0;
final price = fiatConversionStore.prices[receiveCurrency];
if (price == null || price == 0.0) return;
@@ -809,7 +822,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
void setDepositAmountFromFiat({required String fiatAmount}) {
final _enteredAmount = double.tryParse(fiatAmount.replaceAll(',', '.')) ?? 0.0;
final _enteredAmount = double.tryParse(fiatAmount.sanitized()) ?? 0.0;
final price = fiatConversionStore.prices[depositCurrency];
if (price == null || price == 0.0) return;
@@ -822,34 +835,40 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
Future<void> changeDepositAmount({required String amount, bool isCanonical = false}) async {
_depositAmount = isCanonical ? amount.replaceAll(',', '.') : _appStore.amountParsingProxy
.getCanonicalCryptoAmount(amount.replaceAll(',', '.'), depositCurrency);
if (amount.isEmpty) {
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
return;
}
_depositAmount = isCanonical
? Money.tryParse(amount.sanitized(), depositCurrency)
: _appStore.amountParsingProxy.tryParseCryptoString(amount.sanitized(), depositCurrency);
if (_depositAmount == null) {
_receiveAmount = null;
return;
}
/// For fixed-rate transactions, we don't want to recalculate receive amount
/// as it should remain exactly what the user set
if (isFixedRateMode) return;
final _enteredAmount = double.tryParse(_depositAmount) ?? 0;
final _enteredAmount = double.tryParse(_depositAmount.toString()) ?? 0;
/// in case the best rate was not calculated yet
if (bestRate == 0) {
_receiveAmount = "...";
_receiveAmount = null;
await calculateBestRate();
}
final amount_ = _enteredAmount * (forcedProvider == null ? bestRate : forcedProviderRate);
_receiveAmount = amount_.toString().withMaxDecimals(receiveCurrency.decimals);
_receiveAmount =
Money.tryParse(amount_.toStringAsFixed(min(20, receiveCurrency.decimals)), receiveCurrency);
}
bool checkIfInputMeetsMinOrMaxCondition(String input) {
final _enteredAmount = double.tryParse(input.replaceAll(',', '.')) ?? 0;
final _enteredAmount = double.tryParse(input.sanitized()) ?? 0;
double minLimit = limits.min ?? 0;
double? maxLimit = limits.max;
@@ -866,8 +885,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
return;
}
final amount = double.tryParse(isFixedRateMode ? _receiveAmount : _depositAmount) ??
initialAmountByAssets(isFixedRateMode ? receiveCurrency : depositCurrency);
final amount =
double.tryParse(isFixedRateMode ? _receiveAmount.toString() : _depositAmount.toString()) ??
initialAmountByAssets(isFixedRateMode ? receiveCurrency : depositCurrency);
forcedProviderRate = await forcedProvider!.fetchRate(
from: depositCurrency,
@@ -891,8 +911,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
bestRateProvider = null;
return;
}
final amount = double.tryParse(isFixedRateMode ? _receiveAmount : _depositAmount) ??
initialAmountByAssets(isFixedRateMode ? receiveCurrency : depositCurrency);
final amount =
double.tryParse(isFixedRateMode ? _receiveAmount.toString() : _depositAmount.toString()) ??
initialAmountByAssets(isFixedRateMode ? receiveCurrency : depositCurrency);
final validProvidersForAmount = _tradeAvailableProviders.where((provider) {
if (_excludeProviderForSwapAll(provider)) return false;
@@ -1025,13 +1046,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
Future<void> createTrade() async {
final depositAmountValue =
double.tryParse(_depositAmount.replaceAll(',', '.')) ?? 0.0;
final depositAmountValue = _depositAmount ?? Money.zero(depositCurrency);
final receiveAmountValue = _receiveAmount ?? Money.zero(receiveCurrency);
final receiveAmountValue =
double.tryParse(_receiveAmount.replaceAll(',', '.')) ?? 0.0;
if (depositAmountValue <= 0 || receiveAmountValue <= 0) {
if (depositAmountValue.isZero ||
depositAmountValue.isNegative ||
receiveAmountValue.isZero ||
receiveAmountValue.isNegative) {
ExchangeProviderLogger.logError(
provider: forcedProvider?.description ?? bestRateProvider?.description,
function: 'createTrade',
@@ -1047,25 +1068,26 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
},
);
final invalidAmountError = depositAmountValue <= 0
final invalidAmountError = depositAmountValue.isZero || depositAmountValue.isNegative
? '$depositAmountValue is not a valid amount for depositAmount'
: '$receiveAmountValue is not a valid amount for receiveAmount';
tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created,
error: invalidAmountError,
error: invalidAmountError
);
return;
}
if (isSendAllEnabled) {
await calculateDepositAllAmount();
final amount = double.tryParse(_depositAmount);
final amount = double.tryParse(_depositAmount.toString());
if (limits.min != null && amount != null && amount < limits.min!) {
tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created,
error: S.current.amount_is_below_minimum_limit(limits.min!.toString()));
error: S.current.amount_is_below_minimum_limit(limits.min!.toString()),
);
return;
}
}
@@ -1090,8 +1112,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
if (forcedProvider != null && _excludeProviderForReceiveExtraId(forcedProvider!)) {
tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created,
error: S.current.none_of_selected_providers_can_exchange);
title: S.current.trade_not_created,
error: S.current.none_of_selected_providers_can_exchange,
);
return;
}
@@ -1183,7 +1206,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
if (isFixedRateMode && provider.supportsFixedRate == false) {
continue;
}
if (_excludeProviderForReceiveExtraId(provider)) {
continue;
}
@@ -1200,13 +1223,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
bestRate = providerRate;
bestRateProvider = provider;
await changeDepositAmount(amount: _depositAmount, isCanonical: true);
await changeDepositAmount(amount: _depositAmount.toString(), isCanonical: true);
final request = TradeRequest(
fromCurrency: depositCurrency,
toCurrency: receiveCurrency,
fromAmount: _depositAmount.replaceAll(',', '.'),
toAmount: _receiveAmount.replaceAll(',', '.'),
fromAmount: _depositAmount.toString(),
toAmount: _receiveAmount.toString(),
refundAddress: depositAddress,
toAddress: receiveAddress,
toAddressExtraId: receiveAddressExtraId.trim(),
@@ -1218,8 +1241,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
await wallet.walletAddresses.saveAddressesInBox();
}
var amount = isFixedRateMode ? _receiveAmount : _depositAmount;
amount = amount.replaceAll(',', '.');
var amount = isFixedRateMode ? _receiveAmount.toString() : _depositAmount.toString();
if (limitsState is LimitsLoadedSuccessfully) {
if (double.tryParse(amount) == null) {
@@ -1242,6 +1264,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
trade.walletId = wallet.id;
trade.chainId = wallet.chainId;
trade.fromWalletAddress = wallet.walletAddresses.address;
if(trade.from == null) {
trade.from = depositCurrency;
}
if(trade.to == null) {
trade.to = receiveCurrency;
}
final canCreateTrade = await isCanCreateTrade(trade);
if (!canCreateTrade.result) {
@@ -1260,7 +1288,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
tradesStore.setTrade(trade);
if (trade.provider != ExchangeProviderDescription.thorChain) await trade.save();
if (trade.provider != ExchangeProviderDescription.thorChain) await trade.save();
tradeState = TradeIsCreatedSuccessfully(trade: trade);
/// return after the first successful trade
@@ -1304,8 +1332,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
void reset() {
_initialPairBasedOnWallet();
isReceiveAmountEntered = false;
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
depositAddress =
depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
receiveAddress =
@@ -1341,8 +1369,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final amount = depositCurrency == CryptoCurrency.btcln
// FIXME amount estimation is broken/impossible for ln, konsti suggested this
? (amountParsingProxy
.parseCryptoString(depositAvailableAmount, depositCurrency).toInt() - 10)
? (amountParsingProxy.parseCryptoString(depositAvailableAmount, depositCurrency) -
Money.fromInt(10, depositCurrency))
: await bitcoin!.estimateFakeSendAllTxAmount(
wallet,
priority,
@@ -1353,7 +1381,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
: UnspentCoinType.any,
);
changeDepositAmount(amount: wallet.currency.formatAmount(BigInt.from(amount)), isCanonical: true);
changeDepositAmount(amount: amount.toString(), isCanonical: true);
} else if (wallet.type == WalletType.monero) {
final amount = await unspentCoinsListViewModel.getSendingBalance(UnspentCoinType.any);
@@ -1371,7 +1399,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
return;
}
final balanceAmount = _appStore.amountParsingProxy.getDisplayCryptoStringFromBigInt(balanceForCurrency.fullAvailableBalance, balanceCurrency!);
final balanceAmount = _appStore.amountParsingProxy.asDisplayString(balanceForCurrency.available);
final balanceDouble = double.tryParse(balanceAmount.replaceAll(',', '.')) ?? 0.0;
if (balanceDouble <= 0) {
changeDepositAmount(amount: wallet.currency.formatAmount(BigInt.zero), isCanonical: true);
@@ -1399,7 +1427,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
return;
}
final balanceWei = balanceForCurrency.fullAvailableBalance;
final balanceWei = balanceForCurrency.available.amount;
final feeWei = BigInt.parse(feeString);
final amountAfterFeeWei = balanceWei > feeWei ? balanceWei - feeWei : BigInt.zero;
changeDepositAmount(
@@ -1449,14 +1477,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
void _onPairChange({bool clearBoth = false}) {
if (clearBoth) {
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
if (!hasAllAmount) isSendAllEnabled = false;
} else {
if (isFixedRateMode) {
_depositAmount = '';
_depositAmount = null;
} else {
_receiveAmount = '';
_receiveAmount = null;
}
}
bestRate = 0.0;
@@ -1559,6 +1587,21 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
}
String? _addressTypeValidation(String refundAddress, String receiveAddress) {
final isRefundAddressSP = RegExp(AddressValidator.silentPaymentAddressPatternMainnet).hasMatch(refundAddress);
if (isRefundAddressSP) return 'Silent Payment ${S.current.address_not_allowed_as_refund}';
final isReceiveAddressSP = RegExp(AddressValidator.silentPaymentAddressPatternMainnet).hasMatch(receiveAddress);
if (isReceiveAddressSP) return 'Silent Payment ${S.current.address_not_allowed_as_receive}';
final isRefundAddressMWEB = RegExp(AddressValidator.mWebAddressPattern).hasMatch(refundAddress);
if (isRefundAddressMWEB) return 'MWEB ${S.current.address_not_allowed_as_refund}';
final isReceiveAddressMWEB = RegExp(AddressValidator.mWebAddressPattern).hasMatch(receiveAddress);
if (isReceiveAddressMWEB) return 'MWEB ${S.current.address_not_allowed_as_receive}';
return null;
}
void _defineIsReceiveAmountEditable() {
/*if ((provider is ChangeNowExchangeProvider)
&&(depositCurrency == CryptoCurrency.xmr)
@@ -1591,8 +1634,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
void saveSelectedProviders() {
_depositAmount = '';
_receiveAmount = '';
_depositAmount = null;
_receiveAmount = null;
isFixedRateMode = false;
_defineIsReceiveAmountEditable();
bestRateProvider = null;
@@ -9,6 +9,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/parse_fixed.dart';
import 'package:cw_core/pending_transaction.dart';
@@ -22,7 +23,9 @@ class DEuroViewModel = DEuroViewModelBase with _$DEuroViewModel;
abstract class DEuroViewModelBase with Store {
final AppStore _appStore;
static BigInt get MIN_ACCRUED_INTEREST => BigInt.parse("1000000000000");
static Money get MIN_ACCRUED_INTEREST => Money(BigInt.parse("1000000000000"), CryptoCurrency.deuro);
static Money get ZERO => Money.zero(CryptoCurrency.deuro);
DEuroViewModelBase(
this._appStore, this.balanceViewModel, this._settingsStore, this._fiatConversationStore,
@@ -51,13 +54,13 @@ abstract class DEuroViewModelBase with Store {
@computed
String get pendingTransactionFiatAmountFormatted {
final amount = transaction == null ? '0.00' : _getDEuroFiatAmount(transaction!.amountFormatted);
return isFiatDisabled ? '' : '$amount ${fiat.title}';
final amount = transaction == null ? "0.00" : _getDEuroFiatAmount(transaction!.amount);
return isFiatDisabled ? "" : "$amount ${fiat.symbol}";
}
@computed
String get pendingTransactionFeeFiatAmountFormatted {
var amount = '0.00';
var amount = "0.00";
try {
if (transaction != null) {
final feeCurrency = CryptoCurrency.eth;
@@ -67,42 +70,43 @@ abstract class DEuroViewModelBase with Store {
);
}
} catch (_) {}
return isFiatDisabled ? '' : '$amount ${fiat.title}';
return isFiatDisabled ? "" : "$amount ${fiat.symbol}";
}
@computed
String get accountBalanceFormated {
Money get accountBalance {
final dEuroKey = balanceViewModel.balances.keys
.firstWhereOrNull((e) => e.title == CryptoCurrency.deuro.title);
if (dEuroKey == null) return '0.00';
return balanceViewModel.balances[dEuroKey]?.availableBalance ?? '0.00';
if (dEuroKey == null) return ZERO;
return wallet.balance[dEuroKey]?.available ?? ZERO;
}
@observable
BigInt savingsBalance = BigInt.zero;
Money savingsBalance = ZERO;
@computed
String get savingsBalanceFormated =>
evm!.formatterEVMAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
String get fiatSavingsBalanceFormated => _getDEuroFiatAmount(savingsBalance);
@observable
Money? savingsBalanceV1 = null;
@computed
String get fiatSavingsBalanceFormated => _getDEuroFiatAmount(savingsBalanceFormated);
String get fiatSavingsBalanceV1Formated => _getDEuroFiatAmount(savingsBalanceV1);
@observable
ExecutionState state = InitialExecutionState();
@observable
String interestRateFormated = '0';
String interestRateFormated = "0";
@observable
BigInt accruedInterest = BigInt.zero;
Money accruedInterest = ZERO;
@computed
String get accruedInterestFormated =>
evm!.formatterEVMAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
String get accruedInterestFormated => accruedInterest.toStringWithPrecision(fractionalDigits: 6);
@computed
String get fiatAccruedInterestFormated => _getDEuroFiatAmount(accruedInterestFormated);
String get fiatAccruedInterestFormated => _getDEuroFiatAmount(accruedInterest);
@observable
BigInt approvedTokens = BigInt.zero;
@@ -128,8 +132,12 @@ abstract class DEuroViewModelBase with Store {
@action
Future<void> reloadSavingsUserData() async {
approvedTokens = await evm!.getDEuroSavingsApproved(_appStore.wallet!) ?? BigInt.zero;
savingsBalance = await evm!.getDEuroSavingsBalance(_appStore.wallet!) ?? BigInt.zero;
accruedInterest = await evm!.getDEuroAccruedInterest(_appStore.wallet!) ?? BigInt.zero;
savingsBalance = await evm!.getDEuroSavingsBalance(_appStore.wallet!) ?? ZERO;
accruedInterest = await evm!.getDEuroAccruedInterest(_appStore.wallet!) ?? ZERO;
final v1Balance = await evm!.getDEuroSavingsV1Balance(_appStore.wallet!) ?? ZERO;
savingsBalanceV1 = v1Balance.isZero ? null : v1Balance;
isLoading = false;
}
@@ -151,9 +159,8 @@ abstract class DEuroViewModelBase with Store {
state = TransactionCommitting();
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
final approval = await evm!.enableDEuroSaving(_appStore.wallet!, priority);
if (approval == null) {
throw Exception('DEuro saving not available');
}
if (approval == null) throw Exception("DEuro saving not available");
approvalTransaction = approval;
state = InitialExecutionState();
} catch (e) {
@@ -167,13 +174,13 @@ abstract class DEuroViewModelBase with Store {
state = TransactionCommitting();
if (amountRaw.isEmpty || amountRaw.trim().isEmpty) {
throw Exception('Invalid amount: amount cannot be empty');
throw Exception("Invalid amount: amount cannot be empty");
}
final amount = tryParseFixed(amountRaw, 18);
if (amount == BigInt.zero || amount == null) {
throw Exception('Invalid amount: amount cannot be zero');
throw Exception("Invalid amount: amount cannot be zero");
}
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
@@ -181,9 +188,24 @@ abstract class DEuroViewModelBase with Store {
final tx = await (isAdding
? evm!.addDEuroSaving(_appStore.wallet!, amount, priority)
: evm!.removeDEuroSaving(_appStore.wallet!, amount, priority));
if (tx == null) {
throw Exception('DEuro saving not available');
}
if (tx == null) throw Exception("DEuro saving not available");
transaction = tx;
state = InitialExecutionState();
} catch (e) {
state = FailureState(e.toString());
}
}
@action
Future<void> prepareSavingsV1Withdraw() async {
try {
state = TransactionCommitting();
actionType = DEuroActionType.withdraw;
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
final tx = await evm!.withdrawDEuroSavingV1(_appStore.wallet!, priority);
if (tx == null) throw Exception("DEuro saving not available");
transaction = tx;
state = InitialExecutionState();
} catch (e) {
@@ -193,13 +215,13 @@ abstract class DEuroViewModelBase with Store {
Future<void> prepareCollectInterest() async {
if (accruedInterest < MIN_ACCRUED_INTEREST) {
state = FailureState('Accrued interest is below minimum threshold');
state = FailureState("Accrued interest is below minimum threshold");
return;
}
final formatted = accruedInterestFormated;
if (formatted.isEmpty || formatted == '0.000000') {
state = FailureState('Invalid accrued interest amount');
if (formatted.isEmpty || formatted == "0.000000") {
state = FailureState("Invalid accrued interest amount");
return;
}
@@ -212,9 +234,8 @@ abstract class DEuroViewModelBase with Store {
actionType = DEuroActionType.reinvest;
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
final tx = await evm!.reinvestDEuroInterest(_appStore.wallet!, priority);
if (tx == null) {
throw Exception('DEuro saving not available');
}
if (tx == null) throw Exception("DEuro saving not available");
transaction = tx;
state = InitialExecutionState();
} catch (e) {
@@ -261,7 +282,9 @@ abstract class DEuroViewModelBase with Store {
state = InitialExecutionState();
}
String _getDEuroFiatAmount(String amount) {
String _getDEuroFiatAmount(Money? amount) {
if (amount == null) return "0.00";
try {
var dEuro = CryptoCurrency.deuro;
final keys = _fiatConversationStore.prices.keys.toList();
@@ -270,19 +293,14 @@ abstract class DEuroViewModelBase with Store {
}
return calculateFiatAmount(
price: _fiatConversationStore.prices[dEuro]!,
cryptoAmount: amount,
cryptoAmount: amount.toStringWithPrecision(fractionalDigits: 6),
);
} catch (_) {
return '0.00';
return "0.00";
}
}
}
class NoEtherState extends ExecutionState {}
enum DEuroActionType {
deposit,
withdraw,
reinvest,
none;
}
enum DEuroActionType { deposit, withdraw, reinvest, none }
@@ -110,10 +110,10 @@ abstract class PayjoinDetailsViewModelBase with Store {
value: '${transactionInfo!.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
if (transactionInfo!.feeFormatted()?.isNotEmpty ?? false)
if (transactionInfo!.fee != null)
StandartListItem(
title: S.current.transaction_details_fee,
value: transactionInfo!.feeFormatted()!,
value: transactionInfo!.fee!.toStringWithSymbol(),
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
]);
@@ -93,7 +93,7 @@ abstract class PaymentViewModelBase with Store {
if (currentWallet != null &&
currentWallet.type == detectedWalletType &&
!isEVMCompatibleChain(detectedWalletType!)) {
return PaymentFlowResult.currentWalletCompatible();
return PaymentFlowResult.currentWalletCompatible(detectionResult);
}
if (detectedWalletType == WalletType.solana) {
@@ -229,8 +229,12 @@ class PaymentFlowResult {
);
/// Current wallet is compatible
factory PaymentFlowResult.currentWalletCompatible() =>
PaymentFlowResult._(type: PaymentFlowType.currentWalletCompatible);
factory PaymentFlowResult.currentWalletCompatible(
AddressDetectionResult addressDetectionResult) =>
PaymentFlowResult._(
type: PaymentFlowType.currentWalletCompatible,
addressDetectionResult: addressDetectionResult,
);
/// Single compatible wallet available
factory PaymentFlowResult.singleWallet(
@@ -32,7 +32,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
void reset() => filePath = '';
@action
Future<void> import(String password) async {
Future<void> import(String password,{bool checkBackupApp = true}) async {
try {
state = IsExecutingState();
@@ -44,7 +44,9 @@ abstract class RestoreFromBackupViewModelBase with Store {
final file = File(filePath);
try {
await backupService.importBackupFile(file, password);
await backupService.importBackupFile(file, password, checkBackupApp: checkBackupApp);
} on IncompatibleBackupAppException {
rethrow;
} catch (e, s) {
if (e.toString().contains("unknown_backup_version")) {
state = FailureState('This is not a valid backup file, please make sure you have selected the correct one');
@@ -74,6 +76,9 @@ abstract class RestoreFromBackupViewModelBase with Store {
});
state = ExecutedSuccessfullyState();
} on IncompatibleBackupAppException {
state = InitialExecutionState();
rethrow;
} catch (e, s) {
var msg = e.toString().toLowerCase();
+42 -91
View File
@@ -1,6 +1,5 @@
import 'dart:math' show min;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
@@ -9,7 +8,6 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
@@ -17,14 +15,12 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/format_fixed.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -50,7 +46,7 @@ abstract class OutputBase with Store {
note = '',
memo = "",
extractedAddress = '',
estimatedFee = '0.0',
estimatedFee = Money.zero(cryptoCurrencyHandler()),
parsedAddress = ParsedAddress(addresses: []) {
autorun((_) {
final status = _wallet.syncStatus;
@@ -64,6 +60,9 @@ abstract class OutputBase with Store {
bool get useSatoshi => _appStore.amountParsingProxy.useSatoshi(cryptoCurrencyHandler());
@observable
bool isFiatEntry = false;
@observable
String fiatAmount;
@@ -74,8 +73,7 @@ abstract class OutputBase with Store {
String? displayName;
@computed
String get displayCryptoAmount =>
_appStore.amountParsingProxy.getDisplayCryptoAmount(cryptoAmount, cryptoCurrencyHandler());
String get displayCryptoAmount => _appStore.amountParsingProxy.asDisplayString(cryptoAmountMoney);
@observable
String cryptoFullBalance;
@@ -106,9 +104,8 @@ abstract class OutputBase with Store {
String roundedCryptoAmount(int digits) => displayCryptoAmount.withMaxDecimals(digits);
String roundedFiatAmount(int digits) {
if (fiatAmount.split(".").last.length <= digits) {
return fiatAmount;
}
if (fiatAmount.split(".").last.length <= digits) return fiatAmount;
return double.tryParse(fiatAmount.replaceAll(",", ""))?.toStringAsPrecision(digits) ?? '0.0';
}
@@ -116,65 +113,18 @@ abstract class OutputBase with Store {
String? stealthAddress;
@computed
int get formattedCryptoAmount {
int amount = 0;
Money get cryptoAmountMoney {
if (cryptoAmount.isEmpty) return Money.zero(cryptoCurrencyHandler());
try {
if (cryptoAmount.isNotEmpty) {
final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
int _amount = 0;
switch (walletType) {
case WalletType.monero:
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.dogecoin:
_amount = cryptoCurrencyHandler().parseAmount(_cryptoAmount).toInt();
break;
case WalletType.decred:
_amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
break;
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
_amount = evm!.formatterEVMParseAmount(_cryptoAmount);
break;
case WalletType.wownero:
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.zano:
_amount = zano!
.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.zcash:
_amount = zcash!.formatterZcashParseAmount(_cryptoAmount);
break;
case WalletType.none:
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
break;
}
if (_amount > 0) {
amount = _amount;
}
}
return cryptoCurrencyHandler().parseAmount(cryptoAmount.sanitized());
} catch (e) {
amount = 0;
return Money.zero(cryptoCurrencyHandler());
}
return amount;
}
@observable
String estimatedFee;
Money estimatedFee;
@action
Future<void> calculateEstimatedFee() async {
@@ -188,7 +138,7 @@ abstract class OutputBase with Store {
if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
fee = _wallet.calculateEstimatedFee(
_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
formattedCryptoAmount,
cryptoAmountMoney.amount.toInt(),
);
}
@@ -199,41 +149,35 @@ abstract class OutputBase with Store {
case WalletType.bitcoinCash:
case WalletType.dogecoin:
case WalletType.decred:
estimatedFee = walletTypeToCryptoCurrency(_wallet.type).formatAmount(BigInt.from(fee));
case WalletType.zano:
estimatedFee = Money.fromInt(fee, walletTypeToCryptoCurrency(_wallet.type));
break;
case WalletType.bitcoin:
if (cryptoCurrencyHandler() == CryptoCurrency.btcln) {
estimatedFee =
_appStore.amountParsingProxy.getDisplayCryptoString(10, cryptoCurrencyHandler());
estimatedFee = Money.fromInt(10, cryptoCurrencyHandler());
break;
}
if (_settingsStore.getPriority(_wallet.type) ==
bitcoin!.getBitcoinTransactionPriorityCustom()) {
fee = bitcoin!.getEstimatedFeeWithFeeRate(
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
_wallet, _settingsStore.customBitcoinFeeRate, cryptoAmountMoney.amount.toInt());
}
estimatedFee = _appStore.amountParsingProxy.getDisplayCryptoString(fee, cryptoCurrencyHandler());
estimatedFee = Money.fromInt(fee, cryptoCurrencyHandler());
break;
case WalletType.solana:
estimatedFee = solana!.getEstimateFees(_wallet).toString();
break;
case WalletType.zano:
estimatedFee = zano!
.formatterIntAmountToDouble(
amount: fee, currency: cryptoCurrencyHandler(), forFee: true)
.toString();
estimatedFee = solana!.getEstimateFees(_wallet) ?? Money.zero(CryptoCurrency.sol);
break;
case WalletType.tron:
if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
estimatedFee = tron!.getTronNativeEstimatedFee(_wallet).toString();
estimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? Money.zero(CryptoCurrency.trx);
} else {
estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet).toString();
estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? Money.zero(CryptoCurrency.trx);
}
break;
case WalletType.zcash:
estimatedFee = zcash!.formatterZcashAmountToDouble(amount: BigInt.from(fee)).toString();
estimatedFee = Money.fromInt(fee, cryptoCurrencyHandler());
break;
/// EVMs
@@ -242,16 +186,19 @@ abstract class OutputBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
cryptoCurrencyHandler() == CryptoCurrency.arbEth ||
cryptoCurrencyHandler() == CryptoCurrency.bnb;
String? fee = isNative
final isNative = [
CryptoCurrency.eth,
CryptoCurrency.maticpoly,
CryptoCurrency.baseEth,
CryptoCurrency.arbEth,
CryptoCurrency.bnb
].contains(cryptoCurrencyHandler());
final fee = isNative
? evm!.getEVMNativeEstimatedFee(_wallet)
: evm!.getEVMERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
estimatedFee = Money(BigInt.parse(fee ?? '0.0'), walletTypeToCryptoCurrency(_wallet.type));
break;
/// end EVMs
@@ -275,12 +222,12 @@ abstract class OutputBase with Store {
try {
final currency = (isEVMCompatibleChain(_wallet.type) ||
[WalletType.solana, WalletType.tron].contains(_wallet.type))
[WalletType.solana, WalletType.tron].contains(_wallet.type)) ||
cryptoCurrencyHandler() == CryptoCurrency.btcln
? _wallet.currency
: cryptoCurrencyHandler();
final cryptoAmount =
double.parse(_appStore.amountParsingProxy.getCanonicalCryptoAmount(estimatedFee, currency));
final cryptoAmount = double.parse(estimatedFee.toString());
return calculateFiatAmountRaw(
price: _fiatConversationStore.prices[currency]!, cryptoAmount: cryptoAmount);
@@ -294,7 +241,7 @@ abstract class OutputBase with Store {
WalletType get walletType => _wallet.type;
final CryptoCurrency Function() cryptoCurrencyHandler;
final CryptoCurrency Function([CryptoCurrency?]) cryptoCurrencyHandler;
final FiatConversionStore _fiatConversationStore;
final AppStore _appStore;
@@ -401,6 +348,10 @@ abstract class OutputBase with Store {
if(parsedAddress.name.isNotEmpty) {
displayName = parsedAddress.name;
}
if (parsedAddress.parseFrom == ParseFrom.lnurlpay) {
cryptoCurrencyHandler(CryptoCurrency.btcln);
}
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description;
}
+50 -39
View File
@@ -53,6 +53,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_mod
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/erc20_token.dart';
@@ -121,7 +122,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
fiatFromSettings = _appStore.settingsStore.fiatCurrency,
fiatCurrencies = FiatCurrency.all,
super(appStore: _appStore) {
outputs.add(Output(wallet, _appStore, _fiatConversationStore, () => selectedCryptoCurrency));
outputs.add(Output(wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler));
unspentCoinsListViewModel
.initialSetup()
@@ -162,12 +163,19 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isEVMWallet => isEVMCompatibleChain(walletType);
@action
CryptoCurrency _outputCryptoCurrencyHandler([CryptoCurrency? override]) {
if (override != null && override != selectedCryptoCurrency) selectedCryptoCurrency = override;
return selectedCryptoCurrency;
}
@action
void setShowAddressBookPopup(bool value) => _settingsStore.showAddressBookPopupEnabled = value;
@action
void addOutput() =>
outputs.add(Output(wallet, _appStore, _fiatConversationStore, () => selectedCryptoCurrency));
outputs.add(Output(wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler));
@action
void removeOutput(Output output) {
@@ -310,10 +318,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Handle case where balance might not be available yet (e.g., during chain switch)
final balanceForCurrency = wallet.balance[selectedCryptoCurrency];
if (balanceForCurrency == null) {
return _appStore.amountParsingProxy.getDisplayCryptoString(0, selectedCryptoCurrency);
return _appStore.amountParsingProxy.asDisplayString(Money.zero(selectedCryptoCurrency));
}
return _appStore.amountParsingProxy.getDisplayCryptoStringFromBigInt(
wallet.balance[selectedCryptoCurrency]!.fullAvailableBalance, selectedCryptoCurrency);
return _appStore.amountParsingProxy
.asDisplayString(wallet.balance[selectedCryptoCurrency]!.available);
}
@action
@@ -340,7 +348,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// only for electrum, monero, wownero, decred wallets atm:
switch (wallet.type) {
case WalletType.bitcoin:
if (coinTypeToSpendFrom == UnspentCoinType.lightning) return balance;
if (selectedCryptoCurrency == CryptoCurrency.btcln) return balance;
return _appStore.amountParsingProxy.getDisplayCryptoString(
await unspentCoinsListViewModel.getSendingBalance(coinTypeToSpendFrom),
walletTypeToCryptoCurrency(walletType));
@@ -420,6 +428,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
String get selectedCryptoCurrencySymbol =>
amountParsingProxy.getCryptoSymbol(selectedCryptoCurrency);
@computed
bool get useBaseUnits => amountParsingProxy.useSatoshi(selectedCryptoCurrency);
List<CryptoCurrency> currencies;
WalletType get walletType => wallet.type;
@@ -681,7 +692,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet,
routerTo,
routerData,
BigInt.zero,
Money.zero(wallet.currency),
priority,
useBlinkProtection: canSupportBlinkProtection(selectedChainId)
? _settingsStore.useBlinkProtection
@@ -804,7 +815,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet,
routerTo,
routerData,
routerValueWei,
Money(routerValueWei, wallet.currency),
priority,
sourceTokenAddress: tokenContract,
sourceTokenAmount: requiredAmount,
@@ -842,7 +853,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Fallback to estimate if not available
final fee = actualFee > 0 ? actualFee : 0.0005;
final amount = double.tryParse(trade.amount) ?? 0.0;
final fromCurrency = trade.from ?? CryptoCurrency.sol;
final amount = Money.tryParse(trade.amount, fromCurrency) ?? Money.zero(fromCurrency);
pendingTransaction = await solana!.signAndPrepareJupiterSwapTransaction(
wallet,
@@ -850,7 +862,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
requestId!,
trade.payoutAddress ?? '',
amount,
fee,
Money.tryParse(fee.toString(), CryptoCurrency.sol) ?? Money.zero(CryptoCurrency.sol),
);
state = ExecutedSuccessfullyState();
@@ -1104,41 +1116,41 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// instead, they rely on the tx to eventually get fetched at sync time, which can take a while
if(isEVMWallet) {
wallet.transactionHistory.addOne(evm!.getTransactionInfo(
id: pendingTransaction!.evmTxHashFromRawHex!,
height: 0,
// FIXME(malik) this is even more critical given all the issues we had with decimal point parsing. with money it'll be easy
ethAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
ethFee: CryptoCurrency.eth.parseAmount(pendingTransaction!.feeFormattedValue),
tokenSymbol: selectedCryptoCurrency.title,
direction: TransactionDirection.outgoing,
isPending: true,
date: DateTime.now(),
confirmations: 0,
chainId: wallet.chainId ?? 0));
id: pendingTransaction!.evmTxHashFromRawHex!,
height: 0,
amount: outputs.first.cryptoAmountMoney,
fee: pendingTransaction!.fee,
tokenSymbol: selectedCryptoCurrency.title,
direction: TransactionDirection.outgoing,
isPending: true,
date: DateTime.now(),
confirmations: 0,
chainId: wallet.chainId ?? 0,
));
}
if(walletType == WalletType.solana) {
wallet.transactionHistory.addOne(solana!.getTransactionInfo(
id: pendingTransaction!.id,
blockTime: DateTime.now(),
tokenSymbol: selectedCryptoCurrency.title,
to: "",
from: "",
direction: TransactionDirection.outgoing,
solAmount: solana!.getPendingTransactionAmount(pendingTransaction!),
isPending: true,
txFee: solana!.getPendingTransactionFee(pendingTransaction!)));
id: pendingTransaction!.id,
blockTime: DateTime.now(),
to: "",
from: "",
direction: TransactionDirection.outgoing,
amount: pendingTransaction!.amount,
isPending: true,
fee: pendingTransaction!.fee,
));
}
if (walletType == WalletType.tron) {
wallet.transactionHistory.addOne(tron!.getTransactionInfo(
id: pendingTransaction!.id,
blockTime: DateTime.now(),
tokenSymbol: selectedCryptoCurrency.title,
direction: TransactionDirection.outgoing,
tronAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
isPending: true,
txFee: CryptoCurrency.trx.parseAmount(outputs.first.estimatedFee).toInt()));
id: pendingTransaction!.id,
blockTime: DateTime.now(),
direction: TransactionDirection.outgoing,
amount: outputs.first.cryptoAmountMoney,
isPending: true,
fee: outputs.first.estimatedFee,
));
}
if (pendingTransaction!.id.isNotEmpty) {
@@ -1419,9 +1431,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return await evm!.createTokenApproval(
wallet,
requiredAmount,
Money(requiredAmount, erc20Token),
spender,
erc20Token,
priority,
useBlinkProtection:
canSupportBlinkProtection(selectedChainId) ? _settingsStore.useBlinkProtection : false,
+10 -5
View File
@@ -23,8 +23,8 @@ abstract class TemplateViewModelBase with Store {
_appStore = appStore,
_fiatConversationStore = fiatConversationStore,
_currency = wallet.currency,
output = Output(wallet, appStore, fiatConversationStore, () => wallet.currency) {
output = Output(_wallet, _appStore, _fiatConversationStore, () => _currency);
output = Output(wallet, appStore, fiatConversationStore, ([_]) => wallet.currency) {
output = Output(_wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler);
}
@observable
@@ -53,15 +53,20 @@ abstract class TemplateViewModelBase with Store {
output.reset();
}
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
return Template(
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) => Template(
isCurrencySelectedRaw: isCryptoSelected,
nameRaw: name,
addressRaw: address,
cryptoCurrencyRaw: cryptoCurrency,
fiatCurrencyRaw: fiatCurrency,
amountRaw: output.cryptoAmount,
amountFiatRaw: output.fiatAmount);
amountFiatRaw: output.fiatAmount,
);
CryptoCurrency _outputCryptoCurrencyHandler([CryptoCurrency? override]) {
if (override != null && _currency != override) changeSelectedCurrency(override);
return _currency;
}
@action
+5 -5
View File
@@ -56,11 +56,11 @@ abstract class SupportViewModelBase with Store {
icon: 'assets/images/change_now.png',
linkTitle: 'support@changenow.io',
link: 'mailto:support@changenow.io'),
LinkListItem(
title: 'SideShift',
icon: 'assets/images/sideshift.png',
linkTitle: 'help.sideshift.ai',
link: 'https://help.sideshift.ai/en/'),
// LinkListItem(
// title: 'SideShift',
// icon: 'assets/images/sideshift.png',
// linkTitle: 'help.sideshift.ai',
// link: 'https://help.sideshift.ai/en/'),
LinkListItem(
title: 'SimpleSwap',
icon: 'assets/images/simpleSwap.png',
@@ -79,22 +79,22 @@ class TxDetailRowDefinition {
title: S.current.transaction_details_date,
valueGetter: (vm) => DateFormatter.withCurrentLocal().format(vm.transactionInfo.date)),
TxDetailRowDefinition(
keyString: "standard_list_item_transaction_details_height_key",
title: S.current.transaction_details_height,
valueGetter: (vm) => vm.transactionInfo.height?.toString() ?? "",
applicable: (vm) => !([WalletType.solana, WalletType.tron].contains(vm.wallet.type) &&
!isLightning(vm.transactionInfo))),
keyString: "standard_list_item_transaction_details_height_key",
title: S.current.transaction_details_height,
valueGetter: (vm) => vm.transactionInfo.height?.toString() ?? "",
applicable: (vm) =>
![WalletType.solana, WalletType.tron].contains(vm.wallet.type) ||
!isLightning(vm.transactionInfo),
),
TxDetailRowDefinition(
keyString: "standard_list_item_transaction_details_fee_key",
title: S.current.transaction_details_fee,
valueGetter: (vm) => vm.transactionInfo.feeFormatted()!,
valueGetter: (vm) => vm.transactionInfo.fee!.toStringWithSymbol(),
applicable: (vm) =>
vm.wallet.type != WalletType.nano &&
(vm.transactionInfo.feeFormatted() ?? "").isNotEmpty),
(vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty),
TxDetailRowDefinition(
@@ -496,14 +496,15 @@ abstract class TransactionDetailsViewModelBase with Store {
RBFListItems.add(
StandartListItem(
title: S.current.old_fee,
value: tx.feeFormatted() ?? '0.0',
value: tx.fee?.toStringWithSymbol() ?? '0.0',
key: ValueKey('standard_list_item_rbf_old_fee_key'),
),
);
if (transactionInfo.fee != null && rawTransaction.isNotEmpty) {
final size = bitcoin!.getTransactionVSize(wallet, rawTransaction);
final recommendedRate = (transactionInfo.fee! / size).round() + 1;
final recommendedRate = (transactionInfo.fee! / BigInt.from(size)) +
transactionInfo.fee!.copyWith(amount: BigInt.one);
RBFListItems.add(
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
@@ -609,14 +610,14 @@ abstract class TransactionDetailsViewModelBase with Store {
if (wallet.type == WalletType.bitcoin) {
final crypto = isLightning(transactionInfo) ? CryptoCurrency.btcln : CryptoCurrency.btc;
final amount = _appStore.amountParsingProxy
.getDisplayCryptoString(transactionInfo.amount, crypto)
.asDisplayString(transactionInfo.amount)
.withMaxDecimals(8)
.withLocalSeperator(_appStore.settingsStore.languageCode);
return '$amount ${_appStore.amountParsingProxy.getCryptoSymbol(crypto)}';
}
return transactionInfo.amountFormatted();
return transactionInfo.amount.toStringWithSymbol();
}
void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee);
@@ -614,11 +614,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void setAddress(WalletAddressListItem address) =>
wallet.walletAddresses.address = address.address;
@observable
bool isRotatingAddress = false;
@action
Future<void> rotateAddress() async {
await createNewAddress(wallet, "");
if (isElectrumWallet) {
if(isRotatingAddress) {
return;
}
try {
isRotatingAddress = true;
await createNewAddress(wallet, "");
wallet.walletAddresses.address = addressList.whereType<WalletAddressListItem>().last.address;
} finally {
isRotatingAddress = false;
}
}