diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 94c4bbd08..11a2025bd 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1268,11 +1268,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): def show_receive_request(self, req): addr = req.get_address() or '' - URI = req.get_bip21_URI() if addr else '' - lnaddr = req.lightning_invoice or '' can_receive_lightning = self.wallet.lnworker and req.get_amount_sat() <= self.wallet.lnworker.num_sats_can_receive() - if not can_receive_lightning: - lnaddr = '' + lnaddr = req.lightning_invoice if can_receive_lightning else None + bip21_lightning = lnaddr if self.config.get('bip21_lightning', False) else None + URI = req.get_bip21_URI(lightning=bip21_lightning) + lnaddr = lnaddr or '' icon_name = "lightning.png" if can_receive_lightning else "lightning_disconnected.png" self.receive_tabs.setTabIcon(2, read_QIcon(icon_name)) # encode lightning invoices as uppercase so QR encoding can use @@ -1673,7 +1673,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): choices = {} if can_pay_onchain: msg = ''.join([ - _('Pay this invoice onchain'), '\n', + _('Pay onchain'), '\n', _('Funds will be sent to the invoice fallback address.') ]) choices[0] = msg @@ -1694,7 +1694,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): msg = _('You cannot pay that invoice using Lightning.') if self.wallet.lnworker.channels: msg += '\n' + _('Your channels can send {}.').format(self.format_amount(num_sats_can_send) + self.base_unit()) - r = self.query_choice(msg, choices) if r is not None: self.save_pending_invoice() @@ -2162,6 +2161,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): amount = out.get('amount') label = out.get('label') message = out.get('message') + lightning = out.get('lightning') + if lightning: + self.set_ln_invoice(lightning) + return # use label as description (not BIP21 compliant) if label and not message: message = label diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py index 52dce5f79..c3fb5037c 100644 --- a/electrum/gui/qt/settings_dialog.py +++ b/electrum/gui/qt/settings_dialog.py @@ -97,6 +97,21 @@ class SettingsDialog(WindowModalDialog): self.window.refresh_tabs() nz.valueChanged.connect(on_nz) + # invoices + bolt11_fallback_cb = QCheckBox(_('Add on-chain fallback to lightning invoices')) + bolt11_fallback_cb.setChecked(bool(self.config.get('bolt11_fallback', True))) + bolt11_fallback_cb.setToolTip(_('Add fallback addresses to BOLT11 lightning invoices.')) + def on_bolt11_fallback(x): + self.config.set_key('bolt11_fallback', bool(x)) + bolt11_fallback_cb.stateChanged.connect(on_bolt11_fallback) + + bip21_lightning_cb = QCheckBox(_('Add lightning invoice to bitcoin URIs')) + bip21_lightning_cb.setChecked(bool(self.config.get('bip21_lightning', False))) + bip21_lightning_cb.setToolTip(_('This may create larger qr codes.')) + def on_bip21_lightning(x): + self.config.set_key('bip21_lightning', bool(x)) + bip21_lightning_cb.stateChanged.connect(on_bip21_lightning) + use_rbf = bool(self.config.get('use_rbf', True)) use_rbf_cb = QCheckBox(_('Use Replace-By-Fee')) use_rbf_cb.setChecked(use_rbf) @@ -483,6 +498,9 @@ class SettingsDialog(WindowModalDialog): gui_widgets.append((nz_label, nz)) gui_widgets.append((msat_cb, None)) gui_widgets.append((thousandsep_cb, None)) + invoices_widgets = [] + invoices_widgets.append((bolt11_fallback_cb, None)) + invoices_widgets.append((bip21_lightning_cb, None)) tx_widgets = [] tx_widgets.append((usechange_cb, None)) tx_widgets.append((use_rbf_cb, None)) @@ -513,6 +531,7 @@ class SettingsDialog(WindowModalDialog): tabs_info = [ (gui_widgets, _('Appearance')), + (invoices_widgets, _('Invoices')), (tx_widgets, _('Transactions')), (lightning_widgets, _('Lightning')), (fiat_widgets, _('Fiat')), diff --git a/electrum/invoices.py b/electrum/invoices.py index 4b769e88e..107487235 100644 --- a/electrum/invoices.py +++ b/electrum/invoices.py @@ -157,12 +157,21 @@ class Invoice(StoredObject): return None return int(amount_msat / 1000) - def get_bip21_URI(self): + def get_bip21_URI(self, lightning=None): from electrum.util import create_bip21_uri addr = self.get_address() amount = int(self.get_amount_sat()) message = self.message - uri = create_bip21_uri(addr, amount, message) + extra = {} + if self.time and self.exp: + extra['time'] = str(self.time) + extra['exp'] = str(self.exp) + # only if we can receive + if lightning: + extra['lightning'] = lightning + if not addr and lightning: + return "bitcoin:?lightning="+lightning + uri = create_bip21_uri(addr, amount, message, extra_query_params=extra) return str(uri) @lightning_invoice.validator diff --git a/electrum/util.py b/electrum/util.py index 19ebc462f..fb0657cb8 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -942,6 +942,7 @@ def parse_URI(uri: str, on_pr: Callable = None, *, loop=None) -> dict: """Raises InvalidBitcoinURI on malformed URI.""" from . import bitcoin from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC + from .lnaddr import lndecode if not isinstance(uri, str): raise InvalidBitcoinURI(f"expected string, not {repr(uri)}") @@ -1004,6 +1005,17 @@ def parse_URI(uri: str, on_pr: Callable = None, *, loop=None) -> dict: out['sig'] = bh2u(bitcoin.base_decode(out['sig'], base=58)) except Exception as e: raise InvalidBitcoinURI(f"failed to parse 'sig' field: {repr(e)}") from e + if 'lightning' in out: + try: + lnaddr = lndecode(out['lightning']) + amount_sat = out.get('amount') + if amount: + assert int(lnaddr.get_amount_sat()) == amount_sat + address = out.get('address') + if address: + assert lnaddr.get_fallback_address() == address + except Exception as e: + raise InvalidBitcoinURI(f"Inconsistent lightning field: {repr(e)}") from e r = out.get('r') sig = out.get('sig') diff --git a/electrum/wallet.py b/electrum/wallet.py index a8d21121f..7f56cc4b7 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -2348,7 +2348,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC): amount_sat = amount_sat or 0 exp_delay = exp_delay or 0 timestamp = int(time.time()) - lightning_invoice = self.lnworker.add_request(amount_sat, message, exp_delay) if lightning else None + fallback_address = address if self.config.get('bolt11_fallback', True) else None + lightning_invoice = self.lnworker.add_request(amount_sat, message, exp_delay, fallback_address) if lightning else None outputs = [ PartialTxOutput.from_address_and_value(address, amount_sat)] if address else [] height = self.get_local_height() req = Invoice(