From 0c2ee6690ff21eced0fcbc80039df161f72e7d84 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 22 Jan 2026 13:09:39 +0200 Subject: [PATCH] add connected device wallet discovery with configurable number of accounts to scan --- .../sparrowwallet/sparrow/AppController.java | 32 ++--- .../sparrow/control/DevicePane.java | 122 +++++++++++++++++- .../MnemonicWalletKeystoreImportPane.java | 12 +- .../sparrow/control/RangeInputDialog.java | 61 +++++++++ .../sparrow/control/WalletImportDialog.java | 10 +- .../sparrow/event/WalletImportEvent.java | 14 +- .../com/sparrowwallet/sparrow/io/Hwi.java | 25 ++-- .../sparrow/net/ElectrumServer.java | 26 +++- .../terminal/wallet/NewWalletDialog.java | 4 +- 9 files changed, 255 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/RangeInputDialog.java diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index e816e592..9a52fc32 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1217,22 +1217,24 @@ public class AppController implements Initializable { List selectedWalletForms = getSelectedWalletForms(); WalletImportDialog dlg = new WalletImportDialog(selectedWalletForms); dlg.initOwner(rootStack.getScene().getWindow()); - Optional optionalWallet = dlg.showAndWait(); - if(optionalWallet.isPresent()) { - Wallet wallet = optionalWallet.get(); + Optional> optionalWallets = dlg.showAndWait(); + if(optionalWallets.isPresent()) { + List wallets = optionalWallets.get(); - List walletTabData = getOpenWalletTabData(); - List xpubs = wallet.getKeystores().stream().map(Keystore::getExtendedPublicKey).collect(Collectors.toList()); - Optional optNewWalletForm = walletTabData.stream() - .map(WalletTabData::getWalletForm) - .filter(wf -> wf.getSettingsWalletForm() != null && wf.getSettingsWalletForm().getWallet().getPolicyType() == PolicyType.MULTI && - wf.getSettingsWalletForm().getWallet().getScriptType() == wallet.getScriptType() && !wf.getSettingsWalletForm().getWallet().isValid() && - wf.getSettingsWalletForm().getWallet().getKeystores().stream().map(Keystore::getExtendedPublicKey).anyMatch(xpubs::contains)).findFirst(); - if(optNewWalletForm.isPresent()) { - EventManager.get().post(new ExistingWalletImportedEvent(optNewWalletForm.get().getWalletId(), wallet)); - selectTab(optNewWalletForm.get().getWallet()); - } else if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) { - addImportedWallet(wallet); + for(Wallet wallet : wallets) { + List walletTabData = getOpenWalletTabData(); + List xpubs = wallet.getKeystores().stream().map(Keystore::getExtendedPublicKey).collect(Collectors.toList()); + Optional optNewWalletForm = walletTabData.stream() + .map(WalletTabData::getWalletForm) + .filter(wf -> wf.getSettingsWalletForm() != null && wf.getSettingsWalletForm().getWallet().getPolicyType() == PolicyType.MULTI && + wf.getSettingsWalletForm().getWallet().getScriptType() == wallet.getScriptType() && !wf.getSettingsWalletForm().getWallet().isValid() && + wf.getSettingsWalletForm().getWallet().getKeystores().stream().map(Keystore::getExtendedPublicKey).anyMatch(xpubs::contains)).findFirst(); + if(optNewWalletForm.isPresent()) { + EventManager.get().post(new ExistingWalletImportedEvent(optNewWalletForm.get().getWalletId(), wallet)); + selectTab(optNewWalletForm.get().getWallet()); + } else if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) { + addImportedWallet(wallet); + } } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java index cd82c548..65a5569b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java @@ -19,6 +19,7 @@ import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.net.ElectrumServer; +import com.sparrowwallet.sparrow.net.ServerType; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -313,6 +314,11 @@ public class DevicePane extends TitledDescriptionPane { }); importMenuButton.getItems().add(item); } + importMenuButton.getItems().add(new SeparatorMenuItem()); + MenuItem discoverItem = new MenuItem("Discover Wallet..."); + discoverItem.setDisable(!AppServices.isConnected()); + discoverItem.setOnAction(_ -> discoverWallet()); + importMenuButton.getItems().add(discoverItem); } else { String[] accounts = new String[] {"Default Account #0", "Account #1", "Account #2", "Account #3", "Account #4", "Account #5", "Account #6", "Account #7", "Account #8", "Account #9"}; int scriptAccountsLength = ScriptType.P2SH.equals(wallet.getScriptType()) ? 1 : accounts.length; @@ -378,7 +384,6 @@ public class DevicePane extends TitledDescriptionPane { discoverKeystoresButton = new Button("Discover"); discoverKeystoresButton.setAlignment(Pos.CENTER_RIGHT); discoverKeystoresButton.setOnAction(event -> { - discoverKeystoresButton.setDisable(true); discoverKeystores(); }); discoverKeystoresButton.managedProperty().bind(discoverKeystoresButton.visibleProperty()); @@ -903,29 +908,129 @@ public class DevicePane extends TitledDescriptionPane { } } + private void discoverWallet() { + importButton.setDisable(true); + importButton.setMaxHeight(importButton.getHeight()); + ProgressIndicator progressIndicator = new ProgressIndicator(0); + progressIndicator.getStyleClass().add("button-progress"); + importButton.setGraphic(progressIndicator); + List wallets = new ArrayList<>(); + + RangeInputDialog rangeInputDialog = new RangeInputDialog(StandardAccount.ACCOUNT_0.getAccountNumber(), StandardAccount.ACCOUNT_30.getAccountNumber(), StandardAccount.ACCOUNT_10.getAccountNumber()); + rangeInputDialog.setTitle("Choose number of accounts"); + rangeInputDialog.setHeaderText("Enter the number of additional accounts to scan for existing funds.\n\nThis may take a few minutes depending on how many accounts are selected."); + Optional optRange = rangeInputDialog.showAndWait(); + if(optRange.isEmpty()) { + return; + } + + List discoveryAccounts = new ArrayList<>(Arrays.asList(StandardAccount.values()).subList(0, optRange.get() + 1)); + Map derivationPaths = new LinkedHashMap<>(); + for(ScriptType scriptType : ScriptType.getAddressableScriptTypes(PolicyType.SINGLE)) { + for(StandardAccount discoveryAccount : discoveryAccounts) { + derivationPaths.put(new Hwi.WalletType(scriptType, discoveryAccount), KeyDerivation.writePath(scriptType.getDefaultDerivation(discoveryAccount.getAccountNumber()))); + } + } + + Hwi.GetXpubsService getXpubsService = new Hwi.GetXpubsService(device, passphrase.get(), derivationPaths); + getXpubsService.setOnSucceeded(_ -> { + Map accountXpubs = getXpubsService.getValue(); + + for(Map.Entry entry : accountXpubs.entrySet()) { + try { + Wallet wallet = new Wallet(device.getModel().toDisplayString()); + wallet.setPolicyType(PolicyType.SINGLE); + wallet.setScriptType(entry.getKey().scriptType()); + Keystore keystore = new Keystore(); + keystore.setLabel(device.getModel().toDisplayString()); + keystore.setSource(KeystoreSource.HW_USB); + keystore.setWalletModel(device.getModel()); + keystore.setKeyDerivation(new KeyDerivation(device.getFingerprint(), derivationPaths.get(entry.getKey()))); + keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(entry.getValue())); + wallet.getKeystores().add(keystore); + wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, entry.getKey().scriptType(), wallet.getKeystores(), 1)); + if(entry.getKey().standardAccount().equals(StandardAccount.ACCOUNT_0)) { + wallets.add(wallet); + } else { + Wallet masterWallet = wallets.getLast(); + wallet.setName(entry.getKey().standardAccount().getName()); + wallet.setMasterWallet(masterWallet); + masterWallet.getChildWallets().add(wallet); + } + } catch(Exception e) { + setError("Could not retrieve xpub", e.getMessage()); + } + } + + ElectrumServer.WalletDiscoveryService walletDiscoveryService = new ElectrumServer.WalletDiscoveryService(wallets); + walletDiscoveryService.setOnSucceeded(_ -> { + importButton.setGraphic(null); + Optional> optWallets = walletDiscoveryService.getValue(); + if(optWallets.isPresent()) { + List discoveredWallets = optWallets.get(); + if(discoveredWallets.size() > 1) { + for(Wallet wallet : discoveredWallets) { + wallet.setName(wallet.getName() + " " + wallet.getScriptType().getDescription()); + } + } + EventManager.get().post(new WalletImportEvent(discoveredWallets)); + } else { + AppServices.showErrorDialog("No existing wallet found", + Config.get().getServerType() == ServerType.BITCOIN_CORE ? "The configured server type is Bitcoin Core, which does not support wallet discovery.\n\n" + + "You can however import the " + device.getModel().toDisplayString() + " and scan the blockchain by supplying a start date." : + "Could not find a wallet with existing transactions using the " + device.getModel().toDisplayString() + "."); + setDefaultStatus(); + importButton.setDisable(false); + } + }); + walletDiscoveryService.setOnFailed(failedEvent -> { + log.error("Failed to discover wallets", failedEvent.getSource().getException()); + setError("Failed to discover wallets", failedEvent.getSource().getException().getMessage()); + importButton.setGraphic(null); + importButton.setDisable(false); + }); + walletDiscoveryService.start(); + }); + getXpubsService.setOnFailed(_ -> { + setError("Could not retrieve xpub", getXpubsService.getException().getMessage()); + importButton.setGraphic(null); + importButton.setDisable(false); + }); + progressIndicator.progressProperty().bind(getXpubsService.progressProperty()); + getXpubsService.progressProperty().addListener((_, _, newValue) -> setDescription("Discovering... (" + Math.round(newValue.doubleValue() * 100) + "%)")); + showHideLink.setVisible(false); + getXpubsService.start(); + } + private void discoverKeystores() { if(wallet.getKeystores().size() != 1) { setError("Could not discover keystores", "Only single signature wallets are supported for keystore discovery"); return; } + discoverKeystoresButton.setDisable(true); + discoverKeystoresButton.setMaxHeight(discoverKeystoresButton.getHeight()); + ProgressIndicator progressIndicator = new ProgressIndicator(0); + progressIndicator.getStyleClass().add("button-progress"); + discoverKeystoresButton.setGraphic(progressIndicator); + String masterFingerprint = wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint(); Wallet copyWallet = wallet.copy(); - Map accountDerivationPaths = new LinkedHashMap<>(); + Map accountDerivationPaths = new LinkedHashMap<>(); for(StandardAccount availableAccount : availableAccounts) { Wallet availableWallet = copyWallet.addChildWallet(availableAccount); Keystore availableKeystore = availableWallet.getKeystores().get(0); String derivationPath = availableKeystore.getKeyDerivation().getDerivationPath(); - accountDerivationPaths.put(availableAccount, derivationPath); + accountDerivationPaths.put(new Hwi.WalletType(wallet.getScriptType(), availableAccount), derivationPath); } Map importedKeystores = new LinkedHashMap<>(); Hwi.GetXpubsService getXpubsService = new Hwi.GetXpubsService(device, passphrase.get(), accountDerivationPaths); getXpubsService.setOnSucceeded(workerStateEvent -> { - Map accountXpubs = getXpubsService.getValue(); + Map accountXpubs = getXpubsService.getValue(); - for(Map.Entry entry : accountXpubs.entrySet()) { + for(Map.Entry entry : accountXpubs.entrySet()) { try { Keystore keystore = new Keystore(); keystore.setLabel(device.getModel().toDisplayString()); @@ -933,7 +1038,7 @@ public class DevicePane extends TitledDescriptionPane { keystore.setWalletModel(device.getModel()); keystore.setKeyDerivation(new KeyDerivation(masterFingerprint, accountDerivationPaths.get(entry.getKey()))); keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(entry.getValue())); - importedKeystores.put(entry.getKey(), keystore); + importedKeystores.put(entry.getKey().standardAccount(), keystore); } catch(Exception e) { setError("Could not retrieve xpub", e.getMessage()); } @@ -947,15 +1052,18 @@ public class DevicePane extends TitledDescriptionPane { accountDiscoveryService.setOnFailed(event -> { log.error("Failed to discover accounts", event.getSource().getException()); setError("Failed to discover accounts", event.getSource().getException().getMessage()); + discoverKeystoresButton.setGraphic(null); discoverKeystoresButton.setDisable(false); }); accountDiscoveryService.start(); }); getXpubsService.setOnFailed(workerStateEvent -> { setError("Could not retrieve xpub", getXpubsService.getException().getMessage()); + discoverKeystoresButton.setGraphic(null); discoverKeystoresButton.setDisable(false); }); - setDescription("Discovering..."); + progressIndicator.progressProperty().bind(getXpubsService.progressProperty()); + getXpubsService.progressProperty().addListener((_, _, newValue) -> setDescription("Discovering... (" + Math.round(newValue.doubleValue() * 100) + "%)")); showHideLink.setVisible(false); getXpubsService.start(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicWalletKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicWalletKeystoreImportPane.java index 9aff3134..494f8eba 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicWalletKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicWalletKeystoreImportPane.java @@ -134,9 +134,15 @@ public class MnemonicWalletKeystoreImportPane extends MnemonicKeystorePane { progressIndicator.progressProperty().bind(walletDiscoveryService.progressProperty()); walletDiscoveryService.setOnSucceeded(successEvent -> { discoverButton.setGraphic(null); - Optional optWallet = walletDiscoveryService.getValue(); - if(optWallet.isPresent()) { - EventManager.get().post(new WalletImportEvent(optWallet.get())); + Optional> optWallets = walletDiscoveryService.getValue(); + if(optWallets.isPresent()) { + List discoveredWallets = optWallets.get(); + if(discoveredWallets.size() > 1) { + for(Wallet wallet : discoveredWallets) { + wallet.setName(wallet.getKeystores().getFirst().getLabel() + " " + wallet.getScriptType().getDescription()); + } + } + EventManager.get().post(new WalletImportEvent(discoveredWallets)); } else { discoverButton.setDisable(false); Optional optButtonType = AppServices.showErrorDialog("No existing wallet found", diff --git a/src/main/java/com/sparrowwallet/sparrow/control/RangeInputDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/RangeInputDialog.java new file mode 100644 index 00000000..d1dc92c9 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/RangeInputDialog.java @@ -0,0 +1,61 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import org.controlsfx.glyphfont.Glyph; + +public class RangeInputDialog extends Dialog { + private final Spinner spinner; + + public RangeInputDialog(int min, int max, int initialValue) { + final DialogPane dialogPane = getDialogPane(); + + setTitle("Select a Value"); + setHeaderText("Choose a value between " + min + " and " + max); + + Glyph key = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SORT_NUMERIC_DOWN); + key.setFontSize(50); + key.setPadding(new Insets(0, 0, 0, 10)); + dialogPane.setGraphic(key); + dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + spinner = new Spinner<>(); + spinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, initialValue)); + spinner.setPrefWidth(80); + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 20, 10, 20)); + + grid.add(new Label("Enter value between " + min + " and " + max + ":"), 0, 0); + grid.add(spinner, 1, 0); + + dialogPane.setContent(grid); + dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); + AppServices.setStageIcon(dialogPane.getScene().getWindow()); + + Platform.runLater(spinner::requestFocus); + + setResultConverter((dialogButton) -> { + ButtonBar.ButtonData data = dialogButton == null ? null : dialogButton.getButtonData(); + return data == ButtonBar.ButtonData.OK_DONE ? spinner.getValue() : null; + }); + + dialogPane.setPrefWidth(500); + dialogPane.setPrefHeight(230); + AppServices.moveToActiveWindowScreen(this); + } + + public void setValue(int value) { + spinner.getValueFactory().setValue(value); + } + + public int getValue() { + return spinner.getValue(); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java index dcdef393..7c56b218 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -21,8 +21,8 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -public class WalletImportDialog extends Dialog { - private Wallet wallet; +public class WalletImportDialog extends Dialog> { + private List wallets; private final Accordion importAccordion; private final Button scanButton; @@ -95,13 +95,13 @@ public class WalletImportDialog extends Dialog { dialogPane.setMinHeight(dialogPane.getPrefHeight()); AppServices.moveToActiveWindowScreen(this); - setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null); + setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallets : null); } @Subscribe public void walletImported(WalletImportEvent event) { - wallet = event.getWallet(); - setResult(wallet); + wallets = event.getWallets(); + setResult(wallets); } private void scan() { diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java index 6dfc7aee..9a6afbef 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java @@ -2,14 +2,20 @@ package com.sparrowwallet.sparrow.event; import com.sparrowwallet.drongo.wallet.Wallet; +import java.util.List; + public class WalletImportEvent { - private Wallet wallet; + private List wallets; public WalletImportEvent(Wallet wallet) { - this.wallet = wallet; + this.wallets = List.of(wallet); } - public Wallet getWallet() { - return wallet; + public WalletImportEvent(List wallets) { + this.wallets = wallets; + } + + public List getWallets() { + return wallets; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java index 75e319fc..0b6ecc82 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java @@ -17,6 +17,9 @@ import com.sparrowwallet.sparrow.SparrowWallet; import com.sparrowwallet.sparrow.control.BitBoxPairingDialog; import com.sparrowwallet.sparrow.control.TextfieldDialog; import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableMap; import javafx.concurrent.ScheduledService; import javafx.concurrent.Service; import javafx.concurrent.Task; @@ -142,9 +145,8 @@ public class Hwi { } } - public Map getXpubs(Device device, String passphrase, Map accountDerivationPaths) throws ImportException { - Map accountXpubs = new LinkedHashMap<>(); - for(Map.Entry entry : accountDerivationPaths.entrySet()) { + public Map getXpubs(Device device, String passphrase, Map accountDerivationPaths, Map accountXpubs) throws ImportException { + for(Map.Entry entry : accountDerivationPaths.entrySet()) { accountXpubs.put(entry.getKey(), getXpub(device, passphrase, entry.getValue())); } @@ -441,23 +443,26 @@ public class Hwi { } } - public static class GetXpubsService extends Service> { + public static class GetXpubsService extends Service> { private final Device device; private final String passphrase; - private final Map accountDerivationPaths; + private final Map accountDerivationPaths; - public GetXpubsService(Device device, String passphrase, Map accountDerivationPaths) { + public GetXpubsService(Device device, String passphrase, Map accountDerivationPaths) { this.device = device; this.passphrase = passphrase; this.accountDerivationPaths = accountDerivationPaths; } @Override - protected Task> createTask() { + protected Task> createTask() { return new Task<>() { - protected Map call() throws ImportException { + protected Map call() throws ImportException { Hwi hwi = new Hwi(); - return hwi.getXpubs(device, passphrase, accountDerivationPaths); + updateProgress(0, accountDerivationPaths.size()); + ObservableMap accountXpubs = FXCollections.observableMap(new LinkedHashMap<>()); + accountXpubs.addListener((MapChangeListener) _ -> updateProgress(accountXpubs.size(), accountDerivationPaths.size())); + return hwi.getXpubs(device, passphrase, accountDerivationPaths, accountXpubs); } }; } @@ -630,4 +635,6 @@ public class Hwi { Platform.runLater(() -> AppServices.showSuccessDialog("Pairing Successful", "The " + deviceInfo + " has been successfully paired.")); } } + + public record WalletType(ScriptType scriptType, StandardAccount standardAccount) {} } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 3c089ee8..9112d128 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -2136,7 +2136,7 @@ public class ElectrumServer { } } - public static class WalletDiscoveryService extends Service> { + public static class WalletDiscoveryService extends Service>> { private final List wallets; public WalletDiscoveryService(List wallets) { @@ -2144,17 +2144,31 @@ public class ElectrumServer { } @Override - protected Task> createTask() { + protected Task>> createTask() { return new Task<>() { - protected Optional call() throws ServerException { + protected Optional> call() throws ServerException { ElectrumServer electrumServer = new ElectrumServer(); + List discoveredWallets = new ArrayList<>(); for(int i = 0; i < wallets.size(); i++) { Wallet wallet = wallets.get(i); updateProgress(i, wallets.size() + StandardAccount.DISCOVERY_ACCOUNTS.size()); Map> nodeTransactionMap = new TreeMap<>(); electrumServer.getReferences(wallet, wallet.getNode(KeyPurpose.RECEIVE).getChildren(), nodeTransactionMap, 0); - if(nodeTransactionMap.values().stream().anyMatch(blockTransactionHashes -> !blockTransactionHashes.isEmpty())) { + boolean found = nodeTransactionMap.values().stream().anyMatch(blockTransactionHashes -> !blockTransactionHashes.isEmpty()); + + for(Iterator iterator = wallet.getChildWallets().iterator(); iterator.hasNext(); ) { + Wallet childWallet = iterator.next(); + Map> childTransactionMap = new TreeMap<>(); + electrumServer.getReferences(childWallet, childWallet.getNode(KeyPurpose.RECEIVE).getChildren(), childTransactionMap, 0); + if(childTransactionMap.values().stream().anyMatch(blockTransactionHashes -> !blockTransactionHashes.isEmpty())) { + found = true; + } else { + iterator.remove(); + } + } + + if(found) { Wallet masterWalletCopy = wallet.copy(); List searchAccounts = getStandardAccounts(wallet); Set foundAccounts = new LinkedHashSet<>(); @@ -2177,11 +2191,11 @@ public class ElectrumServer { wallet.addChildWallet(standardAccount); } - return Optional.of(wallet); + discoveredWallets.add(wallet); } } - return Optional.empty(); + return discoveredWallets.isEmpty() ? Optional.empty() : Optional.of(discoveredWallets); } }; } diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java index 953ecb04..945efe1e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java @@ -88,8 +88,8 @@ public abstract class NewWalletDialog extends DialogWindow { Platform.runLater(() -> { ElectrumServer.WalletDiscoveryService walletDiscoveryService = new ElectrumServer.WalletDiscoveryService(wallets); walletDiscoveryService.setOnSucceeded(successEvent -> { - Optional optWallet = walletDiscoveryService.getValue(); - wallet = optWallet.orElseGet(() -> wallets.get(0)); + Optional> optWallets = walletDiscoveryService.getValue(); + wallet = optWallets.orElseGet(() -> wallets).getFirst(); SparrowTerminal.get().getGuiThread().invokeLater(() -> { SparrowTerminal.get().getGui().removeWindow(discoveringDialog); saveWallet(wallet);