Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle zeroconf lightning requests in QT gui #9598

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions electrum/gui/qt/receive_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ def __init__(self, window: 'ElectrumWindow'):
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:
Expand All @@ -115,6 +117,7 @@ def on_receive_swap():
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)
Expand Down Expand Up @@ -236,26 +239,37 @@ def update_current_request(self):
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')
Expand Down Expand Up @@ -374,9 +388,12 @@ def update_visibility(self, is_qr):
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
Expand Down
42 changes: 34 additions & 8 deletions electrum/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@
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,
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
Fiat, bfh, TxMinedInfo, quantize_feerate, OrderedDictWithIndex)
from .lnutil import MIN_FUNDING_SAT
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
from .bitcoin import COIN, TYPE_ADDRESS
from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold
Expand Down Expand Up @@ -350,13 +352,17 @@ 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)

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?"
Expand Down Expand Up @@ -3278,12 +3284,19 @@ def get_help_texts_for_receive_request(self, req: Request) -> ReceiveRequestHelp
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:
Expand All @@ -3309,21 +3322,33 @@ def get_help_texts_for_receive_request(self, req: Request) -> ReceiveRequestHelp
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 {} sat '
'to purchase a Lightning channel from your service provider.')
.format(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
Expand All @@ -3337,6 +3362,7 @@ def get_help_texts_for_receive_request(self, req: Request) -> ReceiveRequestHelp
ln_is_error=ln_is_error,
ln_rebalance_suggestion=ln_rebalance_suggestion,
ln_swap_suggestion=ln_swap_suggestion,
ln_zeroconf_suggestion=ln_zeroconf_suggestion
)


Expand Down