From ebe8c65407774c49547c2ba86f6ea83c260a8919 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Tue, 15 Oct 2024 00:28:38 +0300 Subject: [PATCH] Peg in and peg out flow (#1745) * version 4.20.0 * update build numbers * UI updates and script fix for ios bundle identifier * disable mweb for desktop * change hardcoded ltc server ip address electrum connection enhancement * additional logging and minor fixes * additional logging and minor fixes * addresses pt.1 * logs of fixes and experimental changes, close wallet before opening next * save * fix icon * fixes * [skip ci] updates * [skip ci] updates * updates * minor optimizations * fix for when switching between wallets * [skip ci] updates * [skip ci] updates * Update cw_bitcoin/lib/litecoin_wallet.dart Co-authored-by: Omar Hatem * Update cw_bitcoin/lib/litecoin_wallet.dart Co-authored-by: Omar Hatem * mobx * mostly logging * stream fix pt.1 [skip ci] * updates * some fixes and enhancements * [skip ci] minor * potential partial fix for streamsink closed * fix stream sink closed errors * fix mweb logo colors * add initial whitelisting for coin types on send screen * MWEB enhancements 2.0 (#1735) * additional logging and minor fixes * additional logging and minor fixes * addresses pt.1 * Allow Wallet Group Names to be the same as Wallet Names (#1730) * fix: Issues with imaging * fix: Allow group names to be the same as wallet names * fix: Bug with wallet grouping when a wallet is minimized * fix: Bug with wallet grouping when a wallet is minimized * logs of fixes and experimental changes, close wallet before opening next * save * fix icon * fixes * [skip ci] updates * [skip ci] updates * updates * minor optimizations * fix for when switching between wallets * [skip ci] updates * [skip ci] updates * Update cw_bitcoin/lib/litecoin_wallet.dart Co-authored-by: Omar Hatem * Update cw_bitcoin/lib/litecoin_wallet.dart Co-authored-by: Omar Hatem * mobx * mostly logging * stream fix pt.1 [skip ci] * updates * some fixes and enhancements * [skip ci] minor * potential partial fix for streamsink closed * fix stream sink closed errors * fix mweb logo colors * save * minor enhancements [skip ci] * save * experimental * minor * minor [skip ci] --------- Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Co-authored-by: Omar Hatem * fix menu list removing from original list * handle pegin and pegout * fix text color * fix import * pegin/out button ui updates * update spacing + tx creation fix * add correct args for link view model [skip ci] --------- Co-authored-by: Matthew Fosse Co-authored-by: fossephate Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com> --- .../lib/bitcoin_transaction_credentials.dart | 4 +- cw_bitcoin/lib/electrum_wallet.dart | 28 +- cw_bitcoin/lib/litecoin_wallet.dart | 7 +- cw_bitcoin/pubspec.lock | 28 +- cw_core/lib/unspent_coin_type.dart | 1 + lib/bitcoin/cw_bitcoin.dart | 60 ++- lib/di.dart | 27 +- lib/router.dart | 24 +- .../screens/dashboard/pages/balance_page.dart | 444 +++++++++++------- lib/src/screens/send/widgets/send_card.dart | 6 +- lib/view_model/link_view_model.dart | 5 +- lib/view_model/send/send_view_model.dart | 25 +- .../unspent_coins_list_view_model.dart | 12 +- tool/configure.dart | 10 +- 14 files changed, 428 insertions(+), 253 deletions(-) create mode 100644 cw_core/lib/unspent_coin_type.dart diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index bda7c39ae..01e905fb0 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -1,11 +1,13 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; +import 'package:cw_core/unspent_coin_type.dart'; class BitcoinTransactionCredentials { BitcoinTransactionCredentials(this.outputs, - {required this.priority, this.feeRate}); + {required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any}); final List outputs; final BitcoinTransactionPriority? priority; final int? feeRate; + final UnspentCoinType coinTypeToSpendFrom; } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 97897046c..ceaa2f088 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -37,6 +37,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/get_height_by_date.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -584,6 +585,7 @@ abstract class ElectrumWalletBase required int credentialsAmount, required bool paysToSilentPayment, int? inputsCount, + UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) { List utxos = []; List vinOutpoints = []; @@ -594,7 +596,20 @@ abstract class ElectrumWalletBase bool spendsUnconfirmedTX = false; int leftAmount = credentialsAmount; - final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); + final availableInputs = unspentCoins.where((utx) { + if (!utx.isSending || utx.isFrozen) { + return false; + } + + switch (coinTypeToSpendFrom) { + case UnspentCoinType.mweb: + return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb; + case UnspentCoinType.nonMweb: + return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb; + case UnspentCoinType.any: + return true; + } + }).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); for (int i = 0; i < availableInputs.length; i++) { @@ -701,11 +716,13 @@ abstract class ElectrumWalletBase String? memo, int credentialsAmount = 0, bool hasSilentPayment = false, + UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { final utxoDetails = _createUTXOS( sendAll: true, credentialsAmount: credentialsAmount, paysToSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); int fee = await calcFee( @@ -772,12 +789,14 @@ abstract class ElectrumWalletBase String? memo, bool? useUnconfirmed, bool hasSilentPayment = false, + UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { final utxoDetails = _createUTXOS( sendAll: false, credentialsAmount: credentialsAmount, inputsCount: inputsCount, paysToSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; @@ -797,6 +816,7 @@ abstract class ElectrumWalletBase inputsCount: utxoDetails.utxos.length + 1, memo: memo, hasSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); } @@ -855,6 +875,7 @@ abstract class ElectrumWalletBase inputsCount: utxoDetails.utxos.length + 1, memo: memo, useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); } @@ -862,6 +883,7 @@ abstract class ElectrumWalletBase outputs, feeRate, memo: memo, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); if (estimatedSendAll.amount == credentialsAmount) { @@ -900,6 +922,7 @@ abstract class ElectrumWalletBase memo: memo, useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, hasSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); } } @@ -957,6 +980,7 @@ abstract class ElectrumWalletBase final hasMultiDestination = transactionCredentials.outputs.length > 1; final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; final memo = transactionCredentials.outputs.first.memo; + final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom; int credentialsAmount = 0; bool hasSilentPayment = false; @@ -1012,6 +1036,7 @@ abstract class ElectrumWalletBase memo: memo, credentialsAmount: credentialsAmount, hasSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); } else { estimatedTx = await estimateTxForAmount( @@ -1020,6 +1045,7 @@ abstract class ElectrumWalletBase feeRateInt, memo: memo, hasSilentPayment: hasSilentPayment, + coinTypeToSpendFrom: coinTypeToSpendFrom, ); } diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index f4d99d807..29a2df48a 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -251,7 +251,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { _syncTimer?.cancel(); try { syncStatus = SyncronizingSyncStatus(); - await subscribeForUpdates(); + try { + await subscribeForUpdates(); + } catch (e) { + print("failed to subcribe for updates: $e"); + } updateFeeRates(); _feeRatesTimer?.cancel(); _feeRatesTimer = @@ -916,6 +920,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } if (!hasMwebInput && !hasMwebOutput) { + tx.isMweb = false; return tx; } diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 4c7c49a9e..36d762ea1 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.6.1" args: dependency: transitive description: @@ -336,10 +336,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: @@ -403,14 +403,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6" + url: "https://pub.dev" + source: hosted + version: "0.3.1+4" googleapis_auth: dependency: transitive description: name: googleapis_auth - sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da + sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.6.0" graphs: dependency: transitive description: @@ -809,10 +817,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: @@ -1031,10 +1039,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" yaml: dependency: transitive description: diff --git a/cw_core/lib/unspent_coin_type.dart b/cw_core/lib/unspent_coin_type.dart new file mode 100644 index 000000000..a042610fc --- /dev/null +++ b/cw_core/lib/unspent_coin_type.dart @@ -0,0 +1 @@ +enum UnspentCoinType { mweb, nonMweb, any } \ No newline at end of file diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e9e83597a..91232792a 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -106,34 +106,33 @@ class CWBitcoin extends Bitcoin { } @override - Object createBitcoinTransactionCredentials(List outputs, - {required TransactionPriority priority, int? feeRate}) { + Object createBitcoinTransactionCredentials( + List outputs, { + required TransactionPriority priority, + int? feeRate, + UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, + }) { final bitcoinFeeRate = priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null; return BitcoinTransactionCredentials( - 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 BitcoinTransactionPriority, - feeRate: bitcoinFeeRate); + 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 BitcoinTransactionPriority, + feeRate: bitcoinFeeRate, + coinTypeToSpendFrom: coinTypeToSpendFrom, + ); } - @override - Object createBitcoinTransactionCredentialsRaw(List outputs, - {TransactionPriority? priority, required int feeRate}) => - BitcoinTransactionCredentials(outputs, - priority: priority != null ? priority as BitcoinTransactionPriority : null, - feeRate: feeRate); - @override @computed List getSubAddresses(Object wallet) { @@ -205,9 +204,20 @@ class CWBitcoin extends Bitcoin { (priority as BitcoinTransactionPriority).labelWithRate(rate, customRate); @override - List getUnspents(Object wallet) { + List getUnspents(Object wallet, + {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) { final bitcoinWallet = wallet as ElectrumWallet; - return bitcoinWallet.unspentCoins; + return bitcoinWallet.unspentCoins.where((element) { + switch(coinTypeToSpendFrom) { + case UnspentCoinType.mweb: + return element.bitcoinAddressRecord.type == SegwitAddresType.mweb; + case UnspentCoinType.nonMweb: + return element.bitcoinAddressRecord.type != SegwitAddresType.mweb; + case UnspentCoinType.any: + return true; + } + + }).toList(); } Future updateUnspents(Object wallet) async { diff --git a/lib/di.dart b/lib/di.dart index 0008ad8fd..13ffd839e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -167,6 +167,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; import 'package:cw_core/nano_account.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/transaction_info.dart'; @@ -725,8 +726,8 @@ Future setup({ getIt.get(), getIt.get())); - getIt.registerFactory( - () => SendViewModel( + getIt.registerFactoryParam( + (coinTypeToSpendFrom, _) => SendViewModel( getIt.get(), getIt.get(), getIt.get(), @@ -734,12 +735,13 @@ Future setup({ getIt.get(), _transactionDescriptionBox, getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, + coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any, ), ); - getIt.registerFactoryParam( - (PaymentRequest? initialPaymentRequest, _) => SendPage( - sendViewModel: getIt.get(), + getIt.registerFactoryParam( + (PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage( + sendViewModel: getIt.get(param1: coinTypeToSpendFrom), authService: getIt.get(), initialPaymentRequest: initialPaymentRequest, )); @@ -1215,14 +1217,21 @@ Future setup({ getIt.registerFactory(() => SupportOtherLinksPage(getIt.get())); - getIt.registerFactory(() { + getIt.registerFactoryParam( + (coinTypeToSpendFrom, _) { final wallet = getIt.get().wallet; - return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource); + return UnspentCoinsListViewModel( + wallet: wallet!, + unspentCoinsInfo: _unspentCoinsInfoSource, + coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any, + ); }); - getIt.registerFactory(() => - UnspentCoinsListPage(unspentCoinsListViewModel: getIt.get())); + getIt.registerFactoryParam( + (coinTypeToSpendFrom, _) => UnspentCoinsListPage( + unspentCoinsListViewModel: + getIt.get(param1: coinTypeToSpendFrom))); getIt.registerFactoryParam( diff --git a/lib/router.dart b/lib/router.dart index 7beace174..3b4c38546 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -120,6 +120,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/nano_account.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; @@ -184,7 +185,8 @@ Route createRoute(RouteSettings settings) { final type = settings.arguments as WalletType; final walletGroupsDisplayVM = getIt.get(param1: type); - return CupertinoPageRoute(builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM)); + return CupertinoPageRoute( + builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM)); case Routes.newWallet: final args = settings.arguments as NewWalletArguments; @@ -348,13 +350,17 @@ Route createRoute(RouteSettings settings) { settings: settings, builder: (_) => getIt.get()); case Routes.send: - final initialPaymentRequest = settings.arguments as PaymentRequest?; + final args = settings.arguments as Map?; + final initialPaymentRequest = args?['paymentRequest'] as PaymentRequest?; + final coinTypeToSpendFrom = args?['coinTypeToSpendFrom'] as UnspentCoinType?; return CupertinoPageRoute( - fullscreenDialog: true, - builder: (_) => getIt.get( - param1: initialPaymentRequest, - )); + fullscreenDialog: true, + builder: (_) => getIt.get( + param1: initialPaymentRequest, + param2: coinTypeToSpendFrom, + ), + ); case Routes.sendTemplate: return CupertinoPageRoute( @@ -604,7 +610,9 @@ Route createRoute(RouteSettings settings) { fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.unspentCoinsList: - return MaterialPageRoute(builder: (_) => getIt.get()); + final coinTypeToSpendFrom = settings.arguments as UnspentCoinType?; + return MaterialPageRoute( + builder: (_) => getIt.get(param1: coinTypeToSpendFrom)); case Routes.unspentCoinsDetails: final args = settings.arguments as List; @@ -778,7 +786,7 @@ Route createRoute(RouteSettings settings) { case Routes.walletGroupDescription: final walletType = settings.arguments as WalletType; - + return MaterialPageRoute( builder: (_) => WalletGroupDescriptionPage( selectedWalletType: walletType, diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 3b88c685a..708941023 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -17,13 +17,15 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -837,216 +839,300 @@ class BalanceRowWidget extends StatelessWidget { color: Theme.of(context).extension()!.syncedBackgroundColor, ), child: Container( - margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Stack( - children: [ - if (currency == CryptoCurrency.ltc) - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - padding: EdgeInsets.only(right: 16, top: 16), - child: Column( - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, + Container( + margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16), + child: Stack( + children: [ + if (currency == CryptoCurrency.ltc) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + padding: EdgeInsets.only(right: 16, top: 16), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: ImageIcon( + AssetImage('assets/images/mweb_logo.png'), + color: Color.fromARGB(255, 11, 70, 129), + size: 40, + ), ), - child: ImageIcon( - AssetImage('assets/images/mweb_logo.png'), - color: Color.fromARGB(255, 11, 70, 129), - size: 40, + const SizedBox(height: 10), + Text( + 'MWEB', + style: TextStyle( + fontSize: 15, + fontFamily: 'Lato', + fontWeight: FontWeight.w800, + color: Theme.of(context) + .extension()! + .assetTitleColor, + height: 1, + ), + ), + ], + ), + ), + ], + ), + if (hasSecondAvailableBalance) + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24), + Text( + '${secondAvailableBalanceLabel}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, ), ), - const SizedBox(height: 10), - Text( - 'MWEB', + SizedBox(height: 8), + AutoSizeText( + secondAvailableBalance, style: TextStyle( - fontSize: 15, + fontSize: 20, fontFamily: 'Lato', - fontWeight: FontWeight.w800, + fontWeight: FontWeight.w400, color: Theme.of(context) .extension()! .assetTitleColor, height: 1, ), + maxLines: 1, + textAlign: TextAlign.center, ), + SizedBox(height: 4), + if (!isTestnet) + Text( + '${secondAvailableFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .textColor, + height: 1, + ), + ), ], ), - ), - ], - ), - if (hasSecondAvailableBalance) - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24), - Text( - '${secondAvailableBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - ), - SizedBox(height: 8), - AutoSizeText( - secondAvailableBalance, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 4), - if (!isTestnet) + ], + ), + ], + ), + ), + Container( + margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16), + child: Stack( + children: [ + if (hasSecondAdditionalBalance) + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24), Text( - '${secondAvailableFiatBalance}', + '${secondAdditionalBalanceLabel}', textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: - Theme.of(context).extension()!.textColor, + color: Theme.of(context) + .extension()! + .labelTextColor, height: 1, ), ), - ], - ), - ], - ), - ], - ), - Stack( - children: [ - if (hasSecondAdditionalBalance) - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24), - Text( - '${secondAdditionalBalanceLabel}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .labelTextColor, - height: 1, - ), - ), - SizedBox(height: 8), - AutoSizeText( - secondAdditionalBalance, - style: TextStyle( - fontSize: 20, - fontFamily: 'Lato', - fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .assetTitleColor, - height: 1, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - SizedBox(height: 4), - if (!isTestnet) - Text( - '${secondAdditionalFiatBalance}', - textAlign: TextAlign.center, + SizedBox(height: 8), + AutoSizeText( + secondAdditionalBalance, style: TextStyle( - fontSize: 12, + fontSize: 20, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: - Theme.of(context).extension()!.textColor, + color: Theme.of(context) + .extension()! + .assetTitleColor, height: 1, ), + maxLines: 1, + textAlign: TextAlign.center, ), - ], - ), - ], - ), - // TODO: smarter peg in / out buttons - // if (currency == CryptoCurrency.ltc) - // Row( - // mainAxisAlignment: MainAxisAlignment.end, - // children: [ - // Container( - // margin: EdgeInsets.only(top: 24, right: 8), - // child: ElevatedButton( - // style: ElevatedButton.styleFrom( - // backgroundColor: Theme.of(context).highlightColor, - // ), - // onPressed: () { - // final mwebAddress = - // bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet); - // if (mwebAddress == null) return; - // final paymentRequest = - // PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}")); - // Navigator.of(context) - // .pushNamed(Routes.send, arguments: paymentRequest); - // }, - // child: Container( - // color: Colors.transparent, - // margin: EdgeInsets.all(4), - // child: Column( - // mainAxisSize: MainAxisSize.max, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Container( - // alignment: Alignment.center, - // decoration: BoxDecoration(shape: BoxShape.circle), - // child: Image.asset( - // 'assets/images/received.png', - // color: Theme.of(context) - // .extension()! - // .balanceAmountColor, - // width: 64, - // height: 32, - // ), - // ), - // SizedBox(height: 4), - // Text( - // S.of(context).litecoin_mweb_pegin, - // style: TextStyle( - // fontSize: 10, - // color: Theme.of(context) - // .extension()! - // .cardTextColor), - // ) - // ], - // ), - // ), - // ), - // ), - // ], - // ), - ], + SizedBox(height: 4), + if (!isTestnet) + Text( + '${secondAdditionalFiatBalance}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .textColor, + height: 1, + ), + ), + ], + ), + ], + ), + ], + ), ), + IntrinsicHeight( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Semantics( + label: S.of(context).litecoin_mweb_pegin, + child: OutlinedButton( + onPressed: () { + final mwebAddress = + bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet); + PaymentRequest? paymentRequest = null; + if ((mwebAddress?.isNotEmpty ?? false)) { + paymentRequest = + PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}")); + } + + Navigator.pushNamed( + context, + Routes.send, + arguments: { + 'paymentRequest': paymentRequest, + 'coinTypeToSpendFrom': UnspentCoinType.nonMweb, + }, + ); + }, + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context) + .extension()! + .textFieldButtonIconColor + .withAlpha(50), + side: BorderSide(color: Colors.grey.shade400, width: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + height: 30, + width: 30, + 'assets/images/received.png', + color: Theme.of(context) + .extension()! + .balanceAmountColor, + ), + const SizedBox(width: 8), + Text( + S.of(context).litecoin_mweb_pegin, + style: TextStyle( + color: Theme.of(context) + .extension()! + .assetTitleColor, + ), + ), + ], + ), + ), + ), + ), + ), + SizedBox(width: 32), + Expanded( + child: Semantics( + label: S.of(context).litecoin_mweb_pegout, + child: OutlinedButton( + onPressed: () { + final litecoinAddress = + bitcoin!.getAddress(dashboardViewModel.wallet); + PaymentRequest? paymentRequest = null; + if (litecoinAddress.isNotEmpty) { + paymentRequest = PaymentRequest.fromUri( + Uri.parse("litecoin:${litecoinAddress}")); + } + + Navigator.pushNamed( + context, + Routes.send, + arguments: { + 'paymentRequest': paymentRequest, + 'coinTypeToSpendFrom': UnspentCoinType.mweb, + }, + ); + }, + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context) + .extension()! + .textFieldButtonIconColor + .withAlpha(50), + side: BorderSide(color: Colors.grey.shade400, width: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + height: 30, + width: 30, + 'assets/images/upload.png', + color: Theme.of(context) + .extension()! + .balanceAmountColor, + ), + const SizedBox(width: 8), + Text( + S.of(context).litecoin_mweb_pegout, + style: TextStyle( + color: Theme.of(context) + .extension()! + .assetTitleColor, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + SizedBox(height: 16), ], ), ), diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 2a14da305..0713fb8c4 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -14,7 +14,6 @@ import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; @@ -373,7 +372,10 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin Navigator.of(context).pushNamed(Routes.unspentCoinsList), + onTap: () => Navigator.of(context).pushNamed( + Routes.unspentCoinsList, + arguments: widget.sendViewModel.coinTypeToSpendFrom, + ), child: Container( color: Colors.transparent, child: Row( diff --git a/lib/view_model/link_view_model.dart b/lib/view_model/link_view_model.dart index 99aed486e..27f0c0560 100644 --- a/lib/view_model/link_view_model.dart +++ b/lib/view_model/link_view_model.dart @@ -65,15 +65,16 @@ class LinkViewModel { if (isNanoGptLink) { switch (currentLink?.authority ?? '') { case "exchange": - case "send": return PaymentRequest.fromUri(currentLink); + case "send": + return {"paymentRequest": PaymentRequest.fromUri(currentLink)}; case "buy": return true; } } if (_isValidPaymentUri) { - return PaymentRequest.fromUri(currentLink); + return {"paymentRequest": PaymentRequest.fromUri(currentLink)}; } return null; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 79f473eb3..0ad8ba376 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -20,6 +20,7 @@ import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:hive/hive.dart'; @@ -67,8 +68,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor this.balanceViewModel, this.contactListViewModel, this.transactionDescriptionBox, - this.ledgerViewModel, - ) : state = InitialExecutionState(), + this.ledgerViewModel, { + this.coinTypeToSpendFrom = UnspentCoinType.any, + }) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), selectedCryptoCurrency = appStore.wallet!.currency, hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) || @@ -97,6 +99,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ObservableList outputs; + final UnspentCoinType coinTypeToSpendFrom; + @action void addOutput() { outputs @@ -217,7 +221,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor PendingTransaction? pendingTransaction; @computed - String get balance => wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; + String get balance { + if (coinTypeToSpendFrom == UnspentCoinType.mweb) { + return balanceViewModel.balances.values.first.secondAvailableBalance; + } else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) { + return balanceViewModel.balances.values.first.availableBalance; + } + return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; + } @computed bool get isFiatDisabled => balanceViewModel.isFiatDisabled; @@ -494,8 +505,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - return bitcoin!.createBitcoinTransactionCredentials(outputs, - priority: priority!, feeRate: customBitcoinFeeRate); + return bitcoin!.createBitcoinTransactionCredentials( + outputs, + priority: priority!, + feeRate: customBitcoinFeeRate, + coinTypeToSpendFrom: coinTypeToSpendFrom, + ); case WalletType.monero: return monero! diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 72dcdb27b..f16b8390f 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/wallet_base.dart'; @@ -16,9 +17,11 @@ part 'unspent_coins_list_view_model.g.dart'; class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel; abstract class UnspentCoinsListViewModelBase with Store { - UnspentCoinsListViewModelBase( - {required this.wallet, required Box unspentCoinsInfo}) - : _unspentCoinsInfo = unspentCoinsInfo, + UnspentCoinsListViewModelBase({ + required this.wallet, + required Box unspentCoinsInfo, + this.coinTypeToSpendFrom = UnspentCoinType.any, + }) : _unspentCoinsInfo = unspentCoinsInfo, _items = ObservableList() { _updateUnspentCoinsInfo(); _updateUnspents(); @@ -26,6 +29,7 @@ abstract class UnspentCoinsListViewModelBase with Store { WalletBase wallet; final Box _unspentCoinsInfo; + final UnspentCoinType coinTypeToSpendFrom; @observable ObservableList _items; @@ -103,7 +107,7 @@ abstract class UnspentCoinsListViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - return bitcoin!.getUnspents(wallet); + return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom); default: return List.empty(); } diff --git a/tool/configure.dart b/tool/configure.dart index eb41c5341..ce079dd29 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -87,6 +87,7 @@ import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/wallet_base.dart'; @@ -94,6 +95,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -108,7 +110,6 @@ import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/bitcoin_receive_page_option.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; @@ -119,8 +120,6 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; -import 'package:cw_core/get_height_by_date.dart'; -import 'package:cw_core/transaction_info.dart'; import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart'; import 'package:mobx/mobx.dart'; """; @@ -166,8 +165,7 @@ abstract class Bitcoin { int getFeeRate(Object wallet, TransactionPriority priority); Future generateNewAddress(Object wallet, String label); Future updateAddress(Object wallet,String address, String label); - Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}); - Object createBitcoinTransactionCredentialsRaw(List outputs, {TransactionPriority? priority, required int feeRate}); + Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}); String getAddress(Object wallet); List getSilentPaymentAddresses(Object wallet); @@ -181,7 +179,7 @@ abstract class Bitcoin { int formatterStringDoubleToBitcoinAmount(String amount); String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, {int? customRate}); - List getUnspents(Object wallet); + List getUnspents(Object wallet, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}); Future updateUnspents(Object wallet); WalletService createBitcoinWalletService( Box walletInfoSource, Box unspentCoinSource, bool alwaysScan, bool isDirect);