mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
add multiple account functionality
This commit is contained in:
parent
429b733140
commit
58e3b9dcdd
19 changed files with 345 additions and 96 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit c9e57fad018750a150a563cd17c8872d7cda48f0
|
Subproject commit 9a9a1b925461ee2a8ae7f6885309b0babd4b6767
|
|
@ -1232,7 +1232,7 @@ public class AppController implements Initializable {
|
||||||
if(walletTabData.getWallet() == wallet.getMasterWallet()) {
|
if(walletTabData.getWallet() == wallet.getMasterWallet()) {
|
||||||
TabPane subTabs = (TabPane)walletTab.getContent();
|
TabPane subTabs = (TabPane)walletTab.getContent();
|
||||||
addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
||||||
Tab masterTab = subTabs.getTabs().get(0);
|
Tab masterTab = subTabs.getTabs().stream().filter(tab -> ((WalletTabData)tab.getUserData()).getWallet().isMasterWallet()).findFirst().orElse(subTabs.getTabs().get(0));
|
||||||
Label masterLabel = (Label)masterTab.getGraphic();
|
Label masterLabel = (Label)masterTab.getGraphic();
|
||||||
masterLabel.setText(getAutomaticName(wallet.getMasterWallet()));
|
masterLabel.setText(getAutomaticName(wallet.getMasterWallet()));
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
@ -1271,6 +1271,11 @@ public class AppController implements Initializable {
|
||||||
subTab.setUserData(tabData);
|
subTab.setUserData(tabData);
|
||||||
|
|
||||||
subTabs.getTabs().add(subTab);
|
subTabs.getTabs().add(subTab);
|
||||||
|
subTabs.getTabs().sort((o1, o2) -> {
|
||||||
|
WalletTabData tabData1 = (WalletTabData) o1.getUserData();
|
||||||
|
WalletTabData tabData2 = (WalletTabData) o2.getUserData();
|
||||||
|
return tabData1.getWallet().compareTo(tabData2.getWallet());
|
||||||
|
});
|
||||||
subTabs.getSelectionModel().select(subTab);
|
subTabs.getSelectionModel().select(subTab);
|
||||||
|
|
||||||
return walletForm;
|
return walletForm;
|
||||||
|
@ -1298,7 +1303,7 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private String getAutomaticName(Wallet wallet) {
|
private String getAutomaticName(Wallet wallet) {
|
||||||
int account = wallet.getAccountIndex();
|
int account = wallet.getAccountIndex();
|
||||||
return account < 0 ? wallet.getName() : (!wallet.isWhirlpoolMasterWallet() || account > 1 ? "Account #" + account : "Deposit");
|
return (account < 0 || account > 9) ? wallet.getName() : (!wallet.isWhirlpoolMasterWallet() || account > 1 ? "Account #" + account : "Deposit");
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletForm getSelectedWalletForm() {
|
public WalletForm getSelectedWalletForm() {
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AddAccountDialog extends Dialog<StandardAccount> {
|
||||||
|
private final ComboBox<StandardAccount> standardAccountCombo;
|
||||||
|
|
||||||
|
public AddAccountDialog(Wallet wallet) {
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
setTitle("Add Account");
|
||||||
|
dialogPane.setHeaderText("Choose an account to add:");
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);
|
||||||
|
dialogPane.setPrefWidth(380);
|
||||||
|
dialogPane.setPrefHeight(200);
|
||||||
|
AppServices.moveToActiveWindowScreen(this);
|
||||||
|
|
||||||
|
Glyph key = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SORT_NUMERIC_DOWN);
|
||||||
|
key.setFontSize(50);
|
||||||
|
dialogPane.setGraphic(key);
|
||||||
|
|
||||||
|
final VBox content = new VBox(10);
|
||||||
|
content.setPrefHeight(50);
|
||||||
|
|
||||||
|
standardAccountCombo = new ComboBox<>();
|
||||||
|
standardAccountCombo.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
List<Integer> existingIndexes = new ArrayList<>();
|
||||||
|
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||||
|
existingIndexes.add(masterWallet.getAccountIndex());
|
||||||
|
for(Wallet childWallet : masterWallet.getChildWallets()) {
|
||||||
|
existingIndexes.add(childWallet.getAccountIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<StandardAccount> availableAccounts = new ArrayList<>();
|
||||||
|
for(StandardAccount standardAccount : StandardAccount.values()) {
|
||||||
|
if(!existingIndexes.contains(standardAccount.getAccountNumber()) && !StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
|
||||||
|
availableAccounts.add(standardAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(WhirlpoolServices.canWalletMix(masterWallet) && !masterWallet.isWhirlpoolMasterWallet()) {
|
||||||
|
availableAccounts.add(StandardAccount.WHIRLPOOL_PREMIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
standardAccountCombo.setItems(FXCollections.observableList(availableAccounts));
|
||||||
|
standardAccountCombo.setConverter(new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(StandardAccount account) {
|
||||||
|
if(account == null) {
|
||||||
|
return "None Available";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(account)) {
|
||||||
|
return "Whirlpool Accounts";
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StandardAccount fromString(String string) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(standardAccountCombo.getItems().isEmpty()) {
|
||||||
|
Button okButton = (Button) dialogPane.lookupButton(ButtonType.OK);
|
||||||
|
okButton.setDisable(true);
|
||||||
|
} else {
|
||||||
|
standardAccountCombo.getSelectionModel().select(0);
|
||||||
|
}
|
||||||
|
content.getChildren().add(standardAccountCombo);
|
||||||
|
|
||||||
|
dialogPane.setContent(content);
|
||||||
|
setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? standardAccountCombo.getValue() : null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
private Button unlockButton;
|
private Button unlockButton;
|
||||||
private Button enterPinButton;
|
private Button enterPinButton;
|
||||||
private Button setPassphraseButton;
|
private Button setPassphraseButton;
|
||||||
private SplitMenuButton importButton;
|
private ButtonBase importButton;
|
||||||
private Button signButton;
|
private Button signButton;
|
||||||
private Button displayAddressButton;
|
private Button displayAddressButton;
|
||||||
private Button signMessageButton;
|
private Button signMessageButton;
|
||||||
|
@ -61,13 +61,13 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
|
|
||||||
private boolean defaultDevice;
|
private boolean defaultDevice;
|
||||||
|
|
||||||
public DevicePane(Wallet wallet, Device device, boolean defaultDevice) {
|
public DevicePane(Wallet wallet, Device device, boolean defaultDevice, KeyDerivation requiredDerivation) {
|
||||||
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
||||||
this.deviceOperation = DeviceOperation.IMPORT;
|
this.deviceOperation = DeviceOperation.IMPORT;
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.outputDescriptor = null;
|
this.outputDescriptor = null;
|
||||||
this.keyDerivation = null;
|
this.keyDerivation = requiredDerivation;
|
||||||
this.message = null;
|
this.message = null;
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.defaultDevice = defaultDevice;
|
this.defaultDevice = defaultDevice;
|
||||||
|
@ -199,35 +199,39 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createImportButton() {
|
private void createImportButton() {
|
||||||
importButton = new SplitMenuButton();
|
importButton = keyDerivation == null ? new SplitMenuButton() : new Button();
|
||||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
importButton.setText("Import Keystore");
|
importButton.setText("Import Keystore");
|
||||||
importButton.setOnAction(event -> {
|
importButton.setOnAction(event -> {
|
||||||
importButton.setDisable(true);
|
importButton.setDisable(true);
|
||||||
importKeystore(wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation());
|
List<ChildNumber> defaultDerivation = wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation();
|
||||||
|
importKeystore(keyDerivation == null ? defaultDerivation : keyDerivation.getDerivation());
|
||||||
});
|
});
|
||||||
if(wallet.getScriptType() == null) {
|
|
||||||
ScriptType[] scriptTypes = new ScriptType[] {ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH};
|
if(importButton instanceof SplitMenuButton importMenuButton) {
|
||||||
for(ScriptType scriptType : scriptTypes) {
|
if(wallet.getScriptType() == null) {
|
||||||
MenuItem item = new MenuItem(scriptType.getDescription());
|
ScriptType[] scriptTypes = new ScriptType[] {ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH};
|
||||||
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
|
for(ScriptType scriptType : scriptTypes) {
|
||||||
item.setOnAction(event -> {
|
MenuItem item = new MenuItem(scriptType.getDescription());
|
||||||
importButton.setDisable(true);
|
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
|
||||||
importKeystore(derivation);
|
item.setOnAction(event -> {
|
||||||
});
|
importMenuButton.setDisable(true);
|
||||||
importButton.getItems().add(item);
|
importKeystore(derivation);
|
||||||
}
|
});
|
||||||
} else {
|
importMenuButton.getItems().add(item);
|
||||||
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;
|
} else {
|
||||||
for(int i = 0; i < scriptAccountsLength; i++) {
|
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"};
|
||||||
MenuItem item = new MenuItem(accounts[i]);
|
int scriptAccountsLength = ScriptType.P2SH.equals(wallet.getScriptType()) ? 1 : accounts.length;
|
||||||
final List<ChildNumber> derivation = wallet.getScriptType().getDefaultDerivation(i);
|
for(int i = 0; i < scriptAccountsLength; i++) {
|
||||||
item.setOnAction(event -> {
|
MenuItem item = new MenuItem(accounts[i]);
|
||||||
importButton.setDisable(true);
|
final List<ChildNumber> derivation = wallet.getScriptType().getDefaultDerivation(i);
|
||||||
importKeystore(derivation);
|
item.setOnAction(event -> {
|
||||||
});
|
importMenuButton.setDisable(true);
|
||||||
importButton.getItems().add(item);
|
importKeystore(derivation);
|
||||||
|
});
|
||||||
|
importMenuButton.getItems().add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
importButton.managedProperty().bind(importButton.visibleProperty());
|
importButton.managedProperty().bind(importButton.visibleProperty());
|
||||||
|
@ -430,7 +434,9 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
} else {
|
} else {
|
||||||
showOperationButton();
|
showOperationButton();
|
||||||
setContent(getTogglePassphraseOn());
|
if(!deviceOperation.equals(DeviceOperation.IMPORT)) {
|
||||||
|
setContent(getTogglePassphraseOn());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setError("Incorrect PIN", null);
|
setError("Incorrect PIN", null);
|
||||||
|
@ -622,7 +628,8 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
importButton.setVisible(true);
|
importButton.setVisible(true);
|
||||||
showHideLink.setText("Show derivation...");
|
showHideLink.setText("Show derivation...");
|
||||||
showHideLink.setVisible(true);
|
showHideLink.setVisible(true);
|
||||||
setContent(getDerivationEntry(wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation()));
|
List<ChildNumber> defaultDerivation = wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation();
|
||||||
|
setContent(getDerivationEntry(keyDerivation == null ? defaultDerivation : keyDerivation.getDerivation()));
|
||||||
} else if(deviceOperation.equals(DeviceOperation.SIGN)) {
|
} else if(deviceOperation.equals(DeviceOperation.SIGN)) {
|
||||||
signButton.setDefaultButton(defaultDevice);
|
signButton.setDefaultButton(defaultDevice);
|
||||||
signButton.setVisible(true);
|
signButton.setVisible(true);
|
||||||
|
@ -642,6 +649,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
TextField derivationField = new TextField();
|
TextField derivationField = new TextField();
|
||||||
derivationField.setPromptText("Derivation path");
|
derivationField.setPromptText("Derivation path");
|
||||||
derivationField.setText(KeyDerivation.writePath(derivation));
|
derivationField.setText(KeyDerivation.writePath(derivation));
|
||||||
|
derivationField.setDisable(keyDerivation != null);
|
||||||
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
||||||
|
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
@ -651,7 +659,8 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid derivation", !KeyDerivation.isValid(newValue))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid derivation", !KeyDerivation.isValid(newValue))
|
||||||
));
|
));
|
||||||
|
|
||||||
Button importDerivationButton = new Button("Import");
|
Button importDerivationButton = new Button("Import Custom Derivation");
|
||||||
|
importDerivationButton.setDisable(true);
|
||||||
importDerivationButton.setOnAction(event -> {
|
importDerivationButton.setOnAction(event -> {
|
||||||
showHideLink.setVisible(true);
|
showHideLink.setVisible(true);
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
|
@ -660,7 +669,8 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
});
|
});
|
||||||
|
|
||||||
derivationField.textProperty().addListener((observable, oldValue, newValue) -> {
|
derivationField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
importDerivationButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue));
|
importButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || !KeyDerivation.parsePath(newValue).equals(derivation));
|
||||||
|
importDerivationButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || KeyDerivation.parsePath(newValue).equals(derivation));
|
||||||
});
|
});
|
||||||
|
|
||||||
HBox contentBox = new HBox();
|
HBox contentBox = new HBox();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
@ -12,11 +13,13 @@ import java.io.*;
|
||||||
public class FileKeystoreImportPane extends FileImportPane {
|
public class FileKeystoreImportPane extends FileImportPane {
|
||||||
protected final Wallet wallet;
|
protected final Wallet wallet;
|
||||||
private final KeystoreFileImport importer;
|
private final KeystoreFileImport importer;
|
||||||
|
private final KeyDerivation requiredDerivation;
|
||||||
|
|
||||||
public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer) {
|
public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer, KeyDerivation requiredDerivation) {
|
||||||
super(importer, importer.getName(), "Keystore import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable());
|
super(importer, importer.getName(), "Keystore import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable());
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
|
this.requiredDerivation = requiredDerivation;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException {
|
protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException {
|
||||||
|
@ -25,6 +28,10 @@ public class FileKeystoreImportPane extends FileImportPane {
|
||||||
keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password);
|
keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
if(requiredDerivation != null && !requiredDerivation.getDerivation().equals(keystore.getKeyDerivation().getDerivation())) {
|
||||||
|
setError("Incorrect derivation", "This account requires a derivation of " + requiredDerivation.getDerivationPath() + ", but the imported keystore has a derivation of " + keystore.getKeyDerivation().getDerivationPath() + ".");
|
||||||
|
} else {
|
||||||
|
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||||
List<Device> devices = enumerateService.getValue();
|
List<Device> devices = enumerateService.getValue();
|
||||||
importAccordion.getPanes().removeIf(titledPane -> titledPane instanceof DevicePane);
|
importAccordion.getPanes().removeIf(titledPane -> titledPane instanceof DevicePane);
|
||||||
for(Device device : devices) {
|
for(Device device : devices) {
|
||||||
DevicePane devicePane = new DevicePane(new Wallet(), device, devices.size() == 1);
|
DevicePane devicePane = new DevicePane(new Wallet(), device, devices.size() == 1, null);
|
||||||
importAccordion.getPanes().add(0, devicePane);
|
importAccordion.getPanes().add(0, devicePane);
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
||||||
|
|
|
@ -62,6 +62,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||||
SIGN_OUT_ALT('\uf2f5'),
|
SIGN_OUT_ALT('\uf2f5'),
|
||||||
SQUARE('\uf0c8'),
|
SQUARE('\uf0c8'),
|
||||||
SNOWFLAKE('\uf2dc'),
|
SNOWFLAKE('\uf2dc'),
|
||||||
|
SORT_NUMERIC_DOWN('\uf162'),
|
||||||
SUN('\uf185'),
|
SUN('\uf185'),
|
||||||
THEATER_MASKS('\uf630'),
|
THEATER_MASKS('\uf630'),
|
||||||
TIMES_CIRCLE('\uf057'),
|
TIMES_CIRCLE('\uf057'),
|
||||||
|
|
|
@ -42,23 +42,27 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||||
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
|
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
|
||||||
keystore.setWalletModel(WalletModel.COLDCARD);
|
keystore.setWalletModel(WalletModel.COLDCARD);
|
||||||
|
|
||||||
if(cck.xpub != null && cck.path != null) {
|
try {
|
||||||
ExtendedKey.Header header = ExtendedKey.Header.fromExtendedKey(cck.xpub);
|
if(cck.xpub != null && cck.path != null) {
|
||||||
if(header.getDefaultScriptType() != scriptType) {
|
ExtendedKey.Header header = ExtendedKey.Header.fromExtendedKey(cck.xpub);
|
||||||
throw new ImportException("This wallet's script type (" + scriptType + ") does not match the " + getName() + " script type (" + header.getDefaultScriptType() + ")");
|
if(header.getDefaultScriptType() != scriptType) {
|
||||||
|
throw new ImportException("This wallet's script type (" + scriptType + ") does not match the " + getName() + " script type (" + header.getDefaultScriptType() + ")");
|
||||||
|
}
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.path));
|
||||||
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.xpub));
|
||||||
|
} else if(scriptType.equals(ScriptType.P2SH)) {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv));
|
||||||
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2sh));
|
||||||
|
} else if(scriptType.equals(ScriptType.P2SH_P2WSH)) {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_p2sh_deriv != null ? cck.p2wsh_p2sh_deriv : cck.p2sh_p2wsh_deriv));
|
||||||
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2wsh_p2sh != null ? cck.p2wsh_p2sh : cck.p2sh_p2wsh));
|
||||||
|
} else if(scriptType.equals(ScriptType.P2WSH)) {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_deriv));
|
||||||
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2wsh));
|
||||||
|
} else {
|
||||||
|
throw new ImportException("Correct derivation not found for script type: " + scriptType);
|
||||||
}
|
}
|
||||||
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.path));
|
} catch(NullPointerException e) {
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.xpub));
|
|
||||||
} else if(scriptType.equals(ScriptType.P2SH)) {
|
|
||||||
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv));
|
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2sh));
|
|
||||||
} else if(scriptType.equals(ScriptType.P2SH_P2WSH)) {
|
|
||||||
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_p2sh_deriv != null ? cck.p2wsh_p2sh_deriv : cck.p2sh_p2wsh_deriv));
|
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2wsh_p2sh != null ? cck.p2wsh_p2sh : cck.p2sh_p2wsh));
|
|
||||||
} else if(scriptType.equals(ScriptType.P2WSH)) {
|
|
||||||
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_deriv));
|
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2wsh));
|
|
||||||
} else {
|
|
||||||
throw new ImportException("Correct derivation not found for script type: " + scriptType);
|
throw new ImportException("Correct derivation not found for script type: " + scriptType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,10 +51,6 @@ public class WalletBackupAndKey implements Comparable<WalletBackupAndKey> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(WalletBackupAndKey other) {
|
public int compareTo(WalletBackupAndKey other) {
|
||||||
if(wallet.getStandardAccountType() != null && other.wallet.getStandardAccountType() != null) {
|
return wallet.compareTo(other.wallet);
|
||||||
return wallet.getStandardAccountType().ordinal() - other.wallet.getStandardAccountType().ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
return wallet.getAccountIndex() - other.wallet.getAccountIndex();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,11 @@ public class HwAirgappedController extends KeystoreImportDetailController {
|
||||||
importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new SpecterDIY());
|
importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new SpecterDIY());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(KeystoreImport importer : importers) {
|
for(KeystoreFileImport importer : importers) {
|
||||||
FileKeystoreImportPane importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);;
|
FileKeystoreImportPane importPane = new FileKeystoreImportPane(getMasterController().getWallet(), importer, getMasterController().getRequiredDerivation());
|
||||||
importAccordion.getPanes().add(importPane);
|
if(getMasterController().getRequiredModel() == null || getMasterController().getRequiredModel() == importer.getWalletModel()) {
|
||||||
|
importAccordion.getPanes().add(importPane);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
||||||
|
|
|
@ -13,8 +13,10 @@ public class HwUsbDevicesController extends KeystoreImportDetailController {
|
||||||
|
|
||||||
public void initializeView(List<Device> devices) {
|
public void initializeView(List<Device> devices) {
|
||||||
for(Device device : devices) {
|
for(Device device : devices) {
|
||||||
DevicePane devicePane = new DevicePane(getMasterController().getWallet(), device, devices.size() == 1);
|
DevicePane devicePane = new DevicePane(getMasterController().getWallet(), device, devices.size() == 1, getMasterController().getRequiredDerivation());
|
||||||
deviceAccordion.getPanes().add(devicePane);
|
if(getMasterController().getRequiredModel() == null || getMasterController().getRequiredModel() == device.getModel()) {
|
||||||
|
deviceAccordion.getPanes().add(devicePane);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package com.sparrowwallet.sparrow.keystoreimport;
|
package com.sparrowwallet.sparrow.keystoreimport;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.io.Device;
|
import com.sparrowwallet.sparrow.io.Device;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -10,6 +12,7 @@ import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Toggle;
|
import javafx.scene.control.Toggle;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
@ -27,6 +30,9 @@ public class KeystoreImportController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane importPane;
|
private StackPane importPane;
|
||||||
|
|
||||||
|
private KeyDerivation requiredDerivation;
|
||||||
|
private WalletModel requiredModel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
|
||||||
|
@ -53,11 +59,12 @@ public class KeystoreImportController implements Initializable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectSource(KeystoreSource keystoreSource) {
|
public void selectSource(KeystoreSource keystoreSource, boolean required) {
|
||||||
for(Toggle toggle : importMenu.getToggles()) {
|
for(Toggle toggle : importMenu.getToggles()) {
|
||||||
if(toggle.getUserData().equals(keystoreSource)) {
|
if(toggle.getUserData().equals(keystoreSource)) {
|
||||||
Platform.runLater(() -> importMenu.selectToggle(toggle));
|
Platform.runLater(() -> importMenu.selectToggle(toggle));
|
||||||
return;
|
} else if(required) {
|
||||||
|
((ToggleButton)toggle).setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,4 +103,20 @@ public class KeystoreImportController implements Initializable {
|
||||||
throw new IllegalStateException("Can't find pane", e);
|
throw new IllegalStateException("Can't find pane", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyDerivation getRequiredDerivation() {
|
||||||
|
return requiredDerivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredDerivation(KeyDerivation requiredDerivation) {
|
||||||
|
this.requiredDerivation = requiredDerivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletModel getRequiredModel() {
|
||||||
|
return requiredModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredModel(WalletModel requiredModel) {
|
||||||
|
this.requiredModel = requiredModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource) {
|
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource) {
|
||||||
|
this(wallet, initialSource, null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeystoreImportDialog(Wallet wallet, KeystoreSource initialSource, KeyDerivation requiredDerivation, WalletModel requiredModel, boolean restrictSource) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
setOnCloseRequest(event -> {
|
setOnCloseRequest(event -> {
|
||||||
EventManager.get().unregister(this);
|
EventManager.get().unregister(this);
|
||||||
|
@ -39,11 +43,16 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||||
dialogPane.setContent(Borders.wrap(ksiLoader.load()).emptyBorder().buildAll());
|
dialogPane.setContent(Borders.wrap(ksiLoader.load()).emptyBorder().buildAll());
|
||||||
keystoreImportController = ksiLoader.getController();
|
keystoreImportController = ksiLoader.getController();
|
||||||
keystoreImportController.initializeView(wallet);
|
keystoreImportController.initializeView(wallet);
|
||||||
keystoreImportController.selectSource(initialSource);
|
keystoreImportController.selectSource(initialSource, restrictSource);
|
||||||
|
keystoreImportController.setRequiredDerivation(requiredDerivation);
|
||||||
|
keystoreImportController.setRequiredModel(requiredModel);
|
||||||
|
|
||||||
final ButtonType watchOnlyButtonType = new javafx.scene.control.ButtonType(Network.get().getXpubHeader().getDisplayName() + " / Watch Only Wallet", ButtonBar.ButtonData.LEFT);
|
final ButtonType watchOnlyButtonType = new javafx.scene.control.ButtonType(Network.get().getXpubHeader().getDisplayName() + " / Watch Only Wallet", ButtonBar.ButtonData.LEFT);
|
||||||
|
if(!restrictSource) {
|
||||||
|
dialogPane.getButtonTypes().add(watchOnlyButtonType);
|
||||||
|
}
|
||||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||||
dialogPane.getButtonTypes().addAll(watchOnlyButtonType, cancelButtonType);
|
dialogPane.getButtonTypes().add(cancelButtonType);
|
||||||
dialogPane.setPrefWidth(650);
|
dialogPane.setPrefWidth(650);
|
||||||
dialogPane.setPrefHeight(690);
|
dialogPane.setPrefHeight(690);
|
||||||
AppServices.moveToActiveWindowScreen(this);
|
AppServices.moveToActiveWindowScreen(this);
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class SwController extends KeystoreImportDetailController {
|
||||||
TitledDescriptionPane importPane = null;
|
TitledDescriptionPane importPane = null;
|
||||||
|
|
||||||
if(importer instanceof KeystoreFileImport) {
|
if(importer instanceof KeystoreFileImport) {
|
||||||
importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);
|
importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer, getMasterController().getRequiredDerivation());
|
||||||
} else if(importer instanceof KeystoreMnemonicImport) {
|
} else if(importer instanceof KeystoreMnemonicImport) {
|
||||||
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer);
|
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer);
|
||||||
} else if(importer instanceof KeystoreXprvImport) {
|
} else if(importer instanceof KeystoreXprvImport) {
|
||||||
|
|
|
@ -5,17 +5,15 @@ import com.sparrowwallet.drongo.*;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.ChildWalletAddedEvent;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
@ -104,6 +102,14 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
selectSourcePane.managedProperty().bind(selectSourcePane.visibleProperty());
|
selectSourcePane.managedProperty().bind(selectSourcePane.visibleProperty());
|
||||||
if(keystore.isValid() || keystore.getExtendedPublicKey() != null) {
|
if(keystore.isValid() || keystore.getExtendedPublicKey() != null) {
|
||||||
selectSourcePane.setVisible(false);
|
selectSourcePane.setVisible(false);
|
||||||
|
} else if(!getWalletForm().getWallet().isMasterWallet() && keystore.getKeyDerivation() != null) {
|
||||||
|
Wallet masterWallet = getWalletForm().getWallet().getMasterWallet();
|
||||||
|
int keystoreIndex = getWalletForm().getWallet().getKeystores().indexOf(keystore);
|
||||||
|
KeystoreSource keystoreSource = masterWallet.getKeystores().get(keystoreIndex).getSource();
|
||||||
|
for(Toggle toggle : keystoreSourceToggleGroup.getToggles()) {
|
||||||
|
ToggleButton toggleButton = (ToggleButton)toggle;
|
||||||
|
toggleButton.setDisable(toggleButton.getUserData() != keystoreSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
||||||
|
@ -163,7 +169,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
scanXpubQR.setVisible(!valid);
|
scanXpubQR.setVisible(!valid);
|
||||||
});
|
});
|
||||||
|
|
||||||
setInputFieldsDisabled(!walletForm.getWallet().isMasterWallet() || !walletForm.getWallet().getChildWallets().isEmpty());
|
setInputFieldsDisabled(keystore.getSource() != KeystoreSource.SW_WATCH && (!walletForm.getWallet().isMasterWallet() || !walletForm.getWallet().getChildWallets().isEmpty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setXpubContext(ExtendedKey extendedKey) {
|
private void setXpubContext(ExtendedKey extendedKey) {
|
||||||
|
@ -310,7 +316,10 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchImportDialog(KeystoreSource initialSource) {
|
private void launchImportDialog(KeystoreSource initialSource) {
|
||||||
KeystoreImportDialog dlg = new KeystoreImportDialog(getWalletForm().getWallet(), initialSource);
|
boolean restrictSource = keystoreSourceToggleGroup.getToggles().stream().anyMatch(toggle -> ((ToggleButton)toggle).isDisabled());
|
||||||
|
KeyDerivation requiredDerivation = restrictSource ? keystore.getKeyDerivation() : null;
|
||||||
|
WalletModel requiredModel = restrictSource ? keystore.getWalletModel() : null;
|
||||||
|
KeystoreImportDialog dlg = new KeystoreImportDialog(getWalletForm().getWallet(), initialSource, requiredDerivation, requiredModel, restrictSource);
|
||||||
Optional<Keystore> result = dlg.showAndWait();
|
Optional<Keystore> result = dlg.showAndWait();
|
||||||
if(result.isPresent()) {
|
if(result.isPresent()) {
|
||||||
selectSourcePane.setVisible(false);
|
selectSourcePane.setVisible(false);
|
||||||
|
@ -421,7 +430,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void childWalletAdded(ChildWalletAddedEvent event) {
|
public void childWalletAdded(ChildWalletAddedEvent event) {
|
||||||
if(event.getMasterWalletId().equals(walletForm.getWalletId())) {
|
if(event.getMasterWalletId().equals(walletForm.getWalletId())) {
|
||||||
setInputFieldsDisabled(true);
|
setInputFieldsDisabled(keystore.getSource() != KeystoreSource.SW_WATCH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,7 @@ import com.sparrowwallet.drongo.crypto.*;
|
||||||
import com.sparrowwallet.drongo.policy.Policy;
|
import com.sparrowwallet.drongo.policy.Policy;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
|
||||||
import com.sparrowwallet.hummingbird.UR;
|
import com.sparrowwallet.hummingbird.UR;
|
||||||
import com.sparrowwallet.hummingbird.registry.*;
|
import com.sparrowwallet.hummingbird.registry.*;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
@ -18,6 +15,7 @@ import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.io.StorageException;
|
import com.sparrowwallet.sparrow.io.StorageException;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
|
@ -32,7 +30,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tornadofx.control.Fieldset;
|
import tornadofx.control.Fieldset;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -79,7 +76,11 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
private TabPane keystoreTabs;
|
private TabPane keystoreTabs;
|
||||||
|
|
||||||
@FXML Button export;
|
@FXML
|
||||||
|
private Button export;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button addAccount;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button apply;
|
private Button apply;
|
||||||
|
@ -254,6 +255,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
scanDescriptorQR.setVisible(!walletForm.getWallet().isValid());
|
scanDescriptorQR.setVisible(!walletForm.getWallet().isValid());
|
||||||
export.setDisable(!walletForm.getWallet().isValid());
|
export.setDisable(!walletForm.getWallet().isValid());
|
||||||
|
addAccount.setDisable(!walletForm.getWallet().isValid());
|
||||||
revert.setDisable(true);
|
revert.setDisable(true);
|
||||||
apply.setDisable(true);
|
apply.setDisable(true);
|
||||||
}
|
}
|
||||||
|
@ -442,6 +444,79 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addAccount(ActionEvent event) {
|
||||||
|
Wallet openWallet = AppServices.get().getOpenWallets().entrySet().stream().filter(entry -> walletForm.getWalletFile().equals(entry.getValue().getWalletFile())).map(Map.Entry::getKey).findFirst().orElseThrow();
|
||||||
|
Wallet masterWallet = openWallet.isMasterWallet() ? openWallet : openWallet.getMasterWallet();
|
||||||
|
|
||||||
|
AddAccountDialog addAccountDialog = new AddAccountDialog(masterWallet);
|
||||||
|
Optional<StandardAccount> optAccount = addAccountDialog.showAndWait();
|
||||||
|
if(optAccount.isPresent()) {
|
||||||
|
StandardAccount standardAccount = optAccount.get();
|
||||||
|
|
||||||
|
if(masterWallet.getKeystores().stream().allMatch(ks -> ks.getSource() == KeystoreSource.SW_SEED)) {
|
||||||
|
if(masterWallet.isEncrypted()) {
|
||||||
|
String walletId = walletForm.getWalletId();
|
||||||
|
WalletPasswordDialog dlg = new WalletPasswordDialog(masterWallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
|
if(password.isPresent()) {
|
||||||
|
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get());
|
||||||
|
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||||
|
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||||
|
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
||||||
|
masterWallet.decrypt(key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
addAndSaveAccount(masterWallet, standardAccount);
|
||||||
|
} finally {
|
||||||
|
masterWallet.encrypt(key);
|
||||||
|
for(Wallet childWallet : masterWallet.getChildWallets()) {
|
||||||
|
if(!childWallet.isEncrypted()) {
|
||||||
|
childWallet.encrypt(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key.clear();
|
||||||
|
encryptionFullKey.clear();
|
||||||
|
password.get().clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||||
|
AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage());
|
||||||
|
});
|
||||||
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||||
|
keyDerivationService.start();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addAndSaveAccount(masterWallet, standardAccount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Wallet childWallet = masterWallet.addChildWallet(standardAccount);
|
||||||
|
EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), masterWallet, childWallet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAndSaveAccount(Wallet masterWallet, StandardAccount standardAccount) {
|
||||||
|
if(StandardAccount.WHIRLPOOL_ACCOUNTS.contains(standardAccount)) {
|
||||||
|
WhirlpoolServices.prepareWhirlpoolWallet(masterWallet, getWalletForm().getWalletId(), getWalletForm().getStorage());
|
||||||
|
} else {
|
||||||
|
Wallet childWallet = masterWallet.addChildWallet(standardAccount);
|
||||||
|
EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), masterWallet, childWallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Wallet childWallet : masterWallet.getChildWallets()) {
|
||||||
|
Storage storage = AppServices.get().getOpenWallets().get(childWallet);
|
||||||
|
if(!storage.isPersisted(childWallet)) {
|
||||||
|
try {
|
||||||
|
storage.saveWallet(childWallet);
|
||||||
|
} catch(Exception e) {
|
||||||
|
AppServices.showErrorDialog("Error saving wallet " + childWallet.getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setInputFieldsDisabled(boolean disabled) {
|
private void setInputFieldsDisabled(boolean disabled) {
|
||||||
policyType.setDisable(disabled);
|
policyType.setDisable(disabled);
|
||||||
scriptType.setDisable(disabled);
|
scriptType.setDisable(disabled);
|
||||||
|
@ -479,6 +554,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
revert.setDisable(false);
|
revert.setDisable(false);
|
||||||
apply.setDisable(!wallet.isValid());
|
apply.setDisable(!wallet.isValid());
|
||||||
export.setDisable(true);
|
export.setDisable(true);
|
||||||
|
addAccount.setDisable(true);
|
||||||
scanDescriptorQR.setVisible(!wallet.isValid());
|
scanDescriptorQR.setVisible(!wallet.isValid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,6 +563,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
public void walletSettingsChanged(WalletSettingsChangedEvent event) {
|
public void walletSettingsChanged(WalletSettingsChangedEvent event) {
|
||||||
if(event.getWalletId().equals(walletForm.getWalletId())) {
|
if(event.getWalletId().equals(walletForm.getWalletId())) {
|
||||||
export.setDisable(!event.getWallet().isValid());
|
export.setDisable(!event.getWallet().isValid());
|
||||||
|
addAccount.setDisable(!event.getWallet().isValid());
|
||||||
scanDescriptorQR.setVisible(!event.getWallet().isValid());
|
scanDescriptorQR.setVisible(!event.getWallet().isValid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolDialog;
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolDialog;
|
||||||
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.WeakChangeListener;
|
import javafx.beans.value.WeakChangeListener;
|
||||||
|
@ -141,11 +142,7 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canWalletMix() {
|
private boolean canWalletMix() {
|
||||||
return Whirlpool.WHIRLPOOL_NETWORKS.contains(Network.get())
|
return WhirlpoolServices.canWalletMix(getWalletForm().getWallet());
|
||||||
&& getWalletForm().getWallet().getKeystores().size() == 1
|
|
||||||
&& getWalletForm().getWallet().getKeystores().get(0).hasSeed()
|
|
||||||
&& getWalletForm().getWallet().getKeystores().get(0).getSeed().getType() == DeterministicSeed.Type.BIP39
|
|
||||||
&& !getWalletForm().getWallet().isWhirlpoolMixWallet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtons(BitcoinUnit unit) {
|
private void updateButtons(BitcoinUnit unit) {
|
||||||
|
@ -262,16 +259,7 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareWhirlpoolWallet(Wallet decryptedWallet) {
|
private void prepareWhirlpoolWallet(Wallet decryptedWallet) {
|
||||||
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWalletId());
|
WhirlpoolServices.prepareWhirlpoolWallet(decryptedWallet, getWalletForm().getWalletId(), getWalletForm().getStorage());
|
||||||
whirlpool.setScode(decryptedWallet.getMasterMixConfig().getScode());
|
|
||||||
whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet);
|
|
||||||
|
|
||||||
for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) {
|
|
||||||
if(decryptedWallet.getChildWallet(whirlpoolAccount) == null) {
|
|
||||||
Wallet childWallet = decryptedWallet.addChildWallet(whirlpoolAccount);
|
|
||||||
EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), decryptedWallet, childWallet));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void previewPremix(Wallet wallet, Tx0Preview tx0Preview, List<UtxoEntry> utxoEntries) {
|
private void previewPremix(Wallet wallet, Tx0Preview tx0Preview, List<UtxoEntry> utxoEntries) {
|
||||||
|
|
|
@ -4,9 +4,12 @@ import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.samourai.whirlpool.client.wallet.WhirlpoolEventService;
|
import com.samourai.whirlpool.client.wallet.WhirlpoolEventService;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
import com.sparrowwallet.drongo.wallet.MixConfig;
|
import com.sparrowwallet.drongo.wallet.MixConfig;
|
||||||
|
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.WalletTabData;
|
import com.sparrowwallet.sparrow.WalletTabData;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
@ -123,6 +126,27 @@ public class WhirlpoolServices {
|
||||||
return whirlpoolMap.values().stream().filter(whirlpool -> walletId.equals(whirlpool.getMixToWalletId())).findFirst().orElse(null);
|
return whirlpoolMap.values().stream().filter(whirlpool -> walletId.equals(whirlpool.getMixToWalletId())).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean canWalletMix(Wallet wallet) {
|
||||||
|
return Whirlpool.WHIRLPOOL_NETWORKS.contains(Network.get())
|
||||||
|
&& wallet.getKeystores().size() == 1
|
||||||
|
&& wallet.getKeystores().get(0).hasSeed()
|
||||||
|
&& wallet.getKeystores().get(0).getSeed().getType() == DeterministicSeed.Type.BIP39
|
||||||
|
&& !wallet.isWhirlpoolMixWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void prepareWhirlpoolWallet(Wallet decryptedWallet, String walletId, Storage storage) {
|
||||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(walletId);
|
||||||
|
whirlpool.setScode(decryptedWallet.getMasterMixConfig().getScode());
|
||||||
|
whirlpool.setHDWallet(walletId, decryptedWallet);
|
||||||
|
|
||||||
|
for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) {
|
||||||
|
if(decryptedWallet.getChildWallet(whirlpoolAccount) == null) {
|
||||||
|
Wallet childWallet = decryptedWallet.addChildWallet(whirlpoolAccount);
|
||||||
|
EventManager.get().post(new ChildWalletAddedEvent(storage, decryptedWallet, childWallet));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void newConnection(ConnectionEvent event) {
|
public void newConnection(ConnectionEvent event) {
|
||||||
startAllWhirlpool();
|
startAllWhirlpool();
|
||||||
|
|
|
@ -123,6 +123,7 @@
|
||||||
</padding>
|
</padding>
|
||||||
<HBox AnchorPane.leftAnchor="0" spacing="20">
|
<HBox AnchorPane.leftAnchor="0" spacing="20">
|
||||||
<Button fx:id="export" text="Export..." onAction="#exportWallet" />
|
<Button fx:id="export" text="Export..." onAction="#exportWallet" />
|
||||||
|
<Button fx:id="addAccount" text="Add Account..." onAction="#addAccount" />
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox AnchorPane.rightAnchor="10" spacing="20">
|
<HBox AnchorPane.rightAnchor="10" spacing="20">
|
||||||
<Button text="Advanced..." onAction="#showAdvanced" />
|
<Button text="Advanced..." onAction="#showAdvanced" />
|
||||||
|
|
Loading…
Reference in a new issue