diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 73d88133a..697947abf 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -392,9 +392,9 @@ class ChannelsList(MyTreeView): vbox.addLayout(Buttons(OkButton(d))) d.exec_() - def new_channel_dialog(self, *, amount_sat=None): + def new_channel_dialog(self, *, amount_sat=None, min_amount_sat=None): from .new_channel_dialog import NewChannelDialog - d = NewChannelDialog(self.parent, amount_sat) + d = NewChannelDialog(self.parent, amount_sat, min_amount_sat) return d.run() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index bcd7af4f5..9984ffac3 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1742,7 +1742,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): lightning_needed += (lightning_needed // 20) # operational safety margin coins = self.get_coins(nonlocal_only=True) can_pay_onchain = invoice.get_address() and self.wallet.can_pay_onchain(invoice.get_outputs(), coins=coins) - can_pay_with_new_channel, channel_funding_sat = self.wallet.can_pay_with_new_channel(amount_sat, coins=coins) + can_pay_with_new_channel = self.wallet.lnworker.suggest_funding_amount(amount_sat, coins=coins) can_pay_with_swap = self.wallet.lnworker.suggest_swap_to_send(amount_sat, coins=coins) can_rebalance = self.wallet.lnworker.suggest_rebalance_to_send(amount_sat) choices = {} @@ -1784,7 +1784,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): elif r == 1: self.pay_onchain_dialog(coins, invoice.get_outputs()) elif r == 2: - self.channels_list.new_channel_dialog(amount_sat=channel_funding_sat) + amount_sat, min_amount_sat = can_pay_with_new_channel + self.channels_list.new_channel_dialog(amount_sat=amount_sat, min_amount_sat=min_amount_sat) elif r == 3: chan, swap_recv_amount_sat = can_pay_with_swap self.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan]) diff --git a/electrum/gui/qt/new_channel_dialog.py b/electrum/gui/qt/new_channel_dialog.py index 0551521db..672ade941 100644 --- a/electrum/gui/qt/new_channel_dialog.py +++ b/electrum/gui/qt/new_channel_dialog.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: class NewChannelDialog(WindowModalDialog): - def __init__(self, window: 'ElectrumWindow', amount_sat: Optional[int] = None): + def __init__(self, window: 'ElectrumWindow', amount_sat: Optional[int] = None, min_amount_sat: Optional[int] = None): WindowModalDialog.__init__(self, window, _('Open Channel')) self.window = window self.network = window.network @@ -30,7 +30,13 @@ class NewChannelDialog(WindowModalDialog): self.lnworker = self.window.wallet.lnworker self.trampolines = hardcoded_trampoline_nodes() self.trampoline_names = list(self.trampolines.keys()) + self.min_amount_sat = min_amount_sat or 10_000 vbox = QVBoxLayout(self) + msg = _('Choose a remote node and an amount to fund the channel.') + if min_amount_sat: + # only displayed if min_amount_sat is passed as parameter + msg += '\n' + _('You need to put at least') + ': ' + self.window.format_amount_and_units(self.min_amount_sat) + vbox.addWidget(WWLabel(msg)) if self.network.channel_db: vbox.addWidget(QLabel(_('Enter Remote Node ID or connection string or invoice'))) self.remote_nodeid = QLineEdit() @@ -38,12 +44,13 @@ class NewChannelDialog(WindowModalDialog): self.suggest_button = QPushButton(self, text=_('Suggest Peer')) self.suggest_button.clicked.connect(self.on_suggest) else: - vbox.addWidget(QLabel(_('Choose a trampoline node to open a channel with'))) self.trampoline_combo = QComboBox() self.trampoline_combo.addItems(self.trampoline_names) self.trampoline_combo.setCurrentIndex(1) self.amount_e = BTCAmountEdit(self.window.get_decimal_point) self.amount_e.setAmount(amount_sat) + self.min_button = EnterButton(_("Min"), self.spend_min) + self.min_button.setEnabled(bool(self.min_amount_sat)) self.max_button = EnterButton(_("Max"), self.spend_max) self.max_button.setFixedWidth(100) self.max_button.setCheckable(True) @@ -56,12 +63,13 @@ class NewChannelDialog(WindowModalDialog): h.addWidget(self.remote_nodeid, 0, 1, 1, 4) h.addWidget(self.suggest_button, 0, 5) else: - h.addWidget(QLabel(_('Trampoline')), 0, 0) + h.addWidget(QLabel(_('Remote Node')), 0, 0) h.addWidget(self.trampoline_combo, 0, 1, 1, 4) h.addWidget(QLabel('Amount'), 2, 0) h.addWidget(self.amount_e, 2, 1) - h.addWidget(self.max_button, 2, 2) - h.addWidget(self.clear_button, 2, 3) + h.addWidget(self.min_button, 2, 2) + h.addWidget(self.max_button, 2, 3) + h.addWidget(self.clear_button, 2, 4) vbox.addLayout(h) vbox.addStretch() ok_button = OkButton(self) @@ -89,6 +97,10 @@ class NewChannelDialog(WindowModalDialog): self.max_button.setChecked(False) self.max_button.repaint() # macOS hack for #6269 + def spend_min(self): + self.max_button.setChecked(False) + self.amount_e.setAmount(self.min_amount_sat) + def spend_max(self): self.amount_e.setFrozen(self.max_button.isChecked()) if not self.max_button.isChecked(): @@ -116,12 +128,17 @@ class NewChannelDialog(WindowModalDialog): funding_sat = '!' else: funding_sat = self.amount_e.get_amount() + if not funding_sat: + return + if self.min_amount_sat and funding_sat < self.min_amount_sat: + self.window.show_error(_('Amount too low')) + return if self.network.channel_db: connect_str = str(self.remote_nodeid.text()).strip() else: name = self.trampoline_names[self.trampoline_combo.currentIndex()] connect_str = str(self.trampolines[name]) - if not connect_str or not funding_sat: + if not connect_str: return self.window.open_channel(connect_str, funding_sat, 0) return True diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 92c92af3c..98c663bb1 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -1067,6 +1067,29 @@ class LNWallet(LNWorker): tx.set_rbf(False) return tx + def suggest_funding_amount(self, amount_to_pay, coins): + """ wether we can pay amount_sat after opening a new channel""" + num_sats_can_send = int(self.num_sats_can_send()) + lightning_needed = amount_to_pay - num_sats_can_send + assert lightning_needed > 0 + min_funding_sat = lightning_needed + (lightning_needed // 20) + 1000 # safety margin + min_funding_sat = max(min_funding_sat, 100_000) # at least 1mBTC + if min_funding_sat > LN_MAX_FUNDING_SAT: + return + try: + self.mktx_for_open_channel(coins=coins, funding_sat=min_funding_sat, node_id=bytes(32), fee_est=None) + funding_sat = min_funding_sat + except NotEnoughFunds: + return + # if available, suggest twice that amount: + if 2 * min_funding_sat <= LN_MAX_FUNDING_SAT: + try: + self.mktx_for_open_channel(coins=coins, funding_sat=2*min_funding_sat, node_id=bytes(32), fee_est=None) + funding_sat = 2 * min_funding_sat + except NotEnoughFunds: + pass + return funding_sat, min_funding_sat + def open_channel(self, *, connect_str: str, funding_tx: PartialTransaction, funding_sat: int, push_amt_sat: int, password: str = None) -> Tuple[Channel, PartialTransaction]: if funding_sat > LN_MAX_FUNDING_SAT: diff --git a/electrum/wallet.py b/electrum/wallet.py index 6bcccfc9c..73eac99db 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -1342,22 +1342,6 @@ class Abstract_Wallet(AddressSynchronizer, ABC): return False return True - def can_pay_with_new_channel(self, amount_sat, coins=None): - """ wether we can pay amount_sat after opening a new channel""" - if self.lnworker is None: - return False, None - num_sats_can_send = int(self.lnworker.num_sats_can_send()) - if amount_sat <= num_sats_can_send: - return True, None - lightning_needed = amount_sat - num_sats_can_send - lightning_needed += (lightning_needed // 20) # operational safety margin - channel_funding_sat = lightning_needed + 1000 # channel reserves safety margin - try: - self.lnworker.mktx_for_open_channel(coins=coins, funding_sat=channel_funding_sat, node_id=bytes(32), fee_est=None) - except NotEnoughFunds: - return False, None - return True, channel_funding_sat - def make_unsigned_transaction( self, *, coins: Sequence[PartialTxInput],