mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-31 12:06:15 +00:00
Compare commits
12
Commits
1.4.2-beta
..
1.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8d997fbf0 | ||
|
|
911ed3a718 | ||
|
|
dbfed31432 | ||
|
|
6f3d4e224e | ||
|
|
4d6609990c | ||
|
|
5482196cc7 | ||
|
|
09f6c9ef81 | ||
|
|
9b8f97c041 | ||
|
|
c68c713a4b | ||
|
|
02e144f802 | ||
|
|
a9ab4d6c78 | ||
|
|
5df4e5761c |
+1
-1
Submodule drongo updated: 729c3a1435...c021300797
@@ -961,6 +961,10 @@ public class AppController implements Initializable {
|
||||
walletFile.delete();
|
||||
}
|
||||
|
||||
if(wallet.isEncrypted()) {
|
||||
throw new IllegalArgumentException("Imported wallet must be unencrypted");
|
||||
}
|
||||
|
||||
Storage storage = new Storage(Storage.getWalletFile(wallet.getName()));
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getName(), WalletPasswordDialog.PasswordRequirement.UPDATE_NEW);
|
||||
Optional<SecureString> password = dlg.showAndWait();
|
||||
@@ -969,8 +973,10 @@ public class AppController implements Initializable {
|
||||
try {
|
||||
storage.setEncryptionPubKey(Storage.NO_PASSWORD_KEY);
|
||||
storage.saveWallet(wallet);
|
||||
checkWalletNetwork(wallet);
|
||||
restorePublicKeysFromSeed(wallet, null);
|
||||
addWalletTabOrWindow(storage, wallet, null, false);
|
||||
} catch(IOException | StorageException e) {
|
||||
} catch(IOException | StorageException | MnemonicException e) {
|
||||
log.error("Error saving imported wallet", e);
|
||||
}
|
||||
} else {
|
||||
@@ -986,8 +992,10 @@ public class AppController implements Initializable {
|
||||
wallet.encrypt(key);
|
||||
storage.setEncryptionPubKey(encryptionPubKey);
|
||||
storage.saveWallet(wallet);
|
||||
checkWalletNetwork(wallet);
|
||||
restorePublicKeysFromSeed(wallet, key);
|
||||
addWalletTabOrWindow(storage, wallet, null, false);
|
||||
} catch(IOException | StorageException e) {
|
||||
} catch(IOException | StorageException | MnemonicException e) {
|
||||
log.error("Error saving imported wallet", e);
|
||||
} finally {
|
||||
encryptionFullKey.clear();
|
||||
|
||||
@@ -585,7 +585,7 @@ public class AppServices {
|
||||
|
||||
String[] lines = content.split("\r\n|\r|\n");
|
||||
if(lines.length > 3) {
|
||||
alert.getDialogPane().setPrefHeight(180 + lines.length * 20);
|
||||
alert.getDialogPane().setPrefHeight(200 + lines.length * 20);
|
||||
}
|
||||
|
||||
moveToActiveWindowScreen(alert);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class MainApp extends Application {
|
||||
public static final String APP_ID = "com.sparrowwallet.sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "1.4.2";
|
||||
public static final String APP_VERSION_SUFFIX = "-beta";
|
||||
public static final String APP_VERSION_SUFFIX = "";
|
||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.sparrowwallet.drongo.policy.Policy;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
@@ -27,6 +28,8 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FileWalletKeystoreImportPane extends FileImportPane {
|
||||
private static final Logger log = LoggerFactory.getLogger(FileWalletKeystoreImportPane.class);
|
||||
@@ -42,35 +45,62 @@ public class FileWalletKeystoreImportPane extends FileImportPane {
|
||||
|
||||
protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException {
|
||||
this.fileName = fileName;
|
||||
try {
|
||||
fileBytes = ByteStreams.toByteArray(inputStream);
|
||||
} catch(IOException e) {
|
||||
throw new ImportException("Could not read file", e);
|
||||
|
||||
List<ScriptType> scriptTypes = ScriptType.getAddressableScriptTypes(PolicyType.SINGLE);
|
||||
if(wallets != null && !wallets.isEmpty()) {
|
||||
if(wallets.size() == 1 && scriptTypes.contains(wallets.get(0).getScriptType())) {
|
||||
Wallet wallet = wallets.get(0);
|
||||
wallet.setPolicyType(PolicyType.SINGLE);
|
||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), null));
|
||||
wallet.setName(importer.getName());
|
||||
EventManager.get().post(new WalletImportEvent(wallets.get(0)));
|
||||
} else {
|
||||
scriptTypes.retainAll(wallets.stream().map(Wallet::getScriptType).collect(Collectors.toList()));
|
||||
if(scriptTypes.isEmpty()) {
|
||||
throw new ImportException("No singlesig script types present in QR code");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
fileBytes = ByteStreams.toByteArray(inputStream);
|
||||
} catch(IOException e) {
|
||||
throw new ImportException("Could not read file", e);
|
||||
}
|
||||
}
|
||||
|
||||
setContent(getScriptTypeEntry());
|
||||
setContent(getScriptTypeEntry(scriptTypes));
|
||||
setExpanded(true);
|
||||
importButton.setDisable(true);
|
||||
}
|
||||
|
||||
private void importWallet(ScriptType scriptType) throws ImportException {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
|
||||
Keystore keystore = importer.getKeystore(scriptType, bais, "");
|
||||
if(wallets != null && !wallets.isEmpty()) {
|
||||
Wallet wallet = wallets.stream().filter(wallet1 -> wallet1.getScriptType() == scriptType).findFirst().orElseThrow(ImportException::new);
|
||||
wallet.setName(importer.getName());
|
||||
wallet.setPolicyType(PolicyType.SINGLE);
|
||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, wallet.getScriptType(), wallet.getKeystores(), null));
|
||||
EventManager.get().post(new WalletImportEvent(wallet));
|
||||
} else {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
|
||||
Keystore keystore = importer.getKeystore(scriptType, bais, "");
|
||||
|
||||
Wallet wallet = new Wallet();
|
||||
wallet.setName(fileName);
|
||||
wallet.setPolicyType(PolicyType.SINGLE);
|
||||
wallet.setScriptType(scriptType);
|
||||
wallet.getKeystores().add(keystore);
|
||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), null));
|
||||
Wallet wallet = new Wallet();
|
||||
wallet.setName(Files.getNameWithoutExtension(fileName));
|
||||
wallet.setPolicyType(PolicyType.SINGLE);
|
||||
wallet.setScriptType(scriptType);
|
||||
wallet.getKeystores().add(keystore);
|
||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), null));
|
||||
|
||||
EventManager.get().post(new WalletImportEvent(wallet));
|
||||
EventManager.get().post(new WalletImportEvent(wallet));
|
||||
}
|
||||
}
|
||||
|
||||
private Node getScriptTypeEntry() {
|
||||
private Node getScriptTypeEntry(List<ScriptType> scriptTypes) {
|
||||
Label label = new Label("Script Type:");
|
||||
ComboBox<ScriptType> scriptTypeComboBox = new ComboBox<>(FXCollections.observableArrayList(ScriptType.getAddressableScriptTypes(PolicyType.SINGLE)));
|
||||
scriptTypeComboBox.setValue(ScriptType.P2WPKH);
|
||||
ComboBox<ScriptType> scriptTypeComboBox = new ComboBox<>(FXCollections.observableArrayList(scriptTypes));
|
||||
if(scriptTypes.contains(ScriptType.P2WPKH)) {
|
||||
scriptTypeComboBox.setValue(ScriptType.P2WPKH);
|
||||
}
|
||||
|
||||
HelpLabel helpLabel = new HelpLabel();
|
||||
helpLabel.setHelpText("P2WPKH is a Native Segwit type and is usually the best choice for new wallets.\nP2SH-P2WPKH is a Wrapped Segwit type and is a reasonable choice for the widest compatibility.\nP2PKH is a Legacy type and should be avoided for new wallets.\nFor existing wallets, be sure to choose the type that matches the wallet you are importing.");
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||
@@ -44,6 +45,9 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
private final TextField address;
|
||||
private final TextArea message;
|
||||
private final TextArea signature;
|
||||
private final ToggleGroup formatGroup;
|
||||
private final ToggleButton formatTrezor;
|
||||
private final ToggleButton formatElectrum;
|
||||
private final Wallet wallet;
|
||||
private WalletNode walletNode;
|
||||
private boolean electrumSignatureFormat;
|
||||
@@ -144,7 +148,22 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
signature.setWrapText(true);
|
||||
signatureField.getInputs().add(signature);
|
||||
|
||||
fieldset.getChildren().addAll(addressField, messageField, signatureField);
|
||||
Field formatField = new Field();
|
||||
formatField.setText("Format:");
|
||||
formatGroup = new ToggleGroup();
|
||||
formatElectrum = new ToggleButton("Standard (Electrum)");
|
||||
formatTrezor = new ToggleButton("BIP137 (Trezor)");
|
||||
SegmentedButton formatButtons = new SegmentedButton(formatElectrum, formatTrezor);
|
||||
formatButtons.setToggleGroup(formatGroup);
|
||||
formatField.getInputs().add(formatButtons);
|
||||
|
||||
formatGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
electrumSignatureFormat = (newValue == formatElectrum);
|
||||
});
|
||||
|
||||
formatButtons.setDisable(wallet != null && walletNode != null && wallet.getScriptType() == ScriptType.P2PKH);
|
||||
|
||||
fieldset.getChildren().addAll(addressField, messageField, signatureField, formatField);
|
||||
form.getChildren().add(fieldset);
|
||||
dialogPane.setContent(form);
|
||||
|
||||
@@ -153,6 +172,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
address.setEditable(false);
|
||||
message.setEditable(false);
|
||||
signature.setEditable(false);
|
||||
formatButtons.setDisable(true);
|
||||
}
|
||||
|
||||
ButtonType signButtonType = new javafx.scene.control.ButtonType("Sign", ButtonBar.ButtonData.BACK_PREVIOUS);
|
||||
@@ -225,6 +245,16 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
|
||||
AppServices.moveToActiveWindowScreen(this);
|
||||
setResultConverter(dialogButton -> dialogButton == signButtonType || dialogButton == verifyButtonType ? ButtonBar.ButtonData.APPLY : dialogButton.getButtonData());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if(address.getText().isEmpty()) {
|
||||
address.requestFocus();
|
||||
} else if(message.getText().isEmpty()) {
|
||||
message.requestFocus();
|
||||
}
|
||||
|
||||
formatGroup.selectToggle(formatElectrum);
|
||||
});
|
||||
}
|
||||
|
||||
private Address getAddress()throws InvalidAddressException {
|
||||
@@ -241,6 +271,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||
* @param electrumSignatureFormat
|
||||
*/
|
||||
public void setElectrumSignatureFormat(boolean electrumSignatureFormat) {
|
||||
formatGroup.selectToggle(electrumSignatureFormat ? formatElectrum : formatTrezor);
|
||||
this.electrumSignatureFormat = electrumSignatureFormat;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
public class TitledDescriptionPane extends TitledPane {
|
||||
private Label mainLabel;
|
||||
private Label descriptionLabel;
|
||||
protected Hyperlink showHideLink;
|
||||
protected HBox buttonBox;
|
||||
@@ -50,7 +51,7 @@ public class TitledDescriptionPane extends TitledPane {
|
||||
VBox labelsBox = new VBox();
|
||||
labelsBox.setSpacing(5);
|
||||
labelsBox.setAlignment(Pos.CENTER_LEFT);
|
||||
Label mainLabel = new Label();
|
||||
mainLabel = new Label();
|
||||
mainLabel.setText(title);
|
||||
mainLabel.getStyleClass().add("main-label");
|
||||
labelsBox.getChildren().add(mainLabel);
|
||||
@@ -99,6 +100,10 @@ public class TitledDescriptionPane extends TitledPane {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mainLabel.getText();
|
||||
}
|
||||
|
||||
protected void setDescription(String text) {
|
||||
descriptionLabel.getStyleClass().remove("description-error");
|
||||
descriptionLabel.getStyleClass().add("description-label");
|
||||
|
||||
@@ -4,7 +4,11 @@ import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.stage.Popup;
|
||||
import org.fxmisc.richtext.CodeArea;
|
||||
import org.fxmisc.richtext.event.MouseOverTextEvent;
|
||||
@@ -33,7 +37,8 @@ public class TransactionHexArea extends CodeArea {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
transaction.bitcoinSerializeToStream(baos);
|
||||
|
||||
String hex = Utils.bytesToHex(baos.toByteArray());
|
||||
String fullHex = Utils.bytesToHex(baos.toByteArray());
|
||||
String hex = fullHex;
|
||||
if(hex.length() > TRUNCATE_AT) {
|
||||
hex = hex.substring(0, TRUNCATE_AT);
|
||||
hex += "[truncated]";
|
||||
@@ -42,6 +47,7 @@ public class TransactionHexArea extends CodeArea {
|
||||
clear();
|
||||
appendText(hex);
|
||||
previousSegmentList = new ArrayList<>();
|
||||
setContextMenu(new TransactionHexContextMenu(fullHex));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Can't happen");
|
||||
}
|
||||
@@ -266,4 +272,18 @@ public class TransactionHexArea extends CodeArea {
|
||||
return Objects.hash(start, length, style);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TransactionHexContextMenu extends ContextMenu {
|
||||
public TransactionHexContextMenu(String hex) {
|
||||
MenuItem copy = new MenuItem("Copy All");
|
||||
copy.setOnAction(AE -> {
|
||||
hide();
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(hex);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
|
||||
getItems().add(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class WalletExportDialog extends Dialog<Wallet> {
|
||||
@@ -53,6 +54,8 @@ public class WalletExportDialog extends Dialog<Wallet> {
|
||||
FileWalletExportPane exportPane = new FileWalletExportPane(wallet, exporter);
|
||||
exportAccordion.getPanes().add(exportPane);
|
||||
}
|
||||
|
||||
exportAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
||||
scrollPane.setContent(exportAccordion);
|
||||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
|
||||
@@ -16,6 +16,7 @@ import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class WalletImportDialog extends Dialog<Wallet> {
|
||||
@@ -47,7 +48,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||
|
||||
importAccordion = new Accordion();
|
||||
List<KeystoreFileImport> keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig());
|
||||
List<KeystoreFileImport> keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new SpecterDIY());
|
||||
for(KeystoreFileImport importer : keystoreImporters) {
|
||||
FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer);
|
||||
importAccordion.getPanes().add(importPane);
|
||||
@@ -58,6 +59,8 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||
FileWalletImportPane importPane = new FileWalletImportPane(importer);
|
||||
importAccordion.getPanes().add(importPane);
|
||||
}
|
||||
|
||||
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
||||
scrollPane.setContent(importAccordion);
|
||||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
|
||||
@@ -19,6 +19,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||
ANGLE_DOUBLE_RIGHT('\uf101'),
|
||||
ARROW_DOWN('\uf063'),
|
||||
ARROW_UP('\uf062'),
|
||||
BAN('\uf05e'),
|
||||
BTC('\uf15a'),
|
||||
CAMERA('\uf030'),
|
||||
CHECK_CIRCLE('\uf058'),
|
||||
|
||||
@@ -57,8 +57,8 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
|
||||
|
||||
return keystore;
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting Cobo Vault keystore", e);
|
||||
throw new ImportException("Error getting Cobo Vault keystore", e);
|
||||
log.error("Error getting " + getName() + " keystore", e);
|
||||
throw new ImportException("Error getting " + getName() + " keystore", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
|
||||
try {
|
||||
wallet.checkWallet();
|
||||
} catch(InvalidWalletException e) {
|
||||
throw new ImportException("Imported Cobo Vault wallet was invalid: " + e.getMessage());
|
||||
throw new ImportException("Imported " + getName() + " wallet was invalid: " + e.getMessage());
|
||||
}
|
||||
|
||||
return wallet;
|
||||
|
||||
@@ -150,8 +150,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
|
||||
return wallet;
|
||||
} catch(Exception e) {
|
||||
log.error("Error importing Coldcard multisig wallet", e);
|
||||
throw new ImportException("Error importing Coldcard multisig wallet", e);
|
||||
log.error("Error importing " + getName() + " wallet", e);
|
||||
throw new ImportException("Error importing " + getName() + " wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
|
||||
writer.flush();
|
||||
} catch(Exception e) {
|
||||
log.error("Error exporting Coldcard multisig wallet", e);
|
||||
throw new ExportException("Error exporting Coldcard multisig wallet", e);
|
||||
log.error("Error exporting " + getName() + " wallet", e);
|
||||
throw new ExportException("Error exporting " + getName() + " wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@ public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting Coldcard keystore", e);
|
||||
throw new ImportException("Error getting Coldcard keystore", e);
|
||||
log.error("Error getting " + getName() + " keystore", e);
|
||||
throw new ImportException("Error getting " + getName() + " keystore", e);
|
||||
}
|
||||
|
||||
throw new ImportException("Correct derivation not found for script type: " + scriptType);
|
||||
|
||||
@@ -62,11 +62,11 @@ public class KeystoneSinglesig implements KeystoreFileImport, WalletImport {
|
||||
|
||||
return keystore;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Error getting Keystone keystore - not an output descriptor");
|
||||
throw new ImportException("Error getting Keystone keystore", e);
|
||||
log.error("Error getting " + getName() + " keystore - not an output descriptor");
|
||||
throw new ImportException("Error getting " + getName() + " keystore", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting Keystone keystore", e);
|
||||
throw new ImportException("Error getting Keystone keystore", e);
|
||||
log.error("Error getting " + getName() + " keystore", e);
|
||||
throw new ImportException("Error getting " + getName() + " keystore", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class KeystoneSinglesig implements KeystoreFileImport, WalletImport {
|
||||
try {
|
||||
wallet.checkWallet();
|
||||
} catch(InvalidWalletException e) {
|
||||
throw new ImportException("Imported Keystone wallet was invalid: " + e.getMessage());
|
||||
throw new ImportException("Imported " + getName() + " wallet was invalid: " + e.getMessage());
|
||||
}
|
||||
|
||||
return wallet;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
|
||||
public class SeedSigner extends SpecterDIY {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SeedSigner";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeystoreImportDescription() {
|
||||
return "Import QR created on your SeedSigner by selecting Generate XPUB in the Signing Tools menu. Note that SeedSigner currently only supports P2WSH Multisig wallets.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public WalletModel getWalletModel() {
|
||||
return WalletModel.SEEDSIGNER;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class Sparrow implements WalletImport, WalletExport {
|
||||
|
||||
@Override
|
||||
public String getWalletExportDescription() {
|
||||
return "Exports your Sparrow wallet file, which can be imported into another Sparrow instance running on any supported platform.";
|
||||
return "Exports your Sparrow wallet file, which can be imported into another Sparrow instance running on any supported platform. If the wallet is encrypted, the same password is used to encrypt the exported file.";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,21 +83,38 @@ public class Sparrow implements WalletImport, WalletExport {
|
||||
|
||||
@Override
|
||||
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
||||
Storage storage = null;
|
||||
Wallet wallet = null;
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("sparrow", null);
|
||||
java.nio.file.Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
Storage storage = new Storage(PersistenceType.JSON, tempFile);
|
||||
storage = new Storage(PersistenceType.JSON, tempFile);
|
||||
if(!isEncrypted(tempFile)) {
|
||||
return storage.loadUnencryptedWallet().getWallet();
|
||||
wallet = storage.loadUnencryptedWallet().getWallet();
|
||||
} else {
|
||||
return storage.loadEncryptedWallet(password).getWallet();
|
||||
WalletBackupAndKey walletBackupAndKey = storage.loadEncryptedWallet(password);
|
||||
wallet = walletBackupAndKey.getWallet();
|
||||
wallet.decrypt(walletBackupAndKey.getKey());
|
||||
}
|
||||
|
||||
return wallet;
|
||||
} catch(IOException | StorageException e) {
|
||||
log.error("Error importing Sparrow wallet", e);
|
||||
throw new ImportException("Error importing Sparrow wallet", e);
|
||||
} finally {
|
||||
if(storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
|
||||
if(tempFile != null) {
|
||||
if(wallet != null) {
|
||||
File migratedWalletFile = Storage.getExistingWallet(tempFile.getParentFile(), wallet.getName());
|
||||
if(migratedWalletFile != null) {
|
||||
migratedWalletFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport {
|
||||
|
||||
return keystore;
|
||||
} catch(IOException e) {
|
||||
log.error("Error getting Specter DIY keystore", e);
|
||||
throw new ImportException("Error getting Specter DIY keystore", e);
|
||||
log.error("Error getting " + getName() + " keystore", e);
|
||||
throw new ImportException("Error getting " + getName() + " keystore", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport {
|
||||
writer.append("addwallet ").append(wallet.getName()).append("&").append(OutputDescriptor.getOutputDescriptor(wallet).toString().replace('\'', 'h')).append("\n");
|
||||
writer.flush();
|
||||
} catch(Exception e) {
|
||||
log.error("Error exporting Specter DIY wallet", e);
|
||||
throw new ExportException("Error exporting Specter DIY wallet", e);
|
||||
log.error("Error exporting " + getName() + " wallet", e);
|
||||
throw new ExportException("Error exporting " + getName() + " wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,7 @@ package com.sparrowwallet.sparrow.io;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||
import com.sparrowwallet.drongo.wallet.InvalidWalletException;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -15,6 +12,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class SpecterDesktop implements WalletImport, WalletExport {
|
||||
private static final Logger log = LoggerFactory.getLogger(SpecterDesktop.class);
|
||||
@@ -32,8 +30,8 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
||||
outputStream.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.flush();
|
||||
} catch(Exception e) {
|
||||
log.error("Error exporting Specter Desktop wallet", e);
|
||||
throw new ExportException("Error exporting Specter Desktop wallet", e);
|
||||
log.error("Error exporting " + getName() + " wallet", e);
|
||||
throw new ExportException("Error exporting " + getName() + " wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,20 +61,43 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
||||
Wallet wallet = outputDescriptor.toWallet();
|
||||
wallet.setName(specterWallet.label);
|
||||
|
||||
if(specterWallet.devices != null && specterWallet.devices.size() == wallet.getKeystores().size()) {
|
||||
boolean uniqueLabels = specterWallet.devices.stream().map(d -> d.label).distinct().count() == specterWallet.devices.size();
|
||||
for(int i = 0; i < specterWallet.devices.size(); i++) {
|
||||
SpecterWalletDevice device = specterWallet.devices.get(i);
|
||||
Keystore keystore = wallet.getKeystores().get(i);
|
||||
keystore.setLabel(device.label + (uniqueLabels ? "" : " " + i));
|
||||
|
||||
WalletModel walletModel = device.getWalletModel();
|
||||
if(walletModel != null) {
|
||||
keystore.setWalletModel(walletModel);
|
||||
if(walletModel == WalletModel.TREZOR_1 || walletModel == WalletModel.TREZOR_T || walletModel == WalletModel.KEEPKEY ||
|
||||
walletModel == WalletModel.LEDGER_NANO_S || walletModel == WalletModel.LEDGER_NANO_X ||
|
||||
walletModel == WalletModel.BITBOX_02 || walletModel == WalletModel.COLDCARD) {
|
||||
keystore.setSource(KeystoreSource.HW_USB);
|
||||
} else if(walletModel == WalletModel.BITCOIN_CORE) {
|
||||
keystore.setSource(KeystoreSource.SW_WATCH);
|
||||
} else {
|
||||
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
wallet.checkWallet();
|
||||
} catch(InvalidWalletException e) {
|
||||
throw new ImportException("Imported Specter wallet was invalid: " + e.getMessage());
|
||||
throw new ImportException("Imported " + getName() + " wallet was invalid: " + e.getMessage());
|
||||
}
|
||||
|
||||
return wallet;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Error importing Specter Desktop wallet", e);
|
||||
throw new ImportException("Error importing Specter Desktop wallet", e);
|
||||
log.error("Error importing " + getName() + " wallet", e);
|
||||
throw new ImportException("Error importing " + getName() + " wallet", e);
|
||||
}
|
||||
|
||||
throw new ImportException("File was not a valid Specter Desktop wallet");
|
||||
throw new ImportException("File was not a valid " + getName() + " wallet");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,5 +134,32 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
||||
public String label;
|
||||
public Integer blockheight;
|
||||
public String descriptor;
|
||||
public List<SpecterWalletDevice> devices;
|
||||
}
|
||||
|
||||
public static class SpecterWalletDevice {
|
||||
public String type;
|
||||
public String label;
|
||||
|
||||
public WalletModel getWalletModel() {
|
||||
if(type != null) {
|
||||
String model = type;
|
||||
if(model.equals("cobo")) {
|
||||
model = "cobovault";
|
||||
}
|
||||
|
||||
WalletModel walletModel = WalletModel.fromType(model);
|
||||
if(walletModel == WalletModel.SPECTER_DESKTOP) {
|
||||
walletModel = WalletModel.SPECTER_DIY;
|
||||
}
|
||||
if(walletModel == WalletModel.TREZOR_1) {
|
||||
walletModel = WalletModel.TREZOR_T;
|
||||
}
|
||||
|
||||
return walletModel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,13 +289,17 @@ public class Storage {
|
||||
}
|
||||
|
||||
public static File getExistingWallet(String walletName) {
|
||||
File encrypted = new File(getWalletsDir(), walletName.trim());
|
||||
return getExistingWallet(getWalletsDir(), walletName);
|
||||
}
|
||||
|
||||
public static File getExistingWallet(File walletsDir, String walletName) {
|
||||
File encrypted = new File(walletsDir, walletName.trim());
|
||||
if(encrypted.exists()) {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
for(PersistenceType persistenceType : PersistenceType.values()) {
|
||||
File unencrypted = new File(getWalletsDir(), walletName.trim() + "." + persistenceType.getExtension());
|
||||
File unencrypted = new File(walletsDir, walletName.trim() + "." + persistenceType.getExtension());
|
||||
if(unencrypted.exists()) {
|
||||
return unencrypted;
|
||||
}
|
||||
|
||||
@@ -555,7 +555,7 @@ public class DbPersistence implements Persistence {
|
||||
log.error("Wallet file may already be in use. Make sure the application is not running elsewhere.", e);
|
||||
throw new StorageException("Wallet file may already be in use. Make sure the application is not running elsewhere.", e);
|
||||
} else if(e.getMessage() != null && (e.getMessage().contains("Wrong user name or password") || e.getMessage().contains("Encryption error in file"))) {
|
||||
throw new InvalidPasswordException("Incorrect password for wallet file.", e);
|
||||
throw new InvalidPasswordException("Incorrect password for wallet file " + walletFile.getAbsolutePath(), e);
|
||||
} else {
|
||||
log.error("Failed to open database file", e);
|
||||
throw new StorageException("Failed to open database file.\n" + e.getMessage(), e);
|
||||
|
||||
@@ -2,11 +2,13 @@ package com.sparrowwallet.sparrow.keystoreimport;
|
||||
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
|
||||
import com.sparrowwallet.sparrow.control.TitledDescriptionPane;
|
||||
import com.sparrowwallet.sparrow.io.*;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Accordion;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class HwAirgappedController extends KeystoreImportDetailController {
|
||||
@@ -16,14 +18,16 @@ public class HwAirgappedController extends KeystoreImportDetailController {
|
||||
public void initializeView() {
|
||||
List<KeystoreFileImport> importers = Collections.emptyList();
|
||||
if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE)) {
|
||||
importers = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SpecterDIY());
|
||||
importers = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new SpecterDIY());
|
||||
} else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI)) {
|
||||
importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SpecterDIY());
|
||||
importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new SpecterDIY());
|
||||
}
|
||||
|
||||
for(KeystoreImport importer : importers) {
|
||||
FileKeystoreImportPane importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);;
|
||||
importAccordion.getPanes().add(importPane);
|
||||
}
|
||||
|
||||
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
||||
//The server may return an error if the transaction has not yet been broadcasted - this is a valid state so only try once
|
||||
return new RetryLogic<Map<String, VerboseTransaction>>(1, RETRY_DELAY, IllegalStateException.class).getResult(batchRequest::execute);
|
||||
} catch(JsonRpcBatchException e) {
|
||||
log.warn("Some errors retrieving transactions: " + e.getErrors());
|
||||
log.debug("Some errors retrieving transactions: " + e.getErrors());
|
||||
return (Map<String, VerboseTransaction>)e.getSuccesses();
|
||||
} catch(Exception e) {
|
||||
throw new ElectrumServerRpcException("Failed to retrieve verbose transactions for txids: " + txids, e);
|
||||
|
||||
@@ -255,6 +255,9 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||
messageSignDialog.setElectrumSignatureFormat(true);
|
||||
Stage stage = (Stage)messageSignDialog.getDialogPane().getScene().getWindow();
|
||||
stage.setAlwaysOnTop(true);
|
||||
messageSignDialog.setOnShown(event -> {
|
||||
stage.setAlwaysOnTop(false);
|
||||
});
|
||||
Optional<ButtonBar.ButtonData> buttonData = messageSignDialog.showAndWait();
|
||||
if(buttonData.isPresent() && buttonData.get() == ButtonBar.ButtonData.OK_DONE) {
|
||||
Address address = getWalletForm().getWallet().getAddress(currentEntry.getNode());
|
||||
|
||||
@@ -161,8 +161,11 @@ public class TransactionsController extends WalletFormController implements Init
|
||||
String date = LOG_DATE_FORMAT.format(new Date());
|
||||
String logLine = "\n" + date + " " + logMessage;
|
||||
Platform.runLater(() -> {
|
||||
loadingLog.appendText(logLine);
|
||||
loadingLog.setScrollLeft(0);
|
||||
int lastLineStart = loadingLog.getText().lastIndexOf("\n");
|
||||
if(lastLineStart < 0 || !loadingLog.getText().substring(lastLineStart).equals(logLine)) {
|
||||
loadingLog.appendText(logLine);
|
||||
loadingLog.setScrollLeft(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +461,9 @@ public class WalletForm {
|
||||
public void walletTabsClosed(WalletTabsClosedEvent event) {
|
||||
for(WalletTabData tabData : event.getClosedWalletTabData()) {
|
||||
if(tabData.getWalletForm() == this) {
|
||||
storage.close();
|
||||
if(wallet.isMasterWallet()) {
|
||||
storage.close();
|
||||
}
|
||||
if(wallet.isValid()) {
|
||||
AppServices.clearTransactionHistoryCache(wallet);
|
||||
}
|
||||
|
||||
@@ -112,8 +112,14 @@
|
||||
</menus>
|
||||
</MenuBar>
|
||||
<DecorationPane fx:id="rootStack" VBox.vgrow="ALWAYS">
|
||||
<Rectangle styleClass="background-box" width="400" height="125" />
|
||||
<Text styleClass="background-text" text="Drag files here to open" />
|
||||
<Rectangle styleClass="background-box" width="450" height="170" />
|
||||
<HBox alignment="CENTER">
|
||||
<VBox alignment="CENTER_LEFT" spacing="15">
|
||||
<Text styleClass="background-text" text="File menu → New Wallet or" />
|
||||
<Text styleClass="background-text" text="File menu → Import Wallet or" />
|
||||
<Text styleClass="background-text" text="Drag files here to open" />
|
||||
</VBox>
|
||||
</HBox>
|
||||
<TabPane fx:id="tabs" />
|
||||
</DecorationPane>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
-fx-control-inner-background: derive(-fx-base, 35%);
|
||||
-fx-control-inner-background-alt: -fx-control-inner-background ;
|
||||
-fx-default-button: -fx-accent;
|
||||
-fx-box-border: ladder(-fx-color, black 20%, derive(-fx-color,40%) 30%);
|
||||
}
|
||||
|
||||
.label{
|
||||
@@ -71,7 +72,7 @@
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
-fx-background-color: black, -fx-body-color;
|
||||
-fx-background-color: derive(-fx-color,-15%), -fx-body-color;
|
||||
}
|
||||
|
||||
.chart .default-color0.chart-series-line {
|
||||
@@ -115,13 +116,13 @@
|
||||
}
|
||||
|
||||
.root .etched-raised-border {
|
||||
-fx-border-color: #ffffff, #000000;
|
||||
-fx-border-color: #ffffff, derive(-fx-color,-15%);
|
||||
-fx-border-style: solid, solid;
|
||||
-fx-border-width: 1px, 1px;
|
||||
}
|
||||
|
||||
.root .line-border {
|
||||
-fx-border-color: #000000;
|
||||
-fx-border-color: derive(-fx-color,-15%);
|
||||
-fx-border-style: solid;
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
@@ -196,3 +197,7 @@
|
||||
.root .box-overlay .label {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
.root #loadingLog {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
Reference in New Issue
Block a user