1
0

qml: separate PI resolving from QEInvoiceParser

separates the resolving step from the QEInvoiceParser so the 'recipient'
can be resolved first and then either an QEInvoiceParser can be used if
it is a sending request that has been resolved (invoice, address,
lnurlp, ...), or RequestDetails can be used if the resolved 'recipient'
turns out to be a voucher/LNURLW string.

# Conflicts:
#	electrum/gui/qml/qeinvoice.py
This commit is contained in:
f321x
2025-07-15 00:17:42 +02:00
parent 72dfc61d9c
commit b90090e2dd
8 changed files with 287 additions and 160 deletions

View File

@@ -13,13 +13,14 @@ ElDialog {
title: qsTr('LNURL Withdraw request')
iconSource: '../../../icons/link.png'
property InvoiceParser invoiceParser
property Wallet wallet: Daemon.currentWallet
property RequestDetails requestDetails
padding: 0
property int walletCanReceive: invoiceParser.wallet.lightningCanReceive.satsInt
property int providerMinWithdrawable: parseInt(invoiceParser.lnurlData['min_withdrawable_sat'])
property int providerMaxWithdrawable: parseInt(invoiceParser.lnurlData['max_withdrawable_sat'])
property int walletCanReceive: 0
property int providerMinWithdrawable: parseInt(requestDetails.lnurlData['min_withdrawable_sat'])
property int providerMaxWithdrawable: parseInt(requestDetails.lnurlData['max_withdrawable_sat'])
property int effectiveMinWithdrawable: Math.max(providerMinWithdrawable, 1)
property int effectiveMaxWithdrawable: Math.min(providerMaxWithdrawable, walletCanReceive)
property bool insufficientLiquidity: effectiveMinWithdrawable > walletCanReceive
@@ -30,6 +31,12 @@ ElDialog {
amountBtc.textAsSats.satsInt <= dialog.effectiveMaxWithdrawable
property bool valid: amountValid
Component.onCompleted: {
// Initialize walletCanReceive (instead of binding wallet.lightningCanReceive.satsInt)
// to prevent binding loop if wallet.lightningCanReceive.satsInt changes
walletCanReceive = wallet.lightningCanReceive.satsInt
}
ColumnLayout {
width: parent.width
@@ -89,17 +96,17 @@ ElDialog {
}
Label {
Layout.fillWidth: true
text: invoiceParser.lnurlData['domain']
text: requestDetails.lnurlData['domain']
}
Label {
text: qsTr('Description')
color: Material.accentColor
visible: invoiceParser.lnurlData['default_description']
visible: requestDetails.lnurlData['default_description']
}
Label {
Layout.fillWidth: true
text: invoiceParser.lnurlData['default_description']
visible: invoiceParser.lnurlData['default_description']
text: requestDetails.lnurlData['default_description']
visible: requestDetails.lnurlData['default_description']
wrapMode: Text.Wrap
}
@@ -117,9 +124,6 @@ ElDialog {
enabled: !dialog.insufficientLiquidity && (dialog.providerMinWithdrawable != dialog.providerMaxWithdrawable)
color: Material.foreground // override gray-out on disabled
fiatfield: amountFiat
onTextAsSatsChanged: {
invoiceParser.amountOverride = textAsSats
}
}
Label {
text: Config.baseUnit
@@ -150,12 +154,12 @@ ElDialog {
Layout.fillWidth: true
text: qsTr('Withdraw...')
icon.source: '../../icons/confirmed.png'
enabled: valid
enabled: valid && !requestDetails.busy
onClicked: {
invoiceParser.lnurlRequestWithdrawal()
dialog.close()
var satsAmount = amountBtc.textAsSats.satsInt;
requestDetails.lnurlRequestWithdrawal(satsAmount);
dialog.close();
}
}
}
}

View File

@@ -12,6 +12,7 @@ ElDialog {
id: dialog
property InvoiceParser invoiceParser
property PIResolver piResolver
signal txFound(data: string)
signal channelBackupFound(data: string)
@@ -36,7 +37,7 @@ ElDialog {
} else if (Daemon.currentWallet.isValidChannelBackup(data)) {
channelBackupFound(data)
} else {
invoiceParser.recipient = data
piResolver.recipient = data
}
}
@@ -71,7 +72,7 @@ ElDialog {
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
enabled: !invoiceParser.busy
enabled: !invoiceParser.busy && !piResolver.busy
icon.source: '../../icons/copy_bw.png'
text: qsTr('Paste')
onClicked: {

View File

@@ -36,7 +36,7 @@ Item {
function openSendDialog() {
// Qt based send dialog if not on android
if (!AppController.isAndroid()) {
_sendDialog = qtSendDialog.createObject(mainView, {invoiceParser: invoiceParser})
_sendDialog = qtSendDialog.createObject(mainView, {invoiceParser: invoiceParser, piResolver: piResolver})
_sendDialog.open()
return
}
@@ -61,7 +61,7 @@ Item {
})
dialog.open()
} else {
invoiceParser.recipient = data
piResolver.recipient = data
}
//scanner.destroy() // TODO
})
@@ -362,7 +362,7 @@ Item {
Layout.preferredWidth: 1
icon.source: '../../icons/tab_send.png'
text: qsTr('Send')
enabled: !invoiceParser.busy
enabled: !invoiceParser.busy && !piResolver.busy && !requestDetails.busy
onClicked: openSendDialog()
onPressAndHold: {
Config.userKnowsPressAndHold = true
@@ -373,6 +373,45 @@ Item {
}
}
PIResolver {
id: piResolver
wallet: Daemon.currentWallet
onResolveError: (code, message) => {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Error'),
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
text: message
})
dialog.open()
}
Component.onCompleted: {
piResolver.invoiceResolved.connect(invoiceParser.fromResolvedPaymentIdentifier)
piResolver.requestResolved.connect(requestDetails.fromResolvedPaymentIdentifier)
}
}
RequestDetails {
id: requestDetails
wallet: Daemon.currentWallet
onNeedsLNURLUserInput: {
closeSendDialog()
var dialog = lnurlWithdrawDialog.createObject(app, {
requestDetails: requestDetails
})
dialog.open()
}
onLnurlError: (code, message) => {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Error'),
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
text: message
})
dialog.open()
}
}
Invoice {
id: invoice
wallet: Daemon.currentWallet
@@ -420,17 +459,12 @@ Item {
})
dialog.open()
}
onLnurlRetrieved: {
closeSendDialog()
if (invoiceParser.invoiceType === Invoice.Type.LNURLPayRequest) {
var dialog = lnurlPayDialog.createObject(app, {
invoiceParser: invoiceParser
})
} else if (invoiceParser.invoiceType === Invoice.Type.LNURLWithdrawRequest) {
var dialog = lnurlWithdrawDialog.createObject(app, {
invoiceParser: invoiceParser
})
} else {
console.log("Unsupported LNURL type:", invoiceParser.invoiceType)
return
@@ -460,7 +494,7 @@ Item {
_intentUri = uri
return
}
invoiceParser.recipient = uri
piResolver.recipient = uri
}
}
@@ -469,7 +503,7 @@ Item {
function onWalletLoaded() {
infobanner.hide() // start hidden when switching wallets
if (_intentUri) {
invoiceParser.recipient = _intentUri
piResolver.recipient = _intentUri
_intentUri = ''
}
}

View File

@@ -33,6 +33,7 @@ from .qebitcoin import QEBitcoin
from .qefx import QEFX
from .qetxfinalizer import QETxFinalizer, QETxRbfFeeBumper, QETxCpfpFeeBumper, QETxCanceller, QETxSweepFinalizer, FeeSlider
from .qeinvoice import QEInvoice, QEInvoiceParser
from .qepiresolver import QEPIResolver
from .qerequestdetails import QERequestDetails
from .qetypes import QEAmount, QEBytes
from .qeaddressdetails import QEAddressDetails
@@ -439,6 +440,7 @@ class ElectrumQmlApplication(QGuiApplication):
qmlRegisterType(QEQRScanner, 'org.electrum', 1, 0, 'QRScanner')
qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX')
qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer')
qmlRegisterType(QEPIResolver, 'org.electrum', 1, 0, 'PIResolver')
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser')
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')

View File

@@ -14,7 +14,7 @@ from electrum.invoices import (
)
from electrum.transaction import PartialTxOutput, TxOutput
from electrum.lnutil import format_short_channel_id
from electrum.lnurl import LNURLData, LNURL3Data, LNURL6Data, request_lnurl_withdraw_callback, LNURLError
from electrum.lnurl import LNURL6Data
from electrum.bitcoin import COIN, address_to_script
from electrum.paymentrequest import PaymentRequest
from electrum.payment_identifier import PaymentIdentifier, PaymentIdentifierState, PaymentIdentifierType
@@ -23,7 +23,6 @@ from electrum.network import Network
from .qetypes import QEAmount
from .qewallet import QEWallet
from .util import status_update_timer_interval, QtEventListener, event_listener
from ...fee_policy import FeePolicy
from ...util import InvoiceError
@@ -34,7 +33,6 @@ class QEInvoice(QObject, QtEventListener):
OnchainInvoice = 0
LightningInvoice = 1
LNURLPayRequest = 2
LNURLWithdrawRequest = 3
@pyqtEnum
class Status(IntEnum):
@@ -453,26 +451,19 @@ class QEInvoiceParser(QEInvoice):
def __init__(self, parent=None):
super().__init__(parent)
self._recipient = ''
self._pi = None
self._lnurlData = None
self._busy = False
self.clear()
recipientChanged = pyqtSignal()
@pyqtProperty(str, notify=recipientChanged)
def recipient(self):
return self._recipient
@recipient.setter
def recipient(self, recipient: str):
@pyqtSlot(object)
def fromResolvedPaymentIdentifier(self, resolved_pi: PaymentIdentifier) -> None:
self.canPay = False
self._recipient = recipient
self.amountOverride = QEAmount()
if recipient:
self.validateRecipient(recipient)
self.recipientChanged.emit()
if resolved_pi:
assert not resolved_pi.need_resolve()
self.validateRecipient(resolved_pi)
@pyqtProperty('QVariantMap', notify=lnurlRetrieved)
def lnurlData(self):
@@ -480,7 +471,7 @@ class QEInvoiceParser(QEInvoice):
@pyqtProperty(bool, notify=lnurlRetrieved)
def isLnurlPay(self):
return self._lnurlData is not None and self.invoiceType == QEInvoice.Type.LNURLPayRequest
return self._lnurlData is not None
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
@@ -488,7 +479,6 @@ class QEInvoiceParser(QEInvoice):
@pyqtSlot()
def clear(self):
self.recipient = ''
self.setInvoiceType(QEInvoice.Type.Invalid)
self._lnurlData = None
self.canSave = False
@@ -515,12 +505,6 @@ class QEInvoiceParser(QEInvoice):
self._effectiveInvoice = None
self.invoiceChanged.emit()
def setValidLNURLWithdrawRequest(self):
self._logger.debug('setValidLNURLWithdrawRequest')
self.setInvoiceType(QEInvoice.Type.LNURLWithdrawRequest)
self._effectiveInvoice = None
self.invoiceChanged.emit()
def create_onchain_invoice(self, outputs, message, payment_request, uri):
return self._wallet.wallet.create_invoice(
outputs=outputs,
@@ -543,17 +527,18 @@ class QEInvoiceParser(QEInvoice):
else:
self.validationError.emit('unknown', f'invoice error:\n{pr.error}')
def validateRecipient(self, recipient):
if not recipient:
def validateRecipient(self, pi: PaymentIdentifier):
if not pi:
self.setInvoiceType(QEInvoice.Type.Invalid)
return
self._pi = PaymentIdentifier(self._wallet.wallet, recipient)
if not self._pi.is_valid() or self._pi.type not in [PaymentIdentifierType.SPK, PaymentIdentifierType.BIP21,
PaymentIdentifierType.BIP70, PaymentIdentifierType.BOLT11,
PaymentIdentifierType.LNURL,
PaymentIdentifierType.EMAILLIKE,
PaymentIdentifierType.DOMAINLIKE]:
self._pi = pi
if not self._pi.is_valid() or self._pi.type not in [
PaymentIdentifierType.SPK, PaymentIdentifierType.BIP21,
PaymentIdentifierType.BIP70, PaymentIdentifierType.BOLT11,
PaymentIdentifierType.LNADDR, PaymentIdentifierType.LNURLP,
PaymentIdentifierType.EMAILLIKE, PaymentIdentifierType.DOMAINLIKE
]:
self.validationError.emit('unknown', _('Unknown invoice'))
return
@@ -566,16 +551,13 @@ class QEInvoiceParser(QEInvoice):
self._update_from_payment_identifier()
def _update_from_payment_identifier(self):
if self._pi.need_resolve():
self.resolve_pi()
return
assert not self._pi.need_resolve(), "Should have been resolved by QEPIResolver"
if self._pi.type in [
PaymentIdentifierType.LNURLP,
PaymentIdentifierType.LNURLW,
PaymentIdentifierType.LNADDR,
]:
self.on_lnurl(self._pi.lnurl_data)
self.on_lnurl_pay(self._pi.lnurl_data)
return
if self._pi.type == PaymentIdentifierType.BIP70:
@@ -624,57 +606,20 @@ class QEInvoiceParser(QEInvoice):
self.setValidOnchainInvoice(invoice)
self.validationSuccess.emit()
def resolve_pi(self):
assert self._pi.need_resolve()
def on_finished(pi: PaymentIdentifier):
self._busy = False
self.busyChanged.emit()
if pi.is_error():
if pi.type in [PaymentIdentifierType.EMAILLIKE, PaymentIdentifierType.DOMAINLIKE]:
msg = _('Could not resolve address')
elif pi.type == PaymentIdentifierType.LNURL:
msg = _('Could not resolve LNURL') + "\n\n" + pi.get_error()
elif pi.type == PaymentIdentifierType.BIP70:
msg = _('Could not resolve BIP70 payment request: {}').format(pi.error)
else:
msg = _('Could not resolve')
self.validationError.emit('resolve', msg)
else:
self._update_from_payment_identifier()
self._busy = True
self.busyChanged.emit()
self._pi.resolve(on_finished=on_finished)
def on_lnurl(self, lnurldata: LNURLData):
def on_lnurl_pay(self, lnurldata: LNURL6Data):
assert isinstance(lnurldata, LNURL6Data)
self._logger.debug('on_lnurl')
self._logger.debug(f'{repr(lnurldata)}')
if isinstance(lnurldata, LNURL6Data):
self._lnurlData = {
'domain': urlparse(lnurldata.callback_url).netloc,
'callback_url': lnurldata.callback_url,
'min_sendable_sat': lnurldata.min_sendable_sat,
'max_sendable_sat': lnurldata.max_sendable_sat,
'metadata_plaintext': lnurldata.metadata_plaintext,
'comment_allowed': lnurldata.comment_allowed,
}
self.setValidLNURLPayRequest()
elif isinstance(lnurldata, LNURL3Data):
self._lnurlData = {
'domain': urlparse(lnurldata.callback_url).netloc,
'callback_url': lnurldata.callback_url,
'min_withdrawable_sat': lnurldata.min_withdrawable_sat,
'max_withdrawable_sat': lnurldata.max_withdrawable_sat,
'default_description': lnurldata.default_description,
'k1': lnurldata.k1,
}
self.setValidLNURLWithdrawRequest()
else:
raise NotImplementedError(f"Invalid lnurl type in on_lnurl {lnurldata=}")
self._lnurlData = {
'domain': urlparse(lnurldata.callback_url).netloc,
'callback_url': lnurldata.callback_url,
'min_sendable_sat': lnurldata.min_sendable_sat,
'max_sendable_sat': lnurldata.max_sendable_sat,
'metadata_plaintext': lnurldata.metadata_plaintext,
'comment_allowed': lnurldata.comment_allowed,
}
self.setValidLNURLPayRequest()
self.lnurlRetrieved.emit()
@pyqtSlot()
@@ -707,54 +652,6 @@ class QEInvoiceParser(QEInvoice):
self._pi.finalize(amount_sat=amount, comment=comment, on_finished=on_finished)
@pyqtSlot()
def lnurlRequestWithdrawal(self):
assert self._lnurlData
assert self.invoiceType == QEInvoice.Type.LNURLWithdrawRequest
self._logger.debug(f'{repr(self._lnurlData)}')
amount_sat = self.amountOverride.satsInt
try:
key = self.wallet.wallet.create_request(
amount_sat=amount_sat,
message=self._lnurlData.get('default_description', ''),
exp_delay=120,
address=None,
)
req = self.wallet.wallet.get_request(key)
_lnaddr, b11_invoice = self.wallet.wallet.lnworker.get_bolt11_invoice(
payment_hash=req.payment_hash,
amount_msat=req.get_amount_msat(),
message=req.get_message(),
expiry=req.exp,
fallback_address=None
)
except Exception as e:
self._logger.exception('')
self.lnurlError.emit(
'lnurl',
_("Failed to create payment request for withdrawal: {}").format(str(e))
)
return
self._busy = True
self.busyChanged.emit()
coro = request_lnurl_withdraw_callback(
callback_url=self._lnurlData['callback_url'],
k1=self._lnurlData['k1'],
bolt_11=b11_invoice,
)
try:
Network.run_from_another_thread(coro)
except LNURLError as e:
self.lnurlError.emit('lnurl', str(e))
self._busy = False
self.busyChanged.emit()
def on_lnurl_invoice(self, orig_amount, invoice):
self._logger.debug('on_lnurl_invoice')
self._logger.debug(f'{repr(invoice)}')
@@ -763,7 +660,9 @@ class QEInvoiceParser(QEInvoice):
if orig_amount * 1000 != invoice.amount_msat: # TODO msat precision can cause trouble here
raise Exception('Unexpected amount in invoice, differs from lnurl-pay specified amount')
self.recipient = invoice.lightning_invoice
self.fromResolvedPaymentIdentifier(
PaymentIdentifier(self._wallet.wallet, invoice.lightning_invoice)
)
@pyqtSlot(result=bool)
def saveInvoice(self) -> bool:

View File

@@ -0,0 +1,100 @@
from enum import IntEnum
from typing import Optional
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
from electrum.logging import get_logger
from electrum.i18n import _
from electrum.payment_identifier import PaymentIdentifier, PaymentIdentifierState, PaymentIdentifierType
from .qewallet import QEWallet
class QEPIResolver(QObject):
"""Intended to handle a user input Payment Identifier (PI), resolve it if necessary, then
allow to distinguish between a Request/voucher/lnurlw and an Invoice (e.g. b11 or lnurlp)."""
_logger = get_logger(__name__)
busyChanged = pyqtSignal()
resolveError = pyqtSignal([str, str], arguments=['code', 'message'])
invoiceResolved = pyqtSignal(object)
requestResolved = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self._wallet = None # type: Optional[QEWallet]
self._recipient = None
self._pi = None
self._busy = False
self.clear()
recipientChanged = pyqtSignal()
@pyqtProperty(str, notify=recipientChanged)
def recipient(self) -> Optional[str]:
return self._recipient
@recipient.setter
def recipient(self, recipient: str) -> None:
self.clear()
if not recipient:
return
self._recipient = recipient
self.recipientChanged.emit()
self._pi = PaymentIdentifier(self._wallet.wallet, recipient)
if self._pi.need_resolve():
self.resolve_pi()
else:
# assuming if the PI is an invoice if it doesn't need resolving
# as there are no request types that do not need resolving currently
self.invoiceResolved.emit(self._pi)
walletChanged = pyqtSignal()
@pyqtProperty(QEWallet, notify=walletChanged)
def wallet(self) -> Optional[QEWallet]:
return self._wallet
@wallet.setter
def wallet(self, wallet: QEWallet) -> None:
self._wallet = wallet
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
return self._busy
def resolve_pi(self) -> None:
assert self._pi is not None
assert self._pi.need_resolve()
def on_finished(pi: PaymentIdentifier):
self._busy = False
self.busyChanged.emit()
if pi.is_error():
if pi.type in [PaymentIdentifierType.EMAILLIKE, PaymentIdentifierType.DOMAINLIKE]:
msg = _('Could not resolve address')
elif pi.type == PaymentIdentifierType.LNURL:
msg = _('Could not resolve LNURL') + "\n\n" + pi.get_error()
elif pi.type == PaymentIdentifierType.BIP70:
msg = _('Could not resolve BIP70 payment request: {}').format(pi.error)
else:
msg = _('Could not resolve')
self.resolveError.emit('resolve', msg)
else:
if pi.type == PaymentIdentifierType.LNURLW:
self.requestResolved.emit(pi)
else:
self.invoiceResolved.emit(pi)
self._busy = True
self.busyChanged.emit()
self._pi.resolve(on_finished=on_finished)
def clear(self) -> None:
self._recipient = None
self._pi = None
self._busy = False
self.busyChanged.emit()
self.recipientChanged.emit()

View File

@@ -1,5 +1,6 @@
from enum import IntEnum
from typing import Optional
from urllib.parse import urlparse
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtEnum
@@ -8,6 +9,10 @@ from electrum.invoices import (
PR_UNPAID, PR_EXPIRED, PR_UNKNOWN, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, LN_EXPIRY_NEVER
)
from electrum.lnutil import MIN_FUNDING_SAT
from electrum.lnurl import LNURL3Data, request_lnurl_withdraw_callback, LNURLError
from electrum.payment_identifier import PaymentIdentifier, PaymentIdentifierType
from electrum.i18n import _
from electrum.network import Network
from .qewallet import QEWallet
from .qetypes import QEAmount
@@ -31,6 +36,9 @@ class QERequestDetails(QObject, QtEventListener):
detailsChanged = pyqtSignal() # generic request properties changed signal
statusChanged = pyqtSignal()
needsLNURLUserInput = pyqtSignal()
lnurlError = pyqtSignal(str, str) # code, message
busyChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
@@ -41,6 +49,9 @@ class QERequestDetails(QObject, QtEventListener):
self._timer = None
self._amount = None
self._lnurlData = None # type: Optional[dict]
self._busy = False
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.timeout.connect(self.updateStatusString)
@@ -150,6 +161,14 @@ class QERequestDetails(QObject, QtEventListener):
def bip21(self):
return self._req.get_bip21_URI() if self._req else ''
@pyqtProperty('QVariantMap', notify=detailsChanged)
def lnurlData(self) -> Optional[dict]:
return self._lnurlData
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
return self._busy
def initRequest(self):
if self._wallet is None or self._key is None:
return
@@ -181,3 +200,70 @@ class QERequestDetails(QObject, QtEventListener):
self.statusChanged.emit()
self.set_status_timer()
@pyqtSlot(object)
def fromResolvedPaymentIdentifier(self, resolved_pi: PaymentIdentifier) -> None:
"""
Called when a payment identifier is resolved to a request (currently only LNURLW, but
could also be used for other "voucher" type input like redeeming ecash tokens or
some bolt12 thing).
"""
if not self._wallet:
return
if resolved_pi.type == PaymentIdentifierType.LNURLW:
lnurldata = resolved_pi.lnurl_data
assert isinstance(lnurldata, LNURL3Data), "Expected LNURL3Data type"
self._lnurlData = {
'domain': urlparse(lnurldata.callback_url).netloc,
'callback_url': lnurldata.callback_url,
'min_withdrawable_sat': lnurldata.min_withdrawable_sat,
'max_withdrawable_sat': lnurldata.max_withdrawable_sat,
'default_description': lnurldata.default_description,
'k1': lnurldata.k1,
}
self.needsLNURLUserInput.emit()
else:
raise NotImplementedError("Cannot request withdrawal for this payment identifier type")
@pyqtSlot(int)
def lnurlRequestWithdrawal(self, amount_sat: int) -> None:
assert self._lnurlData
self._logger.debug(f'requesting lnurlw: {repr(self._lnurlData)}')
try:
key = self.wallet.wallet.create_request(
amount_sat=amount_sat,
message=self._lnurlData.get('default_description', ''),
exp_delay=120,
address=None,
)
req = self.wallet.wallet.get_request(key)
_lnaddr, b11_invoice = self.wallet.wallet.lnworker.get_bolt11_invoice(
payment_hash=req.payment_hash,
amount_msat=req.get_amount_msat(),
message=req.get_message(),
expiry=req.exp,
fallback_address=None
)
except Exception as e:
self._logger.exception('')
self.lnurlError.emit(
'lnurl',
_("Failed to create payment request for withdrawal: {}").format(str(e))
)
return
self._busy = True
self.busyChanged.emit()
coro = request_lnurl_withdraw_callback(
callback_url=self._lnurlData['callback_url'],
k1=self._lnurlData['k1'],
bolt_11=b11_invoice,
)
try:
Network.run_from_another_thread(coro)
except LNURLError as e:
self.lnurlError.emit('lnurl', str(e))
finally:
self._busy = False
self.busyChanged.emit()

View File

@@ -230,6 +230,7 @@ async def callback_lnurl(url: str, params: dict) -> dict:
raise LNURLError(f"Client error: {e}") from e
try:
response = json.loads(response_raw)
_logger.debug(f"lnurl response: {response}")
except json.JSONDecodeError:
raise LNURLError(f"Invalid response from LNURL server")