improve input and output labels on transaction tree and detail panels

This commit is contained in:
Craig Raw 2024-01-12 12:10:38 +02:00
parent 540424a2e3
commit 57dba5d6ae
14 changed files with 367 additions and 266 deletions

2
drongo

@ -1 +1 @@
Subproject commit 78944a7114f5e22d0622dd07ca426e04acdce5b7 Subproject commit 42f279e5e7cfdcf0de80f60f65857d26db8580ad

View file

@ -14,6 +14,7 @@ import com.sparrowwallet.sparrow.event.ExcludeUtxoEvent;
import com.sparrowwallet.sparrow.event.ReplaceChangeAddressEvent; import com.sparrowwallet.sparrow.event.ReplaceChangeAddressEvent;
import com.sparrowwallet.sparrow.event.SorobanInitiatedEvent; import com.sparrowwallet.sparrow.event.SorobanInitiatedEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.soroban.SorobanServices; import com.sparrowwallet.sparrow.soroban.SorobanServices;
import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; import com.sparrowwallet.sparrow.wallet.OptimizationStrategy;
@ -44,7 +45,6 @@ import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.util.Duration; import javafx.util.Duration;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Platform; import org.controlsfx.tools.Platform;
@ -56,6 +56,8 @@ import java.util.*;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.sparrowwallet.sparrow.glyphfont.GlyphUtils.*;
public class TransactionDiagram extends GridPane { public class TransactionDiagram extends GridPane {
private static final int MAX_UTXOS = 8; private static final int MAX_UTXOS = 8;
private static final int REDUCED_MAX_UTXOS = MAX_UTXOS - 2; private static final int REDUCED_MAX_UTXOS = MAX_UTXOS - 2;
@ -686,19 +688,18 @@ public class TransactionDiagram extends GridPane {
List<OutputNode> outputNodes = new ArrayList<>(); List<OutputNode> outputNodes = new ArrayList<>();
for(Payment payment : displayedPayments) { for(Payment payment : displayedPayments) {
Glyph outputGlyph = getOutputGlyph(payment); Glyph outputGlyph = GlyphUtils.getOutputGlyph(walletTx, payment);
boolean labelledPayment = outputGlyph.getStyleClass().stream().anyMatch(style -> List.of("premix-icon", "badbank-icon", "whirlpoolfee-icon").contains(style)) || payment instanceof AdditionalPayment; boolean labelledPayment = outputGlyph.getStyleClass().stream().anyMatch(style -> List.of("premix-icon", "badbank-icon", "whirlpoolfee-icon").contains(style)) || payment instanceof AdditionalPayment;
payment.setLabel(getOutputLabel(payment));
Label recipientLabel = new Label(payment.getLabel() == null || payment.getType() == Payment.Type.FAKE_MIX || payment.getType() == Payment.Type.MIX ? payment.getAddress().toString().substring(0, 8) + "..." : payment.getLabel(), outputGlyph); Label recipientLabel = new Label(payment.getLabel() == null || payment.getType() == Payment.Type.FAKE_MIX || payment.getType() == Payment.Type.MIX ? payment.getAddress().toString().substring(0, 8) + "..." : payment.getLabel(), outputGlyph);
recipientLabel.getStyleClass().add("output-label"); recipientLabel.getStyleClass().add("output-label");
recipientLabel.getStyleClass().add(labelledPayment ? "payment-label" : "recipient-label"); recipientLabel.getStyleClass().add(labelledPayment ? "payment-label" : "recipient-label");
Wallet toWallet = getToWallet(payment); Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null; WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
Wallet toBip47Wallet = getBip47SendWallet(payment); Wallet toBip47Wallet = getBip47SendWallet(payment);
Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ")
+ getSatsValue(payment.getAmount()) + " sats to " + getSatsValue(payment.getAmount()) + " sats to "
+ (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : toWallet.getFullDisplayName()) + "\n" + payment.getAddress().toString()) + (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : toWallet.getFullDisplayName()) + "\n" + payment.getAddress().toString())
+ (isDuplicateAddress(payment) ? " (Duplicate)" : "")); + (walletTx.isDuplicateAddress(payment) ? " (Duplicate)" : ""));
recipientTooltip.getStyleClass().add("recipient-label"); recipientTooltip.getStyleClass().add("recipient-label");
recipientTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY)); recipientTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY));
recipientTooltip.setShowDuration(Duration.INDEFINITE); recipientTooltip.setShowDuration(Duration.INDEFINITE);
@ -928,42 +929,12 @@ public class TransactionDiagram extends GridPane {
return spacer; return spacer;
} }
private String getOutputLabel(Payment payment) {
if(payment.getLabel() != null) {
return payment.getLabel();
}
if(payment.getType() == Payment.Type.WHIRLPOOL_FEE) {
return "Whirlpool fee";
} else if(walletTx.isPremixSend(payment)) {
int premixIndex = getOutputIndex(payment.getAddress(), payment.getAmount(), Collections.emptySet()) - 1;
return "Premix #" + premixIndex;
} else if(walletTx.isBadbankSend(payment)) {
return "Badbank change";
}
return null;
}
private int getOutputIndex(Address address, long amount, Collection<Integer> seenIndexes) { private int getOutputIndex(Address address, long amount, Collection<Integer> seenIndexes) {
List<TransactionOutput> addressOutputs = walletTx.getTransaction().getOutputs().stream().filter(txOutput -> txOutput.getScript().getToAddress() != null).collect(Collectors.toList()); List<TransactionOutput> addressOutputs = walletTx.getTransaction().getOutputs().stream().filter(txOutput -> txOutput.getScript().getToAddress() != null).collect(Collectors.toList());
TransactionOutput output = addressOutputs.stream().filter(txOutput -> address.equals(txOutput.getScript().getToAddress()) && txOutput.getValue() == amount && !seenIndexes.contains(txOutput.getIndex())).findFirst().orElseThrow(); TransactionOutput output = addressOutputs.stream().filter(txOutput -> address.equals(txOutput.getScript().getToAddress()) && txOutput.getValue() == amount && !seenIndexes.contains(txOutput.getIndex())).findFirst().orElseThrow();
return addressOutputs.indexOf(output); return addressOutputs.indexOf(output);
} }
Wallet getToWallet(Payment payment) {
for(Wallet openWallet : AppServices.get().getOpenWallets().keySet()) {
if(openWallet != walletTx.getWallet() && openWallet.isValid()) {
WalletNode addressNode = openWallet.getWalletAddresses().get(payment.getAddress());
if(addressNode != null) {
return addressNode.getWallet();
}
}
}
return null;
}
private Wallet getBip47SendWallet(Payment payment) { private Wallet getBip47SendWallet(Payment payment) {
if(walletTx.getWallet() != null) { if(walletTx.getWallet() != null) {
for(Wallet childWallet : walletTx.getWallet().getChildWallets()) { for(Wallet childWallet : walletTx.getWallet().getChildWallets()) {
@ -981,164 +952,6 @@ public class TransactionDiagram extends GridPane {
return null; return null;
} }
public Glyph getOutputGlyph(Payment payment) {
if(payment.getType().equals(Payment.Type.MIX)) {
return getMixGlyph();
} else if(payment.getType().equals(Payment.Type.FAKE_MIX)) {
return getFakeMixGlyph();
} else if(walletTx.isConsolidationSend(payment)) {
return getConsolidationGlyph();
} else if(walletTx.isPremixSend(payment)) {
return getPremixGlyph();
} else if(walletTx.isBadbankSend(payment)) {
return getBadbankGlyph();
} else if(payment.getType().equals(Payment.Type.WHIRLPOOL_FEE)) {
return getWhirlpoolFeeGlyph();
} else if(payment instanceof AdditionalPayment) {
return ((AdditionalPayment)payment).getOutputGlyph(this);
} else if(getToWallet(payment) != null) {
return getDepositGlyph();
} else if(isDuplicateAddress(payment)) {
return getPaymentWarningGlyph();
}
return getPaymentGlyph();
}
private boolean isDuplicateAddress(Payment payment) {
return walletTx.getPayments().stream().filter(p -> payment != p).anyMatch(p -> payment.getAddress() != null && payment.getAddress().equals(p.getAddress()));
}
public static Glyph getExcludeGlyph() {
Glyph excludeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.TIMES_CIRCLE);
excludeGlyph.getStyleClass().add("exclude-utxo");
excludeGlyph.setFontSize(12);
return excludeGlyph;
}
public static Glyph getPaymentGlyph() {
Glyph paymentGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND);
paymentGlyph.getStyleClass().add("payment-icon");
paymentGlyph.setFontSize(12);
return paymentGlyph;
}
public static Glyph getPaymentWarningGlyph() {
Glyph paymentWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
paymentWarningGlyph.getStyleClass().add("payment-warning-icon");
paymentWarningGlyph.setFontSize(12);
return paymentWarningGlyph;
}
public static Glyph getConsolidationGlyph() {
Glyph consolidationGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.REPLY_ALL);
consolidationGlyph.getStyleClass().add("consolidation-icon");
consolidationGlyph.setFontSize(12);
return consolidationGlyph;
}
public static Glyph getDepositGlyph() {
Glyph depositGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_DOWN);
depositGlyph.getStyleClass().add("deposit-icon");
depositGlyph.setFontSize(12);
return depositGlyph;
}
public static Glyph getPremixGlyph() {
Glyph premixGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.RANDOM);
premixGlyph.getStyleClass().add("premix-icon");
premixGlyph.setFontSize(12);
return premixGlyph;
}
public static Glyph getBadbankGlyph() {
Glyph badbankGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.BIOHAZARD);
badbankGlyph.getStyleClass().add("badbank-icon");
badbankGlyph.setFontSize(12);
return badbankGlyph;
}
public static Glyph getWhirlpoolFeeGlyph() {
Glyph whirlpoolFeeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING_WATER);
whirlpoolFeeGlyph.getStyleClass().add("whirlpoolfee-icon");
whirlpoolFeeGlyph.setFontSize(12);
return whirlpoolFeeGlyph;
}
public static Glyph getFakeMixGlyph() {
Glyph fakeMixGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.THEATER_MASKS);
fakeMixGlyph.getStyleClass().add("fakemix-icon");
fakeMixGlyph.setFontSize(12);
return fakeMixGlyph;
}
public static Glyph getTxoGlyph() {
return getChangeGlyph();
}
public static Glyph getMixGlyph() {
Glyph payjoinGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.RANDOM);
payjoinGlyph.getStyleClass().add("mix-icon");
payjoinGlyph.setFontSize(12);
return payjoinGlyph;
}
public static Glyph getChangeGlyph() {
Glyph changeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.COINS);
changeGlyph.getStyleClass().add("change-icon");
changeGlyph.setFontSize(12);
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;
}
public Glyph getFeeGlyph() {
Glyph feeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING);
feeGlyph.getStyleClass().add("fee-icon");
feeGlyph.setFontSize(12);
return feeGlyph;
}
private Glyph getWarningGlyph() {
Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
feeWarningGlyph.getStyleClass().add("fee-warning-icon");
feeWarningGlyph.setFontSize(12);
return feeWarningGlyph;
}
private Glyph getQuestionGlyph() {
Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.QUESTION_CIRCLE);
feeWarningGlyph.getStyleClass().add("question-icon");
feeWarningGlyph.setFontSize(12);
return feeWarningGlyph;
}
private Glyph getLockGlyph() {
Glyph lockGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK);
lockGlyph.getStyleClass().add("lock-icon");
lockGlyph.setFontSize(12);
return lockGlyph;
}
private Glyph getUserGlyph() {
Glyph userGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.USER);
userGlyph.getStyleClass().add("user-icon");
userGlyph.setFontSize(12);
return userGlyph;
}
private Glyph getUserAddGlyph() { private Glyph getUserAddGlyph() {
Glyph userAddGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.USER_PLUS); Glyph userAddGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.USER_PLUS);
userAddGlyph.getStyleClass().add("useradd-icon"); userAddGlyph.getStyleClass().add("useradd-icon");
@ -1295,7 +1108,7 @@ public class TransactionDiagram extends GridPane {
} }
} }
private static class AdditionalPayment extends Payment { public static class AdditionalPayment extends Payment {
private final List<Payment> additionalPayments; private final List<Payment> additionalPayments;
public AdditionalPayment(List<Payment> additionalPayments) { public AdditionalPayment(List<Payment> additionalPayments) {
@ -1303,10 +1116,10 @@ public class TransactionDiagram extends GridPane {
this.additionalPayments = additionalPayments; this.additionalPayments = additionalPayments;
} }
public Glyph getOutputGlyph(TransactionDiagram transactionDiagram) { public Glyph getOutputGlyph(WalletTransaction walletTx) {
Glyph glyph = null; Glyph glyph = null;
for(Payment payment : additionalPayments) { for(Payment payment : additionalPayments) {
Glyph paymentGlyph = transactionDiagram.getOutputGlyph(payment); Glyph paymentGlyph = GlyphUtils.getOutputGlyph(walletTx, payment);
if(glyph != null && !paymentGlyph.getStyleClass().equals(glyph.getStyleClass())) { if(glyph != null && !paymentGlyph.getStyleClass().equals(glyph.getStyleClass())) {
return getPaymentGlyph(); return getPaymentGlyph();
} }

View file

@ -1,7 +1,9 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -81,13 +83,13 @@ public class TransactionDiagramLabel extends HBox {
List<Payment> badbankOutputs = walletTx.getPayments().stream().filter(walletTx::isBadbankSend).collect(Collectors.toList()); List<Payment> badbankOutputs = walletTx.getPayments().stream().filter(walletTx::isBadbankSend).collect(Collectors.toList());
List<OutputLabel> badbankOutputLabels = badbankOutputs.stream().map(payment -> getBadbankOutputLabel(transactionDiagram, payment)).collect(Collectors.toList()); List<OutputLabel> badbankOutputLabels = badbankOutputs.stream().map(payment -> getBadbankOutputLabel(transactionDiagram, payment)).collect(Collectors.toList());
outputLabels.addAll(badbankOutputLabels); outputLabels.addAll(badbankOutputLabels);
} else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 } else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 && walletTx.getWallet() != null
&& walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_PREMIX && walletTx.getPayments().stream().anyMatch(walletTx::isPostmixSend)) { && walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_PREMIX && walletTx.getPayments().stream().anyMatch(walletTx::isPostmixSend)) {
OutputLabel mixOutputLabel = getMixOutputLabel(transactionDiagram, walletTx.getPayments()); OutputLabel mixOutputLabel = getMixOutputLabel(transactionDiagram, walletTx.getPayments());
if(mixOutputLabel != null) { if(mixOutputLabel != null) {
outputLabels.add(mixOutputLabel); outputLabels.add(mixOutputLabel);
} }
} else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 } else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 && walletTx.getWallet() != null
&& walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && walletTx.getPayments().stream().anyMatch(walletTx::isConsolidationSend)) { && walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && walletTx.getPayments().stream().anyMatch(walletTx::isConsolidationSend)) {
OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments()); OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments());
if(remixOutputLabel != null) { if(remixOutputLabel != null) {
@ -142,7 +144,7 @@ public class TransactionDiagramLabel extends HBox {
Payment premixOutput = premixOutputs.get(0); Payment premixOutput = premixOutputs.get(0);
long total = premixOutputs.stream().mapToLong(Payment::getAmount).sum(); long total = premixOutputs.stream().mapToLong(Payment::getAmount).sum();
Glyph glyph = transactionDiagram.getOutputGlyph(premixOutput); Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), premixOutput);
String text; String text;
if(premixOutputs.size() == 1) { if(premixOutputs.size() == 1) {
text = "Premix transaction with 1 output of " + transactionDiagram.getSatsValue(premixOutput.getAmount()) + " sats"; text = "Premix transaction with 1 output of " + transactionDiagram.getSatsValue(premixOutput.getAmount()) + " sats";
@ -155,7 +157,7 @@ public class TransactionDiagramLabel extends HBox {
} }
private OutputLabel getBadbankOutputLabel(TransactionDiagram transactionDiagram, Payment payment) { private OutputLabel getBadbankOutputLabel(TransactionDiagram transactionDiagram, Payment payment) {
Glyph glyph = transactionDiagram.getOutputGlyph(payment); Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment);
String text = "Badbank change of " + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString(); String text = "Badbank change of " + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString();
return getOutputLabel(glyph, text); return getOutputLabel(glyph, text);
@ -164,7 +166,7 @@ public class TransactionDiagramLabel extends HBox {
private OutputLabel getWhirlpoolFeeOutputLabel(TransactionDiagram transactionDiagram, Payment whirlpoolFee, List<Payment> premixOutputs) { private OutputLabel getWhirlpoolFeeOutputLabel(TransactionDiagram transactionDiagram, Payment whirlpoolFee, List<Payment> premixOutputs) {
long total = premixOutputs.stream().mapToLong(Payment::getAmount).sum(); long total = premixOutputs.stream().mapToLong(Payment::getAmount).sum();
double feePercentage = (double)whirlpoolFee.getAmount() / (total - whirlpoolFee.getAmount()); double feePercentage = (double)whirlpoolFee.getAmount() / (total - whirlpoolFee.getAmount());
Glyph glyph = transactionDiagram.getOutputGlyph(whirlpoolFee); Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), whirlpoolFee);
String text = "Whirlpool fee of " + transactionDiagram.getSatsValue(whirlpoolFee.getAmount()) + " sats (" + String.format("%.2f", feePercentage * 100.0) + "% of total premix value)"; String text = "Whirlpool fee of " + transactionDiagram.getSatsValue(whirlpoolFee.getAmount()) + " sats (" + String.format("%.2f", feePercentage * 100.0) + "% of total premix value)";
return getOutputLabel(glyph, text); return getOutputLabel(glyph, text);
@ -177,7 +179,7 @@ public class TransactionDiagramLabel extends HBox {
Payment remixOutput = mixOutputs.get(0); Payment remixOutput = mixOutputs.get(0);
long total = mixOutputs.stream().mapToLong(Payment::getAmount).sum(); long total = mixOutputs.stream().mapToLong(Payment::getAmount).sum();
Glyph glyph = TransactionDiagram.getPremixGlyph(); Glyph glyph = GlyphUtils.getPremixGlyph();
String text = "Mix transaction with " + mixOutputs.size() + " outputs of " + transactionDiagram.getSatsValue(remixOutput.getAmount()) + " sats each (" String text = "Mix transaction with " + mixOutputs.size() + " outputs of " + transactionDiagram.getSatsValue(remixOutput.getAmount()) + " sats each ("
+ transactionDiagram.getSatsValue(total) + " sats)"; + transactionDiagram.getSatsValue(total) + " sats)";
@ -191,7 +193,7 @@ public class TransactionDiagramLabel extends HBox {
Payment remixOutput = remixOutputs.get(0); Payment remixOutput = remixOutputs.get(0);
long total = remixOutputs.stream().mapToLong(Payment::getAmount).sum(); long total = remixOutputs.stream().mapToLong(Payment::getAmount).sum();
Glyph glyph = TransactionDiagram.getPremixGlyph(); Glyph glyph = GlyphUtils.getPremixGlyph();
String text = "Remix transaction with " + remixOutputs.size() + " outputs of " + transactionDiagram.getSatsValue(remixOutput.getAmount()) + " sats each (" String text = "Remix transaction with " + remixOutputs.size() + " outputs of " + transactionDiagram.getSatsValue(remixOutput.getAmount()) + " sats each ("
+ transactionDiagram.getSatsValue(total) + " sats)"; + transactionDiagram.getSatsValue(total) + " sats)";
@ -200,10 +202,10 @@ public class TransactionDiagramLabel extends HBox {
private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Payment payment) { private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Payment payment) {
WalletTransaction walletTx = transactionDiagram.getWalletTransaction(); WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
Wallet toWallet = transactionDiagram.getToWallet(payment); Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null; WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
Glyph glyph = transactionDiagram.getOutputGlyph(payment); Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment);
String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString(); String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString();
return getOutputLabel(glyph, text); return getOutputLabel(glyph, text);
@ -212,7 +214,7 @@ public class TransactionDiagramLabel extends HBox {
private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Map.Entry<WalletNode, Long> changeEntry) { private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Map.Entry<WalletNode, Long> changeEntry) {
WalletTransaction walletTx = transactionDiagram.getWalletTransaction(); WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
Glyph glyph = TransactionDiagram.getChangeGlyph(); Glyph glyph = GlyphUtils.getChangeGlyph();
String text = "Change of " + transactionDiagram.getSatsValue(changeEntry.getValue()) + " sats to " + walletTx.getChangeAddress(changeEntry.getKey()).toString(); String text = "Change of " + transactionDiagram.getSatsValue(changeEntry.getValue()) + " sats to " + walletTx.getChangeAddress(changeEntry.getKey()).toString();
return getOutputLabel(glyph, text); return getOutputLabel(glyph, text);
@ -224,7 +226,7 @@ public class TransactionDiagramLabel extends HBox {
return null; return null;
} }
Glyph glyph = transactionDiagram.getFeeGlyph(); Glyph glyph = GlyphUtils.getFeeGlyph();
String text = "Fee of " + transactionDiagram.getSatsValue(walletTx.getFee()) + " sats (" + String.format("%.2f", walletTx.getFeePercentage() * 100.0) + "%)"; String text = "Fee of " + transactionDiagram.getSatsValue(walletTx.getFee()) + " sats (" + String.format("%.2f", walletTx.getFeePercentage() * 100.0) + "%)";
return getOutputLabel(glyph, text); return getOutputLabel(glyph, text);

View file

@ -51,8 +51,10 @@ public class FontAwesome5 extends GlyphFont {
LINK('\uf0c1'), LINK('\uf0c1'),
LOCK('\uf023'), LOCK('\uf023'),
LOCK_OPEN('\uf3c1'), LOCK_OPEN('\uf3c1'),
LONG_ARROW_ALT_RIGHT('\uf30b'),
MAGNIFYING_GLASS_PLUS('\uf00e'), MAGNIFYING_GLASS_PLUS('\uf00e'),
MAGNIFYING_GLASS_MINUS('\uf010'), MAGNIFYING_GLASS_MINUS('\uf010'),
MICROCHIP('\uf2db'),
MINUS_CIRCLE('\uf056'), MINUS_CIRCLE('\uf056'),
PEN_FANCY('\uf5ac'), PEN_FANCY('\uf5ac'),
PLUS('\uf067'), PLUS('\uf067'),

View file

@ -0,0 +1,178 @@
package com.sparrowwallet.sparrow.glyphfont;
import com.sparrowwallet.drongo.wallet.Payment;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.control.TransactionDiagram;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
public class GlyphUtils {
public static Glyph getOutputGlyph(WalletTransaction walletTx, Payment payment) {
if(payment.getType().equals(Payment.Type.MIX)) {
return getMixGlyph();
} else if(payment.getType().equals(Payment.Type.FAKE_MIX)) {
return getFakeMixGlyph();
} else if(walletTx.isConsolidationSend(payment)) {
return getConsolidationGlyph();
} else if(walletTx.isPremixSend(payment)) {
return getPremixGlyph();
} else if(walletTx.isBadbankSend(payment)) {
return getBadbankGlyph();
} else if(payment.getType().equals(Payment.Type.WHIRLPOOL_FEE)) {
return getWhirlpoolFeeGlyph();
} else if(payment instanceof TransactionDiagram.AdditionalPayment) {
return ((TransactionDiagram.AdditionalPayment)payment).getOutputGlyph(walletTx);
} else if(walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment) != null) {
return getDepositGlyph();
} else if(walletTx.isDuplicateAddress(payment)) {
return getPaymentWarningGlyph();
}
return getPaymentGlyph();
}
public static Glyph getPaymentGlyph() {
Glyph paymentGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND);
paymentGlyph.getStyleClass().add("payment-icon");
paymentGlyph.setFontSize(12);
return paymentGlyph;
}
public static Glyph getPaymentWarningGlyph() {
Glyph paymentWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
paymentWarningGlyph.getStyleClass().add("payment-warning-icon");
paymentWarningGlyph.setFontSize(12);
return paymentWarningGlyph;
}
public static Glyph getConsolidationGlyph() {
Glyph consolidationGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.REPLY_ALL);
consolidationGlyph.getStyleClass().add("consolidation-icon");
consolidationGlyph.setFontSize(12);
return consolidationGlyph;
}
public static Glyph getDepositGlyph() {
Glyph depositGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_DOWN);
depositGlyph.getStyleClass().add("deposit-icon");
depositGlyph.setFontSize(12);
return depositGlyph;
}
public static Glyph getPremixGlyph() {
Glyph premixGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.RANDOM);
premixGlyph.getStyleClass().add("premix-icon");
premixGlyph.setFontSize(12);
return premixGlyph;
}
public static Glyph getBadbankGlyph() {
Glyph badbankGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.BIOHAZARD);
badbankGlyph.getStyleClass().add("badbank-icon");
badbankGlyph.setFontSize(12);
return badbankGlyph;
}
public static Glyph getWhirlpoolFeeGlyph() {
Glyph whirlpoolFeeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING_WATER);
whirlpoolFeeGlyph.getStyleClass().add("whirlpoolfee-icon");
whirlpoolFeeGlyph.setFontSize(12);
return whirlpoolFeeGlyph;
}
public static Glyph getFakeMixGlyph() {
Glyph fakeMixGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.THEATER_MASKS);
fakeMixGlyph.getStyleClass().add("fakemix-icon");
fakeMixGlyph.setFontSize(12);
return fakeMixGlyph;
}
public static Glyph getTxoGlyph() {
return getChangeGlyph();
}
public static Glyph getMixGlyph() {
Glyph payjoinGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.RANDOM);
payjoinGlyph.getStyleClass().add("mix-icon");
payjoinGlyph.setFontSize(12);
return payjoinGlyph;
}
public static Glyph getExternalInputGlyph() {
Glyph externalGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LONG_ARROW_ALT_RIGHT);
externalGlyph.getStyleClass().add("external-input-icon");
externalGlyph.setFontSize(12);
return externalGlyph;
}
public static Glyph getExcludeGlyph() {
Glyph excludeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.TIMES_CIRCLE);
excludeGlyph.getStyleClass().add("exclude-utxo");
excludeGlyph.setFontSize(12);
return excludeGlyph;
}
public static Glyph getChangeGlyph() {
Glyph changeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.COINS);
changeGlyph.getStyleClass().add("change-icon");
changeGlyph.setFontSize(12);
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;
}
public static Glyph getFeeGlyph() {
Glyph feeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING);
feeGlyph.getStyleClass().add("fee-icon");
feeGlyph.setFontSize(12);
return feeGlyph;
}
public static Glyph getWarningGlyph() {
Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
feeWarningGlyph.getStyleClass().add("fee-warning-icon");
feeWarningGlyph.setFontSize(12);
return feeWarningGlyph;
}
public static Glyph getQuestionGlyph() {
Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.QUESTION_CIRCLE);
feeWarningGlyph.getStyleClass().add("question-icon");
feeWarningGlyph.setFontSize(12);
return feeWarningGlyph;
}
public static Glyph getLockGlyph() {
Glyph lockGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK);
lockGlyph.getStyleClass().add("lock-icon");
lockGlyph.setFontSize(12);
return lockGlyph;
}
public static Glyph getUserGlyph() {
Glyph userGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.USER);
userGlyph.getStyleClass().add("user-icon");
userGlyph.setFontSize(12);
return userGlyph;
}
public static Glyph getOpcodeGlyph() {
Glyph userGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.MICROCHIP);
userGlyph.getStyleClass().add("opcode-icon");
userGlyph.setFontSize(12);
return userGlyph;
}
}

View file

@ -437,8 +437,11 @@ public class HeadersController extends TransactionFormController implements Init
updateFee(feeAmt); updateFee(feeAmt);
} }
headersForm.walletTransactionProperty().addListener((observable, oldValue, walletTransaction) -> {
transactionDiagram.update(walletTransaction);
});
transactionDiagram.labelProperty().set(transactionDiagramLabel); transactionDiagram.labelProperty().set(transactionDiagramLabel);
transactionDiagram.update(getWalletTransaction(headersForm.getInputTransactions())); headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
blockchainForm.managedProperty().bind(blockchainForm.visibleProperty()); blockchainForm.managedProperty().bind(blockchainForm.visibleProperty());
@ -1334,7 +1337,7 @@ public class HeadersController extends TransactionFormController implements Init
if(headersForm.getInputTransactions() != null) { if(headersForm.getInputTransactions() != null) {
allFetchedInputTransactions.putAll(headersForm.getInputTransactions()); allFetchedInputTransactions.putAll(headersForm.getInputTransactions());
} }
transactionDiagram.update(getWalletTransaction(allFetchedInputTransactions)); headersForm.setWalletTransaction(getWalletTransaction(allFetchedInputTransactions));
} }
} }
} }
@ -1500,7 +1503,7 @@ public class HeadersController extends TransactionFormController implements Init
updateType(); updateType();
updateSize(); updateSize();
updateFee(headersForm.getPsbt().getFee()); updateFee(headersForm.getPsbt().getFee());
transactionDiagram.update(getWalletTransaction(headersForm.getInputTransactions())); headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
} }
} }
@ -1597,7 +1600,7 @@ public class HeadersController extends TransactionFormController implements Init
public void psbtReordered(PSBTReorderedEvent event) { public void psbtReordered(PSBTReorderedEvent event) {
if(event.getPsbt().equals(headersForm.getPsbt())) { if(event.getPsbt().equals(headersForm.getPsbt())) {
updateTxId(); updateTxId();
transactionDiagram.update(getWalletTransaction(headersForm.getInputTransactions())); headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
} }
} }

View file

@ -12,6 +12,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
import javafx.collections.MapChangeListener; import javafx.collections.MapChangeListener;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -124,7 +125,7 @@ public class InputController extends TransactionFormController implements Initia
inputForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> { inputForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> {
updateInputLegendFromWallet(txInput, signingWallet); updateInputLegendFromWallet(txInput, signingWallet);
}); });
updateInputLegendFromWallet(txInput, inputForm.getSigningWallet()); updateInputLegendFromWallet(txInput, inputForm.getWallet());
initializeInputFields(txInput, psbtInput); initializeInputFields(txInput, psbtInput);
initializeScriptFields(txInput, psbtInput); initializeScriptFields(txInput, psbtInput);
@ -142,19 +143,19 @@ public class InputController extends TransactionFormController implements Initia
return "Input #" + txInput.getIndex(); return "Input #" + txInput.getIndex();
} }
private void updateInputLegendFromWallet(TransactionInput txInput, Wallet signingWallet) { private void updateInputLegendFromWallet(TransactionInput txInput, Wallet wallet) {
String baseText = getLegendText(txInput); String baseText = getLegendText(txInput);
if(signingWallet != null) { if(wallet != null) {
if(inputForm.isWalletTxo()) { if(inputForm.isWalletTxo()) {
inputFieldset.setText(baseText + " from " + signingWallet.getFullDisplayName()); inputFieldset.setText(baseText + " from " + wallet.getFullDisplayName());
inputFieldset.setIcon(TransactionDiagram.getTxoGlyph()); inputFieldset.setIcon(GlyphUtils.getTxoGlyph());
} else { } else {
inputFieldset.setText(baseText + " - External"); inputFieldset.setText(baseText + (txInput.isCoinBase() ? " - Coinbase" : " - External"));
inputFieldset.setIcon(TransactionDiagram.getMixGlyph()); inputFieldset.setIcon(GlyphUtils.getMixGlyph());
} }
} else { } else {
inputFieldset.setText(baseText); inputFieldset.setText(baseText + (txInput.isCoinBase() ? " - Coinbase" : " - External"));
inputFieldset.setIcon(null); inputFieldset.setIcon(GlyphUtils.getExternalInputGlyph());
} }
} }

View file

@ -1,14 +1,20 @@
package com.sparrowwallet.sparrow.transaction; package com.sparrowwallet.sparrow.transaction;
import com.sparrowwallet.drongo.protocol.TransactionInput; import com.sparrowwallet.drongo.protocol.TransactionInput;
import com.sparrowwallet.drongo.protocol.TransactionOutPoint;
import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.protocol.TransactionOutput;
import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.net.ElectrumServer;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label;
import org.controlsfx.glyphfont.Glyph;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
public class InputForm extends IndexedTransactionForm { public class InputForm extends IndexedTransactionForm {
public InputForm(TransactionData txdata, PSBTInput psbtInput) { public InputForm(TransactionData txdata, PSBTInput psbtInput) {
@ -48,7 +54,7 @@ public class InputForm extends IndexedTransactionForm {
public boolean isWalletTxo() { public boolean isWalletTxo() {
TransactionInput txInput = getTransactionInput(); TransactionInput txInput = getTransactionInput();
return getSigningWallet() != null && getSigningWallet().isWalletTxo(txInput); return getWallet() != null && getWallet().isWalletTxo(txInput);
} }
@Override @Override
@ -67,6 +73,24 @@ public class InputForm extends IndexedTransactionForm {
} }
public String toString() { public String toString() {
return "Input #" + getIndex(); TransactionOutPoint outPoint = getTransactionInput().getOutpoint();
return outPoint.getHash().toString().substring(0, 8) + "..:" + outPoint.getIndex();
}
@Override
public Label getLabel() {
if(getWalletTransaction() != null) {
TransactionOutPoint outPoint = getTransactionInput().getOutpoint();
Optional<BlockTransactionHashIndex> optRef = getWalletTransaction().getSelectedUtxos().keySet().stream()
.filter(txo -> txo.getHash().equals(outPoint.getHash()) && txo.getIndex() == outPoint.getIndex()).findFirst();
Glyph inputGlyph = isWalletTxo() ? GlyphUtils.getTxoGlyph() : (getWallet() != null ? GlyphUtils.getMixGlyph() : GlyphUtils.getExternalInputGlyph());
if(optRef.isPresent() && optRef.get().getLabel() != null) {
return new Label(optRef.get().getLabel(), inputGlyph);
} else {
return new Label(toString(), inputGlyph);
}
}
return super.getLabel();
} }
} }

View file

@ -5,8 +5,8 @@ import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.NonStandardScriptException; import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
import com.sparrowwallet.drongo.protocol.TransactionInput; import com.sparrowwallet.drongo.protocol.TransactionInput;
import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.protocol.TransactionOutput;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.PSBTReorderedEvent; import com.sparrowwallet.sparrow.event.PSBTReorderedEvent;
@ -66,7 +66,10 @@ public class OutputController extends TransactionFormController implements Initi
outputForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> { outputForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> {
updateOutputLegendFromWallet(txOutput, signingWallet); updateOutputLegendFromWallet(txOutput, signingWallet);
}); });
updateOutputLegendFromWallet(txOutput, outputForm.getSigningWallet()); outputForm.walletTransactionProperty().addListener((observable, oldValue, walletTransaction) -> {
updateOutputLegendFromWallet(txOutput, walletTransaction != null ? walletTransaction.getWallet() : null);
});
updateOutputLegendFromWallet(txOutput, outputForm.getWallet());
value.setValue(txOutput.getValue()); value.setValue(txOutput.getValue());
to.setVisible(false); to.setVisible(false);
@ -103,23 +106,40 @@ public class OutputController extends TransactionFormController implements Initi
return "Output #" + txOutput.getIndex(); return "Output #" + txOutput.getIndex();
} }
private void updateOutputLegendFromWallet(TransactionOutput txOutput, Wallet signingWallet) { private void updateOutputLegendFromWallet(TransactionOutput txOutput, Wallet wallet) {
String baseText = getLegendText(txOutput); String baseText = getLegendText(txOutput);
if(signingWallet != null) { WalletTransaction walletTx = outputForm.getWalletTransaction();
if(outputForm.isWalletChange()) { if(walletTx != null) {
outputFieldset.setText(baseText + " - Change"); List<WalletTransaction.Output> outputs = walletTx.getOutputs();
outputFieldset.setIcon(TransactionDiagram.getChangeGlyph()); if(outputForm.getIndex() < outputs.size()) {
} else if(outputForm.isWalletConsolidation()) { WalletTransaction.Output output = outputs.get(outputForm.getIndex());
outputFieldset.setText(baseText + " - Consolidation"); if(output instanceof WalletTransaction.NonAddressOutput) {
outputFieldset.setIcon(TransactionDiagram.getConsolidationGlyph()); outputFieldset.setText(baseText);
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
Payment payment = paymentOutput.getPayment();
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
outputFieldset.setText(baseText + (toWallet == null ? (toNode != null ? " - Consolidation" : " - Payment") : " - Received to " + toWallet.getFullDisplayName()));
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString());
} else { } else {
outputFieldset.setText(baseText + " - Payment"); outputFieldset.setText(baseText);
outputFieldset.setIcon(TransactionDiagram.getPaymentGlyph());
} }
} else { } else {
outputFieldset.setText(baseText); outputFieldset.setText(baseText);
outputFieldset.setIcon(null);
} }
} else if(wallet != null) {
if(outputForm.isWalletChange()) {
outputFieldset.setText(baseText + " - Change");
} else if(outputForm.isWalletConsolidation()) {
outputFieldset.setText(baseText + " - Consolidation");
} else {
outputFieldset.setText(baseText + " - Payment");
}
} else {
outputFieldset.setText(baseText);
}
outputFieldset.setIcon(outputForm.getLabel().getGraphic());
} }
private void updateSpent(List<BlockTransaction> outputTransactions) { private void updateSpent(List<BlockTransaction> outputTransactions) {

View file

@ -1,12 +1,20 @@
package com.sparrowwallet.sparrow.transaction; package com.sparrowwallet.sparrow.transaction;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
import com.sparrowwallet.drongo.protocol.TransactionOutput; import com.sparrowwallet.drongo.protocol.TransactionOutput;
import com.sparrowwallet.drongo.psbt.PSBTOutput; import com.sparrowwallet.drongo.psbt.PSBTOutput;
import com.sparrowwallet.drongo.wallet.Payment;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label;
import java.io.IOException; import java.io.IOException;
import java.util.*;
public class OutputForm extends IndexedTransactionForm { public class OutputForm extends IndexedTransactionForm {
public OutputForm(TransactionData txdata, PSBTOutput psbtOutput) { public OutputForm(TransactionData txdata, PSBTOutput psbtOutput) {
@ -26,15 +34,15 @@ public class OutputForm extends IndexedTransactionForm {
} }
public boolean isWalletConsolidation() { public boolean isWalletConsolidation() {
return (getSigningWallet() != null && getSigningWallet().getWalletOutputScripts(KeyPurpose.RECEIVE).containsKey(getTransactionOutput().getScript())); return (getWallet() != null && getWallet().getWalletOutputScripts(KeyPurpose.RECEIVE).containsKey(getTransactionOutput().getScript()));
} }
public boolean isWalletChange() { public boolean isWalletChange() {
return (getSigningWallet() != null && getSigningWallet().getWalletOutputScripts(getSigningWallet().getChangeKeyPurpose()).containsKey(getTransactionOutput().getScript())); return (getWallet() != null && getWallet().getWalletOutputScripts(getWallet().getChangeKeyPurpose()).containsKey(getTransactionOutput().getScript()));
} }
public boolean isWalletPayment() { public boolean isWalletPayment() {
return getSigningWallet() != null; return getWallet() != null;
} }
@Override @Override
@ -53,6 +61,33 @@ public class OutputForm extends IndexedTransactionForm {
} }
public String toString() { public String toString() {
return "Output #" + getIndex(); Address address = getTransactionOutput().getScript().getToAddress();
return address != null ? address.toString() : "Output #" + getIndex();
}
@Override
public Label getLabel() {
if(getWalletTransaction() != null) {
List<WalletTransaction.Output> outputs = getWalletTransaction().getOutputs();
if(getIndex() < outputs.size()) {
WalletTransaction.Output output = outputs.get(getIndex());
if(output instanceof WalletTransaction.NonAddressOutput) {
List<ScriptChunk> chunks = output.getTransactionOutput().getScript().getChunks();
if(!chunks.isEmpty() && chunks.get(0).isOpCode() && chunks.get(0).getOpcode() == ScriptOpCodes.OP_RETURN) {
return new Label(chunks.get(0).toString(), GlyphUtils.getOpcodeGlyph());
} else {
return new Label("Output #" + getIndex(), GlyphUtils.getOpcodeGlyph());
}
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
Payment payment = paymentOutput.getPayment();
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.getAddress().toString(),
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
return new Label(changeOutput.getWalletNode().getAddress().toString(), GlyphUtils.getChangeGlyph());
}
}
}
return super.getLabel();
} }
} }

View file

@ -10,7 +10,6 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.TransactionTabData; import com.sparrowwallet.sparrow.TransactionTabData;
import com.sparrowwallet.sparrow.control.TransactionDiagram;
import com.sparrowwallet.sparrow.control.TransactionHexArea; import com.sparrowwallet.sparrow.control.TransactionHexArea;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
@ -20,6 +19,7 @@ import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell; import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem; import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView; import javafx.scene.control.TreeView;
@ -158,28 +158,11 @@ public class TransactionController implements Initializable {
setContextMenu(null); setContextMenu(null);
if(form != null) { if(form != null) {
setText(form.toString()); Label label = form.getLabel();
label.setMaxWidth(100);
setGraphic(label);
if(form.getSigningWallet() != null) { if(form.getSigningWallet() != null) {
if(form instanceof InputForm) {
InputForm inputForm = (InputForm)form;
if(inputForm.isWalletTxo()) {
setGraphic(TransactionDiagram.getTxoGlyph());
} else {
setGraphic(TransactionDiagram.getMixGlyph());
}
}
if(form instanceof OutputForm) {
OutputForm outputForm = (OutputForm)form;
if(outputForm.isWalletChange()) {
setGraphic(TransactionDiagram.getChangeGlyph());
} else if(outputForm.isWalletConsolidation()) {
setGraphic(TransactionDiagram.getConsolidationGlyph());
} else {
setGraphic(TransactionDiagram.getPaymentGlyph());
}
}
setOnDragDetected(null); setOnDragDetected(null);
setOnDragOver(null); setOnDragOver(null);
setOnDragDropped(null); setOnDragDropped(null);
@ -196,6 +179,10 @@ public class TransactionController implements Initializable {
txtree.refresh(); txtree.refresh();
}); });
txdata.walletTransactionProperty().addListener((observable, oldValue, newValue) -> {
txtree.refresh();
});
txtree.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedItem) -> { txtree.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedItem) -> {
TransactionForm transactionForm = selectedItem.getValue(); TransactionForm transactionForm = selectedItem.getValue();
if(transactionForm instanceof PageForm) { if(transactionForm instanceof PageForm) {

View file

@ -3,10 +3,7 @@ package com.sparrowwallet.sparrow.transaction;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -30,6 +27,7 @@ public class TransactionData {
private final ObservableMap<Wallet, Storage> availableWallets = FXCollections.observableHashMap(); private final ObservableMap<Wallet, Storage> availableWallets = FXCollections.observableHashMap();
private final SimpleObjectProperty<Wallet> signingWallet = new SimpleObjectProperty<>(this, "signingWallet", null); private final SimpleObjectProperty<Wallet> signingWallet = new SimpleObjectProperty<>(this, "signingWallet", null);
private final ObservableMap<TransactionSignature, Keystore> signatureKeystoreMap = FXCollections.observableMap(new LinkedHashMap<>()); private final ObservableMap<TransactionSignature, Keystore> signatureKeystoreMap = FXCollections.observableMap(new LinkedHashMap<>());
private final SimpleObjectProperty<WalletTransaction> walletTransaction = new SimpleObjectProperty<>(this, "walletTransaction", null);
public TransactionData(String name, PSBT psbt) { public TransactionData(String name, PSBT psbt) {
this(name, psbt.getTransaction()); this(name, psbt.getTransaction());
@ -179,4 +177,20 @@ public class TransactionData {
return signingWalletNodes; return signingWalletNodes;
} }
public WalletTransaction getWalletTransaction() {
return walletTransaction.get();
}
public SimpleObjectProperty<WalletTransaction> walletTransactionProperty() {
return walletTransaction;
}
public void setWalletTransaction(WalletTransaction walletTransaction) {
this.walletTransaction.set(walletTransaction);
}
public Wallet getWallet() {
return getSigningWallet() != null ? getSigningWallet() : (getWalletTransaction() != null ? getWalletTransaction().getWallet() : null);
}
} }

View file

@ -4,14 +4,12 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.protocol.TransactionSignature; import com.sparrowwallet.drongo.protocol.TransactionSignature;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableMap; import javafx.collections.ObservableMap;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@ -98,6 +96,22 @@ public abstract class TransactionForm {
return txdata.getSigningWalletNodes(); return txdata.getSigningWalletNodes();
} }
public WalletTransaction getWalletTransaction() {
return txdata.getWalletTransaction();
}
public SimpleObjectProperty<WalletTransaction> walletTransactionProperty() {
return txdata.walletTransactionProperty();
}
public void setWalletTransaction(WalletTransaction walletTransaction) {
txdata.setWalletTransaction(walletTransaction);
}
public Wallet getWallet() {
return txdata.getWallet();
}
public boolean isEditable() { public boolean isEditable() {
if(getBlockTransaction() != null) { if(getBlockTransaction() != null) {
return false; return false;
@ -120,4 +134,8 @@ public abstract class TransactionForm {
public abstract Node getContents() throws IOException; public abstract Node getContents() throws IOException;
public abstract TransactionView getView(); public abstract TransactionView getView();
public Label getLabel() {
return new Label(toString());
}
} }

View file

@ -31,3 +31,7 @@
.color-8 { -fx-fill: #000000 } .color-8 { -fx-fill: #000000 }
.color-grey { -fx-fill: #e5e5e6 } .color-grey { -fx-fill: #e5e5e6 }
.change-warning-icon, .payment-warning-icon {
-fx-text-fill: rgb(238, 210, 2);
}