request new change address in the transaction diagram

This commit is contained in:
Craig Raw 2021-05-17 13:21:56 +02:00
parent c9cdf6e77d
commit b17c15f702
6 changed files with 77 additions and 9 deletions

2
drongo

@ -1 +1 @@
Subproject commit 7dca0d0c39404bd89a5a4589a79ffe0f688e8181 Subproject commit 567294a4b055cc062650de45fccbbc89db714f39

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.uri.BitcoinURI; import com.sparrowwallet.drongo.uri.BitcoinURI;
@ -10,6 +11,7 @@ import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ExcludeUtxoEvent; import com.sparrowwallet.sparrow.event.ExcludeUtxoEvent;
import com.sparrowwallet.sparrow.event.ReplaceChangeAddressEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -200,6 +202,7 @@ public class TransactionDiagram extends GridPane {
private Pane getInputsLabels(Map<BlockTransactionHashIndex, WalletNode> displayedUtxos) { private Pane getInputsLabels(Map<BlockTransactionHashIndex, WalletNode> displayedUtxos) {
VBox inputsBox = new VBox(); VBox inputsBox = new VBox();
inputsBox.setMaxWidth(150); inputsBox.setMaxWidth(150);
inputsBox.setPrefWidth(150);
inputsBox.setPadding(new Insets(0, 10, 0, 10)); inputsBox.setPadding(new Insets(0, 10, 0, 10));
inputsBox.minHeightProperty().bind(minHeightProperty()); inputsBox.minHeightProperty().bind(minHeightProperty());
inputsBox.setAlignment(Pos.CENTER_RIGHT); inputsBox.setAlignment(Pos.CENTER_RIGHT);
@ -388,14 +391,33 @@ public class TransactionDiagram extends GridPane {
} }
if(walletTx.getChangeNode() != null) { if(walletTx.getChangeNode() != null) {
WalletNode defaultChangeNode = walletTx.getWallet().getFreshNode(KeyPurpose.CHANGE);
boolean overGapLimit = (walletTx.getChangeNode().getIndex() - defaultChangeNode.getIndex()) > walletTx.getWallet().getGapLimit();
HBox actionBox = new HBox();
String changeDesc = walletTx.getChangeAddress().toString().substring(0, 8) + "..."; String changeDesc = walletTx.getChangeAddress().toString().substring(0, 8) + "...";
Label changeLabel = new Label(changeDesc, getChangeGlyph()); Label changeLabel = new Label(changeDesc, overGapLimit ? getChangeWarningGlyph() : getChangeGlyph());
changeLabel.getStyleClass().addAll("output-label", "change-label"); changeLabel.getStyleClass().addAll("output-label", "change-label");
Tooltip changeTooltip = new Tooltip("Change of " + getSatsValue(walletTx.getChangeAmount()) + " sats to " + walletTx.getChangeNode().getDerivationPath().replace("m", "..") + "\n" + walletTx.getChangeAddress().toString()); Tooltip changeTooltip = new Tooltip("Change of " + getSatsValue(walletTx.getChangeAmount()) + " sats to " + walletTx.getChangeNode().getDerivationPath().replace("m", "..") + "\n" + walletTx.getChangeAddress().toString() + (overGapLimit ? "\nAddress is beyond the gap limit!" : ""));
changeTooltip.getStyleClass().add("change-label"); changeTooltip.getStyleClass().add("change-label");
changeTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY)); changeTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY));
changeLabel.setTooltip(changeTooltip); changeLabel.setTooltip(changeTooltip);
outputsBox.getChildren().add(changeLabel);
Button nextChangeAddressButton = new Button("");
nextChangeAddressButton.setGraphic(getChangeReplaceGlyph());
nextChangeAddressButton.setOnAction(event -> {
EventManager.get().post(new ReplaceChangeAddressEvent(walletTx));
});
Tooltip replaceChangeTooltip = new Tooltip("Use next change address");
nextChangeAddressButton.setTooltip(replaceChangeTooltip);
Label replaceChangeLabel = new Label("", nextChangeAddressButton);
replaceChangeLabel.getStyleClass().add("replace-change-label");
replaceChangeLabel.setVisible(false);
actionBox.setOnMouseEntered(event -> replaceChangeLabel.setVisible(true));
actionBox.setOnMouseExited(event -> replaceChangeLabel.setVisible(false));
actionBox.getChildren().addAll(changeLabel, replaceChangeLabel);
outputsBox.getChildren().add(actionBox);
outputsBox.getChildren().add(createSpacer()); outputsBox.getChildren().add(createSpacer());
} }
@ -484,6 +506,20 @@ public class TransactionDiagram extends GridPane {
return changeGlyph; return changeGlyph;
} }
public static Glyph getChangeWarningGlyph() {
Glyph changeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
changeWarningGlyph.getStyleClass().add("change-warning-icon");
changeWarningGlyph.setFontSize(12);
return changeWarningGlyph;
}
public static Glyph getChangeReplaceGlyph() {
Glyph changeReplaceGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_DOWN);
changeReplaceGlyph.getStyleClass().add("change-replace-icon");
changeReplaceGlyph.setFontSize(12);
return changeReplaceGlyph;
}
private Glyph getFeeGlyph() { private Glyph getFeeGlyph() {
Glyph feeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING); Glyph feeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING);
feeGlyph.getStyleClass().add("fee-icon"); feeGlyph.getStyleClass().add("fee-icon");

View file

@ -0,0 +1,15 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
public class ReplaceChangeAddressEvent {
private final WalletTransaction walletTransaction;
public ReplaceChangeAddressEvent(WalletTransaction walletTx) {
this.walletTransaction = walletTx;
}
public WalletTransaction getWalletTransaction() {
return walletTransaction;
}
}

View file

@ -134,6 +134,8 @@ public class SendController extends WalletFormController implements Initializabl
private final BooleanProperty includeSpentMempoolOutputsProperty = new SimpleBooleanProperty(false); private final BooleanProperty includeSpentMempoolOutputsProperty = new SimpleBooleanProperty(false);
private final List<WalletNode> excludedChangeNodes = new ArrayList<>();
private final ChangeListener<String> feeListener = new ChangeListener<>() { private final ChangeListener<String> feeListener = new ChangeListener<>() {
@Override @Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
@ -494,7 +496,7 @@ public class SendController extends WalletFormController implements Initializabl
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs(); boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
boolean includeSpentMempoolOutputs = includeSpentMempoolOutputsProperty.get(); boolean includeSpentMempoolOutputs = includeSpentMempoolOutputsProperty.get();
walletTransactionService = new WalletTransactionService(wallet, getUtxoSelectors(), getUtxoFilters(), payments, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); walletTransactionService = new WalletTransactionService(wallet, getUtxoSelectors(), getUtxoFilters(), payments, excludedChangeNodes, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs);
walletTransactionService.setOnSucceeded(event -> { walletTransactionService.setOnSucceeded(event -> {
if(!walletTransactionService.isIgnoreResult()) { if(!walletTransactionService.isIgnoreResult()) {
walletTransactionProperty.setValue(walletTransactionService.getValue()); walletTransactionProperty.setValue(walletTransactionService.getValue());
@ -545,6 +547,7 @@ public class SendController extends WalletFormController implements Initializabl
private final List<UtxoSelector> utxoSelectors; private final List<UtxoSelector> utxoSelectors;
private final List<UtxoFilter> utxoFilters; private final List<UtxoFilter> utxoFilters;
private final List<Payment> payments; private final List<Payment> payments;
private final List<WalletNode> excludedChangeNodes;
private final double feeRate; private final double feeRate;
private final double longTermFeeRate; private final double longTermFeeRate;
private final Long fee; private final Long fee;
@ -554,11 +557,12 @@ public class SendController extends WalletFormController implements Initializabl
private final boolean includeSpentMempoolOutputs; private final boolean includeSpentMempoolOutputs;
private boolean ignoreResult; private boolean ignoreResult;
public WalletTransactionService(Wallet wallet, List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, List<Payment> payments, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs, boolean includeSpentMempoolOutputs) { public WalletTransactionService(Wallet wallet, List<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, List<Payment> payments, List<WalletNode> excludedChangeNodes, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs, boolean includeSpentMempoolOutputs) {
this.wallet = wallet; this.wallet = wallet;
this.utxoSelectors = utxoSelectors; this.utxoSelectors = utxoSelectors;
this.utxoFilters = utxoFilters; this.utxoFilters = utxoFilters;
this.payments = payments; this.payments = payments;
this.excludedChangeNodes = excludedChangeNodes;
this.feeRate = feeRate; this.feeRate = feeRate;
this.longTermFeeRate = longTermFeeRate; this.longTermFeeRate = longTermFeeRate;
this.fee = fee; this.fee = fee;
@ -572,7 +576,7 @@ public class SendController extends WalletFormController implements Initializabl
protected Task<WalletTransaction> createTask() { protected Task<WalletTransaction> createTask() {
return new Task<>() { return new Task<>() {
protected WalletTransaction call() throws InsufficientFundsException { protected WalletTransaction call() throws InsufficientFundsException {
return wallet.createWalletTransaction(utxoSelectors, utxoFilters, payments, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); return wallet.createWalletTransaction(utxoSelectors, utxoFilters, payments, excludedChangeNodes, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs);
} }
}; };
} }
@ -898,6 +902,7 @@ public class SendController extends WalletFormController implements Initializabl
utxoSelectorProperty.setValue(null); utxoSelectorProperty.setValue(null);
utxoFilterProperty.setValue(null); utxoFilterProperty.setValue(null);
includeSpentMempoolOutputsProperty.set(false); includeSpentMempoolOutputsProperty.set(false);
excludedChangeNodes.clear();
walletTransactionProperty.setValue(null); walletTransactionProperty.setValue(null);
createdWalletTransactionProperty.set(null); createdWalletTransactionProperty.set(null);
@ -1137,6 +1142,14 @@ public class SendController extends WalletFormController implements Initializabl
} }
} }
@Subscribe
public void replaceChangeAddress(ReplaceChangeAddressEvent event) {
if(event.getWalletTransaction() == walletTransactionProperty.get()) {
excludedChangeNodes.add(event.getWalletTransaction().getChangeNode());
updateTransaction();
}
}
@Subscribe @Subscribe
public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) { public void walletUtxoStatusChanged(WalletUtxoStatusChangedEvent event) {
if(event.getWallet().equals(getWalletForm().getWallet())) { if(event.getWallet().equals(getWalletForm().getWallet())) {

View file

@ -157,7 +157,7 @@ public class TransactionsController extends WalletFormController implements Init
private void logMessage(String logMessage) { private void logMessage(String logMessage) {
if(logMessage != null) { if(logMessage != null) {
logMessage = logMessage.replace("m/", "/"); logMessage = logMessage.replace("m/", "../");
String date = LOG_DATE_FORMAT.format(new Date()); String date = LOG_DATE_FORMAT.format(new Date());
String logLine = "\n" + date + " " + logMessage; String logLine = "\n" + date + " " + logMessage;
Platform.runLater(() -> { Platform.runLater(() -> {

View file

@ -89,7 +89,7 @@
-fx-stroke-dash-array: 5px 5px; -fx-stroke-dash-array: 5px 5px;
} }
#transactionDiagram .utxo-label .button { #transactionDiagram .utxo-label .button, #transactionDiagram .replace-change-label .button {
-fx-padding: 0; -fx-padding: 0;
-fx-pref-height: 18; -fx-pref-height: 18;
-fx-pref-width: 18; -fx-pref-width: 18;
@ -105,6 +105,10 @@
-fx-fill: -fx-text-base-color; -fx-fill: -fx-text-base-color;
} }
#transactionDiagram .change-warning-icon {
-fx-text-fill: rgb(238, 210, 2);
}
#targetBlocks .track { #targetBlocks .track {
-fx-background-color: -fx-shadow-highlight-color, -fx-background-color: -fx-shadow-highlight-color,
linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),