qml: handle invoice validation errors on save
What changed, and why it matters
This commit fixes a bug in Electrum's mobile-style QML user interface where saving an invoice could fail silently. Previously, if the invoice data was invalid, the app would not show an error and might still close the dialog or continue as if the invoice was saved. Now the app checks whether saving succeeded, displays an error message if it failed, and stops the process. This is a user-experience and reliability fix rather than a high-severity security flaw.
No urgent action required. This is a routine bug fix. Users of the QML/Android build should update to a version containing this commit to benefit from clearer error handling when creating or saving invoices.
Security signals we found
Error handling improvement for invoice validation failures
Silent failure replaced with user-visible error dialog
Missing return-value checks added to prevent proceeding after failed save
Exception handling added for InvoiceError during amount override
Evidence from the diff
The patch modifies the QML invoice flow so that saveInvoice() returns a boolean success status. Callers in InvoiceDialog.qml and WalletMainView.qml now check the return value and abort navigation/finalization if saving failed. In qeinvoice.py, amount override logic is wrapped in a try/except for InvoiceError, emitting an invoiceCreateError signal with a validation code and returning False. A new user-visible message dialog replaces a silent console.log for invoice creation errors.
Changed components
electrum/gui/qml/components/InvoiceDialog.qmlelectrum/gui/qml/components/WalletMainView.qmlelectrum/gui/qml/qeinvoice.pyInspect captured patch +27 / −14
diff --git a/electrum/gui/qml/components/InvoiceDialog.qml b/electrum/gui/qml/components/InvoiceDialog.qml
index 80bb7a5..59590c6 100644
--- a/electrum/gui/qml/components/InvoiceDialog.qml
+++ b/electrum/gui/qml/components/InvoiceDialog.qml
@@ -470,9 +470,10 @@ ElDialog {
if (amountMax.checked)
invoice.amountOverride.isMax = true
}
- invoice.saveInvoice()
- app.stack.push(Qt.resolvedUrl('Invoices.qml'))
- dialog.close()
+ if (invoice.saveInvoice()) {
+ app.stack.push(Qt.resolvedUrl('Invoices.qml'))
+ dialog.close()
+ }
}
}
FlatButton {
diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml
index b044d51..d87bbd4 100644
--- a/electrum/gui/qml/components/WalletMainView.qml
+++ b/electrum/gui/qml/components/WalletMainView.qml
@@ -114,7 +114,8 @@ Item {
var canComplete = !Daemon.currentWallet.isWatchOnly && Daemon.currentWallet.canSignWithoutCosigner
dialog.accepted.connect(function() {
if (invoice.canSave)
- invoice.saveInvoice()
+ if (!invoice.saveInvoice())
+ return
if (!canComplete) {
if (Daemon.currentWallet.isWatchOnly) {
dialog.finalizer.saveOrShow()
@@ -413,7 +414,11 @@ Item {
dialog.open()
}
onInvoiceCreateError: (code, message) => {
- console.log(code + ' ' + message)
+ var msg = qsTr('Cannot save invoice') + ': ' + message
+ var dialog = app.messageDialog.createObject(app, {
+ text: msg
+ })
+ dialog.open()
}
onLnurlRetrieved: {
diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py
index 12b6d22..9e8d14a 100644
--- a/electrum/gui/qml/qeinvoice.py
+++ b/electrum/gui/qml/qeinvoice.py
@@ -23,6 +23,7 @@ from .qetypes import QEAmount
from .qewallet import QEWallet
from .util import status_update_timer_interval, QtEventListener, event_listener
from ...fee_policy import FeePolicy
+from ...util import InvoiceError
class QEInvoice(QObject, QtEventListener):
@@ -690,18 +691,22 @@ class QEInvoiceParser(QEInvoice):
self.recipient = invoice.lightning_invoice
- @pyqtSlot()
- def saveInvoice(self):
+ @pyqtSlot(result=bool)
+ def saveInvoice(self) -> bool:
if not self._effectiveInvoice:
- return
+ return False
if self.isSaved:
- return
+ return False
- if not self._effectiveInvoice.amount_msat and not self.amountOverride.isEmpty:
- if self.invoiceType == QEInvoice.Type.OnchainInvoice and self.amountOverride.isMax:
- self._effectiveInvoice.set_amount_msat('!')
- else:
- self._effectiveInvoice.set_amount_msat(self.amountOverride.satsInt * 1000)
+ try:
+ if not self._effectiveInvoice.amount_msat and not self.amountOverride.isEmpty:
+ if self.invoiceType == QEInvoice.Type.OnchainInvoice and self.amountOverride.isMax:
+ self._effectiveInvoice.set_amount_msat('!')
+ else:
+ self._effectiveInvoice.set_amount_msat(self.amountOverride.satsInt * 1000)
+ except InvoiceError as e:
+ self.invoiceCreateError.emit('validation', str(e))
+ return False
self.canSave = False
@@ -709,3 +714,5 @@ class QEInvoiceParser(QEInvoice):
self._key = self._effectiveInvoice.get_id()
self._wallet.invoiceModel.addInvoice(self._key)
self.invoiceSaved.emit(self._key)
+
+ return True
Why this scored 24/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.