mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add blue wallet vault import and export and add wallet export via qr
This commit is contained in:
parent
c27a576b3d
commit
622b681109
12 changed files with 141 additions and 19 deletions
|
@ -2,44 +2,81 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.SecureString;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.hummingbird.registry.RegistryType;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.io.CoboVaultMultisig;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.io.WalletExport;
|
import com.sparrowwallet.sparrow.io.WalletExport;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.controlsfx.control.SegmentedButton;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.FileOutputStream;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class FileWalletExportPane extends TitledDescriptionPane {
|
public class FileWalletExportPane extends TitledDescriptionPane {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final WalletExport exporter;
|
private final WalletExport exporter;
|
||||||
|
private final boolean scannable;
|
||||||
|
|
||||||
public FileWalletExportPane(Wallet wallet, WalletExport exporter) {
|
public FileWalletExportPane(Wallet wallet, WalletExport exporter) {
|
||||||
super(exporter.getName(), "Wallet file export", exporter.getWalletExportDescription(), "image/" + exporter.getWalletModel().getType() + ".png");
|
super(exporter.getName(), "Wallet file export", exporter.getWalletExportDescription(), "image/" + exporter.getWalletModel().getType() + ".png");
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.exporter = exporter;
|
this.exporter = exporter;
|
||||||
|
this.scannable = exporter.isWalletExportScannable();
|
||||||
|
|
||||||
|
buttonBox.getChildren().clear();
|
||||||
|
buttonBox.getChildren().add(createButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Control createButton() {
|
protected Control createButton() {
|
||||||
Button exportButton = new Button("Export Wallet...");
|
if(scannable) {
|
||||||
exportButton.setAlignment(Pos.CENTER_RIGHT);
|
ToggleButton showButton = new ToggleButton("Show...");
|
||||||
exportButton.setOnAction(event -> {
|
Glyph cameraGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CAMERA);
|
||||||
exportWallet();
|
cameraGlyph.setFontSize(12);
|
||||||
});
|
showButton.setGraphic(cameraGlyph);
|
||||||
return exportButton;
|
showButton.setOnAction(event -> {
|
||||||
|
showButton.setSelected(false);
|
||||||
|
exportQR();
|
||||||
|
});
|
||||||
|
|
||||||
|
ToggleButton fileButton = new ToggleButton("Export File...");
|
||||||
|
fileButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
fileButton.setOnAction(event -> {
|
||||||
|
fileButton.setSelected(false);
|
||||||
|
exportFile();
|
||||||
|
});
|
||||||
|
|
||||||
|
SegmentedButton segmentedButton = new SegmentedButton();
|
||||||
|
segmentedButton.getButtons().addAll(showButton, fileButton);
|
||||||
|
return segmentedButton;
|
||||||
|
} else {
|
||||||
|
Button exportButton = new Button("Export File...");
|
||||||
|
exportButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
exportButton.setOnAction(event -> {
|
||||||
|
exportFile();
|
||||||
|
});
|
||||||
|
return exportButton;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportWallet() {
|
private void exportQR() {
|
||||||
|
exportWallet(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportFile() {
|
||||||
Stage window = new Stage();
|
Stage window = new Stage();
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
@ -59,17 +96,18 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
||||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
Optional<SecureString> password = dlg.showAndWait();
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
if(password.isPresent()) {
|
if(password.isPresent()) {
|
||||||
|
final File walletFile = AppServices.get().getOpenWallets().get(wallet).getWalletFile();
|
||||||
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
|
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
|
||||||
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(file, TimedEvent.Action.END, "Done"));
|
EventManager.get().post(new StorageEvent(walletFile, TimedEvent.Action.END, "Done"));
|
||||||
Wallet decryptedWallet = decryptWalletService.getValue();
|
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||||
exportWallet(file, decryptedWallet);
|
exportWallet(file, decryptedWallet);
|
||||||
});
|
});
|
||||||
decryptWalletService.setOnFailed(workerStateEvent -> {
|
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(file, TimedEvent.Action.END, "Failed"));
|
EventManager.get().post(new StorageEvent(walletFile, TimedEvent.Action.END, "Failed"));
|
||||||
setError("Export Error", decryptWalletService.getException().getMessage());
|
setError("Export Error", decryptWalletService.getException().getMessage());
|
||||||
});
|
});
|
||||||
EventManager.get().post(new StorageEvent(file, TimedEvent.Action.START, "Decrypting wallet..."));
|
EventManager.get().post(new StorageEvent(walletFile, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||||
decryptWalletService.start();
|
decryptWalletService.start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,9 +117,21 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
||||||
|
|
||||||
private void exportWallet(File file, Wallet decryptedWallet) {
|
private void exportWallet(File file, Wallet decryptedWallet) {
|
||||||
try {
|
try {
|
||||||
OutputStream outputStream = new FileOutputStream(file);
|
if(file != null) {
|
||||||
exporter.exportWallet(decryptedWallet, outputStream);
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
EventManager.get().post(new WalletExportEvent(decryptedWallet));
|
exporter.exportWallet(decryptedWallet, outputStream);
|
||||||
|
EventManager.get().post(new WalletExportEvent(decryptedWallet));
|
||||||
|
} else {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
exporter.exportWallet(decryptedWallet, outputStream);
|
||||||
|
QRDisplayDialog qrDisplayDialog;
|
||||||
|
if(exporter instanceof CoboVaultMultisig) {
|
||||||
|
qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true);
|
||||||
|
} else {
|
||||||
|
qrDisplayDialog = new QRDisplayDialog(outputStream.toString(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
qrDisplayDialog.showAndWait();
|
||||||
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
String errorMessage = e.getMessage();
|
String errorMessage = e.getMessage();
|
||||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class WalletExportDialog extends Dialog<Wallet> {
|
||||||
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||||
exporters = List.of(new Electrum(), new SpecterDesktop());
|
exporters = List.of(new Electrum(), new SpecterDesktop());
|
||||||
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||||
exporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop());
|
exporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop(), new BlueWalletMultisig());
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||||
importAccordion.getPanes().add(importPane);
|
importAccordion.getPanes().add(importPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<WalletImport> walletImporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop());
|
List<WalletImport> walletImporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop(), new BlueWalletMultisig());
|
||||||
for(WalletImport importer : walletImporters) {
|
for(WalletImport importer : walletImporters) {
|
||||||
FileWalletImportPane importPane = new FileWalletImportPane(importer);
|
FileWalletImportPane importPane = new FileWalletImportPane(importer);
|
||||||
importAccordion.getPanes().add(importPane);
|
importAccordion.getPanes().add(importPane);
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class BlueWalletMultisig extends ColdcardMultisig {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Blue Wallet Vault Multisig";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WalletModel getWalletModel() {
|
||||||
|
return WalletModel.BLUE_WALLET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
||||||
|
Wallet wallet = super.importWallet(inputStream, password);
|
||||||
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
|
keystore.setLabel(keystore.getLabel().replace("Coldcard", "Blue Wallet"));
|
||||||
|
keystore.setWalletModel(WalletModel.BLUE_WALLET);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWalletImportDescription() {
|
||||||
|
return "Import file or QR created by using the Wallet > Export Coordination Setup feature on your Blue Wallet Vault wallet.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWalletExportDescription() {
|
||||||
|
return "Export file that can be read by Blue Wallet using the Add Wallet > Vault > Import wallet feature.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletImportScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,4 +62,9 @@ public class CoboVaultMultisig extends ColdcardMultisig {
|
||||||
public boolean isKeystoreImportScannable() {
|
public boolean isKeystoreImportScannable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,4 +222,9 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||||
public boolean isKeystoreImportScannable() {
|
public boolean isKeystoreImportScannable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,6 +359,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletExportDescription() {
|
public String getWalletExportDescription() {
|
||||||
return "Export this wallet as an Electrum wallet file.";
|
return "Export this wallet as an Electrum wallet file.";
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||||
import com.sparrowwallet.drongo.wallet.InvalidWalletException;
|
import com.sparrowwallet.drongo.wallet.InvalidWalletException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
@ -19,7 +20,7 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
||||||
try {
|
try {
|
||||||
SpecterWallet specterWallet = new SpecterWallet();
|
SpecterWallet specterWallet = new SpecterWallet();
|
||||||
specterWallet.label = wallet.getName();
|
specterWallet.label = wallet.getName();
|
||||||
specterWallet.blockheight = wallet.getStoredBlockHeight();
|
specterWallet.blockheight = wallet.getTransactions().values().stream().mapToInt(BlockTransactionHash::getHeight).min().orElse(wallet.getStoredBlockHeight());
|
||||||
specterWallet.descriptor = OutputDescriptor.getOutputDescriptor(wallet).toString(true);
|
specterWallet.descriptor = OutputDescriptor.getOutputDescriptor(wallet).toString(true);
|
||||||
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
@ -83,6 +84,11 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletExportScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Specter Desktop";
|
return "Specter Desktop";
|
||||||
|
|
|
@ -8,4 +8,5 @@ public interface WalletExport extends Export {
|
||||||
void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException;
|
void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException;
|
||||||
String getWalletExportDescription();
|
String getWalletExportDescription();
|
||||||
String getExportFileExtension();
|
String getExportFileExtension();
|
||||||
|
boolean isWalletExportScannable();
|
||||||
}
|
}
|
||||||
|
|
BIN
src/main/resources/image/bluewallet.png
Normal file
BIN
src/main/resources/image/bluewallet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
src/main/resources/image/bluewallet@2x.png
Normal file
BIN
src/main/resources/image/bluewallet@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
src/main/resources/image/bluewallet@3x.png
Normal file
BIN
src/main/resources/image/bluewallet@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue