diff --git a/drongo b/drongo index 70d0b7af..04667558 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 70d0b7afcd6f0575bf424a45f54dd6e039057dba +Subproject commit 0466755883c19a9e679f5e937b2f7db55a73e05b diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 14a4bf7b..b2f599d2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -703,7 +703,15 @@ public class AppController implements Initializable { TabData tabData = (TabData)tab.getUserData(); if(tabData instanceof TransactionTabData) { TransactionTabData transactionTabData = (TransactionTabData)tabData; - if(transactionTabData.getTransaction().getTxId().equals(transaction.getTxId())) { + + //If an exact match bytewise of an existing tab, return that tab + if(Arrays.equals(transactionTabData.getTransaction().bitcoinSerialize(), transaction.bitcoinSerialize())) { + //As per BIP174, combine PSBTs with matching transactions + if(transactionTabData.getPsbt() != null && psbt != null) { + transactionTabData.getPsbt().combine(psbt); + EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt())); + } + return tab; } } @@ -717,7 +725,7 @@ public class AppController implements Initializable { } Tab tab = new Tab(tabName); - TabData tabData = new TransactionTabData(TabData.TabType.TRANSACTION, transaction); + TabData tabData = new TransactionTabData(TabData.TabType.TRANSACTION, transaction, psbt); tab.setUserData(tabData); tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); @@ -733,6 +741,8 @@ public class AppController implements Initializable { controller.setTransaction(transaction); } + controller.setName(name); + if(initialView != null) { controller.setInitialView(initialView, initialIndex); } @@ -919,4 +929,9 @@ public class AppController implements Initializable { public void requestWalletOpen(RequestWalletOpenEvent event) { openWallet(null); } + + @Subscribe + public void requestTransactionOpen(RequestTransactionOpenEvent event) { + openTransactionFromFile(null); + } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/BaseController.java b/src/main/java/com/sparrowwallet/sparrow/BaseController.java index 3bedd60e..9547d7b0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/BaseController.java +++ b/src/main/java/com/sparrowwallet/sparrow/BaseController.java @@ -1,108 +1,37 @@ package com.sparrowwallet.sparrow; -import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.ScriptChunk; -import com.sparrowwallet.sparrow.transaction.ScriptContextMenu; +import com.sparrowwallet.sparrow.control.ScriptArea; import javafx.geometry.Point2D; import javafx.scene.control.Label; import javafx.stage.Popup; -import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.event.MouseOverTextEvent; import org.fxmisc.richtext.model.TwoDimensional; import java.time.Duration; -import static com.sparrowwallet.drongo.protocol.ScriptType.*; -import static com.sparrowwallet.drongo.protocol.ScriptType.P2WSH; import static org.fxmisc.richtext.model.TwoDimensional.Bias.Backward; public abstract class BaseController { - protected void appendScript(CodeArea codeArea, Script script) { - appendScript(codeArea, script, null, null); - } - - protected void appendScript(CodeArea codeArea, Script script, Script redeemScript, Script witnessScript) { - if(P2PKH.isScriptType(script)) { - codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append(script.getChunks().get(1).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append("", "script-hash"); - codeArea.append(" ", ""); - codeArea.append(script.getChunks().get(3).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append(script.getChunks().get(4).toString(), "script-opcode"); - } else if(P2SH.isScriptType(script)) { - codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append("", "script-hash"); - codeArea.append(" ", ""); - codeArea.append(script.getChunks().get(2).toString(), "script-opcode"); - } else if(P2WPKH.isScriptType(script)) { - codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append("", "script-hash"); - } else if(P2WSH.isScriptType(script)) { - codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); - codeArea.append(" ", ""); - codeArea.append("", "script-hash"); - } else { - int signatureCount = 1; - int pubKeyCount = 1; - for (int i = 0; i < script.getChunks().size(); i++) { - ScriptChunk chunk = script.getChunks().get(i); - if(chunk.isOpCode()) { - codeArea.append(chunk.toString(), "script-opcode"); - } else if(chunk.isSignature()) { - codeArea.append("", "script-signature"); - } else if(chunk.isPubKey()) { - codeArea.append("", "script-pubkey"); - } else if(chunk.isScript()) { - Script nestedScript = chunk.getScript(); - if (nestedScript.equals(redeemScript)) { - codeArea.append("", "script-redeem"); - } else if (nestedScript.equals(witnessScript)) { - codeArea.append("", "script-redeem"); - } else { - codeArea.append("(", "script-nest"); - appendScript(codeArea, nestedScript); - codeArea.append(")", "script-nest"); - } - } else { - codeArea.append(chunk.toString(), "script-other"); - } - - if(i < script.getChunks().size() - 1) { - codeArea.append(" ", ""); - } - } - } - - addScriptPopup(codeArea, script); - } - - protected void addScriptPopup(CodeArea area, Script script) { - ScriptContextMenu contextMenu = new ScriptContextMenu(area, script); - area.setContextMenu(contextMenu); - + protected void initializeScriptField(ScriptArea scriptArea) { Popup popup = new Popup(); Label popupMsg = new Label(); popupMsg.getStyleClass().add("tooltip"); popup.getContent().add(popupMsg); - area.setMouseOverTextDelay(Duration.ofMillis(150)); - area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> { - TwoDimensional.Position position = area.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward); + scriptArea.setMouseOverTextDelay(Duration.ofMillis(150)); + scriptArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> { + TwoDimensional.Position position = scriptArea.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward); if(position.getMajor() % 2 == 0) { - ScriptChunk hoverChunk = script.getChunks().get(position.getMajor()/2); + ScriptChunk hoverChunk = scriptArea.getScript().getChunks().get(position.getMajor()/2); if(!hoverChunk.isOpCode()) { Point2D pos = e.getScreenPosition(); popupMsg.setText(describeScriptChunk(hoverChunk)); - popup.show(area, pos.getX(), pos.getY() + 10); + popup.show(scriptArea, pos.getX(), pos.getY() + 10); } } }); - area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> { + scriptArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> { popup.hide(); }); } diff --git a/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java b/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java index 7a7137be..77cb1364 100644 --- a/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java +++ b/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java @@ -1,16 +1,27 @@ package com.sparrowwallet.sparrow; import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.psbt.PSBT; public class TransactionTabData extends TabData { - private Transaction transaction; + private final Transaction transaction; + private final PSBT psbt; public TransactionTabData(TabType type, Transaction transaction) { + this(type, transaction, null); + } + + public TransactionTabData(TabType type, Transaction transaction, PSBT psbt) { super(type); this.transaction = transaction; + this.psbt = psbt; } public Transaction getTransaction() { return transaction; } + + public PSBT getPsbt() { + return psbt; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/ScriptArea.java b/src/main/java/com/sparrowwallet/sparrow/control/ScriptArea.java new file mode 100644 index 00000000..5c4b55ca --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/ScriptArea.java @@ -0,0 +1,99 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.protocol.Script; +import com.sparrowwallet.drongo.protocol.ScriptChunk; +import com.sparrowwallet.sparrow.transaction.ScriptContextMenu; +import javafx.geometry.Pos; +import org.controlsfx.control.decoration.Decorator; +import org.controlsfx.control.decoration.GraphicDecoration; +import org.fxmisc.richtext.CodeArea; + +import static com.sparrowwallet.drongo.protocol.ScriptType.*; + +public class ScriptArea extends CodeArea { + private Script script; + + public void appendScript(Script script) { + appendScript(script, null, null); + } + + public void appendScript(Script script, Script redeemScript, Script witnessScript) { + if(this.script == null) { + this.script = script; + ScriptContextMenu contextMenu = new ScriptContextMenu(this, script); + setContextMenu(contextMenu); + } + + if(P2PKH.isScriptType(script)) { + append(script.getChunks().get(0).toString(), "script-opcode"); + append(" ", ""); + append(script.getChunks().get(1).toString(), "script-opcode"); + append(" ", ""); + append("", "script-hash"); + append(" ", ""); + append(script.getChunks().get(3).toString(), "script-opcode"); + append(" ", ""); + append(script.getChunks().get(4).toString(), "script-opcode"); + } else if(P2SH.isScriptType(script)) { + append(script.getChunks().get(0).toString(), "script-opcode"); + append(" ", ""); + append("", "script-hash"); + append(" ", ""); + append(script.getChunks().get(2).toString(), "script-opcode"); + } else if(P2WPKH.isScriptType(script)) { + append(script.getChunks().get(0).toString(), "script-opcode"); + append(" ", ""); + append("", "script-hash"); + } else if(P2WSH.isScriptType(script)) { + append(script.getChunks().get(0).toString(), "script-opcode"); + append(" ", ""); + append("", "script-hash"); + } else { + int signatureCount = 1; + int pubKeyCount = 1; + for (int i = 0; i < script.getChunks().size(); i++) { + ScriptChunk chunk = script.getChunks().get(i); + if(chunk.isOpCode()) { + append(chunk.toString(), "script-opcode"); + } else if(chunk.isSignature()) { + append("", "script-signature"); + } else if(chunk.isPubKey()) { + append("", "script-pubkey"); + } else if(chunk.isScript()) { + Script nestedScript = chunk.getScript(); + if (nestedScript.equals(redeemScript)) { + append("", "script-redeem"); + } else if(nestedScript.equals(witnessScript)) { + append("", "script-redeem"); + } else { + append("(", "script-nest"); + appendScript(nestedScript); + append(")", "script-nest"); + } + } else { + append(chunk.toString(), "script-other"); + } + + if(i < script.getChunks().size() - 1) { + append(" ", ""); + } + } + } + } + + public void clear() { + super.clear(); + this.script = null; + setDisable(false); + setContextMenu(null); + Decorator.removeAllDecorations(this); + } + + public void addPSBTDecoration(String description, String styleClass) { + Decorator.addDecoration(this, new GraphicDecoration(new TextDecoration("PSBT", description, styleClass), Pos.TOP_RIGHT)); + } + + public Script getScript() { + return script; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/PSBTCombinedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/PSBTCombinedEvent.java new file mode 100644 index 00000000..a98de64e --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/PSBTCombinedEvent.java @@ -0,0 +1,9 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.psbt.PSBT; + +public class PSBTCombinedEvent extends PSBTEvent { + public PSBTCombinedEvent(PSBT psbt) { + super(psbt); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/PSBTEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/PSBTEvent.java new file mode 100644 index 00000000..28db0521 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/PSBTEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.psbt.PSBT; + +public class PSBTEvent { + private final PSBT psbt; + + public PSBTEvent(PSBT psbt) { + this.psbt = psbt; + } + + public PSBT getPsbt() { + return psbt; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java new file mode 100644 index 00000000..e89557df --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java @@ -0,0 +1,5 @@ +package com.sparrowwallet.sparrow.event; + +public class RequestTransactionOpenEvent { + //Empty event class used to request the transaction open dialog +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 1c3b9daa..26b69434 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -15,6 +15,8 @@ public class FontAwesome5 extends GlyphFont { * The individual glyphs offered by the FontAwesome5 font. */ public static enum Glyph implements INamedCharacter { + ARROW_UP('\uf062'), + CAMERA('\uf030'), CHECK_CIRCLE('\uf058'), CIRCLE('\uf111'), COINS('\uf51e'), diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 2ddf9657..583a00c1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -21,6 +21,8 @@ import javafx.fxml.Initializable; import javafx.scene.control.*; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; +import javafx.stage.FileChooser; +import javafx.stage.Stage; import org.controlsfx.glyphfont.Glyph; import tornadofx.control.DateTimePicker; import tornadofx.control.Field; @@ -29,7 +31,10 @@ import com.google.common.eventbus.Subscribe; import tornadofx.control.Form; import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.time.*; import java.util.*; @@ -434,11 +439,47 @@ public class HeadersController extends TransactionFormController implements Init } public void showPSBT(ActionEvent event) { + ToggleButton toggleButton = (ToggleButton)event.getSource(); + toggleButton.setSelected(false); + headersForm.getSignedKeystores().add(headersForm.getSigningWallet().getKeystores().get(0)); } + public void scanPSBT(ActionEvent event) { + ToggleButton toggleButton = (ToggleButton)event.getSource(); + toggleButton.setSelected(false); + } + public void savePSBT(ActionEvent event) { + ToggleButton toggleButton = (ToggleButton)event.getSource(); + toggleButton.setSelected(false); + + Stage window = new Stage(); + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save PSBT"); + + if(headersForm.getName() != null && !headersForm.getName().isEmpty()) { + fileChooser.setInitialFileName(headersForm.getName() + ".psbt"); + } + + File file = fileChooser.showSaveDialog(window); + if(file != null) { + try { + try(PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8)) { + writer.print(headersForm.getPsbt().toBase64String()); + } + } catch(IOException e) { + AppController.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath()); + } + } + } + public void loadPSBT(ActionEvent event) { + ToggleButton toggleButton = (ToggleButton)event.getSource(); + toggleButton.setSelected(false); + + EventManager.get().post(new RequestTransactionOpenEvent()); } public void signPSBT(ActionEvent event) { @@ -570,4 +611,11 @@ public class HeadersController extends TransactionFormController implements Init signaturesForm.setVisible(true); } } + + @Subscribe + public void psbtCombined(PSBTCombinedEvent event) { + if(event.getPsbt().equals(headersForm.getPsbt()) && headersForm.getSigningWallet() != null) { + updateSignedKeystores(headersForm.getSigningWallet()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java index 6f769ab7..dbab2104 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java @@ -14,17 +14,12 @@ import com.sparrowwallet.sparrow.event.*; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.geometry.Pos; -import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.control.Button; import org.controlsfx.control.ToggleSwitch; -import org.controlsfx.control.decoration.Decorator; -import org.controlsfx.control.decoration.GraphicDecoration; +import org.fxmisc.flowless.VirtualizedScrollPane; import org.fxmisc.richtext.CodeArea; import tornadofx.control.Field; import tornadofx.control.Fieldset; -import org.fxmisc.flowless.VirtualizedScrollPane; import java.net.URL; import java.time.Duration; @@ -60,25 +55,25 @@ public class InputController extends TransactionFormController implements Initia private AddressLabel address; @FXML - private CodeArea scriptSigArea; + private ScriptArea scriptSigArea; @FXML private VirtualizedScrollPane redeemScriptScroll; @FXML - private CodeArea redeemScriptArea; + private ScriptArea redeemScriptArea; @FXML private VirtualizedScrollPane witnessScriptScroll; @FXML - private CodeArea witnessScriptArea; + private ScriptArea witnessScriptArea; @FXML private VirtualizedScrollPane witnessesScroll; @FXML - private CodeArea witnessesArea; + private ScriptArea witnessesArea; @FXML private CopyableLabel signatures; @@ -130,12 +125,12 @@ public class InputController extends TransactionFormController implements Initia initializeInputFields(txInput, psbtInput); initializeScriptFields(txInput, psbtInput); - initializeStatusFields(txInput); + initializeStatusFields(txInput, psbtInput); initializeLocktimeFields(txInput); if(psbtInput != null) { inputForm.getSignedKeystores().addListener((ListChangeListener) c -> { - updateSignatures(); + updateSignatures(inputForm.getPsbtInput()); }); } } @@ -223,6 +218,20 @@ public class InputController extends TransactionFormController implements Initia } private void initializeScriptFields(TransactionInput txInput, PSBTInput psbtInput) { + initializeScriptField(scriptSigArea); + initializeScriptField(redeemScriptArea); + initializeScriptField(witnessesArea); + initializeScriptField(witnessScriptArea); + + updateScriptFields(txInput, psbtInput); + } + + private void updateScriptFields(TransactionInput txInput, PSBTInput psbtInput) { + scriptSigArea.clear(); + redeemScriptArea.clear(); + witnessesArea.clear(); + witnessScriptArea.clear(); + //TODO: While we immediately check if the referenced transaction output is P2SH, where this is not present getting the first nested script is not safe Script redeemScript = txInput.getScriptSig().getFirstNestedScript(); if(redeemScript != null && inputForm.getReferencedTransactionOutput() != null) { @@ -233,31 +242,27 @@ public class InputController extends TransactionFormController implements Initia } if(redeemScript == null && psbtInput != null && psbtInput.getRedeemScript() != null) { - addPSBTDecoration(redeemScriptArea, "PSBT Redeem Script", "non-final"); + redeemScriptArea.addPSBTDecoration("PSBT Redeem Script", "non-final"); redeemScript = psbtInput.getRedeemScript(); } if(redeemScript == null && psbtInput != null && psbtInput.getFinalScriptSig() != null) { - addPSBTDecoration(redeemScriptArea, "PSBT Final ScriptSig", "final"); + redeemScriptArea.addPSBTDecoration("PSBT Final ScriptSig", "final"); redeemScript = psbtInput.getFinalScriptSig().getFirstNestedScript(); } - scriptSigArea.clear(); if(txInput.getScriptSig().isEmpty() && psbtInput != null && psbtInput.getFinalScriptSig() != null) { - appendScript(scriptSigArea, psbtInput.getFinalScriptSig(), redeemScript, null); - addPSBTDecoration(scriptSigArea, "PSBT Final ScriptSig", "final"); + scriptSigArea.appendScript(psbtInput.getFinalScriptSig(), redeemScript, null); + scriptSigArea.addPSBTDecoration("PSBT Final ScriptSig", "final"); } else { - appendScript(scriptSigArea, txInput.getScriptSig(), redeemScript, null); + scriptSigArea.appendScript(txInput.getScriptSig(), redeemScript, null); } - redeemScriptArea.clear(); if(redeemScript != null) { - appendScript(redeemScriptArea, redeemScript); + redeemScriptArea.appendScript(redeemScript); } else { redeemScriptScroll.setDisable(true); } - witnessesArea.clear(); - witnessScriptArea.clear(); Script witnesses = null; Script witnessScript = null; @@ -268,36 +273,31 @@ public class InputController extends TransactionFormController implements Initia if(psbtInput.getFinalScriptWitness() != null) { witnesses = new Script(psbtInput.getFinalScriptWitness().asScriptChunks()); witnessScript = psbtInput.getFinalScriptWitness().getWitnessScript(); - addPSBTDecoration(witnessesArea, "PSBT Final ScriptWitness", "final"); - addPSBTDecoration(witnessScriptArea, "PSBT Final ScriptWitness", "final"); + witnessesArea.addPSBTDecoration("PSBT Final ScriptWitness", "final"); + witnessScriptArea.addPSBTDecoration("PSBT Final ScriptWitness", "final"); } else if(psbtInput.getWitnessScript() != null) { witnessScript = psbtInput.getWitnessScript(); - addPSBTDecoration(witnessScriptArea, "PSBT Witness Script", "non-final"); + witnessScriptArea.addPSBTDecoration("PSBT Witness Script", "non-final"); } } if(witnesses != null) { - appendScript(witnessesArea, witnesses, null, witnessScript); + witnessesArea.appendScript(witnesses, null, witnessScript); } else { witnessesScroll.setDisable(true); } if(witnessScript != null) { - appendScript(witnessScriptArea, witnessScript); + witnessScriptArea.appendScript(witnessScript); } else { witnessScriptScroll.setDisable(true); } } - private void addPSBTDecoration(Node target, String description, String styleClass) { - Decorator.addDecoration(target, new GraphicDecoration(new TextDecoration("PSBT", description, styleClass), Pos.TOP_RIGHT)); - } + private void initializeStatusFields(TransactionInput txInput, PSBTInput psbtInput) { + updateSignatures(psbtInput); - private void initializeStatusFields(TransactionInput txInput) { Transaction transaction = inputForm.getTransaction(); - - updateSignatures(); - rbf.setSelected(txInput.isReplaceByFeeEnabled()); rbf.selectedProperty().addListener((observable, oldValue, newValue) -> { if(newValue) { @@ -323,11 +323,9 @@ public class InputController extends TransactionFormController implements Initia rbf.setDisable(!inputForm.isEditable()); } - private void updateSignatures() { + private void updateSignatures(PSBTInput psbtInput) { signatures.setText("Unknown"); if(inputForm.getPsbtInput() != null) { - PSBTInput psbtInput = inputForm.getPsbtInput(); - int reqSigs = -1; if(psbtInput.getUtxo() != null && psbtInput.getSigningScript() != null) { try { @@ -519,4 +517,13 @@ public class InputController extends TransactionFormController implements Initia locktimeRelativeCombo.setDisable(true); } } + + @Subscribe + public void psbtCombined(PSBTCombinedEvent event) { + if(event.getPsbt().equals(inputForm.getPsbt())) { + updateSpends(inputForm.getPsbtInput().getUtxo()); + updateScriptFields(inputForm.getTransactionInput(), inputForm.getPsbtInput()); + updateSignatures(inputForm.getPsbtInput()); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java index 0c82281d..b713681d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java @@ -11,6 +11,7 @@ import com.sparrowwallet.sparrow.control.CoinLabel; import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; import com.sparrowwallet.sparrow.event.BlockTransactionFetchedEvent; +import com.sparrowwallet.sparrow.event.PSBTCombinedEvent; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -106,7 +107,6 @@ public class InputsController extends TransactionFormController implements Initi signatures.setText(foundSigs + "/?"); } - addPieData(inputsPie, outputs); } @@ -173,4 +173,11 @@ public class InputsController extends TransactionFormController implements Initi public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) { total.refresh(event.getBitcoinUnit()); } + + @Subscribe + public void psbtCombined(PSBTCombinedEvent event) { + if(event.getPsbt().equals(inputsForm.getPsbt())) { + updatePSBTInputs(inputsForm.getPsbt()); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java index 7e0d7466..9622d9eb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java @@ -10,6 +10,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.AddressLabel; import com.sparrowwallet.sparrow.control.CoinLabel; import com.sparrowwallet.sparrow.control.CopyableLabel; +import com.sparrowwallet.sparrow.control.ScriptArea; import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; import com.sparrowwallet.sparrow.event.BlockTransactionOutputsFetchedEvent; import com.sparrowwallet.sparrow.event.ViewTransactionEvent; @@ -54,7 +55,7 @@ public class OutputController extends TransactionFormController implements Initi private Hyperlink spentBy; @FXML - private CodeArea scriptPubKeyArea; + private ScriptArea scriptPubKeyArea; @Override public void initialize(URL location, ResourceBundle resources) { @@ -92,8 +93,9 @@ public class OutputController extends TransactionFormController implements Initi spent.setText("Unknown"); } + initializeScriptField(scriptPubKeyArea); scriptPubKeyArea.clear(); - appendScript(scriptPubKeyArea, txOutput.getScript(), null, null); + scriptPubKeyArea.appendScript(txOutput.getScript(), null, null); } private void updateSpent(List outputTransactions) { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java index 5538be5f..0962226d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java @@ -368,6 +368,10 @@ public class TransactionController implements Initializable { this.txdata = new TransactionData(transaction); } + public void setName(String name) { + this.txdata.setName(name); + } + public PSBT getPSBT() { return txdata.getPsbt(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java index f5053b2a..089555e6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionData.java @@ -17,6 +17,7 @@ import java.util.Map; public class TransactionData { private final Transaction transaction; + private String name; private PSBT psbt; private BlockTransaction blockTransaction; private Map inputTransactions; @@ -49,6 +50,14 @@ public class TransactionData { return transaction; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public PSBT getPsbt() { return psbt; } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java index 2acf2cea..84f9a460 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java @@ -27,6 +27,10 @@ public abstract class TransactionForm { return txdata.getTransaction(); } + public String getName() { + return txdata.getName(); + } + public PSBT getPsbt() { return txdata.getPsbt(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java index e9273960..c5aa3839 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java @@ -12,6 +12,7 @@ import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableTextField; +import com.sparrowwallet.sparrow.control.ScriptArea; import com.sparrowwallet.sparrow.event.ReceiveToEvent; import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent; @@ -53,7 +54,7 @@ public class ReceiveController extends WalletFormController implements Initializ private ImageView qrCode; @FXML - private CodeArea scriptPubKeyArea; + private ScriptArea scriptPubKeyArea; @FXML private CodeArea outputDescriptor; @@ -67,7 +68,7 @@ public class ReceiveController extends WalletFormController implements Initializ @Override public void initializeView() { - + initializeScriptField(scriptPubKeyArea); } public void setNodeEntry(NodeEntry nodeEntry) { @@ -88,7 +89,7 @@ public class ReceiveController extends WalletFormController implements Initializ } scriptPubKeyArea.clear(); - appendScript(scriptPubKeyArea, nodeEntry.getOutputScript(), null, null); + scriptPubKeyArea.appendScript(nodeEntry.getOutputScript(), null, null); outputDescriptor.clear(); outputDescriptor.appendText(nodeEntry.getOutputDescriptor()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 83bbd1cc..c0411b76 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -137,6 +137,7 @@ public class SendController extends WalletFormController implements Initializabl targetBlocks.setTooltip(tooltip); userFeeSet.set(false); + revalidate(amount, amountListener); updateTransaction(); } }; diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css index 78ece013..4622e922 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css @@ -41,7 +41,7 @@ -fx-padding: 10 20 10 20; } -.signatures-buttons .button { +.signatures-buttons .segmented-button, .signatures-buttons .button, .signatures-buttons .toggle-button { -fx-pref-height: 75px; -fx-max-width: Infinity; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index cfe98178..f3163319 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -171,16 +171,34 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +