always rebuild the send transaction when a human readable name is pasted over the pay to address
What changed, and why it matters
This commit fixes a bug in Sparrow Wallet's 'Send' screen. Previously, if you pasted a human-readable payment name (like a DNS payment alias) over an already-filled recipient address, the wallet sometimes kept the old transaction details instead of rebuilding it for the new name. The change ensures the transaction is always recalculated after a name is pasted, so the wallet doesn't accidentally prepare a payment to the wrong recipient while the name is still resolving.
Treat as a low-to-moderate reliability fix. Review related recipient-change handlers to ensure no other early-return paths skip transaction rebuilds. No immediate emergency response is warranted unless user reports of wrong-address transactions surface.
Security signals we found
UI state desynchronization between displayed recipient and constructed transaction
Early return bypassing transaction rebuild after recipient field change
Human-readable name resolution race/caching could leave stale recipient in unsigned transaction
No explicit cryptographic bug; risk is user confusion or wrong-address payment
Evidence from the diff
PaymentController.java previously returned early in several paths after a human-readable name (HRN) was pasted into the pay-to field (cached DNS payment found, user chose not to connect, or connection requested). These early returns prevented the subsequent transaction-rebuild logic from running, leaving a stale transaction referencing the prior recipient. The patch removes the early returns and restructures the DNS resolution branches so that, regardless of whether the name is cached, offline, or being resolved asynchronously, execution continues to the wallet-form update and transaction rebuild code below.
Changed components
src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.javaSend transaction construction flowDNS payment / human-readable name resolutionInspect captured patch +17 / −20
### src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
@@ -194,16 +194,15 @@ public void changed(ObservableValue<? extends String> observable, String oldValu
//ignore, not a URI
}
+ //A name is not a recipient until it has resolved, which it may never do. Whatever becomes of it, the transaction is rebuilt below, as the
+ //one already created is for the recipient the name has replaced
Optional<String> optDnsPaymentHrn = DnsPayment.getHrn(newValue);
if(optDnsPaymentHrn.isPresent()) {
String dnsPaymentHrn = optDnsPaymentHrn.get();
DnsPayment cachedDnsPayment = DnsPaymentCache.getDnsPayment(dnsPaymentHrn);
if(cachedDnsPayment != null) {
setDnsPayment(cachedDnsPayment);
- return;
- }
-
- if(Config.get().hasServer() && !AppServices.isConnected() && !AppServices.isConnecting()) {
+ } else if(Config.get().hasServer() && !AppServices.isConnected() && !AppServices.isConnecting()) {
if(Config.get().getConnectToResolve() == null || Config.get().getConnectToResolve() == Boolean.FALSE) {
Platform.runLater(() -> {
ConfirmationAlert confirmationAlert = new ConfirmationAlert("Connect to resolve?", "You are currently offline. Connect to resolve the address?", ButtonType.NO, ButtonType.YES);
@@ -218,23 +217,21 @@ public void changed(ObservableValue<? extends String> observable, String oldValu
} else {
Platform.runLater(() -> EventManager.get().post(new RequestConnectEvent()));
}
- return;
+ } else {
+ DnsPaymentService dnsPaymentService = new DnsPaymentService(dnsPaymentHrn);
+ dnsPaymentService.setOnSucceeded(_ -> {
+ if(isCurrentHrn(dnsPaymentHrn)) {
+ dnsPaymentService.getValue().ifPresent(dnsPayment -> setDnsPayment(dnsPayment));
+ }
+ });
+ dnsPaymentService.setOnFailed(failEvent -> {
+ Throwable exception = failEvent.getSource().getException();
+ if(isCurrentHrn(dnsPaymentHrn) && exception != null && !(exception.getCause() instanceof TimeoutException)) {
+ AppServices.showErrorDialog("Validation failed for " + dnsPaymentHrn, Throwables.getRootCause(exception).getMessage());
+ }
+ });
+ dnsPaymentService.start();
}
-
- DnsPaymentService dnsPaymentService = new DnsPaymentService(dnsPaymentHrn);
- dnsPaymentService.setOnSucceeded(_ -> {
- if(isCurrentHrn(dnsPaymentHrn)) {
- dnsPaymentService.getValue().ifPresent(dnsPayment -> setDnsPayment(dnsPayment));
- }
- });
- dnsPaymentService.setOnFailed(failEvent -> {
- Throwable exception = failEvent.getSource().getException();
- if(isCurrentHrn(dnsPaymentHrn) && exception != null && !(exception.getCause() instanceof TimeoutException)) {
- AppServices.showErrorDialog("Validation failed for " + dnsPaymentHrn, Throwables.getRootCause(exception).getMessage());
- }
- });
- dnsPaymentService.start();
- return;
}
if(sendController.getWalletForm().getWallet().hasPaymentCode()) {Why this scored 42/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.