Rebalance dialog:
- move dialog code to own submodule and class - disable ok button if amount is out of bounds - add max button - add Rebalance button to channels tab
This commit is contained in:
@@ -71,6 +71,7 @@ class ChannelsList(MyTreeView):
|
||||
self.network = self.parent.network
|
||||
self.wallet = self.parent.wallet
|
||||
self.setSortingEnabled(True)
|
||||
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
|
||||
|
||||
@property
|
||||
# property because lnworker might be initialized at runtime
|
||||
@@ -194,6 +195,27 @@ class ChannelsList(MyTreeView):
|
||||
msg = messages.MSG_NON_TRAMPOLINE_CHANNEL_FROZEN_WITHOUT_GOSSIP
|
||||
self.main_window.show_warning(msg, title=_('Channel is frozen for sending'))
|
||||
|
||||
def get_rebalance_pair(self):
|
||||
selected = self.selected_in_column(self.Columns.NODE_ALIAS)
|
||||
if len(selected) == 2:
|
||||
idx1 = selected[0]
|
||||
idx2 = selected[1]
|
||||
channel_id1 = idx1.sibling(idx1.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||
channel_id2 = idx2.sibling(idx2.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||
chan1 = self.lnworker.channels.get(channel_id1)
|
||||
chan2 = self.lnworker.channels.get(channel_id2)
|
||||
if chan1 and chan2 and (self.lnworker.channel_db or chan1.node_id != chan2.node_id):
|
||||
return chan1, chan2
|
||||
return None, None
|
||||
|
||||
def on_rebalance(self):
|
||||
chan1, chan2 = self.get_rebalance_pair()
|
||||
self.parent.rebalance_dialog(chan1, chan2)
|
||||
|
||||
def on_selection_changed(self):
|
||||
chan1, chan2 = self.get_rebalance_pair()
|
||||
self.rebalance_button.setEnabled(chan1 is not None)
|
||||
|
||||
def create_menu(self, position):
|
||||
menu = QMenu()
|
||||
menu.setSeparatorsCollapsible(True) # consecutive separators are merged together
|
||||
@@ -203,13 +225,8 @@ class ChannelsList(MyTreeView):
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
return
|
||||
if len(selected) == 2:
|
||||
idx1 = selected[0]
|
||||
idx2 = selected[1]
|
||||
channel_id1 = idx1.sibling(idx1.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||
channel_id2 = idx2.sibling(idx2.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||
chan1 = self.lnworker.channels.get(channel_id1)
|
||||
chan2 = self.lnworker.channels.get(channel_id2)
|
||||
if chan1 and chan2 and (self.lnworker.channel_db or chan1.node_id != chan2.node_id):
|
||||
chan1, chan2 = self.get_rebalance_pair()
|
||||
if chan1 and chan2:
|
||||
menu.addAction(_("Rebalance"), lambda: self.parent.rebalance_dialog(chan1, chan2))
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
return
|
||||
@@ -356,12 +373,16 @@ class ChannelsList(MyTreeView):
|
||||
self.can_send_label = QLabel('')
|
||||
h.addWidget(self.can_send_label)
|
||||
h.addStretch()
|
||||
self.rebalance_button = EnterButton(_('Rebalance'), lambda x: self.on_rebalance())
|
||||
self.rebalance_button.setToolTip("Select two active channels to rebalance.")
|
||||
self.rebalance_button.setDisabled(True)
|
||||
self.swap_button = EnterButton(_('Swap'), lambda x: self.parent.run_swap_dialog())
|
||||
self.swap_button.setToolTip("Have at least one channel to do swaps.")
|
||||
self.swap_button.setDisabled(True)
|
||||
self.new_channel_button = EnterButton(_('Open Channel'), self.new_channel_with_warning)
|
||||
self.new_channel_button.setEnabled(self.parent.wallet.has_lightning())
|
||||
h.addWidget(self.new_channel_button)
|
||||
h.addWidget(self.rebalance_button)
|
||||
h.addWidget(self.swap_button)
|
||||
return h
|
||||
|
||||
|
||||
@@ -3725,42 +3725,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.close()
|
||||
|
||||
def rebalance_dialog(self, chan1, chan2, amount_sat=None):
|
||||
d = WindowModalDialog(self, _("Rebalance channels"))
|
||||
d.reverse = False
|
||||
vbox = QVBoxLayout(d)
|
||||
vbox.addWidget(WWLabel(_('Rebalance your channels in order to increase your sending or receiving capacity') + ':'))
|
||||
grid = QGridLayout()
|
||||
amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||
amount_e.setAmount(amount_sat)
|
||||
rev_button = QPushButton(u'\U000021c4')
|
||||
label1 = QLabel('')
|
||||
label2 = QLabel('')
|
||||
def update():
|
||||
if d.reverse:
|
||||
d.chan_from = chan2
|
||||
d.chan_to = chan1
|
||||
else:
|
||||
d.chan_from = chan1
|
||||
d.chan_to = chan2
|
||||
label1.setText(d.chan_from.short_id_for_GUI())
|
||||
label2.setText(d.chan_to.short_id_for_GUI())
|
||||
update()
|
||||
def on_reverse():
|
||||
d.reverse = not d.reverse
|
||||
update()
|
||||
rev_button.clicked.connect(on_reverse)
|
||||
grid.addWidget(QLabel(_("From")), 0, 0)
|
||||
grid.addWidget(label1, 0, 1)
|
||||
grid.addWidget(QLabel(_("To")), 1, 0)
|
||||
grid.addWidget(label2, 1, 1)
|
||||
grid.addWidget(QLabel(_("Amount")), 2, 0)
|
||||
grid.addWidget(amount_e, 2, 1)
|
||||
grid.addWidget(rev_button, 0, 2)
|
||||
vbox.addLayout(grid)
|
||||
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
|
||||
if not d.exec_():
|
||||
from .rebalance_dialog import RebalanceDialog
|
||||
if chan1 is None or chan2 is None:
|
||||
return
|
||||
amount_msat = amount_e.get_amount() * 1000
|
||||
coro = self.wallet.lnworker.rebalance_channels(d.chan_from, d.chan_to, amount_msat=amount_msat)
|
||||
self.run_coroutine_from_thread(coro, _('Rebalancing channels'))
|
||||
self.update_current_request() # this will gray out the button
|
||||
d = RebalanceDialog(self, chan1, chan2, amount_sat)
|
||||
d.run()
|
||||
|
||||
69
electrum/gui/qt/rebalance_dialog.py
Normal file
69
electrum/gui/qt/rebalance_dialog.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton
|
||||
|
||||
from electrum.i18n import _
|
||||
from .util import WindowModalDialog, Buttons, OkButton, CancelButton, WWLabel
|
||||
from .amountedit import BTCAmountEdit
|
||||
|
||||
|
||||
class RebalanceDialog(WindowModalDialog):
|
||||
|
||||
def __init__(self, window, chan1, chan2, amount_sat):
|
||||
WindowModalDialog.__init__(self, window, _("Rebalance channels"))
|
||||
self.window = window
|
||||
self.wallet = window.wallet
|
||||
self.chan1 = chan1
|
||||
self.chan2 = chan2
|
||||
vbox = QVBoxLayout(self)
|
||||
vbox.addWidget(WWLabel(_('Rebalance your channels in order to increase your sending or receiving capacity') + ':'))
|
||||
grid = QGridLayout()
|
||||
self.amount_e = BTCAmountEdit(self.window.get_decimal_point)
|
||||
self.amount_e.setAmount(amount_sat)
|
||||
self.amount_e.textChanged.connect(self.on_amount)
|
||||
self.rev_button = QPushButton(u'\U000021c4')
|
||||
self.rev_button.clicked.connect(self.on_reverse)
|
||||
self.max_button = QPushButton('Max')
|
||||
self.max_button.clicked.connect(self.on_max)
|
||||
self.label1 = QLabel('')
|
||||
self.label2 = QLabel('')
|
||||
self.ok_button = OkButton(self)
|
||||
self.ok_button.setEnabled(False)
|
||||
grid.addWidget(QLabel(_("From channel")), 0, 0)
|
||||
grid.addWidget(self.label1, 0, 1)
|
||||
grid.addWidget(QLabel(_("To channel")), 1, 0)
|
||||
grid.addWidget(self.label2, 1, 1)
|
||||
grid.addWidget(QLabel(_("Amount")), 2, 0)
|
||||
grid.addWidget(self.amount_e, 2, 1)
|
||||
grid.addWidget(self.max_button, 2, 2)
|
||||
grid.addWidget(self.rev_button, 0, 2)
|
||||
vbox.addLayout(grid)
|
||||
vbox.addLayout(Buttons(CancelButton(self), self.ok_button))
|
||||
self.update()
|
||||
|
||||
def on_reverse(self, x):
|
||||
a, b = self.chan1, self.chan2
|
||||
self.chan1, self.chan2 = b, a
|
||||
self.amount_e.setAmount(None)
|
||||
self.update()
|
||||
|
||||
def on_amount(self, x):
|
||||
self.update()
|
||||
|
||||
def on_max(self, x):
|
||||
n_sat = self.wallet.lnworker.num_sats_can_rebalance(self.chan1, self.chan2)
|
||||
self.amount_e.setAmount(n_sat)
|
||||
|
||||
def update(self):
|
||||
self.label1.setText(self.chan1.short_id_for_GUI())
|
||||
self.label2.setText(self.chan2.short_id_for_GUI())
|
||||
amount_sat = self.amount_e.get_amount()
|
||||
b = bool(amount_sat) and self.wallet.lnworker.num_sats_can_rebalance(self.chan1, self.chan2) <= amount_sat
|
||||
self.ok_button.setEnabled(b)
|
||||
|
||||
def run(self):
|
||||
if not self.exec_():
|
||||
return
|
||||
amount_msat = self.amount_e.get_amount() * 1000
|
||||
coro = self.wallet.lnworker.rebalance_channels(self.chan1, self.chan2, amount_msat=amount_msat)
|
||||
self.window.run_coroutine_from_thread(coro, _('Rebalancing channels'))
|
||||
self.window.update_current_request() # this will gray out the button
|
||||
@@ -558,6 +558,9 @@ class Channel(AbstractChannel):
|
||||
forwarding_fee_base_msat = 1000
|
||||
forwarding_fee_proportional_millionths = 1
|
||||
|
||||
def __repr__(self):
|
||||
return "Channel(%s)"%self.get_id_for_log()
|
||||
|
||||
def __init__(self, state: 'StoredDict', *, sweep_address=None, name=None, lnworker=None, initial_feerate=None):
|
||||
self.name = name
|
||||
self.channel_id = bfh(state["channel_id"])
|
||||
|
||||
@@ -2232,6 +2232,14 @@ class LNWallet(LNWorker):
|
||||
else:
|
||||
return False
|
||||
|
||||
def num_sats_can_rebalance(self, chan1, chan2):
|
||||
# TODO: we should be able to spend 'max', with variable fee
|
||||
n1 = chan1.available_to_spend(LOCAL)
|
||||
n1 -= self.fee_estimate(n1)
|
||||
n2 = chan2.available_to_spend(REMOTE)
|
||||
amount_sat = min(n1, n2) // 1000
|
||||
return amount_sat
|
||||
|
||||
def suggest_rebalance_to_send(self, amount_sat):
|
||||
return self._suggest_rebalance(SENT, amount_sat)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user