mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
wallet export, settings fixes
This commit is contained in:
parent
bb2ec1882d
commit
d0e5da0ec8
21 changed files with 387 additions and 59 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 766a986abb72ea84317a405b93055abc3d1eaf22
|
Subproject commit f6414a447550eb87a871c54c7e376713cae8eb9c
|
|
@ -13,13 +13,8 @@ import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.control.WalletImportDialog;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.control.WalletNameDialog;
|
|
||||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
|
||||||
import com.sparrowwallet.sparrow.event.TabEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.io.FileType;
|
import com.sparrowwallet.sparrow.io.FileType;
|
||||||
import com.sparrowwallet.sparrow.io.IOUtils;
|
import com.sparrowwallet.sparrow.io.IOUtils;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
@ -46,6 +41,9 @@ public class AppController implements Initializable {
|
||||||
private static final String TRANSACTION_TAB_TYPE = "transaction";
|
private static final String TRANSACTION_TAB_TYPE = "transaction";
|
||||||
public static final String DRAG_OVER_CLASS = "drag-over";
|
public static final String DRAG_OVER_CLASS = "drag-over";
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem exportWallet;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckMenuItem showTxHex;
|
private CheckMenuItem showTxHex;
|
||||||
|
|
||||||
|
@ -96,12 +94,19 @@ public class AppController implements Initializable {
|
||||||
TabData tabData = (TabData)selectedTab.getUserData();
|
TabData tabData = (TabData)selectedTab.getUserData();
|
||||||
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
||||||
EventManager.get().post(new TransactionTabSelectedEvent(selectedTab));
|
EventManager.get().post(new TransactionTabSelectedEvent(selectedTab));
|
||||||
|
exportWallet.setDisable(true);
|
||||||
|
showTxHex.setDisable(false);
|
||||||
|
} else if(tabData.getType() == TabData.TabType.WALLET) {
|
||||||
|
EventManager.get().post(new WalletTabSelectedEvent(selectedTab));
|
||||||
|
exportWallet.setDisable(false);
|
||||||
|
showTxHex.setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
showTxHex.setSelected(true);
|
showTxHex.setSelected(true);
|
||||||
showTxHexProperty = true;
|
showTxHexProperty = true;
|
||||||
|
exportWallet.setDisable(true);
|
||||||
|
|
||||||
//addWalletTab("newWallet", new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH));
|
//addWalletTab("newWallet", new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH));
|
||||||
}
|
}
|
||||||
|
@ -269,6 +274,19 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void exportWallet(ActionEvent event) {
|
||||||
|
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
|
||||||
|
TabData tabData = (TabData)selectedTab.getUserData();
|
||||||
|
if(tabData.getType() == TabData.TabType.WALLET) {
|
||||||
|
WalletTabData walletTabData = (WalletTabData)tabData;
|
||||||
|
WalletExportDialog dlg = new WalletExportDialog(walletTabData.getWallet());
|
||||||
|
Optional<Wallet> wallet = dlg.showAndWait();
|
||||||
|
if(wallet.isPresent()) {
|
||||||
|
//Successful export
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
|
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
|
||||||
try {
|
try {
|
||||||
String name = walletFile.getName();
|
String name = walletFile.getName();
|
||||||
|
@ -276,7 +294,7 @@ public class AppController implements Initializable {
|
||||||
name = name.substring(0, name.lastIndexOf('.'));
|
name = name.substring(0, name.lastIndexOf('.'));
|
||||||
}
|
}
|
||||||
Tab tab = new Tab(name);
|
Tab tab = new Tab(name);
|
||||||
TabData tabData = new TabData(TabData.TabType.WALLET);
|
TabData tabData = new WalletTabData(TabData.TabType.WALLET, wallet, walletFile);
|
||||||
tab.setUserData(tabData);
|
tab.setUserData(tabData);
|
||||||
tab.setContextMenu(getTabContextMenu(tab));
|
tab.setContextMenu(getTabContextMenu(tab));
|
||||||
tab.setClosable(true);
|
tab.setClosable(true);
|
||||||
|
@ -351,7 +369,7 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab tab = new Tab(tabName);
|
Tab tab = new Tab(tabName);
|
||||||
TabData tabData = new TabData(TabData.TabType.TRANSACTION);
|
TabData tabData = new TransactionTabData(TabData.TabType.TRANSACTION, transaction);
|
||||||
tab.setUserData(tabData);
|
tab.setUserData(tabData);
|
||||||
tab.setContextMenu(getTabContextMenu(tab));
|
tab.setContextMenu(getTabContextMenu(tab));
|
||||||
tab.setClosable(true);
|
tab.setClosable(true);
|
||||||
|
@ -397,7 +415,7 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void tabSelected(TabEvent event) {
|
public void tabSelected(TabSelectedEvent event) {
|
||||||
Tab selectedTab = event.getTab();
|
Tab selectedTab = event.getTab();
|
||||||
String tabType = (String)selectedTab.getUserData();
|
String tabType = (String)selectedTab.getUserData();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.sparrowwallet.sparrow;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
|
|
||||||
|
public class TransactionTabData extends TabData {
|
||||||
|
private Transaction transaction;
|
||||||
|
|
||||||
|
public TransactionTabData(TabType type, Transaction transaction) {
|
||||||
|
super(type);
|
||||||
|
this.transaction = transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transaction getTransaction() {
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/com/sparrowwallet/sparrow/WalletTabData.java
Normal file
35
src/main/java/com/sparrowwallet/sparrow/WalletTabData.java
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package com.sparrowwallet.sparrow;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class WalletTabData extends TabData {
|
||||||
|
private Wallet wallet;
|
||||||
|
private final File walletFile;
|
||||||
|
|
||||||
|
public WalletTabData(TabType type, Wallet wallet, File walletFile) {
|
||||||
|
super(type);
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.walletFile = walletFile;
|
||||||
|
|
||||||
|
EventManager.get().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet getWallet() {
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getWalletFile() {
|
||||||
|
return walletFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletChanged(WalletChangedEvent event) {
|
||||||
|
if(event.getWalletFile().equals(walletFile)) {
|
||||||
|
wallet = event.getWallet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ public abstract class FileImportPane extends TitledDescriptionPane {
|
||||||
Stage window = new Stage();
|
Stage window = new Stage();
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore");
|
fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " File");
|
||||||
fileChooser.getExtensionFilters().addAll(
|
fileChooser.getExtensionFilters().addAll(
|
||||||
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
||||||
new FileChooser.ExtensionFilter("JSON", "*.json")
|
new FileChooser.ExtensionFilter("JSON", "*.json")
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.io.WalletExport;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class FileWalletExportPane extends TitledDescriptionPane {
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final WalletExport exporter;
|
||||||
|
|
||||||
|
public FileWalletExportPane(Wallet wallet, WalletExport exporter) {
|
||||||
|
super(exporter.getName(), "Wallet file export", exporter.getWalletExportDescription(), "image/" + exporter.getWalletModel().getType() + ".png");
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.exporter = exporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Control createButton() {
|
||||||
|
Button exportButton = new Button("Export Wallet...");
|
||||||
|
exportButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
exportButton.setOnAction(event -> {
|
||||||
|
exportWallet();
|
||||||
|
});
|
||||||
|
return exportButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportWallet() {
|
||||||
|
Stage window = new Stage();
|
||||||
|
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File");
|
||||||
|
|
||||||
|
File file = fileChooser.showSaveDialog(window);
|
||||||
|
if(file != null) {
|
||||||
|
exportWallet(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportWallet(File file) {
|
||||||
|
Wallet copy = wallet.copy();
|
||||||
|
|
||||||
|
if(copy.isEncrypted()) {
|
||||||
|
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
|
Optional<String> password = dlg.showAndWait();
|
||||||
|
if(password.isPresent()) {
|
||||||
|
copy.decrypt(password.get(), "");
|
||||||
|
|
||||||
|
for(Keystore keystore : copy.getKeystores()) {
|
||||||
|
if(keystore.hasSeed() && keystore.getSeed().needPassphrase()) {
|
||||||
|
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(keystore);
|
||||||
|
Optional<String> passphrase = passphraseDialog.showAndWait();
|
||||||
|
if(passphrase.isPresent()) {
|
||||||
|
keystore.setPassphrase(passphrase.get());
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
|
exporter.exportWallet(copy, outputStream);
|
||||||
|
EventManager.get().post(new WalletExportEvent(copy));
|
||||||
|
} catch(Exception e) {
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||||
|
errorMessage = e.getCause().getMessage();
|
||||||
|
}
|
||||||
|
setError("Export Error", errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
|
import javafx.scene.control.DialogPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||||
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
|
||||||
|
public class KeystorePassphraseDialog extends Dialog<String> {
|
||||||
|
private final CustomPasswordField passphrase;
|
||||||
|
|
||||||
|
public KeystorePassphraseDialog(Keystore keystore) {
|
||||||
|
this.passphrase = (CustomPasswordField) TextFields.createClearablePasswordField();
|
||||||
|
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
setTitle("Keystore Passphrase");
|
||||||
|
dialogPane.setHeaderText("Please enter the passphrase for keystore: " + keystore.getLabel());
|
||||||
|
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
|
||||||
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);
|
||||||
|
dialogPane.setPrefWidth(380);
|
||||||
|
dialogPane.setPrefHeight(200);
|
||||||
|
|
||||||
|
Glyph lock = new Glyph("FontAwesome5", FontAwesome5.Glyph.KEY);
|
||||||
|
lock.setFontSize(50);
|
||||||
|
dialogPane.setGraphic(lock);
|
||||||
|
|
||||||
|
final VBox content = new VBox(10);
|
||||||
|
content.setPrefHeight(50);
|
||||||
|
content.getChildren().add(passphrase);
|
||||||
|
|
||||||
|
dialogPane.setContent(content);
|
||||||
|
passphrase.requestFocus();
|
||||||
|
|
||||||
|
setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? passphrase.getText() : null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletImportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.io.ColdcardMultisig;
|
||||||
|
import com.sparrowwallet.sparrow.io.Electrum;
|
||||||
|
import com.sparrowwallet.sparrow.io.WalletExport;
|
||||||
|
import com.sparrowwallet.sparrow.io.WalletImport;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class WalletExportDialog extends Dialog<Wallet> {
|
||||||
|
private Wallet wallet;
|
||||||
|
|
||||||
|
public WalletExportDialog(Wallet wallet) {
|
||||||
|
EventManager.get().register(this);
|
||||||
|
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
|
||||||
|
StackPane stackPane = new StackPane();
|
||||||
|
dialogPane.setContent(stackPane);
|
||||||
|
|
||||||
|
AnchorPane anchorPane = new AnchorPane();
|
||||||
|
stackPane.getChildren().add(anchorPane);
|
||||||
|
|
||||||
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
|
scrollPane.setPrefHeight(280);
|
||||||
|
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
anchorPane.getChildren().add(scrollPane);
|
||||||
|
scrollPane.setFitToWidth(true);
|
||||||
|
AnchorPane.setLeftAnchor(scrollPane, 0.0);
|
||||||
|
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||||
|
|
||||||
|
List<WalletExport> exporters;
|
||||||
|
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||||
|
exporters = List.of(new Electrum());
|
||||||
|
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||||
|
exporters = List.of(new ColdcardMultisig(), new Electrum());
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
||||||
|
}
|
||||||
|
|
||||||
|
Accordion exportAccordion = new Accordion();
|
||||||
|
for (WalletExport exporter : exporters) {
|
||||||
|
FileWalletExportPane exportPane = new FileWalletExportPane(wallet, exporter);
|
||||||
|
exportAccordion.getPanes().add(exportPane);
|
||||||
|
}
|
||||||
|
scrollPane.setContent(exportAccordion);
|
||||||
|
|
||||||
|
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||||
|
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||||
|
dialogPane.setPrefWidth(500);
|
||||||
|
dialogPane.setPrefHeight(360);
|
||||||
|
|
||||||
|
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletExported(WalletExportEvent event) {
|
||||||
|
wallet = event.getWallet();
|
||||||
|
setResult(wallet);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
|
||||||
|
public class TabSelectedEvent extends TabEvent {
|
||||||
|
public TabSelectedEvent(Tab tab) {
|
||||||
|
super(tab);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
|
|
||||||
public class TransactionTabSelectedEvent extends TabEvent {
|
public class TransactionTabSelectedEvent extends TabSelectedEvent {
|
||||||
public TransactionTabSelectedEvent(Tab tab) {
|
public TransactionTabSelectedEvent(Tab tab) {
|
||||||
super(tab);
|
super(tab);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,22 @@ package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
public class WalletChangedEvent {
|
import java.io.File;
|
||||||
private Wallet wallet;
|
|
||||||
|
|
||||||
public WalletChangedEvent(Wallet wallet) {
|
public class WalletChangedEvent {
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final File walletFile;
|
||||||
|
|
||||||
|
public WalletChangedEvent(Wallet wallet, File walletFile) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
|
this.walletFile = walletFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getWalletFile() {
|
||||||
|
return walletFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
|
public class WalletExportEvent {
|
||||||
|
private Wallet wallet;
|
||||||
|
|
||||||
|
public WalletExportEvent(Wallet wallet) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet getWallet() {
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
|
||||||
|
public class WalletTabSelectedEvent extends TabSelectedEvent {
|
||||||
|
public WalletTabSelectedEvent(Tab tab) {
|
||||||
|
super(tab);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||||
CIRCLE('\uf111'),
|
CIRCLE('\uf111'),
|
||||||
EXCLAMATION_CIRCLE('\uf06a'),
|
EXCLAMATION_CIRCLE('\uf06a'),
|
||||||
EYE('\uf06e'),
|
EYE('\uf06e'),
|
||||||
|
KEY('\uf084'),
|
||||||
LAPTOP('\uf109'),
|
LAPTOP('\uf109'),
|
||||||
SD_CARD('\uf7c2'),
|
SD_CARD('\uf7c2'),
|
||||||
WALLET('\uf555');
|
WALLET('\uf555');
|
||||||
|
|
|
@ -157,15 +157,36 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy");
|
throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType());
|
ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), false);
|
||||||
|
ExtendedKey.Header xprvHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), true);
|
||||||
|
|
||||||
int index = 1;
|
int index = 1;
|
||||||
for(Keystore keystore : wallet.getKeystores()) {
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
ElectrumKeystore ek = new ElectrumKeystore();
|
ElectrumKeystore ek = new ElectrumKeystore();
|
||||||
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
|
||||||
|
if(keystore.getSource() == KeystoreSource.HW_USB || keystore.getSource() == KeystoreSource.HW_AIRGAPPED) {
|
||||||
|
ek.label = keystore.getLabel();
|
||||||
ek.derivation = keystore.getKeyDerivation().getDerivationPath();
|
ek.derivation = keystore.getKeyDerivation().getDerivationPath();
|
||||||
ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint();
|
ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint();
|
||||||
ek.label = keystore.getLabel();
|
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||||
|
ek.type = "hardware";
|
||||||
|
ek.hw_type = keystore.getWalletModel().getType();
|
||||||
|
ew.use_encryption = false;
|
||||||
|
} else if(keystore.getSource() == KeystoreSource.SW_SEED) {
|
||||||
|
ek.type = "bip32";
|
||||||
|
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||||
|
ek.xprv = keystore.getExtendedPrivateKey().toString(xprvHeader);
|
||||||
|
ek.pw_hash_version = 1;
|
||||||
|
ew.seed_type = "bip39";
|
||||||
|
ew.use_encryption = false;
|
||||||
|
} else if(keystore.getSource() == KeystoreSource.SW_WATCH) {
|
||||||
|
ek.type = "bip32";
|
||||||
|
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||||
|
ek.pw_hash_version = 1;
|
||||||
|
ew.use_encryption = false;
|
||||||
|
} else {
|
||||||
|
throw new ExportException("Cannot export a keystore of source " + keystore.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
||||||
ew.keystores.put("keystore", ek);
|
ew.keystores.put("keystore", ek);
|
||||||
|
@ -179,6 +200,12 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject();
|
JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject();
|
||||||
eJson.addProperty("wallet_type", ew.wallet_type);
|
eJson.addProperty("wallet_type", ew.wallet_type);
|
||||||
|
if(ew.use_encryption != null) {
|
||||||
|
eJson.addProperty("use_encryption", ew.use_encryption);
|
||||||
|
}
|
||||||
|
if(ew.seed_type != null) {
|
||||||
|
eJson.addProperty("seed_type", ew.seed_type);
|
||||||
|
}
|
||||||
|
|
||||||
gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
String json = gson.toJson(eJson);
|
String json = gson.toJson(eJson);
|
||||||
|
@ -203,6 +230,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
private static class ElectrumJsonWallet {
|
private static class ElectrumJsonWallet {
|
||||||
public Map<String, ElectrumKeystore> keystores = new LinkedHashMap<>();
|
public Map<String, ElectrumKeystore> keystores = new LinkedHashMap<>();
|
||||||
public String wallet_type;
|
public String wallet_type;
|
||||||
|
public String seed_type;
|
||||||
|
public Boolean use_encryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ElectrumKeystore {
|
public static class ElectrumKeystore {
|
||||||
|
@ -216,5 +245,6 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
public String type;
|
public String type;
|
||||||
public String derivation;
|
public String derivation;
|
||||||
public String seed;
|
public String seed;
|
||||||
|
public Integer pw_hash_version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
public interface Export {
|
public interface Export {
|
||||||
String getName();
|
String getName();
|
||||||
|
WalletModel getWalletModel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
public class ExportException extends Throwable {
|
public class ExportException extends Exception {
|
||||||
public ExportException() {
|
public ExportException() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.ExtendedKey;
|
import com.sparrowwallet.drongo.ExtendedKey;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
|
||||||
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.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
@ -20,10 +19,8 @@ import org.controlsfx.validation.ValidationResult;
|
||||||
import org.controlsfx.validation.ValidationSupport;
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
import tornadofx.control.Form;
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -94,7 +91,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT));
|
||||||
});
|
});
|
||||||
derivation.textProperty().addListener((observable, oldValue, newValue) -> {
|
derivation.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(KeyDerivation.isValid(newValue) && !matchesAnotherScriptType(newValue)) {
|
if(KeyDerivation.isValid(newValue) && !walletForm.getWallet().derivationMatchesAnotherScriptType(newValue)) {
|
||||||
keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue));
|
keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue));
|
||||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_DERIVATION));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_DERIVATION));
|
||||||
}
|
}
|
||||||
|
@ -140,7 +137,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
validationSupport.registerValidator(derivation, Validator.combine(
|
validationSupport.registerValidator(derivation, Validator.combine(
|
||||||
Validator.createEmptyValidator("Derivation is required"),
|
Validator.createEmptyValidator("Derivation is required"),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation is invalid", !KeyDerivation.isValid(newValue)),
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation is invalid", !KeyDerivation.isValid(newValue)),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation matches another script type", matchesAnotherScriptType(newValue))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation matches another script type", walletForm.getWallet().derivationMatchesAnotherScriptType(newValue))
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.registerValidator(fingerprint, Validator.combine(
|
validationSupport.registerValidator(fingerprint, Validator.combine(
|
||||||
|
@ -151,14 +148,6 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchesAnotherScriptType(String derivationPath) {
|
|
||||||
if(walletForm.getWallet().getScriptType() != null && walletForm.getWallet().getScriptType().getAccount(derivationPath) > -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arrays.stream(ScriptType.values()).anyMatch(scriptType -> !scriptType.equals(walletForm.getWallet().getScriptType()) && scriptType.getAccount(derivationPath) > -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateType() {
|
private void updateType() {
|
||||||
type.setText(getTypeLabel(keystore));
|
type.setText(getTypeLabel(keystore));
|
||||||
|
|
||||||
|
@ -211,9 +200,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void update(SettingsChangedEvent event) {
|
public void update(SettingsChangedEvent event) {
|
||||||
if(event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE) && !derivation.getText().isEmpty()) {
|
if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE) && !derivation.getText().isEmpty()) {
|
||||||
String derivationPath = derivation.getText();
|
String derivationPath = derivation.getText();
|
||||||
derivation.setText("");
|
derivation.setText(derivationPath + " ");
|
||||||
derivation.setText(derivationPath);
|
derivation.setText(derivationPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
walletForm.save();
|
walletForm.save();
|
||||||
revert.setDisable(true);
|
revert.setDisable(true);
|
||||||
apply.setDisable(true);
|
apply.setDisable(true);
|
||||||
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
|
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile()));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
AppController.showErrorDialog("Error saving file", e.getMessage());
|
AppController.showErrorDialog("Error saving file", e.getMessage());
|
||||||
|
@ -226,13 +226,11 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
if(result.getErrors().isEmpty()) {
|
if(result.getErrors().isEmpty()) {
|
||||||
tab.getStyleClass().remove("tab-error");
|
tab.getStyleClass().remove("tab-error");
|
||||||
tab.setTooltip(null);
|
tab.setTooltip(null);
|
||||||
apply.setDisable(false);
|
|
||||||
} else {
|
} else {
|
||||||
if(!tab.getStyleClass().contains("tab-error")) {
|
if(!tab.getStyleClass().contains("tab-error")) {
|
||||||
tab.getStyleClass().add("tab-error");
|
tab.getStyleClass().add("tab-error");
|
||||||
}
|
}
|
||||||
tab.setTooltip(new Tooltip(result.getErrors().iterator().next().getText()));
|
tab.setTooltip(new Tooltip(result.getErrors().iterator().next().getText()));
|
||||||
apply.setDisable(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -242,28 +240,20 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tabsValidate() {
|
|
||||||
for(Tab tab : keystoreTabs.getTabs()) {
|
|
||||||
if(tab.getStyleClass().contains("tab-error")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void update(SettingsChangedEvent event) {
|
public void update(SettingsChangedEvent event) {
|
||||||
Wallet wallet = event.getWallet();
|
Wallet wallet = event.getWallet();
|
||||||
|
if(walletForm.getWallet().equals(wallet)) {
|
||||||
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1));
|
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1));
|
||||||
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue()));
|
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
spendingMiniscript.setText(event.getWallet().getDefaultPolicy().getMiniscript().getScript());
|
spendingMiniscript.setText(wallet.getDefaultPolicy().getMiniscript().getScript());
|
||||||
revert.setDisable(false);
|
revert.setDisable(false);
|
||||||
Platform.runLater(() -> apply.setDisable(!tabsValidate()));
|
apply.setDisable(!wallet.isValid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ECKey> requestEncryption(ECKey existingPubKey) {
|
private Optional<ECKey> requestEncryption(ECKey existingPubKey) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import java.io.IOException;
|
||||||
public class WalletForm {
|
public class WalletForm {
|
||||||
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECIESKeyCrypter.deriveECKey(""));
|
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECIESKeyCrypter.deriveECKey(""));
|
||||||
|
|
||||||
private File walletFile;
|
private final File walletFile;
|
||||||
private ECKey encryptionPubKey;
|
private ECKey encryptionPubKey;
|
||||||
private Wallet oldWallet;
|
private Wallet oldWallet;
|
||||||
private Wallet wallet;
|
private Wallet wallet;
|
||||||
|
@ -27,6 +27,10 @@ public class WalletForm {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getWalletFile() {
|
||||||
|
return walletFile;
|
||||||
|
}
|
||||||
|
|
||||||
public ECKey getEncryptionPubKey() {
|
public ECKey getEncryptionPubKey() {
|
||||||
return encryptionPubKey;
|
return encryptionPubKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
<MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/>
|
<MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/>
|
||||||
|
<MenuItem fx:id="exportWallet" mnemonicParsing="false" text="Export Wallet..." onAction="#exportWallet"/>
|
||||||
<MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/>
|
<MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
Loading…
Reference in a new issue