diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index d32824dc0..91169f46a 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -205,9 +205,14 @@ class SendTab(QWidget, MessageBoxMixin, Logger): def on_amount_changed(self, text): # FIXME: implement full valid amount check to enable/disable Pay button - pi_valid = self.payto_e.payment_identifier.is_valid() if self.payto_e.payment_identifier else False - pi_error = self.payto_e.payment_identifier.is_error() if pi_valid else False - self.send_button.setEnabled(bool(self.amount_e.get_amount()) and pi_valid and not pi_error) + pi = self.payto_e.payment_identifier + if not pi: + self.send_button.setEnabled(False) + return + pi_error = pi.is_error() if pi.is_valid() else False + is_spk_script = pi.type == PaymentIdentifierType.SPK and not pi.spk_is_address + valid_amount = is_spk_script or bool(self.amount_e.get_amount()) + self.send_button.setEnabled(pi.is_valid() and not pi_error and valid_amount) def do_paste(self): self.logger.debug('do_paste') @@ -224,16 +229,20 @@ class SendTab(QWidget, MessageBoxMixin, Logger): self.show_error(_('Invalid payment identifier')) def spend_max(self): - if self.payto_e.payment_identifier is None: + pi = self.payto_e.payment_identifier + + if pi is None or pi.type == PaymentIdentifierType.UNKNOWN: return - assert self.payto_e.payment_identifier.type in [PaymentIdentifierType.SPK, PaymentIdentifierType.MULTILINE, - PaymentIdentifierType.BIP21, PaymentIdentifierType.OPENALIAS] - assert not self.payto_e.payment_identifier.is_amount_locked() + assert pi.type in [PaymentIdentifierType.SPK, PaymentIdentifierType.MULTILINE, + PaymentIdentifierType.BIP21, PaymentIdentifierType.OPENALIAS] + + if pi.type == PaymentIdentifierType.BIP21: + assert 'amount' not in pi.bip21 if run_hook('abort_send', self): return - outputs = self.payto_e.payment_identifier.get_onchain_outputs('!') + outputs = pi.get_onchain_outputs('!') if not outputs: return make_tx = lambda fee_est, *, confirmed_only=False: self.wallet.make_unsigned_transaction( @@ -446,10 +455,13 @@ class SendTab(QWidget, MessageBoxMixin, Logger): self.spend_max() pi_unusable = pi.is_error() or (not self.wallet.has_lightning() and not pi.is_onchain()) + is_spk_script = pi.type == PaymentIdentifierType.SPK and not pi.spk_is_address + + amount_valid = is_spk_script or bool(self.amount_e.get_amount()) - self.send_button.setEnabled(not pi_unusable and bool(self.amount_e.get_amount()) and not pi.has_expired()) - self.save_button.setEnabled(not pi_unusable and pi.type not in [PaymentIdentifierType.LNURLP, - PaymentIdentifierType.LNADDR]) + self.send_button.setEnabled(not pi_unusable and amount_valid and not pi.has_expired()) + self.save_button.setEnabled(not pi_unusable and not is_spk_script and \ + pi.type not in [PaymentIdentifierType.LNURLP, PaymentIdentifierType.LNADDR]) def _handle_payment_identifier(self): self.update_fields() @@ -479,11 +491,8 @@ class SendTab(QWidget, MessageBoxMixin, Logger): def read_invoice(self) -> Optional[Invoice]: if self.check_payto_line_and_show_errors(): return - amount_sat = self.read_amount() - if not amount_sat: - self.show_error(_('No amount')) - return + amount_sat = self.read_amount() invoice = invoice_from_payment_identifier( self.payto_e.payment_identifier, self.wallet, amount_sat, self.get_message()) if not invoice: @@ -551,15 +560,19 @@ class SendTab(QWidget, MessageBoxMixin, Logger): def do_edit_invoice(self, invoice: 'Invoice'): # FIXME broken assert not bool(invoice.get_amount_sat()) text = invoice.lightning_invoice if invoice.is_lightning() else invoice.get_address() - self.payto_e._on_input_btn(text) + self.set_payment_identifier(text) self.amount_e.setFocus() # disable save button, because it would create a new invoice self.save_button.setEnabled(False) def do_pay_invoice(self, invoice: 'Invoice'): if not bool(invoice.get_amount_sat()): - self.show_error(_('No amount')) - return + pi = self.payto_e.payment_identifier + if pi.type == PaymentIdentifierType.SPK and not pi.spk_is_address: + pass + else: + self.show_error(_('No amount')) + return if invoice.is_lightning(): self.pay_lightning_invoice(invoice) else: diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py index c95e47467..67b78a6ce 100644 --- a/electrum/payment_identifier.py +++ b/electrum/payment_identifier.py @@ -120,6 +120,7 @@ class PaymentIdentifier(Logger): self.bolt11 = None # type: Optional[Invoice] self.bip21 = None self.spk = None + self.spk_is_address = False # self.emaillike = None self.domainlike = None @@ -258,9 +259,11 @@ class PaymentIdentifier(Logger): except InvoiceError as e: self.logger.debug(self._get_error_from_invoiceerror(e)) self.set_state(PaymentIdentifierState.AVAILABLE) - elif scriptpubkey := self.parse_output(text): + elif self.parse_output(text)[0]: + scriptpubkey, is_address = self.parse_output(text) self._type = PaymentIdentifierType.SPK self.spk = scriptpubkey + self.spk_is_address = is_address self.set_state(PaymentIdentifierState.AVAILABLE) elif self.contacts and (contact := self.contacts.by_name(text)): if contact['type'] == 'address': @@ -464,7 +467,8 @@ class PaymentIdentifier(Logger): return [PartialTxOutput(scriptpubkey=self.spk, value=amount)] elif self.bip21: address = self.bip21.get('address') - scriptpubkey = self.parse_output(address) + scriptpubkey, is_address = self.parse_output(address) + assert is_address # unlikely, but make sure it is an address, not a script return [PartialTxOutput(scriptpubkey=scriptpubkey, value=amount)] else: raise Exception('not onchain') @@ -499,25 +503,25 @@ class PaymentIdentifier(Logger): x, y = line.split(',') except ValueError: raise Exception("expected two comma-separated values: (address, amount)") from None - scriptpubkey = self.parse_output(x) + scriptpubkey, is_address = self.parse_output(x) if not scriptpubkey: raise Exception('Invalid address') amount = self.parse_amount(y) return PartialTxOutput(scriptpubkey=scriptpubkey, value=amount) - def parse_output(self, x: str) -> bytes: + def parse_output(self, x: str) -> Tuple[bytes, bool]: try: address = self.parse_address(x) - return bytes.fromhex(bitcoin.address_to_script(address)) + return bytes.fromhex(bitcoin.address_to_script(address)), True except Exception as e: pass try: script = self.parse_script(x) - return bytes.fromhex(script) + return bytes.fromhex(script), False except Exception as e: pass - # raise Exception("Invalid address or script.") + return None, False def parse_script(self, x: str): script = ''