save psbts in binary format by default, add file submenu item for base64

This commit is contained in:
Craig Raw 2021-04-02 14:12:48 +02:00
parent 5d91f033c0
commit 08edc04c6d
3 changed files with 88 additions and 41 deletions

View file

@ -50,7 +50,10 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.input.*; import javafx.scene.input.*;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.*; import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import javafx.util.Duration; import javafx.util.Duration;
import org.controlsfx.control.Notifications; import org.controlsfx.control.Notifications;
import org.controlsfx.control.StatusBar; import org.controlsfx.control.StatusBar;
@ -64,7 +67,6 @@ import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.*;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.AppServices.*; import static com.sparrowwallet.sparrow.AppServices.*;
@ -81,6 +83,9 @@ public class AppController implements Initializable {
@FXML @FXML
private MenuItem saveTransaction; private MenuItem saveTransaction;
@FXML
private Menu savePSBT;
@FXML @FXML
private MenuItem exportWallet; private MenuItem exportWallet;
@ -243,6 +248,7 @@ public class AppController implements Initializable {
openWalletsInNewWindows.setSelected(Config.get().isOpenWalletsInNewWindows()); openWalletsInNewWindows.setSelected(Config.get().isOpenWalletsInNewWindows());
hideEmptyUsedAddresses.setSelected(Config.get().isHideEmptyUsedAddresses()); hideEmptyUsedAddresses.setSelected(Config.get().isHideEmptyUsedAddresses());
showTxHex.setSelected(Config.get().isShowTransactionHex()); showTxHex.setSelected(Config.get().isShowTransactionHex());
savePSBT.visibleProperty().bind(saveTransaction.visibleProperty().not());
exportWallet.setDisable(true); exportWallet.setDisable(true);
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not()))); refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
@ -482,26 +488,18 @@ public class AppController implements Initializable {
TransactionTabData transactionTabData = (TransactionTabData)tabData; TransactionTabData transactionTabData = (TransactionTabData)tabData;
Transaction transaction = transactionTabData.getTransaction(); Transaction transaction = transactionTabData.getTransaction();
//Save a transaction if the PSBT is null or transaction has already been extracted, otherwise save PSBT
//The PSBT's transaction is not altered with transaction extraction, but the extracted transaction is stored in TransactionData
boolean saveTx = (transactionTabData.getPsbt() == null || transactionTabData.getPsbt().getTransaction() != transaction);
Stage window = new Stage(); Stage window = new Stage();
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save " + (saveTx ? "Transaction" : "PSBT")); fileChooser.setTitle("Save Transaction");
String fileName = selectedTab.getText(); String fileName = ((Label)selectedTab.getGraphic()).getText();
if(fileName != null && !fileName.isEmpty()) { if(fileName != null && !fileName.isEmpty()) {
if(transactionTabData.getPsbt() != null) { if(fileName.endsWith(".psbt")) {
if(!fileName.endsWith(".psbt")) { fileName = fileName.substring(0, fileName.length() - ".psbt".length());
fileName += ".psbt";
}
} else if(!fileName.endsWith(".txn")) {
fileName += ".txn";
} }
if(saveTx && fileName.endsWith(".psbt")) { if(!fileName.endsWith(".txn")) {
fileName = fileName.replace(".psbt", "") + ".txn"; fileName += ".txn";
} }
fileChooser.setInitialFileName(fileName); fileChooser.setInitialFileName(fileName);
@ -509,18 +507,8 @@ public class AppController implements Initializable {
File file = fileChooser.showSaveDialog(window); File file = fileChooser.showSaveDialog(window);
if(file != null) { if(file != null) {
if(!saveTx && !file.getName().toLowerCase().endsWith(".psbt")) {
file = new File(file.getAbsolutePath() + ".psbt");
}
try {
try(PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8)) { try(PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8)) {
if(saveTx) {
writer.print(Utils.bytesToHex(transaction.bitcoinSerialize())); writer.print(Utils.bytesToHex(transaction.bitcoinSerialize()));
} else {
writer.print(transactionTabData.getPsbt().toBase64String());
}
}
} catch(IOException e) { } catch(IOException e) {
log.error("Error saving transaction", e); log.error("Error saving transaction", e);
AppServices.showErrorDialog("Error saving transaction", "Cannot write to " + file.getAbsolutePath()); AppServices.showErrorDialog("Error saving transaction", "Cannot write to " + file.getAbsolutePath());
@ -529,6 +517,58 @@ public class AppController implements Initializable {
} }
} }
public void savePSBTBinary(ActionEvent event) {
savePSBT(false);
}
public void savePSBTText(ActionEvent event) {
savePSBT(true);
}
public void savePSBT(boolean asText) {
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
TabData tabData = (TabData)selectedTab.getUserData();
if(tabData.getType() == TabData.TabType.TRANSACTION) {
TransactionTabData transactionTabData = (TransactionTabData)tabData;
Stage window = new Stage();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save PSBT");
String fileName = ((Label)selectedTab.getGraphic()).getText();
if(fileName != null && !fileName.isEmpty()) {
if(!fileName.endsWith(".psbt")) {
fileName += ".psbt";
}
if(asText) {
fileName += ".txt";
}
fileChooser.setInitialFileName(fileName);
}
File file = fileChooser.showSaveDialog(window);
if(file != null) {
if(!asText && !file.getName().toLowerCase().endsWith(".psbt")) {
file = new File(file.getAbsolutePath() + ".psbt");
}
try(FileOutputStream outputStream = new FileOutputStream(file)) {
if(asText) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
writer.print(transactionTabData.getPsbt().toBase64String());
writer.flush();
} else {
outputStream.write(transactionTabData.getPsbt().serialize());
}
} catch(IOException e) {
log.error("Error saving PSBT", e);
AppServices.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath());
}
}
}
}
public List<WalletTabData> getOpenWalletTabData() { public List<WalletTabData> getOpenWalletTabData() {
List<WalletTabData> openWalletTabData = new ArrayList<>(); List<WalletTabData> openWalletTabData = new ArrayList<>();
@ -1065,7 +1105,7 @@ public class AppController implements Initializable {
//As per BIP174, combine PSBTs with matching transactions so long as they are not yet finalized //As per BIP174, combine PSBTs with matching transactions so long as they are not yet finalized
transactionTabData.getPsbt().combine(psbt); transactionTabData.getPsbt().combine(psbt);
if(name != null && !name.isEmpty()) { if(name != null && !name.isEmpty()) {
tab.setText(name); ((Label)tab.getGraphic()).setText(name);
} }
EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt())); EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt()));
@ -1080,7 +1120,7 @@ public class AppController implements Initializable {
} }
if(name != null && !name.isEmpty()) { if(name != null && !name.isEmpty()) {
tab.setText(name); ((Label)tab.getGraphic()).setText(name);
} }
EventManager.get().post(new PSBTFinalizedEvent(transactionTabData.getPsbt())); EventManager.get().post(new PSBTFinalizedEvent(transactionTabData.getPsbt()));
@ -1268,15 +1308,19 @@ public class AppController implements Initializable {
if(event instanceof TransactionTabSelectedEvent) { if(event instanceof TransactionTabSelectedEvent) {
TransactionTabSelectedEvent txTabEvent = (TransactionTabSelectedEvent)event; TransactionTabSelectedEvent txTabEvent = (TransactionTabSelectedEvent)event;
TransactionTabData transactionTabData = txTabEvent.getTransactionTabData(); TransactionTabData transactionTabData = txTabEvent.getTransactionTabData();
if(transactionTabData.getPsbt() == null || transactionTabData.getPsbt().getTransaction() != transactionTabData.getTransaction()) {
saveTransaction.setVisible(true);
saveTransaction.setDisable(false); saveTransaction.setDisable(false);
saveTransaction.setText("Save " + (transactionTabData.getPsbt() == null || transactionTabData.getPsbt().getTransaction() != transactionTabData.getTransaction() ? "Transaction..." : "PSBT...")); } else {
saveTransaction.setVisible(false);
}
exportWallet.setDisable(true); exportWallet.setDisable(true);
showTxHex.setDisable(false); showTxHex.setDisable(false);
} else if(event instanceof WalletTabSelectedEvent) { } else if(event instanceof WalletTabSelectedEvent) {
WalletTabSelectedEvent walletTabEvent = (WalletTabSelectedEvent)event; WalletTabSelectedEvent walletTabEvent = (WalletTabSelectedEvent)event;
WalletTabData walletTabData = walletTabEvent.getWalletTabData(); WalletTabData walletTabData = walletTabEvent.getWalletTabData();
saveTransaction.setVisible(true);
saveTransaction.setDisable(true); saveTransaction.setDisable(true);
saveTransaction.setText("Save Transaction...");
exportWallet.setDisable(walletTabData.getWallet() == null || !walletTabData.getWallet().isValid()); exportWallet.setDisable(walletTabData.getWallet() == null || !walletTabData.getWallet().isValid());
showTxHex.setDisable(true); showTxHex.setDisable(true);
} }
@ -1290,7 +1334,8 @@ public class AppController implements Initializable {
if(tabData instanceof TransactionTabData) { if(tabData instanceof TransactionTabData) {
TransactionTabData transactionTabData = (TransactionTabData)tabData; TransactionTabData transactionTabData = (TransactionTabData)tabData;
if(transactionTabData.getTransaction() == event.getFinalTransaction()) { if(transactionTabData.getTransaction() == event.getFinalTransaction()) {
saveTransaction.setText("Save Transaction..."); saveTransaction.setVisible(true);
saveTransaction.setDisable(false);
} }
} }
} }

View file

@ -680,10 +680,8 @@ public class HeadersController extends TransactionFormController implements Init
file = new File(file.getAbsolutePath() + ".psbt"); file = new File(file.getAbsolutePath() + ".psbt");
} }
try { try(FileOutputStream outputStream = new FileOutputStream(file)) {
try(PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8)) { outputStream.write(headersForm.getPsbt().serialize());
writer.print(headersForm.getPsbt().toBase64String());
}
} catch(IOException e) { } catch(IOException e) {
log.error("Error saving PSBT", e); log.error("Error saving PSBT", e);
AppServices.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath()); AppServices.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath());

View file

@ -27,6 +27,10 @@
</items> </items>
</Menu> </Menu>
<MenuItem fx:id="saveTransaction" mnemonicParsing="false" text="Save Transaction..." accelerator="Shortcut+S" onAction="#saveTransaction"/> <MenuItem fx:id="saveTransaction" mnemonicParsing="false" text="Save Transaction..." accelerator="Shortcut+S" onAction="#saveTransaction"/>
<Menu fx:id="savePSBT" mnemonicParsing="false" text="Save PSBT">
<MenuItem text="As Binary..." onAction="#savePSBTBinary" accelerator="Shortcut+S"/>
<MenuItem text="As Base64..." onAction="#savePSBTText"/>
</Menu>
<SeparatorMenuItem /> <SeparatorMenuItem />
<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 fx:id="exportWallet" mnemonicParsing="false" text="Export Wallet..." onAction="#exportWallet"/>