Browse Source

qml: refactor qeinvoice.py

QEInvoice/QEInvoiceParser now properly split for mapping to Invoice type (QEInvoice)
and parsing/resolving of payment identifiers (QEInvoiceParser).
additionally, old, unused QEUserEnteredPayment was removed.

invoices are now never saved with user-entered amount if the original invoice
did not specify an amount (e.g. address-only, no-amount bip21 uri, or no-amount
lightning invoice). Furthermore, QEInvoice now adds an isSaved property so the
UI doesn't need to infer that from the existence of the invoice key.

Payments of lightning invoices are now triggered through QEInvoice.pay_lightning_invoice(),
using the internally kept Invoice instance. This replaces the old call path of
QEWallet.pay_lightning_invoice(invoice_key) which required the invoice to be saved
in the backend wallet before payment.

The LNURLpay flow arriving on InvoiceDialog implicitly triggered payment, this is
now indicated by InvoiceDialog.payImmediately property instead of inferrred from the
QEInvoiceParser isLnurlPay property.
master
Sander van Grieken 3 years ago
parent
commit
6c65161d27
  1. 27
      electrum/gui/qml/components/InvoiceDialog.qml
  2. 28
      electrum/gui/qml/components/WalletMainView.qml
  3. 3
      electrum/gui/qml/qeapp.py
  4. 454
      electrum/gui/qml/qeinvoice.py
  5. 10
      electrum/gui/qml/qewallet.py

27
electrum/gui/qml/components/InvoiceDialog.qml

@ -11,7 +11,7 @@ ElDialog {
id: dialog id: dialog
property Invoice invoice property Invoice invoice
property string invoice_key property bool payImmediately: false
signal doPay signal doPay
signal invoiceAmountChanged signal invoiceAmountChanged
@ -392,13 +392,13 @@ ElDialog {
Layout.preferredWidth: 1 Layout.preferredWidth: 1
text: qsTr('Save') text: qsTr('Save')
icon.source: '../../icons/save.png' icon.source: '../../icons/save.png'
enabled: invoice_key == '' && invoice.canSave enabled: !invoice.isSaved && invoice.canSave
onClicked: { onClicked: {
app.stack.push(Qt.resolvedUrl('Invoices.qml'))
if (invoice.amount.isEmpty) { if (invoice.amount.isEmpty) {
invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text) invoice.amountOverride = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
} }
invoice.save_invoice() invoice.save_invoice()
app.stack.push(Qt.resolvedUrl('Invoices.qml'))
dialog.close() dialog.close()
} }
} }
@ -410,15 +410,10 @@ ElDialog {
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
onClicked: { onClicked: {
if (invoice.amount.isEmpty) { if (invoice.amount.isEmpty) {
invoice.amount = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text) invoice.amountOverride = amountMax.checked ? MAX : Config.unitsToSats(amountBtc.text)
if (invoice_key != '') {
// delete the existing invoice because this affects get_id()
invoice.wallet.delete_invoice(invoice_key)
invoice_key = ''
}
} }
if (invoice_key == '') { if (!invoice.isSaved) {
// save invoice if new or modified // save invoice if newly parsed
invoice.save_invoice() invoice.save_invoice()
} }
doPay() // only signal here doPay() // only signal here
@ -429,18 +424,14 @@ ElDialog {
} }
Component.onCompleted: { Component.onCompleted: {
if (invoice_key != '') {
invoice.initFromKey(invoice_key)
}
if (invoice.amount.isEmpty && !invoice.status == Invoice.Expired) { if (invoice.amount.isEmpty && !invoice.status == Invoice.Expired) {
amountContainer.editmode = true amountContainer.editmode = true
} else if (invoice.amount.isMax) { } else if (invoice.amount.isMax) {
amountMax.checked = true amountMax.checked = true
} }
if (invoice.isLnurlPay) { if (payImmediately) {
// we arrive from a lnurl-pay confirm dialog where the user already indicated the intent to pay.
if (invoice.canPay) { if (invoice.canPay) {
if (invoice_key == '') { if (!invoice.isSaved) {
invoice.save_invoice() invoice.save_invoice()
} }
doPay() doPay()

28
electrum/gui/qml/components/WalletMainView.qml

@ -21,7 +21,8 @@ Item {
property string _request_expiry property string _request_expiry
function openInvoice(key) { function openInvoice(key) {
var dialog = invoiceDialog.createObject(app, { invoice: invoiceParser, invoice_key: key }) invoice.key = key
var dialog = invoiceDialog.createObject(app, { invoice: invoice })
dialog.open() dialog.open()
return dialog return dialog
} }
@ -195,6 +196,7 @@ Item {
Config.userKnowsPressAndHold = true Config.userKnowsPressAndHold = true
Daemon.currentWallet.delete_expired_requests() Daemon.currentWallet.delete_expired_requests()
app.stack.push(Qt.resolvedUrl('ReceiveRequests.qml')) app.stack.push(Qt.resolvedUrl('ReceiveRequests.qml'))
AppController.haptic()
} }
} }
FlatButton { FlatButton {
@ -207,11 +209,17 @@ Item {
onPressAndHold: { onPressAndHold: {
Config.userKnowsPressAndHold = true Config.userKnowsPressAndHold = true
app.stack.push(Qt.resolvedUrl('Invoices.qml')) app.stack.push(Qt.resolvedUrl('Invoices.qml'))
AppController.haptic()
} }
} }
} }
} }
Invoice {
id: invoice
wallet: Daemon.currentWallet
}
InvoiceParser { InvoiceParser {
id: invoiceParser id: invoiceParser
wallet: Daemon.currentWallet wallet: Daemon.currentWallet
@ -232,7 +240,7 @@ Item {
} }
onValidationSuccess: { onValidationSuccess: {
closeSendDialog() closeSendDialog()
var dialog = invoiceDialog.createObject(app, { invoice: invoiceParser }) var dialog = invoiceDialog.createObject(app, { invoice: invoiceParser, payImmediately: invoiceParser.isLnurlPay })
dialog.open() dialog.open()
} }
onInvoiceCreateError: console.log(code + ' ' + message) onInvoiceCreateError: console.log(code + ' ' + message)
@ -307,10 +315,16 @@ Item {
height: parent.height height: parent.height
onDoPay: { onDoPay: {
if (invoice.invoiceType == Invoice.OnchainInvoice || (invoice.invoiceType == Invoice.LightningInvoice && invoice.amount.satsInt > Daemon.currentWallet.lightningCanSend ) ) { if (invoice.invoiceType == Invoice.OnchainInvoice
|| (invoice.invoiceType == Invoice.LightningInvoice
&& invoice.amountOverride.isEmpty
? invoice.amount.satsInt > Daemon.currentWallet.lightningCanSend
: invoice.amountOverride.satsInt > Daemon.currentWallet.lightningCanSend
))
{
var dialog = confirmPaymentDialog.createObject(mainView, { var dialog = confirmPaymentDialog.createObject(mainView, {
address: invoice.address, address: invoice.address,
satoshis: invoice.amount, satoshis: invoice.amountOverride.isEmpty ? invoice.amount : invoice.amountOverride,
message: invoice.message message: invoice.message
}) })
var canComplete = !Daemon.currentWallet.isWatchOnly && Daemon.currentWallet.canSignWithoutCosigner var canComplete = !Daemon.currentWallet.isWatchOnly && Daemon.currentWallet.canSignWithoutCosigner
@ -328,11 +342,7 @@ Item {
dialog.open() dialog.open()
} else if (invoice.invoiceType == Invoice.LightningInvoice) { } else if (invoice.invoiceType == Invoice.LightningInvoice) {
console.log('About to pay lightning invoice') console.log('About to pay lightning invoice')
if (invoice.key == '') { invoice.pay_lightning_invoice()
console.log('No invoice key, aborting')
return
}
Daemon.currentWallet.pay_lightning_invoice(invoice.key)
} }
} }

3
electrum/gui/qml/qeapp.py

@ -29,7 +29,7 @@ from .qewalletdb import QEWalletDB
from .qebitcoin import QEBitcoin from .qebitcoin import QEBitcoin
from .qefx import QEFX from .qefx import QEFX
from .qetxfinalizer import QETxFinalizer, QETxRbfFeeBumper, QETxCpfpFeeBumper, QETxCanceller from .qetxfinalizer import QETxFinalizer, QETxRbfFeeBumper, QETxCpfpFeeBumper, QETxCanceller
from .qeinvoice import QEInvoice, QEInvoiceParser, QEUserEnteredPayment from .qeinvoice import QEInvoice, QEInvoiceParser
from .qerequestdetails import QERequestDetails from .qerequestdetails import QERequestDetails
from .qetypes import QEAmount from .qetypes import QEAmount
from .qeaddressdetails import QEAddressDetails from .qeaddressdetails import QEAddressDetails
@ -315,7 +315,6 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer') qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice') qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser') qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser')
qmlRegisterType(QEUserEnteredPayment, 'org.electrum', 1, 0, 'UserEnteredPayment')
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails') qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener') qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')

454
electrum/gui/qml/qeinvoice.py

@ -26,7 +26,7 @@ from .qewallet import QEWallet
from .util import status_update_timer_interval, QtEventListener, event_listener from .util import status_update_timer_interval, QtEventListener, event_listener
class QEInvoice(QObject): class QEInvoice(QObject, QtEventListener):
class Type: class Type:
Invalid = -1 Invalid = -1
OnchainInvoice = 0 OnchainInvoice = 0
@ -48,118 +48,31 @@ class QEInvoice(QObject):
_logger = get_logger(__name__) _logger = get_logger(__name__)
def __init__(self, parent=None):
super().__init__(parent)
self._wallet = None # type: Optional[QEWallet]
self._canSave = False
self._canPay = False
self._key = None
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
return self._wallet
@wallet.setter
def wallet(self, wallet: QEWallet):
if self._wallet != wallet:
self._wallet = wallet
self.walletChanged.emit()
canSaveChanged = pyqtSignal()
@pyqtProperty(bool, notify=canSaveChanged)
def canSave(self):
return self._canSave
@canSave.setter
def canSave(self, canSave):
if self._canSave != canSave:
self._canSave = canSave
self.canSaveChanged.emit()
canPayChanged = pyqtSignal()
@pyqtProperty(bool, notify=canPayChanged)
def canPay(self):
return self._canPay
@canPay.setter
def canPay(self, canPay):
if self._canPay != canPay:
self._canPay = canPay
self.canPayChanged.emit()
keyChanged = pyqtSignal()
@pyqtProperty(str, notify=keyChanged)
def key(self):
return self._key
@key.setter
def key(self, key):
if self._key != key:
self._key = key
self.keyChanged.emit()
userinfoChanged = pyqtSignal()
@pyqtProperty(str, notify=userinfoChanged)
def userinfo(self):
return self._userinfo
@userinfo.setter
def userinfo(self, userinfo):
if self._userinfo != userinfo:
self._userinfo = userinfo
self.userinfoChanged.emit()
def get_max_spendable_onchain(self):
spendable = self._wallet.confirmedBalance.satsInt
if not self._wallet.wallet.config.get('confirmed_only', False):
spendable += self._wallet.unconfirmedBalance.satsInt
return spendable
def get_max_spendable_lightning(self):
return self._wallet.wallet.lnworker.num_sats_can_send() if self._wallet.wallet.lnworker else 0
class QEInvoiceParser(QEInvoice, QtEventListener):
_logger = get_logger(__name__)
invoiceChanged = pyqtSignal() invoiceChanged = pyqtSignal()
invoiceSaved = pyqtSignal([str], arguments=['key']) invoiceSaved = pyqtSignal([str], arguments=['key'])
validationSuccess = pyqtSignal()
validationWarning = pyqtSignal([str,str], arguments=['code', 'message'])
validationError = pyqtSignal([str,str], arguments=['code', 'message'])
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
lnurlRetrieved = pyqtSignal()
lnurlError = pyqtSignal([str,str], arguments=['code', 'message'])
amountOverrideChanged = pyqtSignal() amountOverrideChanged = pyqtSignal()
_bip70PrResolvedSignal = pyqtSignal([PaymentRequest], arguments=['pr'])
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self._wallet = None # type: Optional[QEWallet]
self._isSaved = False
self._canSave = False
self._canPay = False
self._key = None
self._invoiceType = QEInvoice.Type.Invalid self._invoiceType = QEInvoice.Type.Invalid
self._recipient = ''
self._effectiveInvoice = None self._effectiveInvoice = None
self._amount = QEAmount()
self._userinfo = '' self._userinfo = ''
self._lnprops = {} self._lnprops = {}
self._amount = QEAmount()
self._amountOverride = QEAmount()
self._timer = QTimer(self) self._timer = QTimer(self)
self._timer.setSingleShot(True) self._timer.setSingleShot(True)
self._timer.timeout.connect(self.updateStatusString) self._timer.timeout.connect(self.updateStatusString)
self._amountOverride = QEAmount()
self._amountOverride.valueChanged.connect(self._on_amountoverride_value_changed) self._amountOverride.valueChanged.connect(self._on_amountoverride_value_changed)
self._bip70PrResolvedSignal.connect(self._bip70_payment_request_resolved)
self.clear()
self.register_callbacks() self.register_callbacks()
self.destroyed.connect(lambda: self.on_destroy()) self.destroyed.connect(lambda: self.on_destroy())
@ -188,39 +101,40 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
self.determine_can_pay() self.determine_can_pay()
self.userinfo = _('In progress...') self.userinfo = _('In progress...')
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self):
return self._wallet
@wallet.setter
def wallet(self, wallet: QEWallet):
if self._wallet != wallet:
self._wallet = wallet
self.walletChanged.emit()
@pyqtProperty(int, notify=invoiceChanged) @pyqtProperty(int, notify=invoiceChanged)
def invoiceType(self): def invoiceType(self):
return self._invoiceType return self._invoiceType
# not a qt setter, don't let outside set state # not a qt setter, don't let outside set state
def setInvoiceType(self, invoiceType: QEInvoice.Type): def setInvoiceType(self, invoiceType: Type):
self._invoiceType = invoiceType self._invoiceType = invoiceType
recipientChanged = pyqtSignal() @pyqtProperty(str, notify=invoiceChanged)
@pyqtProperty(str, notify=recipientChanged) def message(self):
def recipient(self): return self._effectiveInvoice.message if self._effectiveInvoice else ''
return self._recipient
@recipient.setter
def recipient(self, recipient: str):
self.canPay = False
self._recipient = recipient
self.amountOverride = QEAmount()
if recipient:
self.validateRecipient(recipient)
self.recipientChanged.emit()
@pyqtProperty('QVariantMap', notify=lnurlRetrieved) @pyqtProperty('quint64', notify=invoiceChanged)
def lnurlData(self): def time(self):
return self._lnurlData return self._effectiveInvoice.time if self._effectiveInvoice else 0
@pyqtProperty(bool, notify=lnurlRetrieved) @pyqtProperty('quint64', notify=invoiceChanged)
def isLnurlPay(self): def expiration(self):
return self._lnurlData is not None return self._effectiveInvoice.exp if self._effectiveInvoice else 0
@pyqtProperty(str, notify=invoiceChanged) @pyqtProperty(str, notify=invoiceChanged)
def message(self): def address(self):
return self._effectiveInvoice.message if self._effectiveInvoice else '' return self._effectiveInvoice.get_address() if self._effectiveInvoice else ''
@pyqtProperty(QEAmount, notify=invoiceChanged) @pyqtProperty(QEAmount, notify=invoiceChanged)
def amount(self): def amount(self):
@ -230,16 +144,6 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
self._amount.copyFrom(QEAmount(from_invoice=self._effectiveInvoice)) self._amount.copyFrom(QEAmount(from_invoice=self._effectiveInvoice))
return self._amount return self._amount
@amount.setter
def amount(self, new_amount):
self._logger.debug(f'set new amount {repr(new_amount)}')
if self._effectiveInvoice:
self._effectiveInvoice.amount_msat = '!' if new_amount.isMax else int(new_amount.satsInt * 1000)
self.update_userinfo()
self.determine_can_pay()
self.invoiceChanged.emit()
@pyqtProperty(QEAmount, notify=amountOverrideChanged) @pyqtProperty(QEAmount, notify=amountOverrideChanged)
def amountOverride(self): def amountOverride(self):
return self._amountOverride return self._amountOverride
@ -255,14 +159,6 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
self.update_userinfo() self.update_userinfo()
self.determine_can_pay() self.determine_can_pay()
@pyqtProperty('quint64', notify=invoiceChanged)
def time(self):
return self._effectiveInvoice.time if self._effectiveInvoice else 0
@pyqtProperty('quint64', notify=invoiceChanged)
def expiration(self):
return self._effectiveInvoice.exp if self._effectiveInvoice else 0
statusChanged = pyqtSignal() statusChanged = pyqtSignal()
@pyqtProperty(int, notify=statusChanged) @pyqtProperty(int, notify=statusChanged)
def status(self): def status(self):
@ -277,9 +173,59 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
status = self._wallet.wallet.get_invoice_status(self._effectiveInvoice) status = self._wallet.wallet.get_invoice_status(self._effectiveInvoice)
return self._effectiveInvoice.get_status_str(status) return self._effectiveInvoice.get_status_str(status)
@pyqtProperty(str, notify=invoiceChanged) isSavedChanged = pyqtSignal()
def address(self): @pyqtProperty(bool, notify=isSavedChanged)
return self._effectiveInvoice.get_address() if self._effectiveInvoice else '' def isSaved(self):
return self._isSaved
canSaveChanged = pyqtSignal()
@pyqtProperty(bool, notify=canSaveChanged)
def canSave(self):
return self._canSave
@canSave.setter
def canSave(self, canSave):
if self._canSave != canSave:
self._canSave = canSave
self.canSaveChanged.emit()
canPayChanged = pyqtSignal()
@pyqtProperty(bool, notify=canPayChanged)
def canPay(self):
return self._canPay
@canPay.setter
def canPay(self, canPay):
if self._canPay != canPay:
self._canPay = canPay
self.canPayChanged.emit()
keyChanged = pyqtSignal()
@pyqtProperty(str, notify=keyChanged)
def key(self):
return self._key
@key.setter
def key(self, key):
if self._key != key:
self._key = key
if self._effectiveInvoice and self._effectiveInvoice.get_id() == key:
return
invoice = self._wallet.wallet.get_invoice(key)
self._logger.debug(f'invoice from key {key}: {repr(invoice)}')
self.set_effective_invoice(invoice)
self.keyChanged.emit()
userinfoChanged = pyqtSignal()
@pyqtProperty(str, notify=userinfoChanged)
def userinfo(self):
return self._userinfo
@userinfo.setter
def userinfo(self, userinfo):
if self._userinfo != userinfo:
self._userinfo = userinfo
self.userinfoChanged.emit()
@pyqtProperty('QVariantMap', notify=invoiceChanged) @pyqtProperty('QVariantMap', notify=invoiceChanged)
def lnprops(self): def lnprops(self):
@ -304,38 +250,19 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
} }
def name_for_node_id(self, node_id): def name_for_node_id(self, node_id):
node_alias = self._wallet.wallet.lnworker.get_node_alias(node_id) or node_id.hex() return self._wallet.wallet.lnworker.get_node_alias(node_id) or node_id.hex()
return node_alias
@pyqtSlot()
def clear(self):
self.recipient = ''
self.setInvoiceType(QEInvoice.Type.Invalid)
self._bip21 = None
self._lnurlData = None
self.canSave = False
self.canPay = False
self.userinfo = ''
self.invoiceChanged.emit()
# don't parse the recipient string, but init qeinvoice from an invoice key
# this should not emit validation signals
@pyqtSlot(str)
def initFromKey(self, key):
self.clear()
invoice = self._wallet.wallet.get_invoice(key)
self._logger.debug(repr(invoice))
if invoice:
self.set_effective_invoice(invoice)
self.key = key
def set_effective_invoice(self, invoice: Invoice): def set_effective_invoice(self, invoice: Invoice):
self._effectiveInvoice = invoice self._effectiveInvoice = invoice
if invoice.is_lightning(): if invoice is None:
self.setInvoiceType(QEInvoice.Type.LightningInvoice) self.setInvoiceType(QEInvoice.Type.Invalid)
else: else:
self.setInvoiceType(QEInvoice.Type.OnchainInvoice) if invoice.is_lightning():
self.setInvoiceType(QEInvoice.Type.LightningInvoice)
else:
self.setInvoiceType(QEInvoice.Type.OnchainInvoice)
self._isSaved = self._wallet.wallet.get_invoice(invoice.get_id()) is not None
self.set_lnprops() self.set_lnprops()
@ -344,6 +271,7 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
self.invoiceChanged.emit() self.invoiceChanged.emit()
self.statusChanged.emit() self.statusChanged.emit()
self.isSavedChanged.emit()
self.set_status_timer() self.set_status_timer()
@ -440,6 +368,86 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
# TODO: subtract fee? # TODO: subtract fee?
self.canPay = True self.canPay = True
@pyqtSlot()
def pay_lightning_invoice(self):
if not self.canPay:
raise Exception('can not pay invoice, canPay is false')
if self.invoiceType != QEInvoice.Type.LightningInvoice:
raise Exception('pay_lightning_invoice can only pay lightning invoices')
if self.amount.isEmpty:
if self.amountOverride.isEmpty:
raise Exception('can not pay 0 amount')
# TODO: is update amount_msat for overrideAmount sufficient?
self._effectiveInvoice.amount_msat = self.amountOverride.satsInt * 1000
self._wallet.pay_lightning_invoice(self._effectiveInvoice)
def get_max_spendable_onchain(self):
spendable = self._wallet.confirmedBalance.satsInt
if not self._wallet.wallet.config.get('confirmed_only', False):
spendable += self._wallet.unconfirmedBalance.satsInt
return spendable
def get_max_spendable_lightning(self):
return self._wallet.wallet.lnworker.num_sats_can_send() if self._wallet.wallet.lnworker else 0
class QEInvoiceParser(QEInvoice):
_logger = get_logger(__name__)
validationSuccess = pyqtSignal()
validationWarning = pyqtSignal([str,str], arguments=['code', 'message'])
validationError = pyqtSignal([str,str], arguments=['code', 'message'])
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
lnurlRetrieved = pyqtSignal()
lnurlError = pyqtSignal([str,str], arguments=['code', 'message'])
_bip70PrResolvedSignal = pyqtSignal([PaymentRequest], arguments=['pr'])
def __init__(self, parent=None):
super().__init__(parent)
self._recipient = ''
self._bip70PrResolvedSignal.connect(self._bip70_payment_request_resolved)
self.clear()
recipientChanged = pyqtSignal()
@pyqtProperty(str, notify=recipientChanged)
def recipient(self):
return self._recipient
@recipient.setter
def recipient(self, recipient: str):
self.canPay = False
self._recipient = recipient
self.amountOverride = QEAmount()
if recipient:
self.validateRecipient(recipient)
self.recipientChanged.emit()
@pyqtProperty('QVariantMap', notify=lnurlRetrieved)
def lnurlData(self):
return self._lnurlData
@pyqtProperty(bool, notify=lnurlRetrieved)
def isLnurlPay(self):
return self._lnurlData is not None
@pyqtSlot()
def clear(self):
self.recipient = ''
self.setInvoiceType(QEInvoice.Type.Invalid)
self._bip21 = None
self._lnurlData = None
self.canSave = False
self.canPay = False
self.userinfo = ''
self.invoiceChanged.emit()
def setValidOnchainInvoice(self, invoice: Invoice): def setValidOnchainInvoice(self, invoice: Invoice):
self._logger.debug('setValidOnchainInvoice') self._logger.debug('setValidOnchainInvoice')
if invoice.is_lightning(): if invoice.is_lightning():
@ -641,118 +649,14 @@ class QEInvoiceParser(QEInvoice, QtEventListener):
@pyqtSlot() @pyqtSlot()
def save_invoice(self): def save_invoice(self):
self.canSave = False
if not self._effectiveInvoice: if not self._effectiveInvoice:
return return
if self.isSaved:
self.key = self._effectiveInvoice.get_id()
if self._wallet.wallet.get_invoice(self.key):
self._logger.info(f'invoice {self.key} already exists')
else:
self._wallet.wallet.save_invoice(self._effectiveInvoice)
self._wallet.invoiceModel.addInvoice(self.key)
self.invoiceSaved.emit(self.key)
class QEUserEnteredPayment(QEInvoice):
_logger = get_logger(__name__)
validationError = pyqtSignal([str,str], arguments=['code','message'])
invoiceCreateError = pyqtSignal([str,str], arguments=['code', 'message'])
invoiceSaved = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._amount = QEAmount()
self.clear()
recipientChanged = pyqtSignal()
@pyqtProperty(str, notify=recipientChanged)
def recipient(self):
return self._recipient
@recipient.setter
def recipient(self, recipient: str):
if self._recipient != recipient:
self._recipient = recipient
self.validate()
self.recipientChanged.emit()
messageChanged = pyqtSignal()
@pyqtProperty(str, notify=messageChanged)
def message(self):
return self._message
@message.setter
def message(self, message):
if self._message != message:
self._message = message
self.messageChanged.emit()
amountChanged = pyqtSignal()
@pyqtProperty(QEAmount, notify=amountChanged)
def amount(self):
return self._amount
@amount.setter
def amount(self, amount):
if self._amount != amount:
self._amount.copyFrom(amount)
self.validate()
self.amountChanged.emit()
def validate(self):
self.canPay = False
self.canSave = False
self._logger.debug('validate')
if not self._recipient:
self.validationError.emit('recipient', _('Recipient not specified.'))
return
if not bitcoin.is_address(self._recipient):
self.validationError.emit('recipient', _('Invalid Bitcoin address'))
return
self.canSave = True
if self._amount.isEmpty:
self.validationError.emit('amount', _('Invalid amount'))
return return
if self._amount.isMax:
self.canPay = True
else:
if self.get_max_spendable_onchain() >= self._amount.satsInt:
self.canPay = True
@pyqtSlot()
def save_invoice(self):
assert self.canSave
assert not self._amount.isMax
self._logger.debug('saving invoice to %s, amount=%s, message=%s' % (self._recipient, repr(self._amount), self._message))
inv_amt = self._amount.satsInt
try:
outputs = [PartialTxOutput.from_address_and_value(self._recipient, inv_amt)]
self._logger.debug(repr(outputs))
invoice = self._wallet.wallet.create_invoice(outputs=outputs, message=self._message, pr=None, URI=None)
except InvoiceError as e:
self.invoiceCreateError.emit('fatal', _('Error creating payment') + ':\n' + str(e))
return
self.key = invoice.get_id()
self._wallet.wallet.save_invoice(invoice)
self.invoiceSaved.emit()
@pyqtSlot()
def clear(self):
self._recipient = None
self._amount.clear()
self._message = None
self.canSave = False self.canSave = False
self.canPay = False
self.key = self._effectiveInvoice.get_id()
self._wallet.wallet.save_invoice(self._effectiveInvoice)
self._wallet.invoiceModel.addInvoice(self.key)
self.invoiceSaved.emit(self.key)

10
electrum/gui/qml/qewallet.py

@ -28,7 +28,7 @@ from .util import QtEventListener, qt_event_listener
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum.wallet import Abstract_Wallet from electrum.wallet import Abstract_Wallet
from .qeinvoice import QEInvoice
class QEWallet(AuthMixin, QObject, QtEventListener): class QEWallet(AuthMixin, QObject, QtEventListener):
__instances = [] __instances = []
@ -587,14 +587,8 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
def ln_auth_rejected(self): def ln_auth_rejected(self):
self.paymentAuthRejected.emit() self.paymentAuthRejected.emit()
@pyqtSlot(str)
@auth_protect(reject='ln_auth_rejected') @auth_protect(reject='ln_auth_rejected')
def pay_lightning_invoice(self, invoice_key): def pay_lightning_invoice(self, invoice: 'QEInvoice'):
self._logger.debug('about to pay LN')
invoice = self.wallet.get_invoice(invoice_key)
assert(invoice)
assert(invoice.lightning_invoice)
amount_msat = invoice.get_amount_msat() amount_msat = invoice.get_amount_msat()
def pay_thread(): def pay_thread():

Loading…
Cancel
Save