diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 177d61e87..3d71a0c39 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -54,6 +54,17 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { final String wif; } +class BitcoinWalletFromKeysCredentials extends WalletCredentials { + BitcoinWalletFromKeysCredentials({ + required String name, + required String password, + required this.xpub, + WalletInfo? walletInfo, + }) : super(name: name, password: password, walletInfo: walletInfo); + + final String xpub; +} + class BitcoinRestoreWalletFromHardware extends WalletCredentials { BitcoinRestoreWalletFromHardware({ required String name, diff --git a/cw_bitcoin/lib/bitcoin_wallet_keys.dart b/cw_bitcoin/lib/bitcoin_wallet_keys.dart index 0a4afc10d..4ed0da49c 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_keys.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_keys.dart @@ -1,7 +1,15 @@ class BitcoinWalletKeys { - const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey}); + const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); final String wif; final String privateKey; final String publicKey; + final String xpub; + + Map toJson() => { + 'wif': wif, + 'privateKey': privateKey, + 'publicKey': publicKey, + 'xpub': xpub + }; } \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 317b25bcd..74be5e231 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -20,7 +20,7 @@ import 'package:bip39/bip39.dart' as bip39; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials, + BitcoinWalletFromKeysCredentials, BitcoinRestoreWalletFromHardware> { BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.payjoinSessionSource, this.alwaysScan, this.isDirect); @@ -169,9 +169,24 @@ class BitcoinWalletService extends WalletService< } @override - Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => - throw UnimplementedError(); + Future restoreFromKeys(BitcoinWalletFromKeysCredentials credentials, + {bool? isTestnet}) async { + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; + credentials.walletInfo?.network = network.value; + + final wallet = await BitcoinWallet( + password: credentials.password!, + xpub: credentials.xpub, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + networkParam: network, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.save(); + await wallet.init(); + return wallet; + } @override Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index bb9cea1bc..a29efa48c 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -280,11 +280,26 @@ abstract class ElectrumWalletBase } @override - BitcoinWalletKeys get keys => BitcoinWalletKeys( - wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer), - privateKey: hd.privateKey.toHex(), - publicKey: hd.publicKey.toHex(), - ); + BitcoinWalletKeys get keys { + String? wif; + String? privateKey; + String? publicKey; + try { + wif = WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer); + } catch (_) {} + try { + privateKey = hd.privateKey.toHex(); + } catch (_) {} + try { + publicKey = hd.publicKey.toHex(); + } catch (_) {} + return BitcoinWalletKeys( + wif: wif ?? '', + privateKey: privateKey ?? '', + publicKey: publicKey ?? '', + xpub: xpub, + ); + } String _password; List unspentCoins; diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 6930524eb..c95d4e1d7 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -165,6 +165,6 @@ class PendingBitcoinTransaction with PendingTransaction { @override Future commitUR() { - throw UnimplementedError(); + return Future.value("test"); } } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 131bc3a02..11434b64b 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -18,6 +18,14 @@ class CWBitcoin extends Bitcoin { passphrase: passphrase, ); + @override + WalletCredentials createBitcoinWalletFromKeys({ + required String name, + required String password, + required String xpub, + }) => + BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub); + @override WalletCredentials createBitcoinRestoreWalletFromWIFCredentials( {required String name, @@ -62,11 +70,7 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; final keys = bitcoinWallet.keys; - return { - 'wif': keys.wif, - 'privateKey': keys.privateKey, - 'publicKey': keys.publicKey - }; + return bitcoinWallet.keys.toJson(); } @override diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 9ca5c2508..5713ad7d0 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -198,14 +198,14 @@ class WalletRestoreFromKeysFormState extends State { Widget _restoreFromKeysFormFields() { // Decred can only restore a view only wallet with an account pubkey. Other // fields are not used. - if (widget.walletRestoreViewModel.type == WalletType.decred) { + if (widget.walletRestoreViewModel.onlyViewKeyRestore) { return Column( children: [ BaseTextFormField( controller: viewKeyController, hintText: S.of(context).view_key_public, maxLines: null, - ) + ), ], ); } @@ -253,13 +253,14 @@ class WalletRestoreFromKeysFormState extends State { maxLines: null, ), ), - BlockchainHeightWidget( - key: blockchainHeightKey, - hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, - onHeightChange: (_) => null, - onHeightOrDateEntered: widget.onHeightOrDateEntered, - walletType: widget.walletRestoreViewModel.type, - ), + if (widget.walletRestoreViewModel.hasBlockchainHeightSelector) + BlockchainHeightWidget( + key: blockchainHeightKey, + hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, + onHeightChange: (_) => null, + onHeightOrDateEntered: widget.onHeightOrDateEntered, + walletType: widget.walletRestoreViewModel.type, + ), ], ); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index dd4a284be..411178c83 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -199,7 +199,7 @@ class WalletRestorePage extends BasePage { credentials['seed'] = walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; - if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + if (walletRestoreViewModel.hasBlockchainHeightSelector) { credentials['height'] = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ?? -1; @@ -219,7 +219,7 @@ class WalletRestorePage extends BasePage { credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; - if (walletRestoreViewModel.type != WalletType.decred) { + if (!walletRestoreViewModel.onlyViewKeyRestore) { credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; credentials['spendKey'] = @@ -519,7 +519,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } }, onViewKeyEntered: (bool entered) { - if (walletRestoreViewModel.type == WalletType.decred) { + if (widget.walletRestoreViewModel.onlyViewKeyRestore) { walletRestoreViewModel.isButtonEnabled = entered; } }, @@ -536,7 +536,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> key: widget.walletRestoreFromSeedFormKey, restoredWallet: walletRestoreViewModel.restoredWallet, seedSettingsViewModel: widget.seedSettingsViewModel, - displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightSelector, displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector, type: widget.walletRestoreViewModel.type, blockHeightFocusNode: widget.blockHeightFocusNode, @@ -563,7 +563,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } void _validateOnChange({bool isPolyseed = false}) { - if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightSelector) { final hasHeight = walletRestoreFromSeedFormKey .currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty; diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 917891854..6bffd5f12 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -82,7 +82,7 @@ class _WalletKeysPageBodyState extends State showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty; isLegacySeedOnly = widget.walletKeysViewModel.isLegacySeedOnly; - final totalTabs = 1 + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0); + final totalTabs = (_hasSeeds ? 1 : 0) + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0); _tabController = TabController(length: totalTabs, vsync: this); } @@ -126,7 +126,7 @@ class _WalletKeysPageBodyState extends State dividerColor: Colors.transparent, padding: EdgeInsets.zero, tabs: [ - Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), + 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')), ], @@ -137,10 +137,11 @@ class _WalletKeysPageBodyState extends State child: TabBarView( controller: _tabController, children: [ - Padding( - padding: const EdgeInsets.only(left: 22, right: 22), - child: _buildSeedTab(context, false), - ), + if (_hasSeeds) + Padding( + padding: const EdgeInsets.only(left: 22, right: 22), + child: _buildSeedTab(context, false), + ), if (showKeyTab) Padding( padding: const EdgeInsets.only(left: 22, right: 22), diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index da5d04f59..bf181a2ea 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; @@ -162,18 +163,21 @@ 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']!), + ]); + break; case WalletType.none: case WalletType.haven: - // final keys = bitcoin!.getWalletKeys(_appStore.wallet!); - // - // items.addAll([ - // if (keys['wif'] != null) - // StandartListItem(title: "WIF", value: keys['wif']!), - // if (keys['privateKey'] != null) - // StandartListItem(title: S.current.private_key, value: keys['privateKey']!), - // if (keys['publicKey'] != null) - // StandartListItem(title: S.current.public_key, value: keys['publicKey']!), - // ]); break; } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 6e00ba4cc..9497f27e0 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -32,17 +32,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, SeedSettingsViewModel seedSettingsViewModel, {required WalletType type, this.restoredWallet}) - : hasSeedLanguageSelector = - type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero, - hasBlockchainHeightLanguageSelector = - type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero, - hasRestoreFromPrivateKey = type == WalletType.ethereum || - type == WalletType.polygon || - type == WalletType.nano || - type == WalletType.banano || - type == WalletType.solana || - type == WalletType.tron, - isButtonEnabled = false, + : isButtonEnabled = false, hasPassphrase = false, mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, @@ -60,9 +50,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.ethereum: case WalletType.polygon: case WalletType.decred: + case WalletType.bitcoin: availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys]; break; - case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.zano: @@ -82,9 +72,34 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { static const decredSeedMnemonicLength = 15; late List availableModes; - final bool hasSeedLanguageSelector; - final bool hasBlockchainHeightLanguageSelector; - final bool hasRestoreFromPrivateKey; + late final bool hasSeedLanguageSelector = [ + WalletType.monero, + WalletType.haven, + WalletType.wownero + ].contains(type); + + late final bool hasBlockchainHeightSelector = [ + WalletType.monero, + WalletType.haven, + WalletType.wownero + ].contains(type); + + late final bool hasRestoreFromPrivateKey = [ + WalletType.ethereum, + WalletType.polygon, + WalletType.nano, + WalletType.banano, + WalletType.solana, + WalletType.tron + ].contains(type); + + late final bool onlyViewKeyRestore = [ + WalletType.bitcoin, + WalletType.litecoin, + WalletType.bitcoinCash, + WalletType.decred + ].contains(type); + final RestoredWallet? restoredWallet; @observable @@ -198,6 +213,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { final address = options['address'] as String?; switch (type) { + case WalletType.bitcoin: + return bitcoin!.createBitcoinWalletFromKeys( + name: name, + password: password, + xpub: viewKey!, + ); + case WalletType.monero: return monero!.createMoneroRestoreWalletFromKeysCredentials( name: name, @@ -276,8 +298,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.litecoin: String? mnemonic = credentials['seed'] as String?; String? passphrase = credentials['passphrase'] as String?; + if (mnemonic == null) break; return bitcoin!.getDerivationsFromMnemonic( - mnemonic: mnemonic!, + mnemonic: mnemonic, node: node, passphrase: passphrase, ); diff --git a/tool/configure.dart b/tool/configure.dart index f6e1496de..42f137375 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -163,6 +163,7 @@ abstract class Bitcoin { String? passphrase, }); WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo}); + WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub}); WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}); List getWordList();