From 4849d4d7702afafbb24da4dc4ac33dd15ee596be Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 9 Jun 2025 15:09:30 +0200 Subject: [PATCH 1/2] fix: use eta:2 as funding tx fee policy in qml swap qml used the user config fee policy for the forward swap onchain funding tx which can be too high or low, depending on what transactions the user did previously with the wallet. Setting it to eta:2 ensures that the funding tx is paying a sane fee. --- electrum/gui/qml/qeswaphelper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index edce023ad..d3a264885 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -543,7 +543,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): return outputs = [PartialTxOutput.from_address_and_value(DummyAddress.SWAP, onchain_amount)] coins = self._wallet.wallet.get_spendable_coins(None) - fee_policy = FeePolicy(self._wallet.wallet.config.FEE_POLICY) + fee_policy = FeePolicy('eta:2') try: self._tx = self._wallet.wallet.make_unsigned_transaction( coins=coins, @@ -677,7 +677,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): if max_amount > max_swap_amount: onchain_amount = max_swap_amount outputs = [PartialTxOutput.from_address_and_value(DummyAddress.SWAP, onchain_amount)] - fee_policy = FeePolicy(self._wallet.wallet.config.FEE_POLICY) + fee_policy = FeePolicy('eta:2') try: tx = self._wallet.wallet.make_unsigned_transaction( coins=coins, From 37181cd3a85a2d511f2083760f285b166dce1725 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 9 Jun 2025 16:56:11 +0200 Subject: [PATCH 2/2] disable qt swap fee slider on reverse swaps, change eta disables the fee slider in the swap dialog for reverse swaps as the tx fee for claiming is not configurable by the user. Also replaces calls to `sm.get_swap_tx_fee()` with `sm.get_fee_for_txbatcher()` as this is the correct fee estimate for claim transactions, instead of the config fee eta used by `get_swap_tx_fee()`. --- electrum/commands.py | 2 +- electrum/gui/qml/qeswaphelper.py | 4 ++-- electrum/gui/qt/swap_dialog.py | 36 ++++++++++++++++++++++++-------- electrum/submarine_swaps.py | 7 ++----- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 04e1bfc23..4594676d5 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1943,7 +1943,7 @@ class Commands(Logger): funding_txid = None else: lightning_amount_sat = satoshis(lightning_amount) - claim_fee = sm.get_swap_tx_fee() + claim_fee = sm.get_fee_for_txbatcher() onchain_amount_sat = satoshis(onchain_amount) + claim_fee funding_txid = await wallet.lnworker.swap_manager.reverse_swap( transport, diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index d3a264885..de972853b 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -581,7 +581,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): server_miningfee = swap_manager.mining_fee self.serverMiningfee = QEAmount(amount_sat=server_miningfee) if self.isReverse: - self.miningfee = QEAmount(amount_sat=swap_manager.get_swap_tx_fee()) + self.miningfee = QEAmount(amount_sat=swap_manager.get_fee_for_txbatcher()) self.check_valid(self._send_amount, self._receive_amount) else: # update tx only if slider isn't moved for a while @@ -703,7 +703,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener): txid = await swap_manager.reverse_swap( self.swap_transport, lightning_amount_sat=lightning_amount, - expected_onchain_amount_sat=onchain_amount + swap_manager.get_swap_tx_fee(), + expected_onchain_amount_sat=onchain_amount + swap_manager.get_fee_for_txbatcher(), ) try: # swaphelper might be destroyed at this point if txid: diff --git a/electrum/gui/qt/swap_dialog.py b/electrum/gui/qt/swap_dialog.py index 0992a13ab..72f16ab87 100644 --- a/electrum/gui/qt/swap_dialog.py +++ b/electrum/gui/qt/swap_dialog.py @@ -78,9 +78,13 @@ class SwapDialog(WindowModalDialog, QtEventListener): self.send_amount_e.setEnabled(recv_amount_sat is None) self.recv_amount_e.setEnabled(recv_amount_sat is None) self.max_button.setEnabled(recv_amount_sat is None) + self.fee_policy = FeePolicy(self.config.FEE_POLICY) - fee_slider = FeeSlider(parent=self, network=self.network, fee_policy=self.fee_policy, callback=self.fee_slider_callback) - fee_combo = FeeComboBox(fee_slider) + self.fee_slider = FeeSlider(parent=self, network=self.network, fee_policy=self.fee_policy, callback=self.fee_slider_callback) + self.fee_combo = FeeComboBox(self.fee_slider) + self.fee_target_label = QLabel() + self._set_fee_slider_visibility(is_visible=not self.is_reverse) + self.swap_limits_label = QLabel() self.fee_label = QLabel() self.server_fee_label = QLabel() @@ -100,8 +104,9 @@ class SwapDialog(WindowModalDialog, QtEventListener): h.addWidget(self.server_fee_label, 5, 1, 1, 2) h.addWidget(QLabel(_('Mining fee')+':'), 6, 0) h.addWidget(self.fee_label, 6, 1, 1, 2) - h.addWidget(fee_slider, 7, 1) - h.addWidget(fee_combo, 7, 2) + h.addWidget(self.fee_slider, 7, 1) + h.addWidget(self.fee_combo, 7, 2) + h.addWidget(self.fee_target_label, 7, 0) vbox.addLayout(h) vbox.addStretch(1) self.ok_button = OkButton(self) @@ -113,7 +118,7 @@ class SwapDialog(WindowModalDialog, QtEventListener): self.update() self.needs_tx_update = True self.window.gui_object.timer.timeout.connect(self.timer_actions) - fee_slider.update() + self.fee_slider.update() self.register_callbacks() def closeEvent(self, event): @@ -146,14 +151,28 @@ class SwapDialog(WindowModalDialog, QtEventListener): def fee_slider_callback(self, fee_rate): self.config.FEE_POLICY = self.fee_policy.get_descriptor() + if not self.is_reverse: + self.fee_target_label.setText(self.fee_policy.get_target_text()) if self.send_follows: self.on_recv_edited() else: self.on_send_edited() self.update() + def _set_fee_slider_visibility(self, *, is_visible: bool): + if is_visible: + self.fee_slider.setEnabled(True) + self.fee_combo.setEnabled(True) + self.fee_target_label.setText(self.fee_policy.get_target_text()) + else: + self.fee_slider.setEnabled(False) + self.fee_combo.setEnabled(False) + # show the eta of the swap claim + self.fee_target_label.setText(FeePolicy(self.config.FEE_POLICY_SWAPS).get_target_text()) + def toggle_direction(self): self.is_reverse = not self.is_reverse + self._set_fee_slider_visibility(is_visible=not self.is_reverse) self.send_amount_e.setAmount(None) self.recv_amount_e.setAmount(None) self.max_button.setChecked(False) @@ -222,7 +241,6 @@ class SwapDialog(WindowModalDialog, QtEventListener): self.needs_tx_update = True def update(self): - from .util import IconLabel sm = self.swap_manager w_base_unit = self.window.base_unit() send_icon = read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png") @@ -266,10 +284,10 @@ class SwapDialog(WindowModalDialog, QtEventListener): """Updates self.fee_label. No other side-effects.""" if self.is_reverse: sm = self.swap_manager - fee = sm.get_swap_tx_fee() + fee = sm.get_fee_for_txbatcher() else: fee = tx.get_fee() if tx else None - fee_text = self.window.format_amount(fee) + ' ' + self.window.base_unit() if fee else '' + fee_text = self.window.format_amount(fee) + ' ' + self.window.base_unit() if fee else _("no input") self.fee_label.setText(fee_text) self.fee_label.repaint() # macOS hack for #6269 @@ -286,7 +304,7 @@ class SwapDialog(WindowModalDialog, QtEventListener): coro = sm.reverse_swap( transport, lightning_amount_sat=lightning_amount, - expected_onchain_amount_sat=onchain_amount + self.swap_manager.get_swap_tx_fee(), + expected_onchain_amount_sat=onchain_amount + self.swap_manager.get_fee_for_txbatcher(), ) try: # we must not leave the context, so we use run_couroutine_dialog diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index fa1904f6f..8770bd859 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -476,9 +476,6 @@ class SwapManager(Logger): self.logger.info('utxo value below dust threshold') return - def get_swap_tx_fee(self): - return self._get_tx_fee(self.config.FEE_POLICY) - def get_fee_for_txbatcher(self): return self._get_tx_fee(self.config.FEE_POLICY_SWAPS) @@ -1094,13 +1091,13 @@ class SwapManager(Logger): f"send_amount={send_amount} -> recv_amount={recv_amount} -> inverted_send_amount={inverted_send_amount}") # second, add on-chain claim tx fee if is_reverse and recv_amount is not None: - recv_amount -= self.get_swap_tx_fee() + recv_amount -= self.get_fee_for_txbatcher() return recv_amount def get_send_amount(self, recv_amount: Optional[int], *, is_reverse: bool) -> Optional[int]: # first, add on-chain claim tx fee if is_reverse and recv_amount is not None: - recv_amount += self.get_swap_tx_fee() + recv_amount += self.get_fee_for_txbatcher() # second, add percentage fee send_amount = self._get_send_amount(recv_amount, is_reverse=is_reverse) # sanity check calculation can be inverted