adapt to use declarative style to for consolidation payments

This commit is contained in:
Craig Raw 2025-10-17 10:27:20 +02:00
parent 0974918cff
commit 092267339a
9 changed files with 74 additions and 35 deletions

2
drongo

@ -1 +1 @@
Subproject commit 286e04ad25de8f5739a70ac353ee073eace5e873 Subproject commit 4e68815fa977a45a7caddead35e5d0f90f5e8fd6

View file

@ -727,7 +727,7 @@ public class TransactionDiagram extends GridPane {
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 = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), 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 = payment instanceof WalletNodePayment walletNodePayment ? walletNodePayment.getWalletNode() : null;
Wallet toBip47Wallet = getBip47SendWallet(payment); Wallet toBip47Wallet = getBip47SendWallet(payment);
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment); DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment);
Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ")

View file

@ -90,20 +90,20 @@ public class TransactionDiagramLabel extends HBox {
outputLabels.add(mixOutputLabel); outputLabels.add(mixOutputLabel);
} }
} else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 && walletTx.getWallet() != null } 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.getWalletNodePayments().isEmpty()) {
OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments()); OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments());
if(remixOutputLabel != null) { if(remixOutputLabel != null) {
outputLabels.add(remixOutputLabel); outputLabels.add(remixOutputLabel);
} }
} else { } else {
List<Payment> payments = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT && !walletTx.isConsolidationSend(payment)).collect(Collectors.toList()); List<Payment> payments = walletTx.getExternalPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT).collect(Collectors.toList());
List<OutputLabel> paymentLabels = payments.stream().map(payment -> getOutputLabel(transactionDiagram, payment)).collect(Collectors.toList()); List<OutputLabel> paymentLabels = payments.stream().map(payment -> getOutputLabel(transactionDiagram, payment)).collect(Collectors.toList());
if(walletTx.getSelectedUtxos().values().stream().allMatch(Objects::isNull)) { if(walletTx.getSelectedUtxos().values().stream().allMatch(Objects::isNull)) {
paymentLabels.sort(Comparator.comparingInt(paymentLabel -> (paymentLabel.text.startsWith("Receive") ? 0 : 1))); paymentLabels.sort(Comparator.comparingInt(paymentLabel -> (paymentLabel.text.startsWith("Receive") ? 0 : 1)));
} }
outputLabels.addAll(paymentLabels); outputLabels.addAll(paymentLabels);
List<Payment> consolidations = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT && walletTx.isConsolidationSend(payment)).collect(Collectors.toList()); List<Payment> consolidations = walletTx.getWalletNodePayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT).collect(Collectors.toList());
outputLabels.addAll(consolidations.stream().map(consolidation -> getOutputLabel(transactionDiagram, consolidation)).collect(Collectors.toList())); outputLabels.addAll(consolidations.stream().map(consolidation -> getOutputLabel(transactionDiagram, consolidation)).collect(Collectors.toList()));
List<Payment> mixes = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.MIX || payment.getType() == Payment.Type.FAKE_MIX).collect(Collectors.toList()); List<Payment> mixes = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.MIX || payment.getType() == Payment.Type.FAKE_MIX).collect(Collectors.toList());
@ -203,7 +203,7 @@ 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 = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), 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 = payment instanceof WalletNodePayment walletNodePayment ? walletNodePayment.getWalletNode() : null;
Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment); Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment);
String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment; String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment;

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.glyphfont; package com.sparrowwallet.sparrow.glyphfont;
import com.sparrowwallet.drongo.wallet.Payment; import com.sparrowwallet.drongo.wallet.Payment;
import com.sparrowwallet.drongo.wallet.WalletNodePayment;
import com.sparrowwallet.drongo.wallet.WalletTransaction; import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.control.TransactionDiagram; import com.sparrowwallet.sparrow.control.TransactionDiagram;
@ -15,7 +16,7 @@ public class GlyphUtils {
return getFakeMixGlyph(); return getFakeMixGlyph();
} else if(payment.getType().equals(Payment.Type.ANCHOR)) { } else if(payment.getType().equals(Payment.Type.ANCHOR)) {
return getAnchorGlyph(); return getAnchorGlyph();
} else if(walletTx.isConsolidationSend(payment)) { } else if(payment instanceof WalletNodePayment) {
return getConsolidationGlyph(); return getConsolidationGlyph();
} else if(walletTx.isPremixSend(payment)) { } else if(walletTx.isPremixSend(payment)) {
return getPremixGlyph(); return getPremixGlyph();

View file

@ -643,19 +643,20 @@ public class HeadersController extends TransactionFormController implements Init
List<Payment> payments = new ArrayList<>(); List<Payment> payments = new ArrayList<>();
List<WalletTransaction.Output> outputs = new ArrayList<>(); List<WalletTransaction.Output> outputs = new ArrayList<>();
Map<WalletNode, Long> changeMap = new LinkedHashMap<>(); Map<WalletNode, Long> changeMap = new LinkedHashMap<>();
Map<Script, WalletNode> receiveOutputScripts = wallet.getWalletOutputScripts(KeyPurpose.RECEIVE);
Map<Script, WalletNode> changeOutputScripts = wallet.getWalletOutputScripts(wallet.getChangeKeyPurpose()); Map<Script, WalletNode> changeOutputScripts = wallet.getWalletOutputScripts(wallet.getChangeKeyPurpose());
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) { for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
WalletNode changeNode = changeOutputScripts.get(txOutput.getScript()); WalletNode changeNode = changeOutputScripts.get(txOutput.getScript());
if(changeNode != null) { if(changeNode != null) {
if(headersForm.getTransaction().getOutputs().size() == 4 && headersForm.getTransaction().getOutputs().stream().anyMatch(txo -> txo != txOutput && txo.getValue() == txOutput.getValue())) { if(headersForm.getTransaction().getOutputs().size() == 4 && headersForm.getTransaction().getOutputs().stream().anyMatch(txo -> txo != txOutput && txo.getValue() == txOutput.getValue())) {
if(selectedTxos.values().stream().allMatch(Objects::nonNull)) { if(selectedTxos.values().stream().allMatch(Objects::nonNull)) {
payments.add(new Payment(txOutput.getScript().getToAddress(), ".." + changeNode + " (Fake Mix)", txOutput.getValue(), false, Payment.Type.FAKE_MIX)); payments.add(new WalletNodePayment(changeNode, ".." + changeNode + " (Fake Mix)", txOutput.getValue(), false, Payment.Type.FAKE_MIX));
} else { } else {
payments.add(new Payment(txOutput.getScript().getToAddress(), ".." + changeNode + " (Mix)", txOutput.getValue(), false, Payment.Type.MIX)); payments.add(new WalletNodePayment(changeNode, ".." + changeNode + " (Mix)", txOutput.getValue(), false, Payment.Type.MIX));
} }
} else { } else {
if(changeMap.containsKey(changeNode)) { if(changeMap.containsKey(changeNode)) {
payments.add(new Payment(txOutput.getScript().getToAddress(), headersForm.getName(), txOutput.getValue(), false, Payment.Type.DEFAULT)); payments.add(new WalletNodePayment(changeNode, headersForm.getName(), txOutput.getValue(), false, Payment.Type.DEFAULT));
} else { } else {
changeMap.put(changeNode, txOutput.getValue()); changeMap.put(changeNode, txOutput.getValue());
} }
@ -672,12 +673,18 @@ public class HeadersController extends TransactionFormController implements Init
BlockTransactionHashIndex receivedTxo = walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txOutput.getHash()) && txo.getIndex() == txOutput.getIndex()).findFirst().orElse(null); BlockTransactionHashIndex receivedTxo = walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txOutput.getHash()) && txo.getIndex() == txOutput.getIndex()).findFirst().orElse(null);
String label = headersForm.getName() == null || (headersForm.getName().startsWith("[") && headersForm.getName().endsWith("]") && headersForm.getName().length() == 8) ? null : headersForm.getName(); String label = headersForm.getName() == null || (headersForm.getName().startsWith("[") && headersForm.getName().endsWith("]") && headersForm.getName().length() == 8) ? null : headersForm.getName();
Address address = txOutput.getScript().getToAddress(); Address address = txOutput.getScript().getToAddress();
WalletNode receiveNode = receiveOutputScripts.get(txOutput.getScript());
SilentPaymentAddress silentPaymentAddress = headersForm.getSilentPaymentAddress(txOutput); SilentPaymentAddress silentPaymentAddress = headersForm.getSilentPaymentAddress(txOutput);
label = receivedTxo != null ? receivedTxo.getLabel() : label; label = receivedTxo != null ? receivedTxo.getLabel() : label;
if(address != null || silentPaymentAddress != null) { if(address != null || silentPaymentAddress != null) {
Payment payment = (silentPaymentAddress == null ? Payment payment;
new Payment(address, label, txOutput.getValue(), false, paymentType) : if(silentPaymentAddress != null) {
new SilentPayment(silentPaymentAddress, address, label, txOutput.getValue(), false)); payment = new SilentPayment(silentPaymentAddress, address, label, txOutput.getValue(), false);
} else if(receiveNode != null) {
payment = new WalletNodePayment(receiveNode, label, txOutput.getValue(), false, paymentType);
} else {
payment = new Payment(address, label, txOutput.getValue(), false, paymentType);
}
WalletTransaction createdTx = AppServices.get().getCreatedTransaction(selectedTxos.keySet()); WalletTransaction createdTx = AppServices.get().getCreatedTransaction(selectedTxos.keySet());
if(createdTx != null) { if(createdTx != null) {
Optional<String> optLabel = createdTx.getPayments().stream() Optional<String> optLabel = createdTx.getPayments().stream()
@ -689,8 +696,13 @@ public class HeadersController extends TransactionFormController implements Init
} }
} }
payments.add(payment); payments.add(payment);
outputs.add(payment instanceof SilentPayment silentPayment ? new WalletTransaction.SilentPaymentOutput(txOutput, silentPayment) : if(payment instanceof SilentPayment silentPayment) {
new WalletTransaction.PaymentOutput(txOutput, payment)); outputs.add(new WalletTransaction.SilentPaymentOutput(txOutput, silentPayment));
} else if(payment instanceof WalletNodePayment walletNodePayment) {
outputs.add(new WalletTransaction.ConsolidationOutput(txOutput, walletNodePayment, walletNodePayment.getAmount()));
} else {
outputs.add(new WalletTransaction.PaymentOutput(txOutput, payment));
}
} else { } else {
outputs.add(new WalletTransaction.NonAddressOutput(txOutput)); outputs.add(new WalletTransaction.NonAddressOutput(txOutput));
} }

View file

@ -126,13 +126,14 @@ public class OutputController extends TransactionFormController implements Initi
WalletTransaction.Output output = outputs.get(outputForm.getIndex()); WalletTransaction.Output output = outputs.get(outputForm.getIndex());
if(output instanceof WalletTransaction.NonAddressOutput) { if(output instanceof WalletTransaction.NonAddressOutput) {
outputFieldset.setText(baseText); outputFieldset.setText(baseText);
} else if(output instanceof WalletTransaction.SilentPaymentOutput silentPaymentOutput) { } else if(output instanceof WalletTransaction.SilentPaymentOutput) {
outputFieldset.setText(baseText + " - Silent Payment"); outputFieldset.setText(baseText + " - Silent Payment");
} else if(output instanceof WalletTransaction.ConsolidationOutput) {
outputFieldset.setText(baseText + " - Consolidation");
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) { } else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
Payment payment = paymentOutput.getPayment(); Payment payment = paymentOutput.getPayment();
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment); 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 ? " - Payment" : " - Received to " + toWallet.getFullDisplayName()));
outputFieldset.setText(baseText + (toWallet == null ? (toNode != null ? " - Consolidation" : " - Payment") : " - Received to " + toWallet.getFullDisplayName()));
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) { } else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString()); outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString());
} else { } else {

View file

@ -91,6 +91,10 @@ public class OutputForm extends IndexedTransactionForm {
Payment payment = paymentOutput.getPayment(); Payment payment = paymentOutput.getPayment();
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(), return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(),
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment)); GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
} else if(output instanceof WalletTransaction.ConsolidationOutput consolidationOutput) {
Payment payment = consolidationOutput.getWalletNodePayment();
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(),
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) { } else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
return new Label("Change", GlyphUtils.getChangeGlyph()); return new Label("Change", GlyphUtils.getChangeGlyph());
} }

View file

@ -143,6 +143,8 @@ public class PaymentController extends WalletFormController implements Initializ
} }
}; };
private final ObjectProperty<WalletNode> consolidationNodeProperty = new SimpleObjectProperty<>();
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>(); private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>();
private final ObjectProperty<SilentPaymentAddress> silentPaymentAddressProperty = new SimpleObjectProperty<>(); private final ObjectProperty<SilentPaymentAddress> silentPaymentAddressProperty = new SimpleObjectProperty<>();
@ -168,6 +170,10 @@ public class PaymentController extends WalletFormController implements Initializ
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
address.leftProperty().set(null); address.leftProperty().set(null);
if(consolidationNodeProperty.get() != null && !newValue.equals(consolidationNodeProperty.get().getAddress().toString())) {
consolidationNodeProperty.set(null);
}
if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) { if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) {
payNymProperty.set(null); payNymProperty.set(null);
} }
@ -259,6 +265,17 @@ public class PaymentController extends WalletFormController implements Initializ
//ignore, not a silent payment address //ignore, not a silent payment address
} }
try {
Address toAddress = Address.fromString(newValue);
WalletNode walletNode = sendController.getWalletNode(toAddress);
if(walletNode != null) {
consolidationNodeProperty.set(walletNode);
}
label.requestFocus();
} catch(Exception e) {
//ignore, not an address
}
revalidateAmount(); revalidateAmount();
maxButton.setDisable(!isMaxButtonEnabled()); maxButton.setDisable(!isMaxButtonEnabled());
sendController.updateTransaction(); sendController.updateTransaction();
@ -658,8 +675,11 @@ public class PaymentController extends WalletFormController implements Initializ
if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) { if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) {
Payment payment; Payment payment;
SilentPaymentAddress silentPaymentAddress = silentPaymentAddressProperty.get(); SilentPaymentAddress silentPaymentAddress = silentPaymentAddressProperty.get();
WalletNode consolidationNode = consolidationNodeProperty.get();
if(silentPaymentAddress != null) { if(silentPaymentAddress != null) {
payment = new SilentPayment(silentPaymentAddress, label.getText(), value, sendAll); payment = new SilentPayment(silentPaymentAddress, label.getText(), value, sendAll);
} else if(consolidationNode != null) {
payment = new WalletNodePayment(consolidationNode, label.getText(), value, sendAll);
} else { } else {
payment = new Payment(recipientAddress, label.getText(), value, sendAll); payment = new Payment(recipientAddress, label.getText(), value, sendAll);
} }
@ -718,6 +738,7 @@ public class PaymentController extends WalletFormController implements Initializ
setSendMax(false); setSendMax(false);
dustAmountProperty.set(false); dustAmountProperty.set(false);
consolidationNodeProperty.set(null);
payNymProperty.set(null); payNymProperty.set(null);
dnsPaymentProperty.set(null); dnsPaymentProperty.set(null);
silentPaymentAddressProperty.set(null); silentPaymentAddressProperty.set(null);
@ -728,8 +749,7 @@ public class PaymentController extends WalletFormController implements Initializ
if(utxoSelector == null) { if(utxoSelector == null) {
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector(); MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
sendController.utxoSelectorProperty().set(maxUtxoSelector); sendController.utxoSelectorProperty().set(maxUtxoSelector);
} else if(utxoSelector instanceof PresetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) { } else if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true); Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true);
setPayment(payment); setPayment(payment);
return; return;

View file

@ -172,7 +172,7 @@ public class SendController extends WalletFormController implements Initializabl
private final Set<WalletNode> excludedChangeNodes = new HashSet<>(); private final Set<WalletNode> excludedChangeNodes = new HashSet<>();
private final Map<Wallet, Map<Address, WalletNode>> addressNodeMap = new HashMap<>(); private final Map<Address, WalletNode> walletAddresses = new HashMap<>();
private final ChangeListener<String> feeListener = new ChangeListener<>() { private final ChangeListener<String> feeListener = new ChangeListener<>() {
@Override @Override
@ -619,7 +619,7 @@ public class SendController extends WalletFormController implements Initializabl
boolean allowRbf = (replacedTransaction == null || replacedTransaction.getTransaction().isReplaceByFee()) boolean allowRbf = (replacedTransaction == null || replacedTransaction.getTransaction().isReplaceByFee())
&& payments.stream().noneMatch(payment -> payment instanceof SilentPayment); && payments.stream().noneMatch(payment -> payment instanceof SilentPayment);
walletTransactionService = new WalletTransactionService(addressNodeMap, wallet, getUtxoSelectors(payments), getTxoFilters(), walletTransactionService = new WalletTransactionService(wallet, getUtxoSelectors(payments), getTxoFilters(),
payments, opReturnsList, excludedChangeNodes, payments, opReturnsList, excludedChangeNodes,
feeRate, getMinimumFeeRate(), minRelayFeeRate, userFee, feeRate, getMinimumFeeRate(), minRelayFeeRate, userFee,
currentBlockHeight, groupByAddress, includeMempoolOutputs, replacedTransaction, allowRbf); currentBlockHeight, groupByAddress, includeMempoolOutputs, replacedTransaction, allowRbf);
@ -684,7 +684,6 @@ public class SendController extends WalletFormController implements Initializabl
} }
private static class WalletTransactionService extends Service<WalletTransaction> { private static class WalletTransactionService extends Service<WalletTransaction> {
private final Map<Wallet, Map<Address, WalletNode>> addressNodeMap;
private final Wallet wallet; private final Wallet wallet;
private final List<UtxoSelector> utxoSelectors; private final List<UtxoSelector> utxoSelectors;
private final List<TxoFilter> txoFilters; private final List<TxoFilter> txoFilters;
@ -702,13 +701,11 @@ public class SendController extends WalletFormController implements Initializabl
private final boolean allowRbf; private final boolean allowRbf;
private boolean ignoreResult; private boolean ignoreResult;
public WalletTransactionService(Map<Wallet, Map<Address, WalletNode>> addressNodeMap, public WalletTransactionService(Wallet wallet, List<UtxoSelector> utxoSelectors, List<TxoFilter> txoFilters,
Wallet wallet, List<UtxoSelector> utxoSelectors, List<TxoFilter> txoFilters,
List<Payment> payments, List<byte[]> opReturns, Set<WalletNode> excludedChangeNodes, List<Payment> payments, List<byte[]> opReturns, Set<WalletNode> excludedChangeNodes,
double feeRate, double longTermFeeRate, double minRelayFeeRate, Long fee, double feeRate, double longTermFeeRate, double minRelayFeeRate, Long fee,
Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs,
BlockTransaction replacedTransaction, boolean allowRbf) { BlockTransaction replacedTransaction, boolean allowRbf) {
this.addressNodeMap = addressNodeMap;
this.wallet = wallet; this.wallet = wallet;
this.utxoSelectors = utxoSelectors; this.utxoSelectors = utxoSelectors;
this.txoFilters = txoFilters; this.txoFilters = txoFilters;
@ -759,11 +756,8 @@ public class SendController extends WalletFormController implements Initializabl
private WalletTransaction getWalletTransaction() throws InsufficientFundsException { private WalletTransaction getWalletTransaction() throws InsufficientFundsException {
try { try {
updateMessage("Selecting UTXOs..."); updateMessage("Selecting UTXOs...");
WalletTransaction walletTransaction = wallet.createWalletTransaction(utxoSelectors, txoFilters, payments, opReturns, excludedChangeNodes, return wallet.createWalletTransaction(utxoSelectors, txoFilters, payments, opReturns, excludedChangeNodes,
feeRate, longTermFeeRate, minRelayFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, allowRbf); feeRate, longTermFeeRate, minRelayFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, allowRbf);
updateMessage("Deriving keys...");
walletTransaction.updateAddressNodeMap(addressNodeMap, walletTransaction.getWallet());
return walletTransaction;
} finally { } finally {
updateMessage(""); updateMessage("");
} }
@ -1131,7 +1125,7 @@ public class SendController extends WalletFormController implements Initializabl
paymentCodeProperty.set(null); paymentCodeProperty.set(null);
addressNodeMap.clear(); walletAddresses.clear();
} }
public UtxoSelector getUtxoSelector() { public UtxoSelector getUtxoSelector() {
@ -1209,13 +1203,20 @@ public class SendController extends WalletFormController implements Initializabl
WalletTransaction walletTransaction = walletTransactionProperty.get(); WalletTransaction walletTransaction = walletTransactionProperty.get();
Set<WalletNode> nodes = new LinkedHashSet<>(walletTransaction.getSelectedUtxos().values()); Set<WalletNode> nodes = new LinkedHashSet<>(walletTransaction.getSelectedUtxos().values());
nodes.addAll(walletTransaction.getChangeMap().keySet()); nodes.addAll(walletTransaction.getChangeMap().keySet());
Map<Address, WalletNode> addressNodeMap = walletTransaction.getAddressNodeMap(); nodes.addAll(walletTransaction.getWalletNodePayments().stream().map(WalletNodePayment::getWalletNode).collect(Collectors.toList()));
nodes.addAll(addressNodeMap.values().stream().filter(Objects::nonNull).collect(Collectors.toList()));
//All wallet nodes applicable to this transaction are stored so when the subscription status for one is updated, the history for all can be fetched in one atomic update //All wallet nodes applicable to this transaction are stored so when the subscription status for one is updated, the history for all can be fetched in one atomic update
walletForm.addWalletTransactionNodes(nodes); walletForm.addWalletTransactionNodes(nodes);
} }
public WalletNode getWalletNode(Address address) {
if(walletAddresses.isEmpty()) {
walletAddresses.putAll(getWalletForm().getWallet().getWalletAddresses());
}
return walletAddresses.get(address);
}
public void broadcastNotification(ActionEvent event) { public void broadcastNotification(ActionEvent event) {
Wallet wallet = getWalletForm().getWallet(); Wallet wallet = getWalletForm().getWallet();
Storage storage = AppServices.get().getOpenWallets().get(wallet); Storage storage = AppServices.get().getOpenWallets().get(wallet);
@ -1667,12 +1668,12 @@ public class SendController extends WalletFormController implements Initializabl
public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) { public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) {
List<Payment> payments = walletTransaction.getPayments(); List<Payment> payments = walletTransaction.getPayments();
List<Payment> userPayments = payments.stream().filter(payment -> payment.getType() != Payment.Type.FAKE_MIX).collect(Collectors.toList()); List<Payment> userPayments = payments.stream().filter(payment -> payment.getType() != Payment.Type.FAKE_MIX).collect(Collectors.toList());
Map<Address, WalletNode> walletAddresses = walletTransaction.getAddressNodeMap(); List<WalletNodePayment> walletNodePayments = walletTransaction.getWalletNodePayments();
OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy(); OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy();
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX); boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0); boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0);
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType()); boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType());
boolean addressReuse = userPayments.stream().anyMatch(payment -> walletAddresses.get(payment.getAddress()) != null && !walletAddresses.get(payment.getAddress()).getTransactionOutputs().isEmpty()); boolean addressReuse = walletNodePayments.stream().anyMatch(walletNodePayment -> !walletNodePayment.getWalletNode().getTransactionOutputs().isEmpty());
boolean payjoinPresent = userPayments.stream().anyMatch(payment -> AppServices.getPayjoinURI(payment.getAddress()) != null); boolean payjoinPresent = userPayments.stream().anyMatch(payment -> AppServices.getPayjoinURI(payment.getAddress()) != null);
if(optimizationStrategy == OptimizationStrategy.PRIVACY) { if(optimizationStrategy == OptimizationStrategy.PRIVACY) {