diff --git a/drongo b/drongo index 130fea09..9d15c27b 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 130fea0937629b62305892d00bc8099872259095 +Subproject commit 9d15c27bfd8bd6c51f03fe081d6e70718ecf2eb0 diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index a49dd95d..7eecf036 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -189,6 +189,12 @@ public class HeadersController extends TransactionFormController implements Init @Override public void updated(Transaction transaction) { updateTxId(); - locktimeFieldset.setText(headersForm.getTransaction().isLocktimeSequenceEnabled() ? "Absolute Locktime" : "Absolute Locktime (sequence disabled)"); + + boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled(); + locktimeNoneType.setDisable(!locktimeEnabled); + locktimeBlockType.setDisable(!locktimeEnabled); + locktimeBlock.setDisable(!locktimeEnabled); + locktimeDateType.setDisable(!locktimeEnabled); + locktimeDate.setDisable(!locktimeEnabled); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java index 64e41904..1d15e9ab 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java @@ -1,14 +1,12 @@ package com.sparrowwallet.sparrow.transaction; -import com.sparrowwallet.drongo.protocol.Script; -import com.sparrowwallet.drongo.protocol.ScriptChunk; -import com.sparrowwallet.drongo.protocol.Transaction; -import com.sparrowwallet.drongo.protocol.TransactionInput; +import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.sparrow.EventManager; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; +import org.controlsfx.control.ToggleSwitch; import org.fxmisc.richtext.CodeArea; import tornadofx.control.Field; import tornadofx.control.Fieldset; @@ -55,6 +53,12 @@ public class InputController extends TransactionFormController implements Initia @FXML private CodeArea witnessesArea; + @FXML + private TextField signatures; + + @FXML + private ToggleSwitch rbf; + @FXML private ToggleGroup locktimeToggleGroup; @@ -70,23 +74,20 @@ public class InputController extends TransactionFormController implements Initia @FXML private Fieldset locktimeFieldset; - @FXML - private Field locktimeNoneField; - @FXML private Field locktimeAbsoluteField; @FXML private Field locktimeRelativeField; - @FXML - private Spinner locktimeNone; - @FXML private TextField locktimeAbsolute; @FXML - private Spinner locktimeRelative; + private Spinner locktimeRelativeBlocks; + + @FXML + private Spinner locktimeRelativeSeconds; @FXML private ComboBox locktimeRelativeCombo; @@ -106,7 +107,7 @@ public class InputController extends TransactionFormController implements Initia //TODO: Enable select outpoint when wallet present outpointSelect.setDisable(true); initializeScriptFields(txInput); - + initializeStatusFields(txInput); initializeLocktimeFields(txInput); } @@ -142,71 +143,137 @@ public class InputController extends TransactionFormController implements Initia } } + private void initializeStatusFields(TransactionInput txInput) { + Transaction transaction = inputForm.getTransaction(); + + signatures.setText("Unknown"); + if(inputForm.getPsbtInput() != null) { + PSBTInput psbtInput = inputForm.getPsbtInput(); + + try { + int reqSigs = psbtInput.getSigningScript().getNumRequiredSignatures(); + int foundSigs = psbtInput.getPartialSignatures().size(); + signatures.setText(foundSigs + "/" + reqSigs); + } catch (NonStandardScriptException e) { + //TODO: Handle unusual transaction sig + } + } + + rbf.setSelected(txInput.isReplaceByFeeEnabled()); + rbf.selectedProperty().addListener((observable, oldValue, newValue) -> { + if(newValue) { + if(txInput.isAbsoluteTimeLockDisabled()) { + locktimeToggleGroup.selectToggle(locktimeAbsoluteType); + } else if(txInput.isAbsoluteTimeLocked()) { + txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED); + EventManager.get().notify(transaction); + } + } else { + if(txInput.isAbsoluteTimeLocked()) { + txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1); + EventManager.get().notify(transaction); + } else if(txInput.isRelativeTimeLocked()) { + locktimeToggleGroup.selectToggle(locktimeAbsoluteType); + } + } + }); + } + private void initializeLocktimeFields(TransactionInput txInput) { Transaction transaction = inputForm.getTransaction(); locktimeToggleGroup.selectedToggleProperty().addListener((ov, old_toggle, new_toggle) -> { if(locktimeToggleGroup.getSelectedToggle() != null) { String selection = locktimeToggleGroup.getSelectedToggle().getUserData().toString(); if(selection.equals("none")) { - locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField); - locktimeFieldset.getChildren().add(locktimeNoneField); + locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField); + locktimeFieldset.getChildren().add(locktimeAbsoluteField); + updateAbsoluteLocktimeField(transaction); + locktimeAbsoluteField.setDisable(true); txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED); + rbf.setSelected(false); EventManager.get().notify(transaction); } else if(selection.equals("absolute")) { - locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField); + locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField); locktimeFieldset.getChildren().add(locktimeAbsoluteField); - long locktime = transaction.getLocktime(); - if(locktime < Transaction.MAX_BLOCK_LOCKTIME) { - locktimeAbsoluteField.setText("Block:"); - locktimeAbsolute.setText(Long.toString(locktime)); + updateAbsoluteLocktimeField(transaction); + locktimeAbsoluteField.setDisable(false); + if(rbf.selectedProperty().getValue()) { + txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED); } else { - locktimeAbsoluteField.setText("Date:"); - LocalDateTime localDateTime = Instant.ofEpochSecond(locktime).atZone(ZoneId.systemDefault()).toLocalDateTime(); - locktimeAbsolute.setText(DateTimeFormatter.ofPattern(HeadersController.LOCKTIME_DATE_FORMAT).format(localDateTime)); + txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1); } - //TODO: Check RBF field and set appropriately - txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1); EventManager.get().notify(transaction); } else { - locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField); + locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField); locktimeFieldset.getChildren().add(locktimeRelativeField); - setRelativeLocktime(txInput, transaction, locktimeRelative.getValue()); + if(locktimeRelativeCombo.getValue() == null) { + locktimeRelativeCombo.getSelectionModel().select(0); + } else { + setRelativeLocktime(txInput, transaction); + } + rbf.setSelected(true); } } }); - locktimeNone.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)Transaction.MAX_BLOCK_LOCKTIME-1, (int)transaction.getLocktime())); - locktimeRelativeCombo.getSelectionModel().select(0); - locktimeRelative.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)TransactionInput.MAX_RELATIVE_TIMELOCK_IN_BLOCKS, 0)); + locktimeRelativeBlocks.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK, 0)); + locktimeRelativeSeconds.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, + (int)TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK*TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT, 0, + TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT)); + + locktimeRelativeBlocks.managedProperty().bind(locktimeRelativeBlocks.visibleProperty()); + locktimeRelativeSeconds.managedProperty().bind(locktimeRelativeSeconds.visibleProperty()); + locktimeRelativeCombo.getSelectionModel().selectedItemProperty().addListener((ov, old_toggle, new_toggle) -> { + boolean blocks = locktimeRelativeCombo.getValue().equals("blocks"); + locktimeRelativeSeconds.setVisible(!blocks); + locktimeRelativeBlocks.setVisible(blocks); + setRelativeLocktime(txInput, transaction); + }); + + locktimeRelativeType.setDisable(!transaction.isRelativeLocktimeAllowed()); if(txInput.isAbsoluteTimeLockDisabled()) { locktimeToggleGroup.selectToggle(locktimeNoneType); } else if(txInput.isAbsoluteTimeLocked()) { locktimeToggleGroup.selectToggle(locktimeAbsoluteType); } else { - locktimeRelative.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime()); if(txInput.isRelativeTimeLockedInBlocks()) { + locktimeRelativeBlocks.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime()); locktimeRelativeCombo.getSelectionModel().select(0); } else { + locktimeRelativeSeconds.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime() * TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT); locktimeRelativeCombo.getSelectionModel().select(1); } locktimeToggleGroup.selectToggle(locktimeRelativeType); } - locktimeRelative.valueProperty().addListener((obs, oldValue, newValue) -> { - setRelativeLocktime(txInput, transaction, newValue); + locktimeRelativeBlocks.valueProperty().addListener((obs, oldValue, newValue) -> { + setRelativeLocktime(txInput, transaction); }); - - locktimeRelativeCombo.getSelectionModel().selectedItemProperty().addListener((ov, old_toggle, new_toggle) -> { - setRelativeLocktime(txInput, transaction, locktimeRelative.getValue()); + locktimeRelativeSeconds.valueProperty().addListener((obs, oldValue, newValue) -> { + setRelativeLocktime(txInput, transaction); }); } - private void setRelativeLocktime(TransactionInput txInput, Transaction transaction, Integer value) { - String relativeSelection = locktimeRelativeCombo.getValue(); - if (relativeSelection.equals("blocks")) { - txInput.setSequenceNumber(value & 0xFFFF); + private void updateAbsoluteLocktimeField(Transaction transaction) { + long locktime = transaction.getLocktime(); + if(locktime < Transaction.MAX_BLOCK_LOCKTIME) { + locktimeAbsoluteField.setText("Block:"); + locktimeAbsolute.setText(Long.toString(locktime)); } else { - txInput.setSequenceNumber((value & 0xFFFF) | 0x400000); + locktimeAbsoluteField.setText("Date:"); + LocalDateTime localDateTime = Instant.ofEpochSecond(locktime).atZone(ZoneId.systemDefault()).toLocalDateTime(); + locktimeAbsolute.setText(DateTimeFormatter.ofPattern(HeadersController.LOCKTIME_DATE_FORMAT).format(localDateTime)); + } + } + + private void setRelativeLocktime(TransactionInput txInput, Transaction transaction) { + String relativeSelection = locktimeRelativeCombo.getValue(); + if(relativeSelection.equals("blocks")) { + Integer value = locktimeRelativeBlocks.getValue(); + txInput.setSequenceNumber(value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK); + } else { + Integer value = locktimeRelativeSeconds.getValue() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT; + txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG); } EventManager.get().notify(transaction); } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java index 0ff10924..f679feeb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionFormController.java @@ -106,7 +106,7 @@ public abstract class TransactionFormController { Popup popup = new Popup(); Label popupMsg = new Label(); - popupMsg.setStyle("-fx-background-color: #696c77; -fx-text-fill: white; -fx-padding: 5;"); + popupMsg.getStyleClass().add("tooltip"); popup.getContent().add(popupMsg); area.setMouseOverTextDelay(Duration.ofMillis(150)); diff --git a/src/main/resources/com/sparrowwallet/sparrow/general.css b/src/main/resources/com/sparrowwallet/sparrow/general.css index ae82d8ae..9ab7d98a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/general.css +++ b/src/main/resources/com/sparrowwallet/sparrow/general.css @@ -20,4 +20,10 @@ .form-separator { -fx-padding: -15 0 0 0; -} \ No newline at end of file +} + +.toggle-switch { + -fx-translate-x: -20px; +} + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/input.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/input.fxml index 76ff8d82..f47f869a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/input.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/input.fxml @@ -10,6 +10,7 @@ + @@ -83,7 +84,18 @@ -
+ +
+ + + + + + +
+
+ +
@@ -91,20 +103,30 @@ - - - + + + + + + + + + + + + + + + - - - - - + + +