diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 2a189c181..695293595 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -73,6 +73,19 @@ class CWBitcoin extends Bitcoin { return bitcoinWallet.keys.toJson(); } + @override + Map getSilentPaymentKeys(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + final keysOwner = bitcoinWallet.walletAddresses.silentAddress!; + + return { + 'privateSpendKey': keysOwner.b_spend.toHex(), + 'publicSpendKey': keysOwner.B_spend.toHex(), + 'privateViewKey': keysOwner.b_scan.toHex(), + 'publicViewKey': keysOwner.B_scan.toHex(), + }; + } + @override List getTransactionPriorities() => BitcoinTransactionPriority.all; @@ -116,9 +129,7 @@ class CWBitcoin extends Bitcoin { String? payjoinUri, }) { final bitcoinFeeRate = - priority == BitcoinTransactionPriority.custom && feeRate != null - ? feeRate - : null; + priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null; return BitcoinTransactionCredentials( outputs .map((out) => OutputInfo( @@ -237,12 +248,12 @@ class CWBitcoin extends Bitcoin { Box unspentCoinSource, Box payjoinSessionSource, bool isDirect) { - return BitcoinWalletService(walletInfoSource, unspentCoinSource, - payjoinSessionSource, isDirect); + return BitcoinWalletService( + walletInfoSource, unspentCoinSource, payjoinSessionSource, isDirect); } - WalletService createLitecoinWalletService(Box walletInfoSource, - Box unspentCoinSource, bool isDirect) { + WalletService createLitecoinWalletService( + Box walletInfoSource, Box unspentCoinSource, bool isDirect) { return LitecoinWalletService(walletInfoSource, unspentCoinSource, isDirect); } @@ -775,4 +786,20 @@ class CWBitcoin extends Bitcoin { (_wallet.walletAddresses as BitcoinWalletAddresses).currentPayjoinReceiver = null; (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinEndpoint = null; } + + @override + String? getTransactionAddress(Object wallet, TransactionInfo tx) { + final bitcoinWallet = wallet as BitcoinWallet; + final bitcoinTx = tx as ElectrumTransactionInfo; + + if (bitcoinTx.unspents == null || bitcoinTx.unspents!.isEmpty) { + return null; + } + + // final bitcoinSPAddrs = + // bitcoinTx.unspents!.first.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; + final bitcoinSPAddrs = wallet.walletAddresses as BitcoinWalletAddresses; + final bitcoinSPAddr = bitcoinSPAddrs.silentAddresses.first; + return bitcoinSPAddr.address; + } } diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 6bffd5f12..014805d85 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/seedphrase_grid_widget.dart'; import 'package:cake_wallet/src/widgets/text_info_box.dart'; @@ -67,6 +68,7 @@ class _WalletKeysPageBodyState extends State with SingleTickerProviderStateMixin { late TabController _tabController; late bool showKeyTab; + late bool showSilentPaymentsTab; late bool showLegacySeedTab; late bool isLegacySeedOnly; @@ -79,10 +81,15 @@ class _WalletKeysPageBodyState extends State super.initState(); showKeyTab = widget.walletKeysViewModel.items.isNotEmpty; + showSilentPaymentsTab = + widget.walletKeysViewModel.isBitcoin && widget.walletKeysViewModel.items.length > 4; showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty; isLegacySeedOnly = widget.walletKeysViewModel.isLegacySeedOnly; - final totalTabs = (_hasSeeds ? 1 : 0) + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0); + final totalTabs = (_hasSeeds ? 1 : 0) + + (showKeyTab ? 1 : 0) + + (showLegacySeedTab ? 1 : 0) + + (showSilentPaymentsTab ? 1 : 0); _tabController = TabController(length: totalTabs, vsync: this); } @@ -108,13 +115,11 @@ class _WalletKeysPageBodyState extends State isScrollable: true, labelStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 18, - fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurfaceVariant, ), unselectedLabelStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 18, - fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5), ), @@ -126,9 +131,20 @@ class _WalletKeysPageBodyState extends State dividerColor: Colors.transparent, padding: EdgeInsets.zero, tabs: [ - if (_hasSeeds) Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), - if (showKeyTab) Tab(text: S.of(context).keys, key: ValueKey('wallet_keys_page_keys'),), - if (showLegacySeedTab) Tab(text: S.of(context).legacy, key: ValueKey('wallet_keys_page_seed_legacy')), + if (_hasSeeds) + Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), + if (showKeyTab) + Tab( + text: S.of(context).keys, + key: ValueKey('wallet_keys_page_keys'), + ), + if (showSilentPaymentsTab) + Tab( + text: S.of(context).silent_payments, + key: ValueKey('wallet_keys_silent_payments_keys'), + ), + if (showLegacySeedTab) + Tab(text: S.of(context).legacy, key: ValueKey('wallet_keys_page_seed_legacy')), ], ), ), @@ -145,7 +161,12 @@ class _WalletKeysPageBodyState extends State if (showKeyTab) Padding( padding: const EdgeInsets.only(left: 22, right: 22), - child: _buildKeysTab(context), + child: _buildKeysTab(context, showSilentPaymentsTab ? widget.walletKeysViewModel.items.sublist(0, 4) : widget.walletKeysViewModel.items), + ), + if (showSilentPaymentsTab) + Padding( + padding: const EdgeInsets.only(left: 22, right: 22), + child: _buildKeysTab(context, widget.walletKeysViewModel.items.sublist(4)), ), if (showLegacySeedTab) Padding( @@ -163,7 +184,7 @@ class _WalletKeysPageBodyState extends State Widget _buildSeedTab(BuildContext context, bool isLegacySeed) { return Column( children: [ - if (isLegacySeedOnly || isLegacySeed) ...[ + if (isLegacySeedOnly || isLegacySeed || widget.walletKeysViewModel.isBitcoin) ...[ _buildHeightBox(), const SizedBox(height: 20), ], @@ -189,15 +210,15 @@ class _WalletKeysPageBodyState extends State ); } - Widget _buildKeysTab(BuildContext context) { + Widget _buildKeysTab(BuildContext context, List items) { return Column( children: [ Expanded( child: ListView.separated( shrinkWrap: true, - itemCount: widget.walletKeysViewModel.items.length, + itemCount: items.length, itemBuilder: (context, index) { - final item = widget.walletKeysViewModel.items[index]; + final item = items[index]; return TextInfoBox( key: item.key, title: item.title, diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 691c94f56..bdb29e1c0 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -90,7 +90,7 @@ abstract class TransactionDetailsViewModelBase with Store { break; case WalletType.none: case WalletType.banano: - break; + break; } final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; @@ -235,9 +235,8 @@ abstract class TransactionDetailsViewModelBase with Store { void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) { final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; final description = transactionDescriptionBox.values.firstWhere( - (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, - orElse: () => TransactionDescription(id: descriptionKey)); - + (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, + orElse: () => TransactionDescription(id: descriptionKey)); final key = tx.additionalInfo['key'] as String? ?? description.transactionKey; final accountIndex = tx.additionalInfo['accountIndex'] as int; @@ -344,6 +343,25 @@ abstract class TransactionDetailsViewModelBase with Store { ), ]; + if (wallet.type == WalletType.bitcoin && tx.direction == TransactionDirection.incoming) { + try { + final address = bitcoin!.getTransactionAddress(wallet, tx); + + if (address != null) { + isRecipientAddressShown = true; + _items.add( + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: address, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + ); + } + } catch (e) { + printV(e.toString()); + } + } + items.addAll(_items); } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 31b4cc53e..2e7eb8144 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -49,6 +49,8 @@ abstract class WalletKeysViewModelBase with Store { } } + bool get isBitcoin => _wallet.type == WalletType.bitcoin; + final ObservableList items; final String title; @@ -66,8 +68,7 @@ abstract class WalletKeysViewModelBase with Store { bool get isLegacySeedOnly => [WalletType.monero, WalletType.wownero].contains(_wallet.type) && _wallet.seed != null && - !(Polyseed.isValidSeed(_wallet.seed!) || - _wallet.seed!.split(' ').length == 12); + !(Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12); String get legacySeed { if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) && @@ -163,18 +164,22 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - final keys = bitcoin!.getWalletKeys(_appStore.wallet!); - - items.addAll([ - if ((keys['wif']??'').isNotEmpty) - StandartListItem(title: "WIF", value: keys['wif']!), - if ((keys['privateKey']??'').isNotEmpty) - StandartListItem(title: S.current.private_key, value: keys['privateKey']!), - if (keys['publicKey'] != null) - StandartListItem(title: S.current.public_key, value: keys['publicKey']!), - if (keys['xpub'] != null) - StandartListItem(title: "xPub", value: keys['xpub']!), - ]); + if (_wallet.type == WalletType.bitcoin) { + keys = bitcoin!.getSilentPaymentKeys(_appStore.wallet!); + } + + final electrumKeys = bitcoin!.getWalletKeys(_appStore.wallet!); + + items.addAll([ + if ((electrumKeys['wif'] ?? '').isNotEmpty) + StandartListItem(title: "WIF", value: electrumKeys['wif']!), + if ((electrumKeys['privateKey'] ?? '').isNotEmpty) + StandartListItem(title: S.current.private_key, value: electrumKeys['privateKey']!), + if (electrumKeys['publicKey'] != null) + StandartListItem(title: S.current.public_key, value: electrumKeys['publicKey']!), + if (electrumKeys['xpub'] != null) + StandartListItem(title: "xPub", value: electrumKeys['xpub']!), + ]); break; case WalletType.none: case WalletType.haven: diff --git a/tool/configure.dart b/tool/configure.dart index 39b52c771..d91c1154b 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -253,6 +253,8 @@ abstract class Bitcoin { String getPayjoinEndpoint(Object wallet); void resumePayjoinSessions(Object wallet); void stopPayjoinSessions(Object wallet); + Map getSilentPaymentKeys(Object wallet); + String? getTransactionAddress(Object wallet, TransactionInfo tx); } """; @@ -939,15 +941,12 @@ abstract class BitcoinCash { """; const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n'; - const bitcoinCashCWDefinition = - 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; + const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; final output = '$bitcoinCashCommonHeaders\n' + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') + - (hasImplementation - ? bitcoinCashCWDefinition - : bitcoinCashEmptyDefinition) + + (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) + '\n' + bitcoinCashContent; @@ -1082,8 +1081,7 @@ abstract class NanoUtil { """; const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n'; - const nanoCWDefinition = - 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n'; + const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n'; final output = '$nanoCommonHeaders\n' + (hasImplementation ? '$nanoCWHeaders\n' : '\n') + @@ -1324,11 +1322,11 @@ abstract class Zano { const zanoCWDefinition = 'Zano? zano = CWZano();\n'; final output = '$zanoCommonHeaders\n' + - (hasImplementation ? '$zanoCWHeaders\n' : '\n') + - (hasImplementation ? '$zanoCwPart\n\n' : '\n') + - (hasImplementation ? zanoCWDefinition : zanoEmptyDefinition) + - '\n' + - zanoContent; + (hasImplementation ? '$zanoCWHeaders\n' : '\n') + + (hasImplementation ? '$zanoCwPart\n\n' : '\n') + + (hasImplementation ? zanoCWDefinition : zanoEmptyDefinition) + + '\n' + + zanoContent; if (outputFile.existsSync()) { await outputFile.delete();