clear the max selection and revalidate the amount when a payment uri sets the send amount
What changed, and why it matters
This commit fixes a UI bug in Sparrow Wallet's send screen. When a user clicked a Bitcoin payment link (a 'payment URI') that included a specific amount, the wallet could leave the 'send maximum' option turned on. That combination could lead to an incorrect or confusing transaction being prepared. The patch now clears the 'send maximum' setting and re-checks the amount whenever a payment URI supplies its own amount.
Review whether the same URI-driven amount update can affect other send controllers or coin-selection paths, and add regression tests for payment-URI handling when send-max is active. No urgent security deployment is indicated from the diff alone.
Security signals we found
Transaction amount/UI-state inconsistency in a Bitcoin wallet
Payment URI (BIP21) handling bypassing existing send-max reset logic
Potential for user-confirmed transaction with unintended total amount
Evidence from the diff
PaymentController.updateFromURI() previously set the amount fields from a BitcoinURI without first disabling the send-max state. If the user had previously selected ‘Send Max’ (MaxUtxoSelector), the URI-specified amount could coexist with a max-selection UI state, potentially causing inconsistent transaction construction or validation. The patch extracts the send-max clearing logic into clearSendMax(), calls it before setRecipientValueSats(), and invokes revalidateAmount() afterward. This is a correctness/UX fix in transaction preparation, not a cryptographic or network-layer vulnerability.
Changed components
src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.javaBitcoin URI parsing and send-amount populationSend-max (MaxUtxoSelector) UI stateInspect captured patch +14 / −8
### src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java
@@ -119,14 +119,7 @@ public class PaymentController extends WalletFormController implements Initializ
private final ChangeListener<String> amountListener = new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
- if(sendController.getUtxoSelector() instanceof MaxUtxoSelector) {
- sendController.utxoSelectorProperty().setValue(null);
- }
-
- for(Tab tab : sendController.getPaymentTabs().getTabs()) {
- PaymentController controller = (PaymentController) tab.getUserData();
- controller.setSendMax(false);
- }
+ clearSendMax();
Long recipientValueSats = getRecipientValueSats();
if(recipientValueSats != null) {
@@ -840,8 +833,10 @@ private void updateFromURI(BitcoinURI bitcoinURI) {
label.setText(bitcoinURI.getLabel());
}
if(bitcoinURI.getAmount() != null) {
+ clearSendMax();
setRecipientValueSats(bitcoinURI.getAmount());
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), bitcoinURI.getAmount());
+ revalidateAmount();
}
setPayjoinURI(bitcoinURI);
sendController.updateTransaction();
@@ -881,6 +876,17 @@ public boolean isSendMax() {
return maxButton.isSelected();
}
+ private void clearSendMax() {
+ if(sendController.getUtxoSelector() instanceof MaxUtxoSelector) {
+ sendController.utxoSelectorProperty().setValue(null);
+ }
+
+ for(Tab tab : sendController.getPaymentTabs().getTabs()) {
+ PaymentController controller = (PaymentController)tab.getUserData();
+ controller.setSendMax(false);
+ }
+ }
+
public void setSendMax(boolean sendMax) {
maxButton.setSelected(sendMax);
}Why this scored 32/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.