adapt to use declarative style to for consolidation payments
What changed, and why it matters
This commit refactors how Sparrow Wallet labels and handles payments that send bitcoin back to the user's own wallet (called 'consolidation' payments). It replaces an older imperative check with a new explicit 'WalletNodePayment' type. There is no direct evidence this fixes a security vulnerability; it appears to be a code-quality and UI-labeling improvement.
No immediate security action required. Treat as a normal refactor; review the companion drongo library changes for completeness if available.
Security signals we found
Refactor of transaction output classification logic
Removal of addressNodeMap derivation step from WalletTransactionService
New WalletNodePayment and ConsolidationOutput types used for internal sends
Evidence from the diff
The change introduces a declarative WalletNodePayment class to mark payments that are internal wallet consolidations, instead of inferring them via address-to-node map lookups. UI controllers, transaction diagram labels, and output forms are updated to use instanceof WalletNodePayment and new ConsolidationOutput objects. A new getWalletNode() helper caches wallet addresses. No cryptographic, network, or access-control changes are present in the diff.
Changed components
TransactionDiagram UITransactionDiagramLabel UIGlyphUtils icon renderingHeadersController transaction parsingOutputController output labelingOutputForm output form renderingPaymentController payment entrySendController wallet transaction constructionInspect captured patch +74 / −35
diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java
index bddf074..46305d4 100644
--- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java
+++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java
@@ -727,7 +727,7 @@ public class TransactionDiagram extends GridPane {
recipientLabel.getStyleClass().add("output-label");
recipientLabel.getStyleClass().add(labelledPayment ? "payment-label" : "recipient-label");
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);
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment);
Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ")
diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java
index 76432d7..825bd2e 100644
--- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java
+++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagramLabel.java
@@ -90,20 +90,20 @@ public class TransactionDiagramLabel extends HBox {
outputLabels.add(mixOutputLabel);
}
} 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());
if(remixOutputLabel != null) {
outputLabels.add(remixOutputLabel);
}
} 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());
if(walletTx.getSelectedUtxos().values().stream().allMatch(Objects::isNull)) {
paymentLabels.sort(Comparator.comparingInt(paymentLabel -> (paymentLabel.text.startsWith("Receive") ? 0 : 1)));
}
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()));
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) {
WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
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);
String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment;
diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/GlyphUtils.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/GlyphUtils.java
index ef263c8..ac29d81 100644
--- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/GlyphUtils.java
+++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/GlyphUtils.java
@@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.glyphfont;
import com.sparrowwallet.drongo.wallet.Payment;
+import com.sparrowwallet.drongo.wallet.WalletNodePayment;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.control.TransactionDiagram;
@@ -15,7 +16,7 @@ public class GlyphUtils {
return getFakeMixGlyph();
} else if(payment.getType().equals(Payment.Type.ANCHOR)) {
return getAnchorGlyph();
- } else if(walletTx.isConsolidationSend(payment)) {
+ } else if(payment instanceof WalletNodePayment) {
return getConsolidationGlyph();
} else if(walletTx.isPremixSend(payment)) {
return getPremixGlyph();
diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java
index 5fa7e9c..0e87444 100644
--- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java
@@ -643,19 +643,20 @@ public class HeadersController extends TransactionFormController implements Init
List<Payment> payments = new ArrayList<>();
List<WalletTransaction.Output> outputs = new ArrayList<>();
Map<WalletNode, Long> changeMap = new LinkedHashMap<>();
+ Map<Script, WalletNode> receiveOutputScripts = wallet.getWalletOutputScripts(KeyPurpose.RECEIVE);
Map<Script, WalletNode> changeOutputScripts = wallet.getWalletOutputScripts(wallet.getChangeKeyPurpose());
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
WalletNode changeNode = changeOutputScripts.get(txOutput.getScript());
if(changeNode != null) {
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)) {
- 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 {
- 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 {
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 {
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);
String label = headersForm.getName() == null || (headersForm.getName().startsWith("[") && headersForm.getName().endsWith("]") && headersForm.getName().length() == 8) ? null : headersForm.getName();
Address address = txOutput.getScript().getToAddress();
+ WalletNode receiveNode = receiveOutputScripts.get(txOutput.getScript());
SilentPaymentAddress silentPaymentAddress = headersForm.getSilentPaymentAddress(txOutput);
label = receivedTxo != null ? receivedTxo.getLabel() : label;
if(address != null || silentPaymentAddress != null) {
- Payment payment = (silentPaymentAddress == null ?
- new Payment(address, label, txOutput.getValue(), false, paymentType) :
- new SilentPayment(silentPaymentAddress, address, label, txOutput.getValue(), false));
+ Payment payment;
+ if(silentPaymentAddress != null) {
+ 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());
if(createdTx != null) {
Optional<String> optLabel = createdTx.getPayments().stream()
@@ -689,8 +696,13 @@ public class HeadersController extends TransactionFormController implements Init
}
}
payments.add(payment);
- outputs.add(payment instanceof SilentPayment silentPayment ? new WalletTransaction.SilentPaymentOutput(txOutput, silentPayment) :
- new WalletTransaction.PaymentOutput(txOutput, payment));
+ if(payment instanceof SilentPayment silentPayment) {
+ 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 {
outputs.add(new WalletTransaction.NonAddressOutput(txOutput));
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java
index b4cabac..831ccc6 100644
--- a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputController.java
@@ -126,13 +126,14 @@ public class OutputController extends TransactionFormController implements Initi
WalletTransaction.Output output = outputs.get(outputForm.getIndex());
if(output instanceof WalletTransaction.NonAddressOutput) {
outputFieldset.setText(baseText);
- } else if(output instanceof WalletTransaction.SilentPaymentOutput silentPaymentOutput) {
+ } else if(output instanceof WalletTransaction.SilentPaymentOutput) {
outputFieldset.setText(baseText + " - Silent Payment");
+ } else if(output instanceof WalletTransaction.ConsolidationOutput) {
+ outputFieldset.setText(baseText + " - Consolidation");
} 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()));
+ outputFieldset.setText(baseText + (toWallet == null ? " - Payment" : " - Received to " + toWallet.getFullDisplayName()));
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString());
} else {
diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java
index fee0718..95a451d 100644
--- a/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java
+++ b/src/main/java/com/sparrowwallet/sparrow/transaction/OutputForm.java
@@ -91,6 +91,10 @@ public class OutputForm extends IndexedTransactionForm {
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(),
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) {
return new Label("Change", GlyphUtils.getChangeGlyph());
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
index 7d8f30b..d15e905 100644
--- a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
@@ -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<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) {
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())) {
payNymProperty.set(null);
}
@@ -259,6 +265,17 @@ public class PaymentController extends WalletFormController implements Initializ
//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();
maxButton.setDisable(!isMaxButtonEnabled());
sendController.updateTransaction();
@@ -658,8 +675,11 @@ public class PaymentController extends WalletFormController implements Initializ
if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) {
Payment payment;
SilentPaymentAddress silentPaymentAddress = silentPaymentAddressProperty.get();
+ WalletNode consolidationNode = consolidationNodeProperty.get();
if(silentPaymentAddress != null) {
payment = new SilentPayment(silentPaymentAddress, label.getText(), value, sendAll);
+ } else if(consolidationNode != null) {
+ payment = new WalletNodePayment(consolidationNode, label.getText(), value, sendAll);
} else {
payment = new Payment(recipientAddress, label.getText(), value, sendAll);
}
@@ -718,6 +738,7 @@ public class PaymentController extends WalletFormController implements Initializ
setSendMax(false);
dustAmountProperty.set(false);
+ consolidationNodeProperty.set(null);
payNymProperty.set(null);
dnsPaymentProperty.set(null);
silentPaymentAddressProperty.set(null);
@@ -728,8 +749,7 @@ public class PaymentController extends WalletFormController implements Initializ
if(utxoSelector == null) {
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
sendController.utxoSelectorProperty().set(maxUtxoSelector);
- } else if(utxoSelector instanceof PresetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
- PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
+ } else if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true);
setPayment(payment);
return;
diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
index 21720f1..435b501 100644
--- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java
@@ -172,7 +172,7 @@ public class SendController extends WalletFormController implements Initializabl
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<>() {
@Override
@@ -619,7 +619,7 @@ public class SendController extends WalletFormController implements Initializabl
boolean allowRbf = (replacedTransaction == null || replacedTransaction.getTransaction().isReplaceByFee())
&& 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,
feeRate, getMinimumFeeRate(), minRelayFeeRate, userFee,
currentBlockHeight, groupByAddress, includeMempoolOutputs, replacedTransaction, allowRbf);
@@ -684,7 +684,6 @@ public class SendController extends WalletFormController implements Initializabl
}
private static class WalletTransactionService extends Service<WalletTransaction> {
- private final Map<Wallet, Map<Address, WalletNode>> addressNodeMap;
private final Wallet wallet;
private final List<UtxoSelector> utxoSelectors;
private final List<TxoFilter> txoFilters;
@@ -702,13 +701,11 @@ public class SendController extends WalletFormController implements Initializabl
private final boolean allowRbf;
private boolean ignoreResult;
- public WalletTransactionService(Map<Wallet, Map<Address, WalletNode>> addressNodeMap,
- Wallet wallet, List<UtxoSelector> utxoSelectors, List<TxoFilter> txoFilters,
+ public WalletTransactionService(Wallet wallet, List<UtxoSelector> utxoSelectors, List<TxoFilter> txoFilters,
List<Payment> payments, List<byte[]> opReturns, Set<WalletNode> excludedChangeNodes,
double feeRate, double longTermFeeRate, double minRelayFeeRate, Long fee,
Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs,
BlockTransaction replacedTransaction, boolean allowRbf) {
- this.addressNodeMap = addressNodeMap;
this.wallet = wallet;
this.utxoSelectors = utxoSelectors;
this.txoFilters = txoFilters;
@@ -759,11 +756,8 @@ public class SendController extends WalletFormController implements Initializabl
private WalletTransaction getWalletTransaction() throws InsufficientFundsException {
try {
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);
- updateMessage("Deriving keys...");
- walletTransaction.updateAddressNodeMap(addressNodeMap, walletTransaction.getWallet());
- return walletTransaction;
} finally {
updateMessage("");
}
@@ -1131,7 +1125,7 @@ public class SendController extends WalletFormController implements Initializabl
paymentCodeProperty.set(null);
- addressNodeMap.clear();
+ walletAddresses.clear();
}
public UtxoSelector getUtxoSelector() {
@@ -1209,13 +1203,20 @@ public class SendController extends WalletFormController implements Initializabl
WalletTransaction walletTransaction = walletTransactionProperty.get();
Set<WalletNode> nodes = new LinkedHashSet<>(walletTransaction.getSelectedUtxos().values());
nodes.addAll(walletTransaction.getChangeMap().keySet());
- Map<Address, WalletNode> addressNodeMap = walletTransaction.getAddressNodeMap();
- nodes.addAll(addressNodeMap.values().stream().filter(Objects::nonNull).collect(Collectors.toList()));
+ nodes.addAll(walletTransaction.getWalletNodePayments().stream().map(WalletNodePayment::getWalletNode).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
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) {
Wallet wallet = getWalletForm().getWallet();
Storage storage = AppServices.get().getOpenWallets().get(wallet);
@@ -1667,12 +1668,12 @@ public class SendController extends WalletFormController implements Initializabl
public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) {
List<Payment> payments = walletTransaction.getPayments();
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();
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
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 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);
if(optimizationStrategy == OptimizationStrategy.PRIVACY) {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.