add initial submarine swap functionality
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Controls.Material 2.0
|
||||
|
||||
import org.electrum 1.0
|
||||
@@ -8,8 +8,25 @@ import org.electrum 1.0
|
||||
import "controls"
|
||||
|
||||
Pane {
|
||||
id: root
|
||||
property string title: qsTr("Lightning Channels")
|
||||
|
||||
property QtObject menu: Menu {
|
||||
id: menu
|
||||
MenuItem {
|
||||
icon.color: 'transparent'
|
||||
action: Action {
|
||||
text: qsTr('Swap');
|
||||
enabled: Daemon.currentWallet.lightningCanSend.satsInt > 0 || Daemon.currentWallet.lightningCanReceive.satInt > 0
|
||||
onTriggered: {
|
||||
var dialog = swapDialog.createObject(root)
|
||||
dialog.open()
|
||||
}
|
||||
icon.source: '../../icons/status_waiting.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: parent.width
|
||||
@@ -129,4 +146,8 @@ Pane {
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: swapDialog
|
||||
Swap {}
|
||||
}
|
||||
}
|
||||
|
||||
205
electrum/gui/qml/components/Swap.qml
Normal file
205
electrum/gui/qml/components/Swap.qml
Normal file
@@ -0,0 +1,205 @@
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.0
|
||||
import QtQuick.Controls.Material 2.0
|
||||
|
||||
import org.electrum 1.0
|
||||
|
||||
import "controls"
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
title: qsTr('Lightning Swap')
|
||||
standardButtons: Dialog.Cancel
|
||||
|
||||
modal: true
|
||||
parent: Overlay.overlay
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa000000"
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: layout
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
columns: 2
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('You send')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
id: tosend
|
||||
text: Config.formatSats(swaphelper.tosend)
|
||||
font.family: FixedFont
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
Label {
|
||||
text: swaphelper.isReverse ? qsTr('(offchain)') : qsTr('(onchain)')
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('You receive')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
id: toreceive
|
||||
text: Config.formatSats(swaphelper.toreceive)
|
||||
font.family: FixedFont
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
Label {
|
||||
text: swaphelper.isReverse ? qsTr('(onchain)') : qsTr('(offchain)')
|
||||
visible: swaphelper.valid
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Server fee')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: swaphelper.serverfeeperc
|
||||
}
|
||||
Label {
|
||||
text: Config.formatSats(swaphelper.serverfee)
|
||||
font.family: FixedFont
|
||||
}
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Mining fee')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: Config.formatSats(swaphelper.miningfee)
|
||||
font.family: FixedFont
|
||||
}
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: swapslider
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredWidth: 2/3 * layout.width
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
from: swaphelper.rangeMin
|
||||
to: swaphelper.rangeMax
|
||||
|
||||
onValueChanged: {
|
||||
if (activeFocus)
|
||||
swaphelper.sliderPos = value
|
||||
}
|
||||
Component.onCompleted: {
|
||||
value = swaphelper.sliderPos
|
||||
}
|
||||
Connections {
|
||||
target: swaphelper
|
||||
function onSliderPosChanged() {
|
||||
swapslider.value = swaphelper.sliderPos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfoTextArea {
|
||||
Layout.columnSpan: 2
|
||||
visible: swaphelper.userinfo != ''
|
||||
text: swaphelper.userinfo
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.columnSpan: 2
|
||||
text: qsTr('Ok')
|
||||
enabled: swaphelper.valid
|
||||
onClicked: swaphelper.executeSwap()
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; Layout.preferredWidth: 1; Layout.columnSpan: 2 }
|
||||
}
|
||||
|
||||
SwapHelper {
|
||||
id: swaphelper
|
||||
wallet: Daemon.currentWallet
|
||||
onError: {
|
||||
var dialog = app.messageDialog.createObject(root, {'text': message})
|
||||
dialog.open()
|
||||
}
|
||||
onConfirm: {
|
||||
var dialog = app.messageDialog.createObject(app, {'text': message, 'yesno': true})
|
||||
dialog.yesClicked.connect(function() {
|
||||
dialog.close()
|
||||
swaphelper.executeSwap(true)
|
||||
root.close()
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
onAuthRequired: { // TODO: don't replicate this code
|
||||
if (swaphelper.wallet.verify_password('')) {
|
||||
// wallet has no password
|
||||
console.log('wallet has no password, proceeding')
|
||||
swaphelper.authProceed()
|
||||
} else {
|
||||
var dialog = app.passwordDialog.createObject(app, {'title': qsTr('Enter current password')})
|
||||
dialog.accepted.connect(function() {
|
||||
if (swaphelper.wallet.verify_password(dialog.password)) {
|
||||
swaphelper.wallet.authProceed()
|
||||
} else {
|
||||
swaphelper.wallet.authCancel()
|
||||
}
|
||||
})
|
||||
dialog.rejected.connect(function() {
|
||||
swaphelper.wallet.authCancel()
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,6 +251,13 @@ ApplicationWindow
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
// TODO: add to notification queue instead of barging through
|
||||
function onPaymentSucceeded(key) {
|
||||
notificationPopup.show(qsTr('Payment Succeeded'))
|
||||
}
|
||||
function onPaymentFailed(key, reason) {
|
||||
notificationPopup.show(qsTr('Payment Failed') + ': ' + reason)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -26,6 +26,7 @@ from .qetxdetails import QETxDetails
|
||||
from .qechannelopener import QEChannelOpener
|
||||
from .qelnpaymentdetails import QELnPaymentDetails
|
||||
from .qechanneldetails import QEChannelDetails
|
||||
from .qeswaphelper import QESwapHelper
|
||||
|
||||
notification = None
|
||||
|
||||
@@ -148,12 +149,12 @@ class ElectrumQmlApplication(QGuiApplication):
|
||||
qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice')
|
||||
qmlRegisterType(QEInvoiceParser, 'org.electrum', 1, 0, 'InvoiceParser')
|
||||
qmlRegisterType(QEUserEnteredPayment, 'org.electrum', 1, 0, 'UserEnteredPayment')
|
||||
|
||||
qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails')
|
||||
qmlRegisterType(QETxDetails, 'org.electrum', 1, 0, 'TxDetails')
|
||||
qmlRegisterType(QEChannelOpener, 'org.electrum', 1, 0, 'ChannelOpener')
|
||||
qmlRegisterType(QELnPaymentDetails, 'org.electrum', 1, 0, 'LnPaymentDetails')
|
||||
qmlRegisterType(QEChannelDetails, 'org.electrum', 1, 0, 'ChannelDetails')
|
||||
qmlRegisterType(QESwapHelper, 'org.electrum', 1, 0, 'SwapHelper')
|
||||
|
||||
qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
||||
|
||||
|
||||
324
electrum/gui/qml/qeswaphelper.py
Normal file
324
electrum/gui/qml/qeswaphelper.py
Normal file
@@ -0,0 +1,324 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import get_logger
|
||||
from electrum.lnutil import ln_dummy_address
|
||||
from electrum.transaction import PartialTxOutput
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, profiler
|
||||
|
||||
from .qewallet import QEWallet
|
||||
from .qetypes import QEAmount
|
||||
from .auth import AuthMixin, auth_protect
|
||||
|
||||
class QESwapHelper(AuthMixin, QObject):
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
_wallet = None
|
||||
_sliderPos = 0
|
||||
_rangeMin = 0
|
||||
_rangeMax = 0
|
||||
_tx = None
|
||||
_valid = False
|
||||
_userinfo = ''
|
||||
_tosend = QEAmount()
|
||||
_toreceive = QEAmount()
|
||||
_serverfeeperc = ''
|
||||
_serverfee = QEAmount()
|
||||
_miningfee = QEAmount()
|
||||
_isReverse = False
|
||||
|
||||
_send_amount = 0
|
||||
_receive_amount = 0
|
||||
|
||||
error = pyqtSignal([str], arguments=['message'])
|
||||
confirm = pyqtSignal([str], arguments=['message'])
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
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.init_swap_slider_range()
|
||||
self.walletChanged.emit()
|
||||
|
||||
sliderPosChanged = pyqtSignal()
|
||||
@pyqtProperty(float, notify=sliderPosChanged)
|
||||
def sliderPos(self):
|
||||
return self._sliderPos
|
||||
|
||||
@sliderPos.setter
|
||||
def sliderPos(self, sliderPos):
|
||||
if self._sliderPos != sliderPos:
|
||||
self._sliderPos = sliderPos
|
||||
self.swap_slider_moved()
|
||||
self.sliderPosChanged.emit()
|
||||
|
||||
rangeMinChanged = pyqtSignal()
|
||||
@pyqtProperty(float, notify=rangeMinChanged)
|
||||
def rangeMin(self):
|
||||
return self._rangeMin
|
||||
|
||||
@rangeMin.setter
|
||||
def rangeMin(self, rangeMin):
|
||||
if self._rangeMin != rangeMin:
|
||||
self._rangeMin = rangeMin
|
||||
self.rangeMinChanged.emit()
|
||||
|
||||
rangeMaxChanged = pyqtSignal()
|
||||
@pyqtProperty(float, notify=rangeMaxChanged)
|
||||
def rangeMax(self):
|
||||
return self._rangeMax
|
||||
|
||||
@rangeMax.setter
|
||||
def rangeMax(self, rangeMax):
|
||||
if self._rangeMax != rangeMax:
|
||||
self._rangeMax = rangeMax
|
||||
self.rangeMaxChanged.emit()
|
||||
|
||||
validChanged = pyqtSignal()
|
||||
@pyqtProperty(bool, notify=validChanged)
|
||||
def valid(self):
|
||||
return self._valid
|
||||
|
||||
@valid.setter
|
||||
def valid(self, valid):
|
||||
if self._valid != valid:
|
||||
self._valid = valid
|
||||
self.validChanged.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()
|
||||
|
||||
tosendChanged = pyqtSignal()
|
||||
@pyqtProperty(QEAmount, notify=tosendChanged)
|
||||
def tosend(self):
|
||||
return self._tosend
|
||||
|
||||
@tosend.setter
|
||||
def tosend(self, tosend):
|
||||
if self._tosend != tosend:
|
||||
self._tosend = tosend
|
||||
self.tosendChanged.emit()
|
||||
|
||||
toreceiveChanged = pyqtSignal()
|
||||
@pyqtProperty(QEAmount, notify=toreceiveChanged)
|
||||
def toreceive(self):
|
||||
return self._toreceive
|
||||
|
||||
@toreceive.setter
|
||||
def toreceive(self, toreceive):
|
||||
if self._toreceive != toreceive:
|
||||
self._toreceive = toreceive
|
||||
self.toreceiveChanged.emit()
|
||||
|
||||
serverfeeChanged = pyqtSignal()
|
||||
@pyqtProperty(QEAmount, notify=serverfeeChanged)
|
||||
def serverfee(self):
|
||||
return self._serverfee
|
||||
|
||||
@serverfee.setter
|
||||
def serverfee(self, serverfee):
|
||||
if self._serverfee != serverfee:
|
||||
self._serverfee = serverfee
|
||||
self.serverfeeChanged.emit()
|
||||
|
||||
serverfeepercChanged = pyqtSignal()
|
||||
@pyqtProperty(str, notify=serverfeepercChanged)
|
||||
def serverfeeperc(self):
|
||||
return self._serverfeeperc
|
||||
|
||||
@serverfeeperc.setter
|
||||
def serverfeeperc(self, serverfeeperc):
|
||||
if self._serverfeeperc != serverfeeperc:
|
||||
self._serverfeeperc = serverfeeperc
|
||||
self.serverfeepercChanged.emit()
|
||||
|
||||
miningfeeChanged = pyqtSignal()
|
||||
@pyqtProperty(QEAmount, notify=miningfeeChanged)
|
||||
def miningfee(self):
|
||||
return self._miningfee
|
||||
|
||||
@miningfee.setter
|
||||
def miningfee(self, miningfee):
|
||||
if self._miningfee != miningfee:
|
||||
self._miningfee = miningfee
|
||||
self.miningfeeChanged.emit()
|
||||
|
||||
isReverseChanged = pyqtSignal()
|
||||
@pyqtProperty(bool, notify=isReverseChanged)
|
||||
def isReverse(self):
|
||||
return self._isReverse
|
||||
|
||||
@isReverse.setter
|
||||
def isReverse(self, isReverse):
|
||||
if self._isReverse != isReverse:
|
||||
self._isReverse = isReverse
|
||||
self.isReverseChanged.emit()
|
||||
|
||||
|
||||
def init_swap_slider_range(self):
|
||||
lnworker = self._wallet.wallet.lnworker
|
||||
swap_manager = lnworker.swap_manager
|
||||
asyncio.run(swap_manager.get_pairs())
|
||||
"""Sets the minimal and maximal amount that can be swapped for the swap
|
||||
slider."""
|
||||
# tx is updated again afterwards with send_amount in case of normal swap
|
||||
# this is just to estimate the maximal spendable onchain amount for HTLC
|
||||
self.update_tx('!')
|
||||
try:
|
||||
max_onchain_spend = self._tx.output_value_for_address(ln_dummy_address())
|
||||
except AttributeError: # happens if there are no utxos
|
||||
max_onchain_spend = 0
|
||||
reverse = int(min(lnworker.num_sats_can_send(),
|
||||
swap_manager.get_max_amount()))
|
||||
max_recv_amt_ln = int(swap_manager.num_sats_can_receive())
|
||||
max_recv_amt_oc = swap_manager.get_send_amount(max_recv_amt_ln, is_reverse=False) or float('inf')
|
||||
forward = int(min(max_recv_amt_oc,
|
||||
# maximally supported swap amount by provider
|
||||
swap_manager.get_max_amount(),
|
||||
max_onchain_spend))
|
||||
# we expect range to adjust the value of the swap slider to be in the
|
||||
# correct range, i.e., to correct an overflow when reducing the limits
|
||||
self._logger.debug(f'Slider range {-reverse} - {forward}')
|
||||
self.rangeMin = -reverse
|
||||
self.rangeMax = forward
|
||||
|
||||
self.swap_slider_moved()
|
||||
|
||||
@profiler
|
||||
def update_tx(self, onchain_amount: Union[int, str]):
|
||||
"""Updates the transaction associated with a forward swap."""
|
||||
if onchain_amount is None:
|
||||
self._tx = None
|
||||
self.valid = False
|
||||
return
|
||||
outputs = [PartialTxOutput.from_address_and_value(ln_dummy_address(), onchain_amount)]
|
||||
coins = self._wallet.wallet.get_spendable_coins(None)
|
||||
try:
|
||||
self._tx = self._wallet.wallet.make_unsigned_transaction(
|
||||
coins=coins,
|
||||
outputs=outputs)
|
||||
except (NotEnoughFunds, NoDynamicFeeEstimates):
|
||||
self._tx = None
|
||||
self.valid = False
|
||||
|
||||
def swap_slider_moved(self):
|
||||
position = int(self._sliderPos)
|
||||
|
||||
swap_manager = self._wallet.wallet.lnworker.swap_manager
|
||||
|
||||
# pay_amount and receive_amounts are always with fees already included
|
||||
# so they reflect the net balance change after the swap
|
||||
if position < 0: # reverse swap
|
||||
self.userinfo = _('Adds Lightning receiving capacity.')
|
||||
self.isReverse = True
|
||||
|
||||
pay_amount = abs(position)
|
||||
self._send_amount = pay_amount
|
||||
self.tosend = QEAmount(amount_sat=pay_amount)
|
||||
|
||||
receive_amount = swap_manager.get_recv_amount(
|
||||
send_amount=pay_amount, is_reverse=True)
|
||||
self._receive_amount = receive_amount
|
||||
self.toreceive = QEAmount(amount_sat=receive_amount)
|
||||
|
||||
# fee breakdown
|
||||
self.serverfeeperc = f'{swap_manager.percentage:0.1f}%'
|
||||
self.serverfee = QEAmount(amount_sat=swap_manager.lockup_fee)
|
||||
self.miningfee = QEAmount(amount_sat=swap_manager.get_claim_fee())
|
||||
|
||||
else: # forward (normal) swap
|
||||
self.userinfo = _('Adds Lightning sending capacity.')
|
||||
self.isReverse = False
|
||||
self._send_amount = position
|
||||
|
||||
self.update_tx(self._send_amount)
|
||||
# add lockup fees, but the swap amount is position
|
||||
pay_amount = position + self._tx.get_fee() if self._tx else 0
|
||||
self.tosend = QEAmount(amount_sat=pay_amount)
|
||||
|
||||
receive_amount = swap_manager.get_recv_amount(send_amount=position, is_reverse=False)
|
||||
self._receive_amount = receive_amount
|
||||
self.toreceive = QEAmount(amount_sat=receive_amount)
|
||||
|
||||
# fee breakdown
|
||||
self.serverfeeperc = f'{swap_manager.percentage:0.1f}%'
|
||||
self.serverfee = QEAmount(amount_sat=swap_manager.normal_fee)
|
||||
self.miningfee = QEAmount(amount_sat=self._tx.get_fee())
|
||||
|
||||
if pay_amount and receive_amount:
|
||||
self.valid = True
|
||||
else:
|
||||
# add more nuanced error reporting?
|
||||
self.userinfo = _('Swap below minimal swap size, change the slider.')
|
||||
self.valid = False
|
||||
|
||||
def do_normal_swap(self, lightning_amount, onchain_amount, password):
|
||||
assert self._tx
|
||||
if lightning_amount is None or onchain_amount is None:
|
||||
return
|
||||
loop = self._wallet.wallet.network.asyncio_loop
|
||||
coro = self._wallet.wallet.lnworker.swap_manager.normal_swap(
|
||||
lightning_amount_sat=lightning_amount,
|
||||
expected_onchain_amount_sat=onchain_amount,
|
||||
password=password,
|
||||
tx=self._tx,
|
||||
)
|
||||
asyncio.run_coroutine_threadsafe(coro, loop)
|
||||
|
||||
def do_reverse_swap(self, lightning_amount, onchain_amount, password):
|
||||
if lightning_amount is None or onchain_amount is None:
|
||||
return
|
||||
swap_manager = self._wallet.wallet.lnworker.swap_manager
|
||||
loop = self._wallet.wallet.network.asyncio_loop
|
||||
coro = swap_manager.reverse_swap(
|
||||
lightning_amount_sat=lightning_amount,
|
||||
expected_onchain_amount_sat=onchain_amount + swap_manager.get_claim_fee(),
|
||||
)
|
||||
asyncio.run_coroutine_threadsafe(coro, loop)
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(bool)
|
||||
def executeSwap(self, confirm=False):
|
||||
if not self._wallet.wallet.network:
|
||||
self.error.emit(_("You are offline."))
|
||||
return
|
||||
if confirm:
|
||||
self._do_execute_swap()
|
||||
return
|
||||
|
||||
if self.isReverse:
|
||||
self.confirm.emit(_('Do you want to do a reverse submarine swap?'))
|
||||
else:
|
||||
self.confirm.emit(_('Do you want to do a submarine swap? '
|
||||
'You will need to wait for the swap transaction to confirm.'
|
||||
))
|
||||
|
||||
@auth_protect
|
||||
def _do_execute_swap(self):
|
||||
if self.isReverse:
|
||||
lightning_amount = self._send_amount
|
||||
onchain_amount = self._receive_amount
|
||||
self.do_reverse_swap(lightning_amount, onchain_amount, None)
|
||||
else:
|
||||
lightning_amount = self._receive_amount
|
||||
onchain_amount = self._send_amount
|
||||
self.do_normal_swap(lightning_amount, onchain_amount, None)
|
||||
@@ -120,8 +120,11 @@ class QEWallet(AuthMixin, QObject):
|
||||
self._logger.debug('invoice status update for key %s' % key)
|
||||
# FIXME event doesn't pass the new status, so we need to retrieve
|
||||
invoice = self.wallet.get_invoice(key)
|
||||
status = self.wallet.get_invoice_status(invoice)
|
||||
self.invoiceStatusChanged.emit(key, status)
|
||||
if invoice:
|
||||
status = self.wallet.get_invoice_status(invoice)
|
||||
self.invoiceStatusChanged.emit(key, status)
|
||||
else:
|
||||
self._logger.debug(f'No invoice found for key {key}')
|
||||
elif event == 'new_transaction':
|
||||
wallet, tx = args
|
||||
if wallet == self.wallet:
|
||||
|
||||
Reference in New Issue
Block a user