(this somehow escaped attention before, as most objects usually don't have multiple instances, unless multiple wallets are open at the same time.) Also, move all signal declarations, class constants and variables to the top of class definitions.
215 lines
7.7 KiB
Python
215 lines
7.7 KiB
Python
import asyncio
|
|
|
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Q_ENUMS
|
|
|
|
from electrum.i18n import _
|
|
from electrum.gui import messages
|
|
from electrum.logging import get_logger
|
|
from electrum.lnutil import LOCAL, REMOTE
|
|
from electrum.lnchannel import ChanCloseOption, ChannelState
|
|
|
|
from .qewallet import QEWallet
|
|
from .qetypes import QEAmount
|
|
from .util import QtEventListener, qt_event_listener, event_listener
|
|
|
|
class QEChannelDetails(QObject, QtEventListener):
|
|
_logger = get_logger(__name__)
|
|
|
|
class State: # subset, only ones we currently need in UI
|
|
Closed = ChannelState.CLOSED
|
|
Redeemed = ChannelState.REDEEMED
|
|
|
|
Q_ENUMS(State)
|
|
|
|
channelChanged = pyqtSignal()
|
|
channelCloseSuccess = pyqtSignal()
|
|
channelCloseFailed = pyqtSignal([str], arguments=['message'])
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self._wallet = None
|
|
self._channelid = None
|
|
self._channel = None
|
|
|
|
self.register_callbacks()
|
|
self.destroyed.connect(lambda: self.on_destroy())
|
|
|
|
@event_listener
|
|
def on_event_channel(self, wallet, channel):
|
|
if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex():
|
|
self.channelChanged.emit()
|
|
|
|
def on_destroy(self):
|
|
self.unregister_callbacks()
|
|
|
|
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()
|
|
|
|
channelidChanged = pyqtSignal()
|
|
@pyqtProperty(str, notify=channelidChanged)
|
|
def channelid(self):
|
|
return self._channelid
|
|
|
|
@channelid.setter
|
|
def channelid(self, channelid: str):
|
|
if self._channelid != channelid:
|
|
self._channelid = channelid
|
|
if channelid:
|
|
self.load()
|
|
self.channelidChanged.emit()
|
|
|
|
def load(self):
|
|
lnchannels = self._wallet.wallet.lnworker.get_channel_objects()
|
|
for channel in lnchannels.values():
|
|
if self._channelid == channel.channel_id.hex():
|
|
self._channel = channel
|
|
self.channelChanged.emit()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def name(self):
|
|
if not self._channel:
|
|
return
|
|
return self._wallet.wallet.lnworker.get_node_alias(self._channel.node_id) or ''
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def pubkey(self):
|
|
return self._channel.node_id.hex()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def short_cid(self):
|
|
return self._channel.short_id_for_GUI()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def state(self):
|
|
return self._channel.get_state_for_GUI()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def initiator(self):
|
|
if self._channel.is_backup():
|
|
return ''
|
|
return 'Local' if self._channel.constraints.is_initiator else 'Remote'
|
|
|
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
|
def capacity(self):
|
|
self._capacity = QEAmount(amount_sat=self._channel.get_capacity())
|
|
return self._capacity
|
|
|
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
|
def canSend(self):
|
|
if self._channel.is_backup():
|
|
self._can_send = QEAmount()
|
|
else:
|
|
self._can_send = QEAmount(amount_sat=self._channel.available_to_spend(LOCAL)/1000)
|
|
return self._can_send
|
|
|
|
@pyqtProperty(QEAmount, notify=channelChanged)
|
|
def canReceive(self):
|
|
if self._channel.is_backup():
|
|
self._can_receive = QEAmount()
|
|
else:
|
|
self._can_receive = QEAmount(amount_sat=self._channel.available_to_spend(REMOTE)/1000)
|
|
return self._can_receive
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def frozenForSending(self):
|
|
return self._channel.is_frozen_for_sending()
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def frozenForReceiving(self):
|
|
return self._channel.is_frozen_for_receiving()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def channelType(self):
|
|
return self._channel.storage['channel_type'].name_minimal if 'channel_type' in self._channel.storage else 'Channel Backup'
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def isOpen(self):
|
|
return self._channel.is_open()
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def canClose(self):
|
|
return self.canCoopClose or self.canForceClose
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def canCoopClose(self):
|
|
return ChanCloseOption.COOP_CLOSE in self._channel.get_close_options()
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def canForceClose(self):
|
|
return any([o in [ChanCloseOption.LOCAL_FCLOSE, ChanCloseOption.REQUEST_REMOTE_FCLOSE] for o in self._channel.get_close_options()])
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def canDelete(self):
|
|
return self._channel.can_be_deleted()
|
|
|
|
@pyqtProperty(str, notify=channelChanged)
|
|
def message_force_close(self, notify=channelChanged):
|
|
return _(messages.MSG_REQUEST_FORCE_CLOSE)
|
|
|
|
@pyqtProperty(bool, notify=channelChanged)
|
|
def isBackup(self):
|
|
return self._channel.is_backup()
|
|
|
|
@pyqtSlot()
|
|
def freezeForSending(self):
|
|
lnworker = self._channel.lnworker
|
|
if lnworker.channel_db or lnworker.is_trampoline_peer(self._channel.node_id):
|
|
self._channel.set_frozen_for_sending(not self.frozenForSending)
|
|
self.channelChanged.emit()
|
|
else:
|
|
self._logger.debug(messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP)
|
|
|
|
@pyqtSlot()
|
|
def freezeForReceiving(self):
|
|
lnworker = self._channel.lnworker
|
|
if lnworker.channel_db or lnworker.is_trampoline_peer(self._channel.node_id):
|
|
self._channel.set_frozen_for_receiving(not self.frozenForReceiving)
|
|
self.channelChanged.emit()
|
|
else:
|
|
self._logger.debug(messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP)
|
|
|
|
# this method assumes the qobject is not destroyed before the close either fails or succeeds
|
|
@pyqtSlot(str)
|
|
def close_channel(self, closetype):
|
|
async def do_close(closetype, channel_id):
|
|
try:
|
|
if closetype == 'remote_force':
|
|
await self._wallet.wallet.lnworker.request_force_close(channel_id)
|
|
elif closetype == 'local_force':
|
|
await self._wallet.wallet.lnworker.force_close_channel(channel_id)
|
|
else:
|
|
await self._wallet.wallet.lnworker.close_channel(channel_id)
|
|
self.channelCloseSuccess.emit()
|
|
except Exception as e:
|
|
self._logger.exception("Could not close channel: " + repr(e))
|
|
self.channelCloseFailed.emit(_('Could not close channel: ') + repr(e))
|
|
|
|
loop = self._wallet.wallet.network.asyncio_loop
|
|
coro = do_close(closetype, self._channel.channel_id)
|
|
asyncio.run_coroutine_threadsafe(coro, loop)
|
|
|
|
@pyqtSlot()
|
|
def deleteChannel(self):
|
|
self._wallet.wallet.lnworker.remove_channel(self._channel.channel_id)
|
|
|
|
@pyqtSlot(result=str)
|
|
def channelBackup(self):
|
|
return self._wallet.wallet.lnworker.export_channel_backup(self._channel.channel_id)
|
|
|
|
@pyqtSlot(result=str)
|
|
def channelBackupHelpText(self):
|
|
return ' '.join([
|
|
_("Channel backups can be imported in another instance of the same wallet, by scanning this QR code."),
|
|
_("Please note that channel backups cannot be used to restore your channels."),
|
|
_("If you lose your wallet file, the only thing you can do with a backup is to request your channel to be closed, so that your funds will be sent on-chain."),
|
|
])
|