Introduce Money class and refactor currency handling (#3157)

* feat: add `Money` class for precise currency handling and amount parsing

- Introduced `Money` class for handling money values with fixed-point precision.
- Added parsing and comparison methods to `CryptoCurrency` and `FiatCurrency` interfaces.
- Implemented `smartAmountSanitizer` for format normalization.

Includes extensive unit tests for validation.

* refactor: optimize Money class with new factories and string formatting improvements

* refactor: unify balance handling with `Money` refactor

* refactor: update `ElectrumBalance` and transaction logic to use `Money` for currency precision

* refactor: standardize balance handling with `Money` across `Monero`, `Wownero`, and `Zano` wallets

* refactor: migrate EVM balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Decred balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate nano balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Solana balance and transaction logic to `Money` for precise currency handling

* refactor: migrate Tron balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Zcash balance and transaction handling to `Money` for consistent currency precision

* refactor: adjust lib to handle money

* refactor: remove legacy Decred amount formatting and migrate to `Money`

* refactor: migrate pending transaction amount and fee to `Money`

* refactor: migrate dEuro savings and transaction logic to `Money` for improved precision and consistency

* refactor: migrate Bitcoin and lightning transaction logic to `Money` for consistent currency precision

* refactor: migrate Wownero, EVM, Zano, and Zcash transaction logic to `Money` for consistent and precise currency handling

* refactor: standardize balance handling across wallets, migrate to `Money.zero` and refine `Balance` class for consistency

* refactor: migrate Solana transaction and wallet logic to `Money` for consistent and precise currency handling

* refactor: improve fiat balance and formatting for consistency on dashboard

* fix: fiat input mode switching and standardize crypto amount display logic

* fix: cw_evm generator

* refactor: migrate exchange amount handling to `Money` for improved precision and consistency

* test: remove redundant and commented-out tests, tidy test cases, and apply minor formatting adjustments

* fix using Money wrapper in BridgeViewModel and USDT0Service

* refactor: streamline `AmountConverter` and `LightningWallet`, add `cw_core` tests to GitHub workflow, and remove unused/deprecated logic

* refactor: enhance `changeReceiveAmount` with `isCanonical` support, improve null handling, and refine amount parsing logic

* minor fix

* try/catch the lightning log file error

* feat: add copy-to-clipboard functionality for transaction amounts, fix typo in trailing widget method name (#3219)

* Cw 1234 disallow sp and mweb addresses as refund for swaps (#3187)

* disallow SP and MWEB for swaps

* Simplify address selection in addressForExchange

Refactor addressForExchange method to simplify address selection logic.

* validate refund and receive address types

* localize address validation messages

* minor ui fix [skip ci]

* minor fix [skip ci]

* Revert "localize address validation messages"

* fix translations

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* refactor: replace manual amount sanitization with `sanitized()` extension across the codebase, add unit tests for `AmountSanitizer`

* fix: correct `sanitized()` test expectations for decimal handling in `amount_sanitizer_test.dart`

* refactor: fee display logic and append currency symbol in `send_confirm_sheet.dart`

* refactor: simplify `ZcashBalance` class by replacing custom fields with inherited properties, update references accordingly

* refactor: replace hardcoded USDT contract address with `DefaultTronTokens` lookup, improve maintainability

* refactor: replace `currency` with `inputAmount.currency` in Solana transaction signing methods to avoid inconsistency

* fix: handle null `fee` in `electrum_transaction_info.dart` to prevent potential runtime errors

* refactor: replace raw fee and amount handling with `Money` object for improved consistency and maintainability

* fix: limit fiat balance display to 2 decimal places in `balance_view_model.dart`

* refactor: replace raw `amount` and `fee` types with `Money` in Solana transaction methods for consistency and precision

* refactor: replace raw `amount` and `fee` handling with `Money` and improve balance calculation consistency across view models

* refactor: replace raw balance strings with `Money` and simplify `UnconfirmedBalanceModal` widget logic

* refactor: replace `print` with `printV` for better logging, add equality and hashCode overrides to `ERC20Currency` for object comparison

* feat: prepare deuro savings migration

* feat: add V1 savings migration support and improve formatting consistency in DEuro view model

* feat: add V1 savings withdrawal support and update associated UI components and logic

* refactor: replace `Image.asset` with `CakeImageWidget`, update balance string handling, and streamline address validation logic

* refactor: use `Money.tryParse` with fallback to `Money.zero` in `NanoBalance.fromRawString` for safer balance parsing

* refactor: replace `ERC20Currency` with `Erc20Token` for consistency and remove unused `evm_erc20_currency.dart` file

* refactor: add computed `hasDepositAmount` for deposit null-checks, optimize balance handling in `zano_wallet`, update Breez SDK to v0.14.0, and implement equality/hashCode for `Currency`

* refactor: rename `amountSats` to `amount` in `PrepareLnurlPayRequest` for consistency

* revert: downgrade Breez SDK to v0.11.0 in `pubspec.yaml` and update relevant references in `pubspec.lock`

* fix: use `cryptoAmount.copyWith` for currency consistency and replace `Image.asset` with `CakeImageWidget` in swap address modal

* fix: ensure currency consistency by using `cryptoAmount.copyWith` in Solana and EVM wallet transactions

* refactor: simplify CSV row generation logic by removing `_splitAmountCurrency` and streamlining amount/fee handling

* fix: LateInitializationError Field 'sdk' has not been initialized

* fix: use `Money.tryParse` for safer parsing of deposit and receive amounts

* chore: remove debug print statement

* refactor: update transaction processing to consistently use `Money` for amounts and fees

* refactor: improve currency and amount handling, simplify logic, and enhance code readability in Send and Swap pages

* refactor: integrate `DecimalInputFormatter` for consistent decimal input across Send and Swap pages, simplify input handling logic

* fix: "fiatMode" per output on sendPage

* refactor: make transaction_details_height applicable more readable

* fix: use correct base unit for deposit

* refactor: use Money in estimateFakeSendAllTxAmount method

* fix decred frozen balance
minor fixes

* fix decred frozen balance
minor fixes

* fix: automatically switch to lightning if lnurl or invoice detected

* fix: getBolt11Amount test cases

* fix: standardise crypto amount formatting and display

* fix: use base unit for Tron transaction fee parsing
* refactor: hardcode fractional digits to 8 in `AmountParsingProxy` display methods
* refactor: ensure consistent 8-decimal precision for fee formatting in `PendingTransaction`
* fix: use `AmountParsingProxy` for available balance display in exchange view model
* fix: improve amount formatting and layout in swap and send confirmation sheets
* chore: remove redundant decimal truncation in balance view model and send confirmation sheet

* fix: allow for editing text field if it is filled with "ALL"

* fix: allow for editing text field if it is filled with "ALL"

* fix: update currency detection logic for lightning mode

* avoid potential infinite loop [skip ci]

* fix: getDisplayCryptoAmount

* fix: update estimated fee calculation

* chore: simplify fee calculation logic [skip-ci]

* fix value display in hidden mode

* fix stale state in send confirm sheet

* fix decimals for mweb

* fix available balance display for sol/trx on swap

* restore proper hint text

* fix swap receive amount calculation for xno

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: Serhii <17529954+serhii-bor@users.noreply.github.com>
Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
This commit is contained in:
Konstantin Ullrich
2026-06-30 02:45:44 +03:00
committed by GitHub
co-authored by Omar Hatem Serhii Robert Malikowski
parent 8768aa8995
commit 75f3fe735b
178 changed files with 4238 additions and 3560 deletions
+3 -8
View File
@@ -156,13 +156,12 @@ class CWBitcoin extends Bitcoin {
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo.isNotEmpty ? out.memo : null,
extra: out.extra,
))
@@ -191,7 +190,7 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority,
Future<Money> estimateFakeSendAllTxAmount(WalletBase wallet, TransactionPriority priority,
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async {
try {
final sk = ECPrivate.random();
@@ -233,7 +232,7 @@ class CWBitcoin extends Bitcoin {
return estimatedTx.amount;
} catch (_) {
return 0;
return Money.zero(wallet.currency);
}
}
@@ -247,10 +246,6 @@ class CWBitcoin extends Bitcoin {
String formatterBitcoinAmountToString({required int amount}) =>
bitcoinAmountToString(amount: amount);
@override
double formatterBitcoinAmountToDouble({required int amount}) =>
bitcoinAmountToDouble(amount: amount);
@override
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
@@ -22,7 +22,6 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.da
import 'package:cake_wallet/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/typography.dart';
@@ -36,7 +35,6 @@ import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
class CakePayBuyCardPage extends BasePage {
@@ -612,16 +610,13 @@ class CakePayBuyCardPage extends BasePage {
titleIconPath: _sendViewModel.selectedCryptoCurrency.iconPath,
currency: _sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue: _sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
_sendViewModel.pendingTransaction!.amountFormatted,
_sendViewModel.selectedCryptoCurrency),
amountValue: _sendViewModel.amountParsingProxy.asDisplayStringWithSymbol(
_sendViewModel.pendingTransaction!.amount),
quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}',
fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(bottomSheetContext).send_fee,
feeValue: "${_sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
_sendViewModel.pendingTransaction!.feeFormattedValue,
_sendViewModel.selectedCryptoCurrency)} ${_sendViewModel.amountParsingProxy
.getCryptoSymbol(_sendViewModel.wallet.currency)}",
feeValue: _sendViewModel.amountParsingProxy.asDisplayStringWithSymbol(
_sendViewModel.pendingTransaction!.fee),
feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: displayingOutputs,
footerType: FooterType.slideActionButton,
@@ -699,13 +694,12 @@ class CakePayBuyCardPage extends BasePage {
child: Icon(Icons.check, size: 16, color: Colors.white),
),
output: displayingOutputs.first,
currency: _sendViewModel.selectedCryptoCurrency,
amount: S.of(sheetCtx).send_amount,
amountValue: _sendViewModel.pendingTransaction!.amountFormatted,
amountValue: _sendViewModel.pendingTransaction!.amount.toStringWithSymbol(),
quantity: 'QTY: ${cakePayBuyCardViewModel.quantity}',
fiatAmountValue: _sendViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(sheetCtx).send_fee,
feeValue: _sendViewModel.pendingTransaction!.feeFormatted,
feeValue: _sendViewModel.pendingTransaction!.fee.toStringWithSymbol(),
feeFiatAmount: _sendViewModel.pendingTransactionFeeFiatAmountFormatted,
paymentId: 'Order ID',
paymentIdValue: order?.orderId ?? '',
+32 -46
View File
@@ -1,7 +1,9 @@
import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.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/currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
class AmountParsingProxy {
@@ -20,70 +22,54 @@ class AmountParsingProxy {
/// [getCryptoOutputAmount] turns the input [amount] into the preferred representation of [cryptoCurrency]
String getDisplayCryptoAmount(String amount, CryptoCurrency cryptoCurrency) {
try {
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
return cryptoCurrency.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)).toString();
return cryptoCurrency
.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals))
.toStringWithPrecision(useBaseUnit: true);
}
return amount.withMaxDecimals(cryptoCurrency.decimals);
} catch(_) {
printV("failed to parse amount $amount for currency ${cryptoCurrency.title}, falling back to showing unparsed");
} catch (_) {
printV(
"failed to parse amount $amount for currency ${cryptoCurrency.title}, falling back to showing unparsed");
return amount;
}
}
/// [getCryptoStringRepresentation] turns the input [amount] into the preferred representation of [cryptoCurrency]
String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) {
if (useSatoshi(cryptoCurrency)) {
return "$amount";
}
String getDisplayCryptoString(int amount, CryptoCurrency cryptoCurrency) =>
asDisplayString(Money.fromInt(amount, cryptoCurrency));
return cryptoCurrency.formatAmount(BigInt.from(amount));
}
/// [getDisplayCryptoString] turns the input [amount] into the preferred representation of [cryptoCurrency]
///
/// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000
/// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1
/// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that
String asDisplayString(Money amount) =>
amount.toStringWithPrecision(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8);
String getDisplayCryptoStringFromBigInt(BigInt amount, CryptoCurrency cryptoCurrency) {
if (useSatoshi(cryptoCurrency)) {
return "$amount";
}
/// [asDisplayStringWithSymbol] turns the input [amount] into the preferred representation of
/// [cryptoCurrency] following the symbol of the unit
///
/// if [displayMode] is [BitcoinAmountDisplayMode.satoshi] it returns 1 BTC as 100000000 sats
/// if [displayMode] is [BitcoinAmountDisplayMode.bitcoin] it returns 1 BTC as 1 BTC
/// hardcoding fractionalDigits to 8 as it's a display string, so no need to show more than that
String asDisplayStringWithSymbol(Money amount) =>
amount.toStringWithSymbol(useBaseUnit: useSatoshi(amount.currency), fractionalDigits: 8);
return cryptoCurrency.formatAmount(amount);
}
/// [parseCryptoString] turns the the display representation [string] into `Money` with [cryptoCurrency]
Money parseCryptoString(String amount, CryptoCurrency cryptoCurrency) =>
Money.parse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency));
/// [getCryptoStringFromDouble] turns the input [amount] into the preferred representation of [cryptoCurrency] and
String getDisplayCryptoStringFromDouble(double amount, CryptoCurrency cryptoCurrency) {
if (useSatoshi(cryptoCurrency)) {
return "$amount";
}
return cryptoCurrency.formatAmount(BigInt.from(amount));
}
/// [parseCryptoString] turns the input [string] into a `BigInt` presentation of the [cryptoCurrency]
BigInt parseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
if (useSatoshi(cryptoCurrency)) {
return BigInt.parse(amount);
}
return cryptoCurrency.parseAmount(amount);
}
/// [tryParseCryptoString] tries to turn the display representation [string] into a `BigInt` presentation of the [cryptoCurrency]
BigInt? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) {
if (useSatoshi(cryptoCurrency)) {
return BigInt.tryParse(amount);
}
return cryptoCurrency.tryParseAmount(amount);
}
/// [tryParseCryptoString] tries to turn the display representation [string] into `Money` with [cryptoCurrency]
Money? tryParseCryptoString(String amount, CryptoCurrency cryptoCurrency) =>
Money.tryParse(amount, cryptoCurrency, isBaseUnit: useSatoshi(cryptoCurrency));
/// [getCryptoSymbol] returns the correct Symbol related to the presentation
String getCryptoSymbol(CryptoCurrency cryptoCurrency) =>
useSatoshi(cryptoCurrency) ? "sats" : cryptoCurrency.title.safeSubString(0, 8);
bool useSatoshi(CryptoCurrency cryptoCurrency) =>
bool useSatoshi(Currency cryptoCurrency) =>
([CryptoCurrency.btc, CryptoCurrency.btcln].contains(cryptoCurrency) &&
displayMode == BitcoinAmountDisplayMode.satoshi) ||
(CryptoCurrency.btcln == cryptoCurrency &&
+1 -1
View File
@@ -213,7 +213,7 @@ class BackgroundSync {
final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
await showNotification(
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amount.toString()}");
}
printV(
"${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
+4 -19
View File
@@ -80,21 +80,15 @@ class CsvExportService {
? tx.to!
: (tx.outputAddresses?.firstOrNull ?? ''));
// Split "0.2769 LTC" → amount="0.2769", currency="LTC"
final (amount, currency) = _splitAmountCurrency(tx.amountFormatted());
// fee is stored as (inputAmount - totalOutputAmount) which is negative for incoming
// transactions — only include it when the raw value is a sensible positive fee.
final fee = tx.fee != null && tx.fee! > 0 ? (tx.feeFormatted() ?? '') : '';
final (feeAmount, _) = fee.isNotEmpty ? _splitAmountCurrency(fee) : ('', '');
final fee = tx.fee != null && !tx.fee!.isZero ? tx.fee.toString() : '';
return _row([
'transaction',
_isoDate(tx.date),
type,
amount,
currency,
feeAmount,
tx.amount.toString(),
tx.amount.currency.symbol,
fee,
tx.id,
address,
status,
@@ -109,15 +103,6 @@ class CsvExportService {
]);
}
/// Splits a formatted amount like "0.2769 LTC" into ("0.2769", "LTC").
/// If there is no trailing symbol the whole string is returned as the amount.
(String, String) _splitAmountCurrency(String formatted) {
final trimmed = formatted.trim();
final idx = trimmed.lastIndexOf(' ');
if (idx == -1) return (trimmed, '');
return (trimmed.substring(0, idx), trimmed.substring(idx + 1));
}
String _tradeRow(TradeListItem item) {
final trade = item.trade;
return _row([
+9 -20
View File
@@ -19,9 +19,8 @@ class CWDecred extends Decred {
DecredRestoreWalletFromPubkeyCredentials(name: name, pubkey: pubkey, password: password);
@override
WalletService createDecredWalletService(Box<UnspentCoinsInfo> unspentCoinSource) {
return DecredWalletService(unspentCoinSource);
}
WalletService createDecredWalletService(Box<UnspentCoinsInfo> unspentCoinSource) =>
DecredWalletService(unspentCoinSource);
@override
List<TransactionPriority> getTransactionPriorities() => DecredTransactionPriority.all;
@@ -39,18 +38,19 @@ class CWDecred extends Decred {
@override
Object createDecredTransactionCredentials(List<Output> outputs, TransactionPriority priority) =>
DecredTransactionCredentials(
outputs
.map((out) => OutputInfo(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as DecredTransactionPriority);
))
.toList(),
priority: priority as DecredTransactionPriority,
);
List<WalletInfoAddressInfo> getAddressInfos(Object wallet) {
final decredWallet = wallet as DecredWallet;
@@ -69,17 +69,6 @@ class CWDecred extends Decred {
await decredWallet.walletAddresses.generateNewAddress(label);
}
@override
String formatterDecredAmountToString({required int amount}) =>
decredAmountToString(amount: amount);
@override
double formatterDecredAmountToDouble({required int amount}) =>
decredAmountToDouble(amount: amount);
@override
int formatterStringDoubleToDecredAmount(String amount) => stringDoubleToDecredAmount(amount);
@override
List<Unspent> getUnspents(Object wallet) {
final decredWallet = wallet as DecredWallet;
+3 -1
View File
@@ -1,9 +1,11 @@
import 'package:cw_core/amount/amount_sanitizer.dart';
String calculateFiatAmount({double? price, String? cryptoAmount, bool raw = false}) {
if (price == null || cryptoAmount == null) {
return '0.00';
}
cryptoAmount = cryptoAmount.replaceAll(',', '.');
cryptoAmount = cryptoAmount.sanitized();
final _amount = double.tryParse(cryptoAmount);
if (_amount == null || _amount.isNaN) return '0.00';
+15 -1
View File
@@ -1,18 +1,26 @@
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/enumerable_item.dart';
class FiatCurrency extends EnumerableItem<String> with Serializable<String> implements Currency {
const FiatCurrency({
required String symbol,
required this.symbol,
required this.countryCode,
required this.fullName,
this.decimals = 2,
}) : super(title: symbol, raw: symbol);
final String countryCode;
@override
final String fullName;
@override
final int decimals;
@override
final String symbol;
static List<FiatCurrency> get all => _all.values.toList();
static List<FiatCurrency> get currenciesAvailableToBuyWith => [
@@ -182,4 +190,10 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
@override
String get iconPath => "assets/images/flags/$countryCode.png";
@override
Money parseAmount(String value) => Money.parse(value, this);
@override
Money? tryParseAmount(String value) => Money.tryParse(value, this);
}
+61 -81
View File
@@ -105,26 +105,25 @@ class CWEVM extends EVM {
TransactionPriority? priority,
int? feeRate,
bool useBlinkProtection = true,
}) {
return EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
useBlinkProtection: useBlinkProtection,
);
}
}) =>
EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
memo: out.memo,
))
.toList(),
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
useBlinkProtection: useBlinkProtection,
);
@override
Object createEVMTransactionCredentialsRaw(
@@ -144,29 +143,28 @@ class CWEVM extends EVM {
}
@override
TransactionInfo getTransactionInfo(
{
required String id,
required int height,
required BigInt ethAmount,
required BigInt ethFee,
required String tokenSymbol,
int exponent = 18,
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations,
String? to,
String? from,
String? evmSignatureName,
String? contractAddress,
required int chainId,
}) =>
EVMChainTransactionInfo(id: id,
TransactionInfo getTransactionInfo({
required String id,
required int height,
required Money amount,
required Money fee,
required String tokenSymbol,
int exponent = 18,
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations,
String? to,
String? from,
String? evmSignatureName,
String? contractAddress,
required int chainId,
}) =>
EVMChainTransactionInfo(
id: id,
height: height,
ethAmount: ethAmount,
ethFee: ethFee,
amount: amount,
fee: fee,
tokenSymbol: tokenSymbol,
direction: direction,
isPending: isPending,
@@ -179,22 +177,6 @@ class CWEVM extends EVM {
@override
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterEVMAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20Currencies;
@@ -294,20 +276,16 @@ class CWEVM extends EVM {
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
Money amount,
String spender,
CryptoCurrency token,
TransactionPriority? priority, {
bool useBlinkProtection = true,
}) {
final evmWallet = wallet as EVMChainWallet;
final feeCurrency = EVMChainUtils.getFeeCurrency(evmWallet.selectedChainId);
return evmWallet.createApprovalTransaction(
amount,
spender,
token,
priority as EVMChainTransactionPriority?,
feeCurrency,
useBlinkProtection: useBlinkProtection,
);
}
@@ -317,7 +295,7 @@ class CWEVM extends EVM {
WalletBase wallet,
String to,
String dataHex,
BigInt valueWei,
Money valueWei,
TransactionPriority? priority, {
bool useBlinkProtection = true,
String? sourceTokenAddress,
@@ -399,7 +377,7 @@ class CWEVM extends EVM {
// Chain-specific integrations (only for Ethereum)
@override
Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) {
Future<Money>? getDEuroSavingsBalance(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).savingsBalance;
}
@@ -407,7 +385,15 @@ class CWEVM extends EVM {
}
@override
Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) {
Future<Money>? getDEuroSavingsV1Balance(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).savingsBalanceV1;
}
return null;
}
@override
Future<Money>? getDEuroAccruedInterest(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).accruedInterest;
}
@@ -448,6 +434,15 @@ class CWEVM extends EVM {
return null;
}
@override
Future<PendingTransaction>? withdrawDEuroSavingV1(
WalletBase wallet, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).withdrawSavingsV1(priority as EVMChainTransactionPriority);
}
return null;
}
@override
Future<PendingTransaction>? reinvestDEuroInterest(
WalletBase wallet, TransactionPriority priority) {
@@ -465,13 +460,6 @@ class CWEVM extends EVM {
return null;
}
@override
BigInt getPendingTransactionFee(PendingTransaction tx) => (tx as PendingEVMChainTransaction).fee;
@override
String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingEVMChainTransaction).amount;
// Registry helper methods
static final EvmChainRegistry _registry = EvmChainRegistry();
@@ -532,14 +520,6 @@ class CWEVM extends EVM {
return _registry.getWalletTypeByChainId(chainId);
}
@override
BigInt? getERC20AvailableBalance(Object balance) {
if (balance is EVMChainERC20Balance) {
return balance.balance;
}
return null;
}
@override
List<ChainInfo> getAllChains() {
final allChains = _registry.getAllChains();
@@ -18,9 +18,11 @@ class XOSwapExchangeProvider extends ExchangeProvider {
}
void _addAppVersionHeader() async {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
_headers['App-Version'] = currentVersion;
try {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
_headers['App-Version'] = currentVersion;
} catch (_) {}
}
static const _apiAuthority = 'exchange.exodus.io';
+9 -9
View File
@@ -301,14 +301,14 @@ class CWMonero extends Monero {
MoneroTransactionCreationCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
))
.toList(),
priority: priority as MoneroTransactionPriority);
@@ -411,7 +411,7 @@ class CWMonero extends Monero {
}
@override
bool needExportOutputs(Object wallet, int amount) {
bool needExportOutputs(Object wallet, Money amount) {
final moneroWallet = wallet as MoneroWallet;
return moneroWallet.needExportOutputs(amount);
}
+1 -7
View File
@@ -150,13 +150,12 @@ class CWNano extends Nano {
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
))
.toList(),
);
@@ -175,11 +174,6 @@ class CWNano extends Nano {
return (wallet as NanoWallet).updateTransactions();
}
@override
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as NanoTransactionInfo).amountRaw;
}
@override
String getRepresentative(Object wallet) {
return (wallet as NanoWallet).representative;
+52 -45
View File
@@ -26,6 +26,8 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/new_list_row/list_item_regular_row_widget.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/lnurl.dart';
import 'package:cw_core/wallet_info.dart';
import "package:cw_core/wallet_type.dart";
@@ -123,7 +125,7 @@ class SendPageModes {
helpContent: SendPageHelpContent(
title: S.current.about_litecoin_privacy,
imagePath: "assets/new-ui/mweb_help.svg",
description: S.current.mweb_help_desc_1 + "\n\n" + S.current.mweb_help_desc_2,
description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
disclaimer: S.current.mweb_help_disclaimer),
popOnConfirmation: false);
@@ -134,7 +136,7 @@ class SendPageModes {
helpContent: SendPageHelpContent(
title: S.current.about_litecoin_privacy,
imagePath: "assets/new-ui/mweb_help.svg",
description: S.current.mweb_help_desc_1 + "\n\n" + S.current.mweb_help_desc_2,
description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
disclaimer: S.current.mweb_help_disclaimer),
popOnConfirmation: false);
@@ -184,12 +186,12 @@ class NewSendPage extends StatefulWidget {
}
class _NewSendPageState extends State<NewSendPage> {
bool _fiatInputMode = false;
int _selectedOutput = 0;
List<TextEditingController> _amountControllers = [];
List<TextEditingController> _addressControllers = [];
List<TextEditingController> _memoControllers = [];
final _amountControllers = <TextEditingController>[];
final _addressControllers = <TextEditingController>[];
final _memoControllers = <TextEditingController>[];
final _formKey = GlobalKey<FormState>();
final _addressFocusNode = FocusNode();
BuildContext? loadingBottomSheetContext;
@@ -205,7 +207,7 @@ class _NewSendPageState extends State<NewSendPage> {
reaction((_) => widget.sendViewModel.outputs[_selectedOutput].sendAll, ((bool all) {
if (all) {
_fiatInputMode = false;
widget.sendViewModel.outputs[_selectedOutput].isFiatEntry = false;
_amountControllers[_selectedOutput].text = S.current.all;
}
}));
@@ -439,46 +441,46 @@ class _NewSendPageState extends State<NewSendPage> {
? widget.sendViewModel.allAmountValidator
: widget.sendViewModel.amountValidator(output),
amountController: _amountControllers[_selectedOutput],
currency: _fiatInputMode
currency: output.isFiatEntry
? widget.sendViewModel.fiatCurrency.title
: widget.sendViewModel.selectedCryptoCurrencySymbol,
currencyIconPath: _fiatInputMode
currencyIconPath: output.isFiatEntry
? ""
: widget.sendViewModel.selectedCryptoCurrency
.iconPath ??
"",
hasPicker: (_fiatInputMode ||
hasPicker: (output.isFiatEntry ||
widget.sendViewModel.hasMultipleTokens),
onPickerClicked: () {
_presentCurrencyPicker(context);
},
onPickerClicked: () => _presentCurrencyPicker(context),
maxDecimals: output.isFiatEntry
? widget.sendViewModel.fiatCurrency.decimals
: widget.sendViewModel.useBaseUnits
? 0
: widget
.sendViewModel.selectedCryptoCurrency.decimals,
),
FiatAmountBar(
fiatInputMode: _fiatInputMode,
fiatInputMode: output.isFiatEntry,
onSwitchButtonPressed: () {
setState(() {
if(!_fiatInputMode) {
widget.sendViewModel.outputs[_selectedOutput]
.cryptoAmount =
_amountControllers[_selectedOutput].text;
}
_fiatInputMode = !_fiatInputMode;
_amountControllers[_selectedOutput].text =
_fiatInputMode
? output.fiatAmount
: output.displayCryptoAmount;
});
widget.sendViewModel.outputs[_selectedOutput]
.isFiatEntry = !output.isFiatEntry;
_amountControllers[_selectedOutput].text =
output.isFiatEntry
? output.fiatAmount
: output.displayCryptoAmount;
},
fiatAmount: _wrapAmount(output.roundedFiatAmount(6), 20),
cryptoAmount:
_wrapAmount(output.roundedCryptoAmount(6), 20),
allAmount: widget.sendViewModel.balance,
cryptoCurrency:
cryptoCurrencySymbol:
widget.sendViewModel.selectedCryptoCurrencySymbol,
fiatCurrency: widget.sendViewModel.fiatCurrency.title,
fiatCurrencySymbol:
widget.sendViewModel.fiatCurrency.symbol,
onAllButtonPressed: () async {
output.setSendAll(
await widget.sendViewModel.sendingBalance);
await output.calculateEstimatedFee();
},
),
],
@@ -642,10 +644,10 @@ class _NewSendPageState extends State<NewSendPage> {
return;
}
final amount = _amountControllers[_selectedOutput].text.replaceAll(',', '.');
final amount = _amountControllers[_selectedOutput].text.sanitized();
final output = widget.sendViewModel.outputs[_selectedOutput];
if (_fiatInputMode) {
if (output.isFiatEntry) {
if (amount != output.fiatAmount) {
output.sendAll = false;
output.setFiatAmount(amount);
@@ -705,11 +707,11 @@ class _NewSendPageState extends State<NewSendPage> {
for (var i = 0; i < widget.sendViewModel.outputs.length; i++) {
if (i < _amountControllers.length && !widget.sendViewModel.outputs[i].sendAll) {
if (_fiatInputMode) {
if (widget.sendViewModel.outputs[i].isFiatEntry) {
widget.sendViewModel.outputs[i].setFiatAmount(_amountControllers[i].text);
} else {
final amount = widget.sendViewModel.amountParsingProxy.getCanonicalCryptoAmount(
_amountControllers[i].text.replaceAll(",", "."),
_amountControllers[i].text.sanitized(),
widget.sendViewModel.selectedCryptoCurrency);
widget.sendViewModel.outputs[i].setCryptoAmount(amount);
}
@@ -752,9 +754,9 @@ class _NewSendPageState extends State<NewSendPage> {
}
if (widget.sendViewModel.wallet.type == WalletType.monero) {
int amount = 0;
var amount = Money.zero(widget.sendViewModel.wallet.currency);
for (var item in widget.sendViewModel.outputs) {
amount += item.formattedCryptoAmount;
amount += item.cryptoAmountMoney;
}
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
@@ -811,13 +813,11 @@ class _NewSendPageState extends State<NewSendPage> {
}
void _presentCurrencyPicker(BuildContext context) {
if (!_fiatInputMode && !widget.sendViewModel.hasMultipleTokens) {
return;
}
final output = widget.sendViewModel.outputs[_selectedOutput];
if (_fiatInputMode) {
if (!output.isFiatEntry && !widget.sendViewModel.hasMultipleTokens) return;
if (output.isFiatEntry) {
FiatCurrencyPickerSheet.show(
context: context,
selected: widget.sendViewModel.fiatCurrency,
@@ -836,7 +836,7 @@ class _NewSendPageState extends State<NewSendPage> {
amount: '${r.availableBalance} ${r.asset.title}',
fiat: isFiatDisabled
? null
: '${r.fiatAvailableBalanceRaw} ${r.fiatCurrencyTicker}',
: '${r.fiatAvailableBalanceRaw} ${r.fiatCurrency?.symbol}',
fiatValue: isFiatDisabled ? null : double.tryParse(r.fiatAvailableBalanceRaw),
),
};
@@ -859,10 +859,11 @@ class _NewSendPageState extends State<NewSendPage> {
void _handleLightningInvoicePaste() {
try {
final lnAmount = CryptoCurrency.btcln.formatAmount(
BigInt.from(getBolt11Amount(_addressControllers[_selectedOutput].text) ?? 0));
if (lnAmount != 0) {
_amountControllers[_selectedOutput].text = lnAmount;
final lnAmount = getBolt11Amount(_addressControllers[_selectedOutput].text) ??
Money.zero(CryptoCurrency.btcln);
if (!lnAmount.isZero) {
_amountControllers[_selectedOutput].text =
widget.sendViewModel.amountParsingProxy.asDisplayString(lnAmount);
}
} catch (_) {}
}
@@ -885,6 +886,12 @@ class _NewSendPageState extends State<NewSendPage> {
await widget.sendViewModel.fetchTokenForContractAddress(paymentRequest.contractAddress!);
}
// This automatically switches to lightning mode if you are in a bitcoin wallet
if (result.addressDetectionResult?.detectedCurrency == CryptoCurrency.btcln) {
widget.sendViewModel.selectedCryptoCurrency = CryptoCurrency.btcln;
widget.sendViewModel.coinTypeToSpendFrom = UnspentCoinType.lightning;
}
switch (result.type) {
case PaymentFlowType.singleWallet:
case PaymentFlowType.multipleWallets:
@@ -1216,7 +1223,7 @@ class _NewSendPageState extends State<NewSendPage> {
try{
_amountControllers[_selectedOutput].text = widget.sendViewModel.amountParsingProxy
.getDisplayCryptoAmount(
paymentRequest.amount, widget.sendViewModel.selectedCryptoCurrency);
paymentRequest.amount, widget.sendViewModel.selectedCryptoCurrency);
} catch(e) {
}
+163 -158
View File
@@ -8,7 +8,6 @@ import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
@@ -30,6 +29,7 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cake_wallet/utils/decimal_input_formatter.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:cw_core/wallet_info.dart';
@@ -137,9 +137,8 @@ class _NewSwapPageState extends State<NewSwapPage> {
});
});
receiveFiatAmountController.addListener(() {
if (!receiveKey.currentState!.amountFocusNode.hasFocus) {
return;
}
if (!receiveKey.currentState!.amountFocusNode.hasFocus) return;
widget.exchangeViewModel.enableFixedRateMode();
widget.exchangeViewModel.isSendAllEnabled = false;
Future.delayed(Duration(milliseconds: 200)).then((_) {
@@ -168,6 +167,8 @@ class _NewSwapPageState extends State<NewSwapPage> {
});
reaction((_) => widget.exchangeViewModel.depositAmount, (String amount) {
if (depositKey.currentState!.amountFocusNode.hasFocus) return;
if (widget.exchangeViewModel.isSendAllEnabled) {
depositAmountController.text = S.of(context).all;
} else if (depositAmountController.text != amount && amount != S.of(context).all) {
@@ -191,14 +192,14 @@ class _NewSwapPageState extends State<NewSwapPage> {
widget.exchangeViewModel, widget.exchangeViewModel.depositCurrency, depositKey));
reaction(
(_) => widget.exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, widget.exchangeViewModel, receiveKey));
(_) => widget.exchangeViewModel.receiveCurrency,
(currency) => _onCurrencyChange(currency, widget.exchangeViewModel, receiveKey),
);
reaction(
(_) => widget.exchangeViewModel.depositCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, widget.exchangeViewModel, depositKey));
(_) => widget.exchangeViewModel.depositCurrency,
(currency) => _onCurrencyChange(currency, widget.exchangeViewModel, depositKey),
);
reaction((_) => widget.exchangeViewModel.depositAddress, (String address) {
if (depositKey.currentState!.addressController.text != address) {
@@ -206,7 +207,7 @@ class _NewSwapPageState extends State<NewSwapPage> {
}
});
reaction((_) => widget.exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {});
reaction((_) => widget.exchangeViewModel.isDepositAddressEnabled, (isEnabled) {});
reaction((_) => widget.exchangeViewModel.receiveAmount, (String amount) {
if (receiveKey.currentState!.amountController.text != amount) {
@@ -398,14 +399,12 @@ class _NewSwapPageState extends State<NewSwapPage> {
if (key == depositKey && !isCurrentTypeWallet) exchangeViewModel.isSendFromExternal = true;
if (key == depositKey && isCurrentTypeWallet) exchangeViewModel.isSendFromExternal = false;
key.currentState!.changeSelectedCurrency(currency);
if (key == depositKey)
if (key == depositKey) {
key.currentState!.changeAddress(
address: isCurrentTypeWallet
? exchangeViewModel.wallet.walletAddresses.addressForExchange
: '');
address:
isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.addressForExchange : '',
);
}
}
void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
@@ -492,7 +491,7 @@ class _NewSwapPageState extends State<NewSwapPage> {
child: Form(
key: formKey,
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
physics: const ClampingScrollPhysics(),
controller: ModalScrollController.of(context),
child: Column(
children: [
@@ -508,7 +507,8 @@ class _NewSwapPageState extends State<NewSwapPage> {
: null,
key: depositKey,
title: S.of(context).send,
initialCurrency: widget.exchangeViewModel.depositCurrency,
currency: widget.exchangeViewModel.depositCurrency,
useBaseUnit: widget.exchangeViewModel.useDepositBaseUnit,
hasRefundAddress: true,
currencies: widget.exchangeViewModel.depositCurrencies,
onCurrencySelected: (currency) {
@@ -522,11 +522,13 @@ class _NewSwapPageState extends State<NewSwapPage> {
value != S.of(context).all
? AmountValidator(
isAutovalidate: true,
currency: widget.exchangeViewModel.isFixedRateMode
currency: widget.exchangeViewModel.isFixedRateMode
? widget.exchangeViewModel.receiveCurrency
: widget.exchangeViewModel.depositCurrency,
minValue: widget.exchangeViewModel.limits.min.toString(),
maxValue: widget.exchangeViewModel.limits.max.toString(),
minValue:
widget.exchangeViewModel.limits.min.toString(),
maxValue:
widget.exchangeViewModel.limits.max.toString(),
amountParsingProxy:
widget.exchangeViewModel.amountParsingProxy,
).call(value)
@@ -579,8 +581,9 @@ class _NewSwapPageState extends State<NewSwapPage> {
onDispose: disposeBestRateSync,
key: receiveKey,
title: S.of(context).receive,
initialCurrency: widget.exchangeViewModel.receiveCurrency,
currencies: widget.exchangeViewModel.receiveCurrencies,
currency: widget.exchangeViewModel.receiveCurrency,
useBaseUnit: widget.exchangeViewModel.useReceiveBaseUnit,
onCurrencySelected: (currency) {
if (currency is CryptoCurrency) {
widget.exchangeViewModel
@@ -685,7 +688,6 @@ class _NewSwapPageState extends State<NewSwapPage> {
isLoading: widget.exchangeViewModel.tradeState is TradeIsCreating,
),
),
SizedBox()
],
),
)
@@ -701,18 +703,14 @@ class _NewSwapPageState extends State<NewSwapPage> {
}
bool _swapButtonDisabled() {
if (widget.exchangeViewModel.selectedProviders.isEmpty) {
return true;
}
if (widget.exchangeViewModel.receiveAddress.isEmpty) {
return true;
}
if (widget.exchangeViewModel.status is! SyncedSyncStatus) {
return true;
}
if (widget.exchangeViewModel.depositAmount.isEmpty) {
return true;
}
if (widget.exchangeViewModel.selectedProviders.isEmpty) return true;
if (widget.exchangeViewModel.receiveAddress.isEmpty) return true;
if (widget.exchangeViewModel.status is! SyncedSyncStatus) return true;
if (widget.exchangeViewModel.depositAmount.isEmpty) return true;
return false;
}
}
@@ -725,9 +723,7 @@ class SwapProviderPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
if (exchangeViewModel.depositAmount.isEmpty) {
return SizedBox.shrink();
}
if (exchangeViewModel.depositAmount.isEmpty) return SizedBox.shrink();
final provider = exchangeViewModel.forcedProvider ?? exchangeViewModel.providerDisplay;
final rate = exchangeViewModel.forcedProvider == null
@@ -738,8 +734,10 @@ class SwapProviderPreview extends StatelessWidget {
onTap: () {
if (provider != null) {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) =>
Material(child: ProviderSelectorPage(exchangeViewModel: exchangeViewModel))));
builder: (context) => Material(
child: ProviderSelectorPage(exchangeViewModel: exchangeViewModel),
),
));
}
},
child: Container(
@@ -757,7 +755,7 @@ class SwapProviderPreview extends StatelessWidget {
spacing: 12,
children: [
if (provider != null)
CakeImageWidget(imageUrl:provider.description.image,width:28,height:28),
CakeImageWidget(imageUrl: provider.description.image, width: 28, height: 28),
if (provider == null) CupertinoActivityIndicator(),
Text(
provider?.title ?? "${S.of(context).finding_provider}...",
@@ -799,7 +797,7 @@ class SwapProviderPreview extends StatelessWidget {
class SwapAmountBox extends StatefulWidget {
SwapAmountBox({
Key? key,
required this.initialCurrency,
required this.currency,
required this.currencies,
required this.onCurrencySelected,
this.currencyValueValidator,
@@ -807,6 +805,7 @@ class SwapAmountBox extends StatefulWidget {
this.title = '',
this.hasRefundAddress = false,
this.hasAllAmount = false,
this.useBaseUnit = false,
this.allAmount,
this.onPushPasteButton,
this.onPushAddressBookButton,
@@ -820,7 +819,8 @@ class SwapAmountBox extends StatefulWidget {
final Function(Currency) onCurrencySelected;
final String title;
final bool isReceiverCard;
final Currency initialCurrency;
final Currency currency;
final bool useBaseUnit;
final bool hasRefundAddress;
final FormFieldValidator<String>? currencyValueValidator;
final FormFieldValidator<String>? addressTextFieldValidator;
@@ -848,10 +848,6 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
@override
void initState() {
_selectedCurrency = widget.initialCurrency;
super.initState();
if (widget.isReceiverCard) {
memoController.text = widget.exchangeViewModel.receiveAddressExtraId;
@@ -869,6 +865,8 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
}
});
}
super.initState();
}
@override
@@ -881,18 +879,18 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
super.dispose();
}
late Currency _selectedCurrency;
bool _fiatInputMode = false;
@override
Widget build(BuildContext context) {
final currencyToShow = (_selectedCurrency is CryptoCurrency)
final currencyToShow = (widget.currency is CryptoCurrency)
? widget.exchangeViewModel.amountParsingProxy
.getCryptoSymbol(_selectedCurrency as CryptoCurrency)
: _selectedCurrency.name.toUpperCase();
.getCryptoSymbol(widget.currency as CryptoCurrency)
: widget.currency.name.toUpperCase();
final chainIconPath = (_selectedCurrency is CryptoCurrency) ? _getCurrencyChainIconPath(_selectedCurrency as CryptoCurrency) : null;
final chainIconPath = (widget.currency is CryptoCurrency)
? _getCurrencyChainIconPath(widget.currency as CryptoCurrency)
: null;
return Column(
spacing: 12,
@@ -928,36 +926,48 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
children: [
Flexible(
child: IntrinsicWidth(
child: TextFormField(
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: true),
validator: _fiatInputMode ? null : widget.currencyValueValidator,
controller:
_fiatInputMode ? fiatAmountController : amountController,
focusNode: amountFocusNode,
style: TextStyle(
child: TextFormField(
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: !widget.useBaseUnit,
),
validator: _fiatInputMode ? null : widget.currencyValueValidator,
controller:
_fiatInputMode ? fiatAmountController : amountController,
focusNode: amountFocusNode,
style: TextStyle(
fontSize: 28,
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w400),
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
isDense: true,
hintText: "0",
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
isDense: true,
hintText: "0",
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
),
inputFormatters: <TextInputFormatter>[
DecimalInputFormatter(
maxDecimals:
widget.useBaseUnit ? 0 : widget.currency.decimals,
),
],
),
)),
),
),
if (_fiatInputMode)
Center(
child: Text(
widget.exchangeViewModel.fiat.title,
style: TextStyle(
child: Text(
widget.exchangeViewModel.fiat.title,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant),
)),
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
),
@@ -966,17 +976,19 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(999999)),
borderRadius: BorderRadius.circular(999999),
),
child: Padding(
padding: const EdgeInsets.only(top: 4.0, bottom: 4, left: 4, right: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TokenImageWidget(
imageUrl: _selectedCurrency.iconPath ?? "",
size: 28,
CakeImageWidget(
imageUrl: widget.currency.iconPath ?? "",
width: 28,
height: 28,
),
SizedBox(width: 10),
const SizedBox(width: 10),
Text(
currencyToShow,
textAlign: TextAlign.center,
@@ -988,12 +1000,13 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
width: 12,
height: 12,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onSurfaceVariant,
BlendMode.srcIn),
Theme.of(context).colorScheme.onSurfaceVariant,
BlendMode.srcIn,
),
),
SizedBox(width: 6)
const SizedBox(width: 6)
] else
SizedBox(width: 10),
const SizedBox(width: 10),
Container(
width:16,height:16,
decoration: BoxDecoration(
@@ -1003,17 +1016,20 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
child: Padding(
padding: const EdgeInsets.all(4.0),
child: RotatedBox(
quarterTurns: 2,
child: CakeImageWidget(imageUrl:
"assets/new-ui/dropdown_arrow.svg",
width: 4,
height: 4,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
)),
quarterTurns: 2,
child: CakeImageWidget(
imageUrl: "assets/new-ui/dropdown_arrow.svg",
width: 4,
height: 4,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary,
BlendMode.srcIn,
),
),
),
),
),
SizedBox(width:4),
const SizedBox(width: 4),
],
),
),
@@ -1029,36 +1045,33 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
!widget.exchangeViewModel.hasAllAmount);
return FiatAmountBar(
foregroundElementColor: Theme.of(context).colorScheme.surfaceContainerHigh,
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
allAmountColor: hasAllAmount ? null : Colors.transparent,
allAmountTextColor:
hasAllAmount ? null : Theme.of(context).colorScheme.onSurfaceVariant,
fiatInputMode: _fiatInputMode,
allAmount: !hasAllAmount
? widget.isReceiverCard
? null
: widget.exchangeViewModel.balanceDisplay
: widget.exchangeViewModel.depositAvailableAmount,
onSwitchButtonPressed: () {
setState(() {
_fiatInputMode = !_fiatInputMode;
});
if (_fiatInputMode) {
updateFiatAmount();
}
},
onAllButtonPressed: widget.allAmount,
cryptoAmount: widget.isReceiverCard
? widget.exchangeViewModel.roundedReceiveAmount(6)
: widget.exchangeViewModel.roundedDepositAmount(6),
fiatAmount: widget.isReceiverCard
? widget.exchangeViewModel.roundedReceiveAmountFiat(6)
: widget.exchangeViewModel.roundedDepositAmountFiat(6),
cryptoCurrency: widget.isReceiverCard
? widget.exchangeViewModel.receiveCurrency.title
: widget.exchangeViewModel.depositCurrency.title,
fiatCurrency: widget.exchangeViewModel.fiat.name);
foregroundElementColor: Theme.of(context).colorScheme.surfaceContainerHigh,
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
allAmountColor: hasAllAmount ? null : Colors.transparent,
allAmountTextColor:
hasAllAmount ? null : Theme.of(context).colorScheme.onSurfaceVariant,
fiatInputMode: _fiatInputMode,
allAmount: !hasAllAmount
? widget.isReceiverCard
? null
: widget.exchangeViewModel.balanceDisplay
: widget.exchangeViewModel.depositAvailableAmount,
onSwitchButtonPressed: () {
setState(() => _fiatInputMode = !_fiatInputMode);
if (_fiatInputMode) {
updateFiatAmount();
}
},
onAllButtonPressed: widget.allAmount,
cryptoAmount: widget.isReceiverCard
? widget.exchangeViewModel.roundedReceiveAmount(6)
: widget.exchangeViewModel.roundedDepositAmount(6),
fiatAmount: widget.isReceiverCard
? widget.exchangeViewModel.roundedReceiveAmountFiat(6)
: widget.exchangeViewModel.roundedDepositAmountFiat(6),
cryptoCurrencySymbol: currencyToShow,
fiatCurrencySymbol: widget.exchangeViewModel.fiat.symbol,
);
},
),
Observer(
@@ -1107,22 +1120,25 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface),
),
Text(addressDescription,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary))
Text(
addressDescription,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary),
)
],
),
RotatedBox(
quarterTurns: 2,
child: CakeImageWidget(imageUrl:
"assets/new-ui/dropdown_arrow.svg",
child: CakeImageWidget(
imageUrl: "assets/new-ui/dropdown_arrow.svg",
colorFilter: ColorFilter.mode(
(addressEmpty && widget.isReceiverCard)
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface,
BlendMode.srcIn),
(addressEmpty && widget.isReceiverCard)
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface,
BlendMode.srcIn,
),
),
)
],
@@ -1146,18 +1162,14 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
widget.exchangeViewModel.receiveAddress.isEmpty) ...[
ModernButton.svg(
svgPath: "assets/new-ui/paste.svg",
onPressed: () {
widget.onPushPasteButton?.call(context);
},
onPressed: () => widget.onPushPasteButton?.call(context),
size: 36,
iconSize: 20,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
),
ModernButton.svg(
svgPath: "assets/new-ui/scan.svg",
onPressed: () {
_presentQRScanner(context);
},
onPressed: () => _presentQRScanner(context),
size: 36,
iconSize: 20,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
@@ -1172,7 +1184,7 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
final selected = widget.exchangeViewModel.receiveCurrency;
final labelType = memoLabelTypeFor(selected);
if (labelType == null) return const SizedBox.shrink();
final isDestinationTag = labelType == MemoLabelType.destinationTag;
final hint = isDestinationTag
? S.of(context).destination_tag_optional
@@ -1180,7 +1192,7 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
final disclaimer = isDestinationTag
? S.of(context).destination_tag_swap_disclaimer
: S.of(context).memo_swap_disclaimer;
return NewSendMemoInput(
memoController: memoController,
maxMemoLength: isDestinationTag ? 20 : 256,
@@ -1197,10 +1209,6 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
);
}
void changeSelectedCurrency(Currency currency) {
setState(() => _selectedCurrency = currency);
}
void changeAddress({required String address}) {
setState(() => addressController.text = _normalizeAddressFormat(address));
}
@@ -1343,18 +1351,15 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
}
String? _getCurrencyChainIconPath(CryptoCurrency curr) {
try{
try {
if (curr.chainIconPath != null) return curr.chainIconPath!;
if(curr.chainIconPath != null) return curr.chainIconPath!;
if(curr.tag != null) {
if (curr.tag != null) {
final currencyFromTag = CryptoCurrency.fromString(curr.tag!);
if(currencyFromTag.chainIconPath != null) {
return currencyFromTag.chainIconPath!;
}
if (currencyFromTag.chainIconPath != null) return currencyFromTag.chainIconPath!;
}
}catch(_){}
} catch (_) {}
return null;
}
}
@@ -24,7 +24,6 @@ import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import '../../../pages/receive_page.dart';
import '../../../pages/scan_page.dart';
import 'coin_action_button.dart';
class CoinActionRow extends StatelessWidget {
@@ -165,11 +164,11 @@ class CoinActionRow extends StatelessWidget {
var unspentCoinType = UnspentCoinType.any;
if (SendViewModelBase.isNonZeroAmountLightningInvoice(code)) {
unspentCoinType = UnspentCoinType.lightning;
final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(getBolt11Amount(code) ?? 0));
final amount = getBolt11Amount(code)?.toString() ?? "0";
req = PaymentRequest(code, amount, "", "", "");
} else if (SendViewModelBase.isLnurlInvoice(code)) {
unspentCoinType = UnspentCoinType.lightning;
final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(await LNURL.getPayRequestAmount(code) ?? 0));
final amount = (await LNURL.getPayRequestAmount(code))?.toString() ?? "0";
req = PaymentRequest(code, amount, "", "", "");
} else if (OpenCryptoPayService.isOpenCryptoPayQR(code)) {
req = PaymentRequest(code, "", "", "", "");
@@ -1,4 +1,5 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/anonpay_history_tile.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_order_tile.dart';
@@ -15,6 +16,7 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:flutter/material.dart';
@@ -111,8 +113,8 @@ class HistorySection extends StatelessWidget {
provider: trade.provider,
date:
DateFormat('HH:mm').format(item.trade.createdAt ?? DateTime.now()),
amount: trade.amountFormatted(),
receiveAmount: trade.receiveAmountFormatted(),
amount: dashboardViewModel.balanceDisplayMode == BalanceDisplayMode.hiddenBalance ? "---" : trade.amountFormatted(),
receiveAmount: dashboardViewModel.balanceDisplayMode == BalanceDisplayMode.hiddenBalance ? "---" : trade.receiveAmountFormatted(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
@@ -132,7 +134,7 @@ class HistorySection extends StatelessWidget {
child: HistoryOrderTile(
date: DateFormat('HH:mm').format(item.order.createdAt),
amount: item.orderFormattedAmount,
amountFiat: "USD 0.00",
amountFiat: "",
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
@@ -149,8 +151,8 @@ class HistorySection extends StatelessWidget {
child: PayjoinHistoryTile(
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
amount: dashboardViewModel.appStore.amountParsingProxy
.getDisplayCryptoString(
session.amount.toInt(), CryptoCurrency.btc),
.asDisplayString(Money(
session.amount, CryptoCurrency.btc)),
currency: item.transaction?.from ?? "BTC",
state: item.status,
isSending: session.isSenderSession,
@@ -47,6 +47,8 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
@override
Widget build(BuildContext context) {
final transactionInfoAmount = widget.transactionDetailsViewModel.transactionInfo.amount;
return DraggableScrollableSheet(
expand: false,
initialChildSize: 0.9,
@@ -90,12 +92,12 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
),
CopyWrapper(
requireLongPress: true,
data: ClipboardData(text: widget.transactionDetailsViewModel.formattedCryptoAmount),
data: ClipboardData(text: transactionInfoAmount.toString()),
builder: (context, copied)=> AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text(
key: ValueKey(copied),
copied ? S.of(context).copied : widget.transactionDetailsViewModel.formattedCryptoAmount,
copied ? S.of(context).copied : transactionInfoAmount.toStringWithSymbol(),
style: TextStyle(fontSize: 28, color: copied ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface),
),
),
@@ -122,7 +124,7 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
label: item.title,
trailingWidget: shouldBuildBottomWidget
? null
: _buildTrailingWIdget(item),
: _buildTrailingWidget(item),
bottomWidget: shouldBuildBottomWidget
? _buildBottomWidget(item)
: null);
@@ -203,7 +205,7 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
));
}
Widget _buildTrailingWIdget(TransactionDetailsListItem item) {
Widget _buildTrailingWidget(TransactionDetailsListItem item) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: switch (item.runtimeType) {
@@ -70,8 +70,6 @@ class BalanceCard extends StatelessWidget {
final height = width * 0.62;
return AnimatedContainer(
duration: designSwitchDuration,
width: width,
@@ -208,7 +206,7 @@ class BalanceCard extends StatelessWidget {
},
child: Text(
key: ValueKey(fiatFirst ? balance : fiatBalance),
fiatFirst ? "$assetName $balance" : "$fiatCurrencyTitle $fiatBalance",
fiatFirst ? "$assetName $balance" : fiatBalance,
),
),
),
@@ -119,23 +119,22 @@ class _CardsViewState extends State<CardsView> {
late final String walletBalance;
late final String walletFiatBalance;
if (widget.dashboardViewModel.mwebEnabled && widget.dashboardViewModel.hasMweb) {
if(widget.dashboardViewModel.balanceViewModel.displayMode == BalanceDisplayMode.hiddenBalance) {
if (widget.dashboardViewModel.balanceViewModel.displayMode ==
BalanceDisplayMode.hiddenBalance) {
walletBalance = '●●●●●●';
walletFiatBalance = '●●●●●●';
} else {
walletBalance = walletBalanceRecord?.combinedAvailableBalance ?? "0";
walletFiatBalance = walletBalanceRecord?.combinedFiatAvailableBalance ?? "0.00";
}
} else if(widget.dashboardViewModel.balanceViewModel.showCombinedBalance){
} else if (widget.dashboardViewModel.balanceViewModel.showCombinedBalance) {
walletBalance = "";
walletFiatBalance = widget.dashboardViewModel.balanceViewModel.combinedFiatBalance;
}else {
} else {
walletBalance = walletBalanceRecord?.availableBalance ?? "0";
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalanceRaw ?? "0.00";
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalance ?? "0.00";
}
// the card designs is empty if widget gets built before it loads.
// should get populated before user sees anything
final CardDesign cardDesign;
@@ -191,7 +190,8 @@ class _CardsViewState extends State<CardsView> {
assetName: assetName,
capitalizeAssetName: _shouldCapitalizeAssetName(),
balance: walletBalance,
fiatCurrencyTitle: walletBalanceRecord?.fiatCurrencyTicker ?? widget.dashboardViewModel.settingsStore.fiatCurrency.title,
fiatCurrencyTitle: walletBalanceRecord?.fiatCurrency?.title ??
widget.dashboardViewModel.settingsStore.fiatCurrency.title,
fiatFirst: widget.dashboardViewModel.balanceViewModel.showCombinedBalance,
fiatBalance: walletFiatBalance,
selected: _selectedIndex == visualIndex,
@@ -48,12 +48,10 @@ class UnconfirmedBalanceWidget extends StatelessWidget {
showMaterialModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) {
return UnconfirmedBalanceModal(
balance: "${balance} ${currency.title}",
builder: (context) => UnconfirmedBalanceModal(
balance: balance.toStringWithSymbol(),
currencyIconPath: currency.iconPath ?? "",
);
});
));
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
@@ -101,9 +99,8 @@ class UnconfirmedBalanceWidget extends StatelessWidget {
),
],
)
: const SizedBox(
width: double.infinity,
));
: const SizedBox(width: double.infinity),
);
});
}
}
@@ -7,6 +7,7 @@ import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/decimal_input_formatter.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
@@ -134,8 +135,16 @@ class _ReceiveAmountModalState extends State<ReceiveAmountModal> {
textAlign: TextAlign.left,
textAlignVertical: TextAlignVertical.center,
controller: _amountController,
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: !widget.walletAddressListViewModel.useSatoshi),
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: widget.walletAddressListViewModel.selectedCurrencyDecimals > 0,
),
inputFormatters: [
DecimalInputFormatter(
maxDecimals:
widget.walletAddressListViewModel.selectedCurrencyDecimals,
),
],
decoration: InputDecoration(
hint: Text(
widget.walletAddressListViewModel.useSatoshi ? "0" : "0.00000000",
@@ -150,6 +159,7 @@ class _ReceiveAmountModalState extends State<ReceiveAmountModal> {
border: InputBorder.none,
filled: true,
fillColor: Colors.transparent,
),
style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
),
@@ -2,7 +2,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class FiatAmountBar extends StatelessWidget {
const FiatAmountBar(
@@ -12,8 +11,8 @@ class FiatAmountBar extends StatelessWidget {
this.onAllButtonPressed,
required this.cryptoAmount,
required this.fiatAmount,
required this.cryptoCurrency,
required this.fiatCurrency,
required this.cryptoCurrencySymbol,
required this.fiatCurrencySymbol,
this.allAmount,
this.foregroundElementColor,
this.textColor, this.allAmountColor, this.allAmountTextColor});
@@ -24,8 +23,8 @@ class FiatAmountBar extends StatelessWidget {
final String cryptoAmount;
final String fiatAmount;
final String cryptoCurrency;
final String fiatCurrency;
final String cryptoCurrencySymbol;
final String fiatCurrencySymbol;
final String? allAmount;
final Color? foregroundElementColor;
final Color? textColor;
@@ -47,16 +46,15 @@ class FiatAmountBar extends StatelessWidget {
iconSize: 18,
onPressed: onSwitchButtonPressed,
),
Observer(
builder: (_) => GestureDetector(
onTap: onSwitchButtonPressed,
child: Text(
fiatInputMode
? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} ${cryptoCurrency}"
: "${fiatAmount.isEmpty ? "0" : fiatAmount} ${fiatCurrency}",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
),
)),
GestureDetector(
onTap: onSwitchButtonPressed,
child: Text(
fiatInputMode
? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} $cryptoCurrencySymbol"
: "${fiatAmount.isEmpty ? "0" : fiatAmount} $fiatCurrencySymbol",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
),
),
],
),
if (allAmount != null && allAmount!.isNotEmpty)
@@ -18,7 +18,6 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
enum l2actions { deposit, withdraw }
@@ -1,6 +1,7 @@
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/decimal_input_formatter.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -9,15 +10,18 @@ class NewSendAmountInput extends StatefulWidget {
const NewSendAmountInput(
{super.key,
required this.currency,
required this.maxDecimals,
required this.hasPicker,
required this.onPickerClicked,
required this.currencyIconPath,
required this.amountController,
this.validator});
this.validator,
});
final String currency;
final String currencyIconPath;
final bool hasPicker;
final int maxDecimals;
final VoidCallback onPickerClicked;
final TextEditingController amountController;
final FormFieldValidator<String>? validator;
@@ -63,14 +67,19 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
children: [
Expanded(
child: TextField(
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: true),
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: widget.maxDecimals > 0,
),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'^\d*[.,]?\d*$'))
DecimalInputFormatter(maxDecimals: widget.maxDecimals),
],
onChanged: state.didChange,
controller: widget.amountController,
decoration: InputDecoration(hintText: "0", errorMaxLines: 3),
decoration: InputDecoration(
hintText: widget.maxDecimals > 0 ? "0" : "0.00",
errorMaxLines: 3,
),
onChanged: state.didChange,
),
),
FloatingIconButton(
@@ -81,7 +90,6 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
widget.amountController.text = data.text!;
}
}),
SizedBox.shrink()
],
),
),
@@ -17,13 +17,13 @@ import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mobx/mobx.dart';
class SendConfirmSheet extends StatefulWidget {
@@ -173,8 +173,8 @@ class SendTransactionDetails extends StatelessWidget {
double sumBy<T>(List<T> list, double Function(T) picker) =>
list.map(picker).fold(0.0, (a, b) => a + b);
BigInt sumByBigInt<T>(List<T> list, BigInt Function(T) picker) =>
list.map(picker).fold(BigInt.zero, (a, b) => a + b);
Money sumByMoney<T>(List<T> list, Money Function(T) picker, CryptoCurrency currency) =>
list.map(picker).fold(Money.zero(currency), (a, b) => a + b);
String sumStr<T>(List<T> list, double Function(T) picker) =>
sumBy(list, picker).toString();
@@ -184,220 +184,188 @@ class SendTransactionDetails extends StatelessWidget {
return "${decimals == null ? str : str.withDecimals(decimals)} $unit";
}
Widget _buildMainContent(BuildContext context) {
final transaction = sendViewModel.pendingTransaction;
return Observer(builder: (context) {
final transaction = sendViewModel.pendingTransaction;
final currencySymbol = sendViewModel.amountParsingProxy.getCryptoSymbol(sendViewModel.selectedCryptoCurrency);
final currencySymbol =
sendViewModel.amountParsingProxy.getCryptoSymbol(sendViewModel.selectedCryptoCurrency);
final amount = (transaction == null)
? sendViewModel.amountParsingProxy.getDisplayCryptoStringFromBigInt(
sumByBigInt(sendViewModel.outputs, (o) {
final amount = (transaction == null)
? sendViewModel.amountParsingProxy.asDisplayString(sumByMoney(sendViewModel.outputs, (o) {
final zero = Money.zero(sendViewModel.selectedCryptoCurrency);
if (o.sendAll)
return sendViewModel.amountParsingProxy.tryParseCryptoString(
sendViewModel.balance, sendViewModel.selectedCryptoCurrency) ??
BigInt.zero;
zero;
return sendViewModel.selectedCryptoCurrency.tryParseAmount(o.cryptoAmount) ??
BigInt.zero;
}),
sendViewModel.selectedCryptoCurrency)
: sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
formatAmount(transaction.amountFormatted), sendViewModel.selectedCryptoCurrency).withMaxDecimals(8);
return sendViewModel.selectedCryptoCurrency.tryParseAmount(o.cryptoAmount) ?? zero;
}, sendViewModel.selectedCryptoCurrency))
: sendViewModel.amountParsingProxy.asDisplayString(transaction.amount);
final fee =
"${(transaction == null) ? sendViewModel.amountParsingProxy.getDisplayCryptoStringFromBigInt(sumByBigInt(
sendViewModel.outputs.where((e) => !e.sendAll).toList(),
(o) {
if (sendViewModel.selectedCryptoCurrency == CryptoCurrency.btcln) {
return sendViewModel.amountParsingProxy.tryParseCryptoString(
o.estimatedFee.replaceAll(",", ""),
sendViewModel.selectedCryptoCurrency) ??
BigInt.zero;
}
return sendViewModel.currency.tryParseAmount(o.estimatedFee.replaceAll(",", "")) ??
BigInt.zero;
},
), sendViewModel.currency) : sendViewModel.amountParsingProxy.getDisplayCryptoAmount(transaction.feeFormattedValue, sendViewModel.currency)} ${sendViewModel.currencySymbol}";
final fee = (transaction == null)
? sendViewModel.amountParsingProxy.asDisplayString(sumByMoney(
sendViewModel.outputs,
(o) => o.estimatedFee,
sendViewModel.currency,
))
: sendViewModel.amountParsingProxy.asDisplayString(transaction.fee);
final fiatAmount = (transaction == null)
? sumWithUnit(
sendViewModel.outputs,
(o) => double.tryParse(o.fiatAmount.replaceAll(",", "")) ?? 0,
sendViewModel.fiatCurrency.title,
decimals:2
)
: sendViewModel.pendingTransactionFiatAmountFormatted;
final fiatAmount = (transaction == null)
? sumWithUnit(
sendViewModel.outputs,
(o) => double.tryParse(o.fiatAmount.replaceAll(",", "")) ?? 0,
sendViewModel.fiatCurrency.title,
decimals: 2)
: sendViewModel.pendingTransactionFiatAmountFormatted;
final fiatFee = (transaction == null)
? sumWithUnit(
sendViewModel.outputs,
(o) => double.tryParse(o.estimatedFeeFiatAmount.replaceAll(",", "")) ?? 0,
sendViewModel.fiatCurrency.title,
decimals:2
)
: sendViewModel.pendingTransactionFeeFiatAmountFormatted;
final fiatFee = (transaction == null)
? sumWithUnit(
sendViewModel.outputs,
(o) => double.tryParse(o.estimatedFeeFiatAmount.replaceAll(",", "")) ?? 0,
sendViewModel.fiatCurrency.title,
decimals: 2)
: sendViewModel.pendingTransactionFeeFiatAmountFormatted;
final showAddress = !sendViewModel.outputs.any((e) =>
RegExp(AddressValidator.bolt11InvoiceMatcher).hasMatch(e.address.toLowerCase()) ||
RegExp(AddressValidator.lnurlMatcher).hasMatch(e.address.toLowerCase()) ||
(e.isParsedAddress &&
e.parsedAddress.addresses.isNotEmpty &&
RegExp(AddressValidator.lnurlMatcher)
.hasMatch(e.parsedAddress.addresses.first.toLowerCase())));
final showAddress = !sendViewModel.outputs.any((e) =>
RegExp(AddressValidator.bolt11InvoiceMatcher).hasMatch(e.address.toLowerCase()) ||
RegExp(AddressValidator.lnurlMatcher).hasMatch(e.address.toLowerCase()) ||
(e.isParsedAddress &&
e.parsedAddress.addresses.isNotEmpty &&
RegExp(AddressValidator.lnurlMatcher)
.hasMatch(e.parsedAddress.addresses.firstOrNull?.toLowerCase() ?? "")));
final outputs = sendViewModel.outputs;
final outputs = sendViewModel.outputs;
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24,
children: [
Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
amount,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface),
),
Text(currencySymbol,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant))
],
),
Text(
fiatAmount,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
],
),
if (outputs.length >= 1 && (outputs.first.extractedAddress.isNotEmpty || outputs.first.address.isNotEmpty) && showAddress)
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Flexible(
child: Text(
amount,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface),
),
),
Text(currencySymbol,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant))
],
),
Text(
S.of(context).send_to,
fiatAmount,
style: TextStyle(
fontSize: 16,
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
if (outputs.length == 1)
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: AddressFormatter.buildSegmentedAddress(
address: outputs.first.isParsedAddress ? outputs.first.extractedAddress : outputs.first.address,
evenTextStyle:
TextStyle(color: Theme.of(context).colorScheme.onSurface)),
),
)
else
AnimatedDropdown(
content: Column(
children: outputs
.map(
(item) => Column(children: [
MultiSendAddressPreview(
index: outputs.indexOf(item) + 1,
address: item.isParsedAddress
? item.extractedAddress
: item.address,
amount:
"${item.roundedCryptoAmount(8).withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.currency.title}",
fiatAmount:
"${item.fiatAmount.withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.fiatCurrency.title}",
),
if(item != outputs.last)
Container(width: double.infinity, height: 1, color: Theme.of(context).colorScheme.surfaceContainerHigh)
],),
)
.toList(),
),
dropdownText: "${outputs.length} ${S.of(context).addresses}"),
],
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).fee,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface)),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
fee.withLocalSeperator(sendViewModel.languageCode),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
Text(fiatFee.withLocalSeperator(sendViewModel.languageCode),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant))
],
)
],
),
),
if (sendViewModel.isElectrumWallet) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Container(
height: 1,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
if (outputs.length >= 1 &&
(outputs.first.extractedAddress.isNotEmpty || outputs.first.address.isNotEmpty) &&
showAddress)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: [
Text(
S.of(context).send_to,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
if (outputs.length == 1)
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: AddressFormatter.buildSegmentedAddress(
address: outputs.first.isParsedAddress
? outputs.first.extractedAddress
: outputs.first.address,
evenTextStyle:
TextStyle(color: Theme.of(context).colorScheme.onSurface)),
),
)
else
AnimatedDropdown(
content: Column(
children: outputs
.map(
(item) => Column(
children: [
MultiSendAddressPreview(
index: outputs.indexOf(item) + 1,
address: item.isParsedAddress
? item.extractedAddress
: item.address,
amount:
"${item.roundedCryptoAmount(8).withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.currency.title}",
fiatAmount:
"${item.fiatAmount.withDecimals(2).withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.fiatCurrency.title}",
),
if (item != outputs.last)
Container(
width: double.infinity,
height: 1,
color:
Theme.of(context).colorScheme.surfaceContainerHigh)
],
),
)
.toList(),
),
dropdownText: "${outputs.length} ${S.of(context).addresses}"),
],
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).network,
Text(S.of(context).fee,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface)),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
sendViewModel.selectedCryptoCurrency == CryptoCurrency.btcln
? "Lightning"
: bitcoin!.getNetworkName(sendViewModel.wallet),
"${fee.withLocalSeperator(sendViewModel.languageCode)} ${sendViewModel.currencySymbol}",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
Text(fiatFee.withLocalSeperator(sendViewModel.languageCode),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
@@ -406,18 +374,52 @@ class SendTransactionDetails extends StatelessWidget {
)
],
),
)
),
if (sendViewModel.isElectrumWallet) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Container(
height: 1,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
),
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).network,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface)),
Column(
children: [
Text(
sendViewModel.selectedCryptoCurrency == CryptoCurrency.btcln
? "Lightning"
: bitcoin!.getNetworkName(sendViewModel.wallet),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant))
],
)
],
),
)
],
],
],
),
),
),
SendConfirmBottomWidget(sendViewModel: sendViewModel),
if(Platform.isAndroid) // spacing between bottom widget and system navbar
SizedBox(),
],
SendConfirmBottomWidget(sendViewModel: sendViewModel),
if (Platform.isAndroid) // spacing between bottom widget and system navbar
SizedBox(),
],
),
),
),
);
);
});
}
String formatAmount(String amount) {
@@ -14,6 +14,8 @@ import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/exchange/exchange_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_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currencies_with_memo.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -159,14 +161,16 @@ class SwapTransactionDetails extends StatelessWidget {
builder: (_) => NewListSections(showHeader: true, sections: {
S.of(context).send: [
ListItemRegularRow(
showArrow: false,
keyValue: "send value",
label: exchangeViewModel.depositCurrency.fullName ?? "",
iconPath: exchangeViewModel.depositCurrency.iconPath ?? "",
badgeIconPath: _resolveChainBadgePath(exchangeViewModel.depositCurrency),
trailingText: exchangeTradeViewModel.trade.amountFormatted() +
" " +
(exchangeViewModel.depositCurrency.title)),
showArrow: false,
keyValue: "send value",
label: exchangeViewModel.depositCurrency.fullName ?? "",
iconPath: exchangeViewModel.depositCurrency.iconPath ?? "",
badgeIconPath: _resolveChainBadgePath(exchangeViewModel.depositCurrency),
trailingText: exchangeViewModel.amountParsingProxy
.asDisplayStringWithSymbol(exchangeViewModel.depositCurrency
.tryParseAmount(exchangeTradeViewModel.trade.amount) ??
Money.zero(exchangeViewModel.depositCurrency)),
),
if (exchangeTradeViewModel.sendViewModel.pendingTransaction != null)
ListItemRegularRow(
showArrow: false,
@@ -184,13 +188,15 @@ class SwapTransactionDetails extends StatelessWidget {
],
S.of(context).receive: [
ListItemRegularRow(
showArrow: false,
keyValue: "receive value",
label: exchangeViewModel.receiveCurrency.fullName ?? "",
iconPath: exchangeViewModel.receiveCurrency.iconPath ?? "",
badgeIconPath: _resolveChainBadgePath(exchangeViewModel.receiveCurrency),
trailingText:
(receiveAmount) + " " + (exchangeViewModel.receiveCurrency.title)),
showArrow: false,
keyValue: "receive value",
label: exchangeViewModel.receiveCurrency.fullName ?? "",
iconPath: exchangeViewModel.receiveCurrency.iconPath ?? "",
badgeIconPath: _resolveChainBadgePath(exchangeViewModel.receiveCurrency),
trailingText: (receiveAmount.withMaxDecimals(8)) +
" " +
(exchangeViewModel.receiveCurrency.title),
),
ListItemRegularRow(
keyValue: "receiver",
label: S.of(context).to,
@@ -20,7 +20,9 @@ class SwapLimitPopup extends StatelessWidget {
child: Container(
width: double.infinity,
child: Observer(builder: (_) {
final double? amount = double.tryParse(exchangeViewModel.depositAmountCanonical);
final amount = exchangeViewModel.hasDepositAmount
? double.tryParse(exchangeViewModel.depositAmountCanonical)
: null;
final max = exchangeViewModel.limits.max ?? double.infinity;
final min = exchangeViewModel.limits.min ?? 0;
final tooLarge = amount != null && max != 0 && amount > max;
@@ -1,4 +1,5 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
class SwapModalHeader extends StatelessWidget {
@@ -17,8 +18,12 @@ class SwapModalHeader extends StatelessWidget {
width: 36,
child: Stack(
children: [
Image.asset(fromIconPath, width: 24, height: 24),
Positioned(top: 12, left: 12, child: Image.asset(toIconPath, width: 24, height: 24)),
CakeImageWidget(imageUrl: fromIconPath, width: 24, height: 24),
Positioned(
top: 12,
left: 12,
child: CakeImageWidget(imageUrl: toIconPath, width: 24, height: 24),
),
],
),
),
+27 -41
View File
@@ -53,6 +53,8 @@ class CWSolana extends Solana {
@override
String getPublicKey(WalletBase wallet) =>
(wallet as SolanaWallet).solanaPublicKey.toAddress().address;
@override
Object createSolanaTransactionCredentials(
List<Output> outputs, {
required CryptoCurrency currency,
@@ -60,14 +62,14 @@ class CWSolana extends Solana {
SolanaTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
))
.toList(),
currency: currency,
);
@@ -116,23 +118,15 @@ class CWSolana extends Solana {
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as SolanaTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.sol.title) {
if (transaction.amount.currency.symbol == CryptoCurrency.sol.symbol) {
return CryptoCurrency.sol;
}
wallet as SolanaWallet;
return wallet.splTokenCurrencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
return (wallet as SolanaWallet).splTokenCurrencies.firstWhere(
(element) => transaction.amount.currency.symbol == element.symbol,
);
}
@override
double getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as SolanaTransactionInfo).solAmount.toDouble();
}
@override
String getTokenAddress(CryptoCurrency asset) {
// If it's already an SPLToken, use its mint address
@@ -168,9 +162,7 @@ class CWSolana extends Solana {
}
@override
double? getEstimateFees(WalletBase wallet) {
return (wallet as SolanaWallet).estimatedFee;
}
Money? getEstimateFees(WalletBase wallet) => (wallet as SolanaWallet).estimatedFee;
@override
List<SPLToken> getDefaultSPLTokens() => DefaultSPLTokens().initialSPLTokens;
@@ -197,8 +189,8 @@ class CWSolana extends Solana {
String base64Transaction,
String requestId,
String destinationAddress,
double amount,
double fee,
Money amount,
Money fee,
) async {
final solanaWallet = wallet as SolanaWallet;
final privateKey = solanaWallet.solanaPrivateKey;
@@ -418,24 +410,18 @@ class CWSolana extends Solana {
required String from,
String? tokenSymbol,
required TransactionDirection direction,
required double solAmount,
required Money amount,
required bool isPending,
required double txFee,
required Money fee,
}) =>
SolanaTransactionInfo(
id: id,
blockTime: blockTime,
to: to,
from: from,
tokenSymbol: tokenSymbol ?? "SOL",
direction: direction,
solAmount: solAmount,
isPending: isPending,
txFee: txFee);
@override
double getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingSolanaTransaction).amount;
@override
double getPendingTransactionFee(PendingTransaction tx) => (tx as PendingSolanaTransaction).fee;
id: id,
date: blockTime,
to: to,
from: from,
direction: direction,
amount: amount,
isPending: isPending,
fee: fee,
);
}
+2 -2
View File
@@ -733,7 +733,7 @@ class ExchangePage extends BasePage {
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
},
useSatoshis: exchangeViewModel.useSatoshiDeposit,
useSatoshis: exchangeViewModel.useDepositBaseUnit,
),
);
@@ -782,7 +782,7 @@ class ExchangePage extends BasePage {
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
},
useSatoshis: exchangeViewModel.useSatoshisReceive,
useSatoshis: exchangeViewModel.useReceiveBaseUnit,
),
);
@@ -1,4 +1,3 @@
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
@@ -11,7 +10,6 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_shee
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
@@ -330,10 +328,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
fee: isEVMCompatibleChain(sendVM.walletType)
? S.of(bottomSheetContext).send_estimated_fee
: S.of(bottomSheetContext).send_fee,
feeValue: "${sendVM.amountParsingProxy.getDisplayCryptoAmount(
sendVM.pendingTransaction!.feeFormattedValue,
sendVM.selectedCryptoCurrency)} ${sendVM.amountParsingProxy
.getCryptoSymbol(sendVM.wallet.currency)}",
feeValue: sendVM.amountParsingProxy.asDisplayStringWithSymbol(
sendVM.pendingTransaction!.fee),
feeFiatAmount: sendVM.pendingTransactionFeeFiatAmountFormatted,
outputs: sendVM.outputs,
onSlideActionComplete: () async {
@@ -58,7 +58,8 @@ class DEuroSavingsPage extends BasePage {
Observer(
builder: (_) => SavingsCard(
interestRate: "${_dEuroViewModel.interestRateFormated}%",
savingsBalance: _dEuroViewModel.savingsBalanceFormated,
savingsBalance:
_dEuroViewModel.savingsBalance.toStringWithPrecision(fractionalDigits: 6),
fiatSavingsBalance: _dEuroViewModel.fiatSavingsBalanceFormated,
currency: CryptoCurrency.deuro,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
@@ -68,6 +69,10 @@ class DEuroSavingsPage extends BasePage {
onTooltipPressed: () => _onSavingsTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
isLoading: _dEuroViewModel.isLoading,
onWithdrawV1Pressed: () => _onWithdrawV1(context),
savingsBalanceV1: _dEuroViewModel.savingsBalanceV1
?.toStringWithPrecision(fractionalDigits: 6),
fiatSavingsBalanceV1: _dEuroViewModel.fiatSavingsBalanceV1Formated,
),
),
Observer(
@@ -126,6 +131,16 @@ class DEuroSavingsPage extends BasePage {
_editSheetIsOpen = false;
}
Future<void> _onWithdrawV1(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
try {
await _requireHardwareWallet(context);
await _dEuroViewModel.prepareSavingsV1Withdraw();
} catch (_) {}
_editSheetIsOpen = false;
}
Future<void> _onSavingsRemove(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
@@ -215,7 +230,7 @@ class DEuroSavingsPage extends BasePage {
amount: S.of(bottomSheetContext).send_amount,
amountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.accruedInterestFormated
: tx.amountFormatted,
: tx.amount.toStringWithSymbol(),
fiatAmountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.fiatAccruedInterestFormated
: _dEuroViewModel.pendingTransactionFiatAmountFormatted,
@@ -251,7 +266,7 @@ class DEuroSavingsPage extends BasePage {
amountValue: tx.amountFormatted,
fiatAmountValue: "",
fee: S.of(bottomSheetContext).send_estimated_fee,
feeValue: tx.feeFormatted,
feeValue: tx.fee.toStringWithSymbol(),
feeFiatAmount: "",
outputs: [],
onSlideActionComplete: () {
@@ -411,8 +426,8 @@ class DEuroSavingsPage extends BasePage {
? S.of(context).deuro_savings_available_to_add
: S.of(context).deuro_savings_available_to_remove,
balance: isAdding
? _dEuroViewModel.accountBalanceFormated
: _dEuroViewModel.savingsBalanceFormated,
? _dEuroViewModel.accountBalance
: _dEuroViewModel.savingsBalance,
footerType: FooterType.none,
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
@@ -23,6 +23,9 @@ class SavingsCard extends StatelessWidget {
this.isLoading = false,
this.fiatSavingsBalance,
this.fiatCurrency,
this.savingsBalanceV1,
this.fiatSavingsBalanceV1,
this.onWithdrawV1Pressed,
});
final bool isEnabled;
@@ -30,8 +33,11 @@ class SavingsCard extends StatelessWidget {
final String interestRate;
final String savingsBalance;
final String? fiatSavingsBalance;
final String? savingsBalanceV1;
final String? fiatSavingsBalanceV1;
final FiatCurrency? fiatCurrency;
final CryptoCurrency currency;
final VoidCallback? onWithdrawV1Pressed;
final VoidCallback onAddSavingsPressed;
final VoidCallback onRemoveSavingsPressed;
final VoidCallback onApproveSavingsPressed;
@@ -98,46 +104,72 @@ class SavingsCard extends StatelessWidget {
onTooltipPressed: onTooltipPressed,
),
),
if (savingsBalanceV1 != null)
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: getAssetBalanceRow(
context,
title: "${S.of(context).deuro_savings_balance} V1",
amount: savingsBalanceV1!,
fiatAmount: fiatSavingsBalanceV1,
currency: currency,
fiatCurrency: fiatCurrency,
hideSymbol: true,
),
),
isLoading
? CupertinoActivityIndicator(color: Theme.of(context).colorScheme.onSurface)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: isEnabled
children: savingsBalanceV1 != null
? [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_add,
imagePath: 'assets/images/received.png',
onPressed: onAddSavingsPressed,
label: "${S.of(context).deuro_savings_remove} V1",
onPressed: onWithdrawV1Pressed ?? () {},
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(width: 12),
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_remove,
imagePath: 'assets/images/upload.png',
onPressed: onRemoveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
]
: [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_approve_app,
onPressed: onApproveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
icon: Icons.check,
icon: Icons.arrow_downward,
),
)
],
]
: isEnabled
? [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_add,
imagePath: 'assets/images/received.png',
onPressed: onAddSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(width: 12),
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_remove,
imagePath: 'assets/images/upload.png',
onPressed: onRemoveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
]
: [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_approve_app,
onPressed: onApproveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
icon: Icons.check,
),
)
],
),
],
),
@@ -264,33 +296,33 @@ class SavingsCard extends StatelessWidget {
),
],
),
SizedBox(
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
errorWidget: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
if (!hideSymbol)
SizedBox(
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
errorWidget: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
),
),
if (!hideSymbol) ...[
const SizedBox(height: 3),
Text(
currency.title,
@@ -301,11 +333,10 @@ class SavingsCard extends StatelessWidget {
height: 1,
),
),
]
],
],
),
),
),
),
],
);
}
@@ -3,11 +3,11 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/numpad.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cw_core/parse_fixed.dart';
import 'package:cw_core/amount/money.dart';
import 'package:flutter/material.dart';
class SavingsEditSheet extends BaseBottomSheet {
final String? balance;
final Money? balance;
final String? balanceTitle;
const SavingsEditSheet({
@@ -32,7 +32,7 @@ class SavingsEditSheet extends BaseBottomSheet {
}
class _SavingsEditBody extends StatefulWidget {
final String? balance;
final Money? balance;
final String? balanceTitle;
const _SavingsEditBody({this.balance, this.balanceTitle});
@@ -59,7 +59,7 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
final FocusNode _numpadFocusNode = FocusNode();
void _onPressedAll() => setState(() {
amount = widget.balance!;
amount = widget.balance!.toString();
isValid = _validate();
});
@@ -79,9 +79,12 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
});
bool _validate() {
final amountBigInt = parseFixed(amount, 18);
final balanceBigInt = parseFixed(widget.balance!, 18);
return balanceBigInt >= amountBigInt && amountBigInt > BigInt.zero;
try {
final amountBigInt = Money.parse(amount, widget.balance!.currency);
return widget.balance! >= amountBigInt && amountBigInt.sign > 0;
} catch (_) {
return false;
}
}
@override
@@ -108,7 +111,7 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
Divider(),
AssetBalanceRow(
title: widget.balanceTitle!,
amount: widget.balance!,
amount: widget.balance!.toStringWithSymbol(fractionalDigits: 6),
onAllPressed: _onPressedAll,
),
],
@@ -1,5 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -198,8 +199,7 @@ class CurrencyAmountTextField extends StatelessWidget {
),
validator: isAmountEditable ? currencyValueValidator : null,
onChanged: (value) {
var sanitized =
value.replaceAll(',', '.').withMaxDecimals(selectedCurrencyDecimals);
var sanitized = value.sanitized().withMaxDecimals(selectedCurrencyDecimals);
if (selectedCurrencyDecimals == 0) {
sanitized = sanitized.replaceAll('.', '');
+3 -2
View File
@@ -35,6 +35,7 @@ import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
@@ -445,9 +446,9 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.type == WalletType.monero) {
int amount = 0;
var amount = Money.zero(sendViewModel.wallet.currency);
for (var item in sendViewModel.outputs) {
amount += item.formattedCryptoAmount;
amount += item.cryptoAmountMoney;
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
@@ -3,7 +3,6 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/rounded_icon_button.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -11,7 +10,6 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
const CakePayTransactionSentBottomSheet({
super.key,
required this.titleText,
required this.currency,
required this.amount,
required this.amountValue,
required this.fiatAmountValue,
@@ -28,7 +26,6 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
final String titleText;
final Widget? titleIconWidget;
final CryptoCurrency currency;
final String amount;
final String amountValue;
final String fiatAmountValue;
@@ -41,32 +38,15 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
final String paymentId;
final String paymentIdValue;
TextStyle _titleStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.bodyLarge!;
TextStyle _titleStyle(BuildContext ctx) => Theme.of(ctx).textTheme.bodyLarge!;
TextStyle _valueStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.titleMedium!
.copyWith(
TextStyle _valueStyle(BuildContext ctx) => Theme.of(ctx).textTheme.titleMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
);
TextStyle _subStyle(BuildContext ctx) =>
Theme
.of(ctx)
.textTheme
.labelSmall!
.copyWith(
color: Theme
.of(ctx)
.colorScheme
.onSurfaceVariant,
TextStyle _subStyle(BuildContext ctx) => Theme.of(ctx).textTheme.labelSmall!.copyWith(
color: Theme.of(ctx).colorScheme.onSurfaceVariant,
);
Widget _buildHeader(BuildContext ctx) =>
@@ -78,10 +58,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
height: 5,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Theme
.of(ctx)
.colorScheme
.onSurface,
color: Theme.of(ctx).colorScheme.onSurface,
),
),
const SizedBox(height: 20),
@@ -92,15 +69,11 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
const SizedBox(width: 6),
Text(
titleText,
style: Theme
.of(ctx)
.textTheme
.titleLarge!
.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
style: Theme.of(ctx).textTheme.titleLarge!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
),
],
),
@@ -116,7 +89,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
_StandardTile(
itemTitle: amount,
titleStyle: _titleStyle(context),
itemValue: '$amountValue ${currency.title}',
itemValue: amountValue,
itemValueStyle: _valueStyle(context),
itemSubTitle: fiatAmountValue,
itemSubTitleStyle: _subStyle(context),
@@ -143,9 +116,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
const SizedBox(height: 8),
_StandardTile(
itemTitle: paymentId + ': \n' + paymentIdValue,
titleStyle: _titleStyle(context).copyWith(
fontSize: 14
),
titleStyle: _titleStyle(context).copyWith(fontSize: 14),
itemValue: '',
itemValueStyle: _valueStyle(context),
copyButton: true,
@@ -161,28 +132,15 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
Image.asset('assets/images/envelope.png'),
const SizedBox(height: 18),
Text(
S
.of(context)
.cake_pay_card_email_delivered_message,
style: Theme
.of(context)
.textTheme
.bodyLarge,
S.of(context).cake_pay_card_email_delivered_message,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
PrimaryButton(
text: S
.of(context)
.close,
color: Theme
.of(context)
.colorScheme
.primary,
textColor: Theme
.of(context)
.colorScheme
.onPrimaryContainer,
text: S.of(context).close,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimaryContainer,
onPressed: onClose,
),
],
@@ -194,10 +152,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
final maxHeight = MediaQuery
.of(context)
.size
.height * 0.9;
final maxHeight = MediaQuery.of(context).size.height * 0.9;
return ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
@@ -7,6 +7,7 @@ import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
@@ -134,7 +135,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
),
StandardTile(
itemTitle: amount,
itemValue: '$amountValue ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}',
itemValue: amountValue,
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitle: fiatAmountValue,
itemSubTitleTextStyle: itemSubTitleTextStyle,
@@ -167,7 +168,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final batchContactTitle =
'${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}';
final _address = item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = '${item.cryptoAmount.replaceAll(',', '.')} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}';
final _amount = '${item.cryptoAmount.sanitized()} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}';
return isBatchSending || (contactName.isNotEmpty && !isCakePayName)
? ExpansionAddressTile(
contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact,
@@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.d
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/crypto_amount_format.dart';
@@ -197,7 +198,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
],
onChanged: (value) {
final sanitized = value
.replaceAll(',', '.')
.sanitized()
.withMaxDecimals(widget.exchangeViewModel.receiveCurrency.decimals);
if (sanitized != _amountController.text) {
// Update text while preserving a sane cursor position to avoid auto-selection
+10 -25
View File
@@ -58,13 +58,12 @@ class CWTron extends Tron {
.map(
(out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
),
)
.toList(),
@@ -97,22 +96,16 @@ class CWTron extends Tron {
Future<TronToken?> getTronToken(WalletBase wallet, String contractAddress) async =>
(wallet as TronWallet).getTronToken(contractAddress);
@override
double getTransactionAmountRaw(TransactionInfo transactionInfo) {
final amount = (transactionInfo as TronTransactionInfo).rawTronAmount();
return double.parse(amount);
}
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as TronTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.trx.title) {
if (transaction.amount.currency.symbol == CryptoCurrency.trx.title) {
return CryptoCurrency.trx;
}
wallet as TronWallet;
return wallet.tronTokenCurrencies.firstWhere(
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
return wallet.tronTokenCurrencies.firstWhere((element) =>
transaction.amount.currency.symbol.toLowerCase() == element.symbol.toLowerCase());
}
@override
@@ -123,11 +116,11 @@ class CWTron extends Tron {
(wallet as TronWallet).getTronBase58AddressFromHex(hexAddress);
@override
String? getTronNativeEstimatedFee(WalletBase wallet) =>
Money? getTronNativeEstimatedFee(WalletBase wallet) =>
(wallet as TronWallet).nativeTxEstimatedFee;
@override
String? getTronTRC20EstimatedFee(WalletBase wallet) => (wallet as TronWallet).trc20EstimatedFee;
Money? getTronTRC20EstimatedFee(WalletBase wallet) => (wallet as TronWallet).trc20EstimatedFee;
@override
void updateTronGridUsageState(WalletBase wallet, bool isEnabled) {
@@ -157,9 +150,8 @@ class CWTron extends Tron {
@override
TransactionInfo getTransactionInfo({
required String id,
required BigInt tronAmount,
int? txFee,
String? tokenSymbol,
required Money amount,
Money? fee,
required TransactionDirection direction,
required DateTime blockTime,
String? to,
@@ -167,18 +159,11 @@ class CWTron extends Tron {
required bool isPending,
}) =>
TronTransactionInfo(id: id,
tronAmount: tronAmount,
tokenSymbol: tokenSymbol ?? "TRX",
txFee: txFee,
amount: amount,
fee: fee,
direction: direction,
blockTime: blockTime,
to: to,
from: from,
isPending: isPending);
@override
String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
@override
String getPendingTransactionFee(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/services.dart';
class DecimalInputFormatter extends TextInputFormatter {
const DecimalInputFormatter({this.maxDecimals = 7}) : assert(maxDecimals >= 0);
final int maxDecimals;
@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
final text = newValue.text;
if (text.isEmpty) return newValue;
if (S.current.all.startsWith(text)) return TextEditingValue(text: "");
final regex = maxDecimals == 0 ? RegExp(r'^\d*$') : RegExp('^\\d*([.,]\\d{0,$maxDecimals})?\$');
return regex.hasMatch(text) ? newValue.copyWith(text: text.replaceAll(',', '.')) : oldValue;
}
}
+1
View File
@@ -306,6 +306,7 @@ class ExceptionHandler {
"the timeout of the request was reached",
"support for coin removed, your seedphrase:"
"Exception: Invalid image data"
];
static Future<void> _addDeviceInfo(File file) async {
+8 -8
View File
@@ -1,3 +1,5 @@
import 'package:cw_core/amount/amount_sanitizer.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/lnurl.dart';
import 'package:cw_core/payment_uris.dart';
@@ -16,9 +18,8 @@ class PaymentRequest {
}
factory PaymentRequest.fromBolt11(String invoice) {
final amountRaw = getBolt11Amount(invoice) ?? 0;
final amount = CryptoCurrency.btcln.formatAmount(BigInt.from(amountRaw));
return PaymentRequest(invoice, amount, '', 'lightning', null);
final amount = getBolt11Amount(invoice) ?? Money.zero(CryptoCurrency.btcln);
return PaymentRequest(invoice, amount.toString(), '', 'lightning', null);
}
factory PaymentRequest.fromUri(Uri? uri) {
@@ -39,10 +40,10 @@ class PaymentRequest {
address = uri.queryParameters['address'] ?? uri.path;
try {
final lnAmount =
CryptoCurrency.btcln.formatAmount(BigInt.from(getBolt11Amount(uri.path) ?? 0));
final lnAmount = getBolt11Amount(uri.path) ?? Money.zero(CryptoCurrency.btcln);
if (lnAmount != 0) {
amount = lnAmount;
amount = lnAmount.toString();
}
} catch (_) {}
if (amount.isEmpty) {
@@ -116,8 +117,7 @@ class PaymentRequest {
static bool _isAlreadyUsableAmount(String amount) {
if (amount.isEmpty) return false;
// Try to parse as double - if successful, it's already in usable format
final parsed = double.tryParse(amount.replaceAll(',', '.'));
final parsed = double.tryParse(amount.sanitized());
if (parsed == null) return false;
// Check if the amount contains a decimal point and is a reasonable number,
+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:
+130 -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) {
@@ -1266,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
@@ -1310,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 =
@@ -1347,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,
@@ -1359,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);
@@ -1377,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);
@@ -1405,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(
@@ -1455,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;
@@ -1565,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)
@@ -1597,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(
+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);
+8 -8
View File
@@ -267,14 +267,14 @@ class CWWownero extends Wownero {
WowneroTransactionCreationCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
))
.toList(),
priority: priority as MoneroTransactionPriority);
+19 -17
View File
@@ -70,23 +70,25 @@ class CWZano extends Zano {
}
@override
Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency}) {
return ZanoTransactionCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as MoneroTransactionPriority,
currency: currency,
);
}
Object createZanoTransactionCredentials(
{required List<Output> outputs,
required TransactionPriority priority,
required CryptoCurrency currency}) =>
ZanoTransactionCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
))
.toList(),
priority: priority as MoneroTransactionPriority,
currency: currency,
);
@override
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee}) {
+13 -15
View File
@@ -43,23 +43,21 @@ class CWZcash extends Zcash {
@override
Object createZcashTransactionCredentials(List<Output> outputs,
{required CryptoCurrency currency,
int? feeRate,
TransactionPriority? priority}) {
final txPriority = (priority ?? getZcashTransactionPriorityAutomatic())
as MoneroTransactionPriority;
{required CryptoCurrency currency, int? feeRate, TransactionPriority? priority}) {
final txPriority =
(priority ?? getZcashTransactionPriorityAutomatic()) as MoneroTransactionPriority;
return ZcashTransactionCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
memo: out.memo,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
memo: out.memo,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
))
.toList(),
priority: txPriority,
currency: currency,
@@ -90,7 +88,7 @@ class CWZcash extends Zcash {
@override
int formatterZcashParseAmount(String amount) {
return CryptoCurrency.zec.parseAmount(amount).toInt();
return CryptoCurrency.zec.parseAmount(amount).amount.toInt();
}
@override