From 8b54f6445cd5682dff5f58372b0a45d9b10a683d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 7 Apr 2025 13:08:05 +0200 Subject: [PATCH] qml: when opening channel, validate and show user feedback if amount outside acceptable range --- .../gui/qml/components/OpenChannelDialog.qml | 9 +-- electrum/gui/qml/qechannelopener.py | 60 ++++++++++++++----- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml index dfb056dbe..a7849aeb9 100644 --- a/electrum/gui/qml/components/OpenChannelDialog.qml +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -201,7 +201,6 @@ ElDialog { if (activeFocus) { channelopener.amount.isMax = checked if (checked) { - maxAmountMessage.text = '' channelopener.updateMaxAmount() } } @@ -232,11 +231,12 @@ ElDialog { Item { visible: Daemon.fx.enabled ; height: 1; width: 1 } InfoTextArea { + id: warning Layout.topMargin: constants.paddingMedium Layout.fillWidth: true Layout.columnSpan: 3 - id: maxAmountMessage - visible: is_max.checked && text + text: channelopener.warning + visible: text compact: true } @@ -321,9 +321,6 @@ ElDialog { // TODO: handle incomplete TX root.close() } - onMaxAmountMessage: (message) => { - maxAmountMessage.text = message - } } FontMetrics { diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index 67f12a591..9d84eb5cb 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -9,6 +9,7 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.i18n import _ from electrum.gui import messages from electrum.util import bfh +from electrum.lnutil import MIN_FUNDING_SAT from electrum.lntransport import extract_nodeid, ConnStringFormatError from electrum.bitcoin import DummyAddress from electrum.lnworker import hardcoded_trampoline_nodes @@ -31,7 +32,6 @@ class QEChannelOpener(QObject, AuthMixin): channelOpenError = pyqtSignal([str], arguments=['message']) channelOpenSuccess = pyqtSignal([str, bool, int, bool], arguments=['cid', 'has_onchain_backup', 'min_depth', 'tx_complete']) - maxAmountMessage = pyqtSignal([str], arguments=['message']) dataChanged = pyqtSignal() # generic notify signal @@ -44,6 +44,8 @@ class QEChannelOpener(QObject, AuthMixin): self._valid = False self._opentx = None self._txdetails = None + self._warning = '' + self._determine_max_message = None self._finalizer = None self._node_pubkey = None @@ -92,6 +94,21 @@ class QEChannelOpener(QObject, AuthMixin): def valid(self): return self._valid + def setValid(self, is_valid): + if self._valid != is_valid: + self._valid = is_valid + self.validChanged.emit() + + warningChanged = pyqtSignal() + @pyqtProperty(str, notify=warningChanged) + def warning(self): + return self._warning + + def setWarning(self, warning): + if self._warning != warning: + self._warning = warning + self.warningChanged.emit() + finalizerChanged = pyqtSignal() @pyqtProperty(QETxFinalizer, notify=finalizerChanged) def finalizer(self): @@ -106,10 +123,9 @@ class QEChannelOpener(QObject, AuthMixin): def trampolineNodeNames(self): return list(hardcoded_trampoline_nodes().keys()) - # FIXME min channel funding amount # FIXME have requested funding amount def validate(self): - """side-effects: sets self._valid, self._node_pubkey, self._connect_str_resolved""" + """side-effects: sets self._node_pubkey, self._connect_str_resolved""" connect_str_valid = False if self._connect_str: self._logger.debug(f'checking if {self._connect_str=!r} is valid') @@ -129,19 +145,36 @@ class QEChannelOpener(QObject, AuthMixin): self._connect_str_resolved = self._connect_str connect_str_valid = True + self.setWarning('') + if not connect_str_valid: - self._valid = False - self.validChanged.emit() + self.setValid(False) return self._logger.debug(f'amount={self._amount}') if not self._amount or not (self._amount.satsInt > 0 or self._amount.isMax): - self._valid = False - self.validChanged.emit() + self.setValid(False) return - self._valid = True - self.validChanged.emit() + # for MAX, estimate is assumed to be calculated and set in self._amount.satsInt + if self._amount.satsInt < MIN_FUNDING_SAT: + message = _('Minimum required amount: {}').format( + self._wallet.wallet.config.format_amount_and_units(MIN_FUNDING_SAT) + ) + if self._amount.isMax and self._determine_max_message: + message += '\n' + self._determine_max_message + self.setWarning(message) + self.setValid(False) + return + + if self._amount.satsInt > self._wallet.wallet.config.LIGHTNING_MAX_FUNDING_SAT: + self.setWarning(_('Amount is above maximum channel size: {}').format( + self._wallet.wallet.config.format_amount_and_units(self._wallet.wallet.config.LIGHTNING_MAX_FUNDING_SAT) + )) + self.setValid(False) + return + + self.setValid(True) @pyqtSlot(str, result=bool) def validateConnectString(self, connect_str): @@ -252,13 +285,8 @@ class QEChannelOpener(QObject, AuthMixin): node_id=dummy_nodeid, fee_policy=fee_policy) - amount, message = self._wallet.determine_max(mktx=make_tx) - if amount is None: - self._amount.isMax = False - else: - self._amount.satsInt = amount - if message: - self.maxAmountMessage.emit(message) + amount, self._determine_max_message = self._wallet.determine_max(mktx=make_tx) + self._amount.satsInt = amount if amount else 0 finally: self._updating_max = False self.validate()