From 5134d07b147e30f46949c6bb9e32beeb2eb6c269 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 3 Mar 2025 12:35:21 +0100 Subject: [PATCH] add zeroconf handling to qt gui --- electrum/gui/qt/receive_tab.py | 25 ++++++++++++++++---- electrum/wallet.py | 42 +++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/electrum/gui/qt/receive_tab.py b/electrum/gui/qt/receive_tab.py index cd7d93903..eb0f4a8b7 100644 --- a/electrum/gui/qt/receive_tab.py +++ b/electrum/gui/qt/receive_tab.py @@ -98,6 +98,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.receive_help_text.setLayout(QHBoxLayout()) self.receive_rebalance_button = QPushButton('Rebalance') self.receive_rebalance_button.suggestion = None + self.receive_zeroconf_button = QPushButton(_('Accept')) + self.receive_zeroconf_button.clicked.connect(self.on_accept_zeroconf) def on_receive_rebalance(): if self.receive_rebalance_button.suggestion: @@ -115,6 +117,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): buttons = QHBoxLayout() buttons.addWidget(self.receive_rebalance_button) buttons.addWidget(self.receive_swap_button) + buttons.addWidget(self.receive_zeroconf_button) vbox = QVBoxLayout() vbox.addWidget(self.receive_help_text) vbox.addLayout(buttons) @@ -236,26 +239,37 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.ln_help = help_texts.ln_help can_rebalance = help_texts.can_rebalance() can_swap = help_texts.can_swap() + can_zeroconf = help_texts.can_zeroconf() self.receive_rebalance_button.suggestion = help_texts.ln_rebalance_suggestion self.receive_swap_button.suggestion = help_texts.ln_swap_suggestion self.receive_rebalance_button.setVisible(can_rebalance) self.receive_swap_button.setVisible(can_swap) self.receive_rebalance_button.setEnabled(can_rebalance and self.window.num_tasks() == 0) self.receive_swap_button.setEnabled(can_swap and self.window.num_tasks() == 0) + self.receive_zeroconf_button.setVisible(can_zeroconf) + self.receive_zeroconf_button.setEnabled(can_zeroconf) text, data, help_text, title = self.get_tab_data() self.receive_e.setText(text) self.receive_qr.setData(data) self.receive_help_text.setText(help_text) for w in [self.receive_e, self.receive_qr]: - w.setEnabled(bool(text) and not help_text) + w.setEnabled(bool(text) and (not help_text or can_zeroconf)) w.setToolTip(help_text) # macOS hack (similar to #4777) self.receive_e.repaint() # always show + if can_zeroconf: + # show the help message if zeroconf so user can first accept it and still sees the invoice + # after accepting + self.receive_widget.show_help() self.receive_widget.setVisible(True) self.toggle_qr_button.setEnabled(True) self.update_receive_qr_window() + def on_accept_zeroconf(self): + self.receive_zeroconf_button.setVisible(False) + self.update_receive_widgets() + def get_tab_data(self): if self.URI: out = self.URI, self.URI, self.URI_help, _('Bitcoin URI') @@ -374,9 +388,12 @@ class ReceiveWidget(QWidget): self.textedit.setVisible(not is_qr) self.qr.setVisible(is_qr) else: - self.help_widget.setVisible(True) - self.textedit.setVisible(False) - self.qr.setVisible(False) + self.show_help() + + def show_help(self): + self.help_widget.setVisible(True) + self.textedit.setVisible(False) + self.qr.setVisible(False) def resizeEvent(self, e): # keep square aspect ratio when resized diff --git a/electrum/wallet.py b/electrum/wallet.py index 9b72948c2..0ba02920b 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -53,6 +53,7 @@ from .i18n import _ from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_strpath_to_intpath from .crypto import sha256 from . import util +from .lntransport import extract_nodeid from .util import (NotEnoughFunds, UserCancelled, profiler, OldTaskGroup, ignore_exceptions, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates, WalletFileException, BitcoinException, @@ -60,6 +61,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler, OldTaskGroup, ignore Fiat, bfh, TxMinedInfo, quantize_feerate, OrderedDictWithIndex) from .simple_config import SimpleConfig from .fee_policy import FeePolicy, FixedFeePolicy, FeeMethod, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE +from .lnutil import MIN_FUNDING_SAT from .bitcoin import COIN, TYPE_ADDRESS from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold from .bitcoin import DummyAddress, DummyAddressUsedInTxException @@ -346,6 +348,7 @@ class ReceiveRequestHelp(NamedTuple): ln_swap_suggestion: Optional[Any] = None ln_rebalance_suggestion: Optional[Any] = None + ln_zeroconf_suggestion: bool = False def can_swap(self) -> bool: return bool(self.ln_swap_suggestion) @@ -353,6 +356,9 @@ class ReceiveRequestHelp(NamedTuple): def can_rebalance(self) -> bool: return bool(self.ln_rebalance_suggestion) + def can_zeroconf(self) -> bool: + return self.ln_zeroconf_suggestion + class TxWalletDelta(NamedTuple): is_relevant: bool # "related to wallet?" @@ -3250,12 +3256,19 @@ class Abstract_Wallet(ABC, Logger, EventListener): ln_is_error = False ln_swap_suggestion = None ln_rebalance_suggestion = None + ln_zeroconf_suggestion = False URI = self.get_request_URI(req) or '' lightning_has_channels = ( self.lnworker and len([chan for chan in self.lnworker.channels.values() if chan.is_open()]) > 0 ) lightning_online = self.lnworker and self.lnworker.num_peers() > 0 can_receive_lightning = self.lnworker and amount_sat <= self.lnworker.num_sats_can_receive() + try: + zeroconf_nodeid = extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0] + except Exception: + zeroconf_nodeid = None + can_get_zeroconf_channel = (self.lnworker and self.config.ACCEPT_ZEROCONF_CHANNELS + and zeroconf_nodeid in self.lnworker.peers) status = self.get_invoice_status(req) if status == PR_EXPIRED: @@ -3281,21 +3294,33 @@ class Abstract_Wallet(ABC, Logger, EventListener): address_help = URI_help = (_("This address has already been used. " "For better privacy, do not reuse it for new payments.")) if req.is_lightning(): - if not lightning_has_channels: + if not lightning_has_channels and not can_get_zeroconf_channel: ln_is_error = True ln_help = _("You must have an open Lightning channel to receive payments.") elif not lightning_online: ln_is_error = True ln_help = _('You must be online to receive Lightning payments.') - elif not can_receive_lightning: - ln_is_error = True + elif not can_receive_lightning or (amount_sat <= 0 and not lightning_has_channels): ln_rebalance_suggestion = self.lnworker.suggest_rebalance_to_receive(amount_sat) ln_swap_suggestion = self.lnworker.suggest_swap_to_receive(amount_sat) - ln_help = _('You do not have the capacity to receive this amount with Lightning.') - if bool(ln_rebalance_suggestion): - ln_help += '\n\n' + _('You may have that capacity if you rebalance your channels.') - elif bool(ln_swap_suggestion): - ln_help += '\n\n' + _('You may have that capacity if you swap some of your funds.') + # prefer to use swaps over JIT channels if possible + if can_get_zeroconf_channel and not bool(ln_rebalance_suggestion) and not bool(ln_swap_suggestion): + if amount_sat < MIN_FUNDING_SAT: + ln_is_error = True + ln_help = (_('Cannot receive this payment. Request at least {} ' + 'to purchase a Lightning channel from your service provider.') + .format(self.config.format_amount_and_units(amount_sat=MIN_FUNDING_SAT))) + else: + ln_zeroconf_suggestion = True + ln_help = _(f'Receiving this payment will purchase a payment channel from your ' + f'service provider. Service fees are deducted from the incoming payment.') + else: + ln_is_error = True + ln_help = _('You do not have the capacity to receive this amount with Lightning.') + if bool(ln_rebalance_suggestion): + ln_help += '\n\n' + _('You may have that capacity if you rebalance your channels.') + elif bool(ln_swap_suggestion): + ln_help += '\n\n' + _('You may have that capacity if you swap some of your funds.') # for URI that has LN part but no onchain part, copy error: if not addr and ln_is_error: URI_is_error = ln_is_error @@ -3309,6 +3334,7 @@ class Abstract_Wallet(ABC, Logger, EventListener): ln_is_error=ln_is_error, ln_rebalance_suggestion=ln_rebalance_suggestion, ln_swap_suggestion=ln_swap_suggestion, + ln_zeroconf_suggestion=ln_zeroconf_suggestion )