qt: move RBF dialog out of main_window.py into its own file
This commit is contained in:
@@ -100,6 +100,7 @@ from .update_checker import UpdateCheck, UpdateCheckThread
|
|||||||
from .channels_list import ChannelsList
|
from .channels_list import ChannelsList
|
||||||
from .confirm_tx_dialog import ConfirmTxDialog
|
from .confirm_tx_dialog import ConfirmTxDialog
|
||||||
from .transaction_dialog import PreviewTxDialog
|
from .transaction_dialog import PreviewTxDialog
|
||||||
|
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import ElectrumGui
|
from . import ElectrumGui
|
||||||
@@ -3264,96 +3265,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _rbf_dialog(self, tx: Transaction, func, title, help_text):
|
def bump_fee_dialog(self, tx: Transaction):
|
||||||
txid = tx.txid()
|
txid = tx.txid()
|
||||||
assert txid
|
|
||||||
if not isinstance(tx, PartialTransaction):
|
if not isinstance(tx, PartialTransaction):
|
||||||
tx = PartialTransaction.from_tx(tx)
|
tx = PartialTransaction.from_tx(tx)
|
||||||
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||||
return
|
return
|
||||||
fee = tx.get_fee()
|
d = BumpFeeDialog(main_window=self, tx=tx, txid=txid)
|
||||||
assert fee is not None
|
d.run()
|
||||||
tx_label = self.wallet.get_label_for_txid(txid)
|
|
||||||
tx_size = tx.estimated_size()
|
|
||||||
old_fee_rate = fee / tx_size # sat/vbyte
|
|
||||||
d = WindowModalDialog(self, title)
|
|
||||||
vbox = QVBoxLayout(d)
|
|
||||||
vbox.addWidget(WWLabel(help_text))
|
|
||||||
|
|
||||||
ok_button = OkButton(d)
|
|
||||||
warning_label = WWLabel('\n')
|
|
||||||
warning_label.setStyleSheet(ColorScheme.RED.as_stylesheet())
|
|
||||||
feerate_e = FeerateEdit(lambda: 0)
|
|
||||||
feerate_e.setAmount(max(old_fee_rate * 1.5, old_fee_rate + 1))
|
|
||||||
def on_feerate():
|
|
||||||
fee_rate = feerate_e.get_amount()
|
|
||||||
warning_text = '\n'
|
|
||||||
if fee_rate is not None:
|
|
||||||
try:
|
|
||||||
new_tx = func(fee_rate)
|
|
||||||
except Exception as e:
|
|
||||||
new_tx = None
|
|
||||||
warning_text = str(e).replace('\n',' ')
|
|
||||||
else:
|
|
||||||
new_tx = None
|
|
||||||
ok_button.setEnabled(new_tx is not None)
|
|
||||||
warning_label.setText(warning_text)
|
|
||||||
|
|
||||||
feerate_e.textChanged.connect(on_feerate)
|
|
||||||
def on_slider(dyn, pos, fee_rate):
|
|
||||||
fee_slider.activate()
|
|
||||||
if fee_rate is not None:
|
|
||||||
feerate_e.setAmount(fee_rate / 1000)
|
|
||||||
fee_slider = FeeSlider(self, self.config, on_slider)
|
|
||||||
fee_combo = FeeComboBox(fee_slider)
|
|
||||||
fee_slider.deactivate()
|
|
||||||
feerate_e.textEdited.connect(fee_slider.deactivate)
|
|
||||||
|
|
||||||
grid = QGridLayout()
|
|
||||||
grid.addWidget(QLabel(_('Current Fee') + ':'), 0, 0)
|
|
||||||
grid.addWidget(QLabel(self.format_amount(fee) + ' ' + self.base_unit()), 0, 1)
|
|
||||||
grid.addWidget(QLabel(_('Current Fee rate') + ':'), 1, 0)
|
|
||||||
grid.addWidget(QLabel(self.format_fee_rate(1000 * old_fee_rate)), 1, 1)
|
|
||||||
grid.addWidget(QLabel(_('New Fee rate') + ':'), 2, 0)
|
|
||||||
grid.addWidget(feerate_e, 2, 1)
|
|
||||||
grid.addWidget(fee_slider, 3, 1)
|
|
||||||
grid.addWidget(fee_combo, 3, 2)
|
|
||||||
vbox.addLayout(grid)
|
|
||||||
cb = QCheckBox(_('Final'))
|
|
||||||
vbox.addWidget(cb)
|
|
||||||
vbox.addWidget(warning_label)
|
|
||||||
vbox.addLayout(Buttons(CancelButton(d), ok_button))
|
|
||||||
if not d.exec_():
|
|
||||||
return
|
|
||||||
is_final = cb.isChecked()
|
|
||||||
new_fee_rate = feerate_e.get_amount()
|
|
||||||
try:
|
|
||||||
new_tx = func(new_fee_rate)
|
|
||||||
except Exception as e:
|
|
||||||
self.show_error(str(e))
|
|
||||||
return
|
|
||||||
new_tx.set_rbf(not is_final)
|
|
||||||
self.show_transaction(new_tx, tx_desc=tx_label)
|
|
||||||
|
|
||||||
def bump_fee_dialog(self, tx: Transaction):
|
|
||||||
title = _('Bump Fee')
|
|
||||||
help_text = _("Increase your transaction's fee to improve its position in mempool.")
|
|
||||||
def func(new_fee_rate):
|
|
||||||
return self.wallet.bump_fee(
|
|
||||||
tx=tx,
|
|
||||||
txid=tx.txid(),
|
|
||||||
new_fee_rate=new_fee_rate,
|
|
||||||
coins=self.get_coins())
|
|
||||||
self._rbf_dialog(tx, func, title, help_text)
|
|
||||||
|
|
||||||
def dscancel_dialog(self, tx: Transaction):
|
def dscancel_dialog(self, tx: Transaction):
|
||||||
title = _('Cancel transaction')
|
txid = tx.txid()
|
||||||
help_text = _(
|
if not isinstance(tx, PartialTransaction):
|
||||||
"Cancel an unconfirmed RBF transaction by double-spending "
|
tx = PartialTransaction.from_tx(tx)
|
||||||
"its inputs back to your wallet with a higher fee.")
|
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||||
def func(new_fee_rate):
|
return
|
||||||
return self.wallet.dscancel(tx=tx, new_fee_rate=new_fee_rate)
|
d = DSCancelDialog(main_window=self, tx=tx, txid=txid)
|
||||||
self._rbf_dialog(tx, func, title, help_text)
|
d.run()
|
||||||
|
|
||||||
def save_transaction_into_wallet(self, tx: Transaction):
|
def save_transaction_into_wallet(self, tx: Transaction):
|
||||||
win = self.top_level_window()
|
win = self.top_level_window()
|
||||||
|
|||||||
161
electrum/gui/qt/rbf_dialog.py
Normal file
161
electrum/gui/qt/rbf_dialog.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Copyright (C) 2021 The Electrum developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from PyQt5.QtWidgets import QCheckBox, QLabel, QVBoxLayout, QGridLayout
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.transaction import PartialTransaction
|
||||||
|
from .amountedit import FeerateEdit
|
||||||
|
from .fee_slider import FeeSlider, FeeComboBox
|
||||||
|
from .util import (ColorScheme, WindowModalDialog, Buttons,
|
||||||
|
OkButton, WWLabel, CancelButton)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .main_window import ElectrumWindow
|
||||||
|
|
||||||
|
|
||||||
|
class _BaseRBFDialog(WindowModalDialog):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
main_window: 'ElectrumWindow',
|
||||||
|
tx: PartialTransaction,
|
||||||
|
txid: str,
|
||||||
|
title: str,
|
||||||
|
help_text: str,
|
||||||
|
):
|
||||||
|
WindowModalDialog.__init__(self, main_window, title=title)
|
||||||
|
self.window = main_window
|
||||||
|
self.wallet = main_window.wallet
|
||||||
|
self.tx = tx
|
||||||
|
assert txid
|
||||||
|
self.txid = txid
|
||||||
|
|
||||||
|
fee = tx.get_fee()
|
||||||
|
assert fee is not None
|
||||||
|
tx_size = tx.estimated_size()
|
||||||
|
old_fee_rate = fee / tx_size # sat/vbyte
|
||||||
|
vbox = QVBoxLayout(self)
|
||||||
|
vbox.addWidget(WWLabel(help_text))
|
||||||
|
|
||||||
|
ok_button = OkButton(self)
|
||||||
|
warning_label = WWLabel('\n')
|
||||||
|
warning_label.setStyleSheet(ColorScheme.RED.as_stylesheet())
|
||||||
|
self.feerate_e = FeerateEdit(lambda: 0)
|
||||||
|
self.feerate_e.setAmount(max(old_fee_rate * 1.5, old_fee_rate + 1))
|
||||||
|
|
||||||
|
def on_feerate():
|
||||||
|
fee_rate = self.feerate_e.get_amount()
|
||||||
|
warning_text = '\n'
|
||||||
|
if fee_rate is not None:
|
||||||
|
try:
|
||||||
|
new_tx = self.rbf_func(fee_rate)
|
||||||
|
except Exception as e:
|
||||||
|
new_tx = None
|
||||||
|
warning_text = str(e).replace('\n', ' ')
|
||||||
|
else:
|
||||||
|
new_tx = None
|
||||||
|
ok_button.setEnabled(new_tx is not None)
|
||||||
|
warning_label.setText(warning_text)
|
||||||
|
|
||||||
|
self.feerate_e.textChanged.connect(on_feerate)
|
||||||
|
|
||||||
|
def on_slider(dyn, pos, fee_rate):
|
||||||
|
fee_slider.activate()
|
||||||
|
if fee_rate is not None:
|
||||||
|
self.feerate_e.setAmount(fee_rate / 1000)
|
||||||
|
|
||||||
|
fee_slider = FeeSlider(self.window, self.window.config, on_slider)
|
||||||
|
fee_combo = FeeComboBox(fee_slider)
|
||||||
|
fee_slider.deactivate()
|
||||||
|
self.feerate_e.textEdited.connect(fee_slider.deactivate)
|
||||||
|
|
||||||
|
grid = QGridLayout()
|
||||||
|
grid.addWidget(QLabel(_('Current Fee') + ':'), 0, 0)
|
||||||
|
grid.addWidget(QLabel(self.window.format_amount(fee) + ' ' + self.window.base_unit()), 0, 1)
|
||||||
|
grid.addWidget(QLabel(_('Current Fee rate') + ':'), 1, 0)
|
||||||
|
grid.addWidget(QLabel(self.window.format_fee_rate(1000 * old_fee_rate)), 1, 1)
|
||||||
|
grid.addWidget(QLabel(_('New Fee rate') + ':'), 2, 0)
|
||||||
|
grid.addWidget(self.feerate_e, 2, 1)
|
||||||
|
grid.addWidget(fee_slider, 3, 1)
|
||||||
|
grid.addWidget(fee_combo, 3, 2)
|
||||||
|
vbox.addLayout(grid)
|
||||||
|
self.cb_is_final = QCheckBox(_('Final'))
|
||||||
|
vbox.addWidget(self.cb_is_final)
|
||||||
|
vbox.addWidget(warning_label)
|
||||||
|
vbox.addLayout(Buttons(CancelButton(self), ok_button))
|
||||||
|
|
||||||
|
def rbf_func(self, fee_rate) -> PartialTransaction:
|
||||||
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
if not self.exec_():
|
||||||
|
return
|
||||||
|
is_final = self.cb_is_final.isChecked()
|
||||||
|
new_fee_rate = self.feerate_e.get_amount()
|
||||||
|
try:
|
||||||
|
new_tx = self.rbf_func(new_fee_rate)
|
||||||
|
except Exception as e:
|
||||||
|
self.window.show_error(str(e))
|
||||||
|
return
|
||||||
|
new_tx.set_rbf(not is_final)
|
||||||
|
tx_label = self.wallet.get_label_for_txid(self.txid)
|
||||||
|
self.window.show_transaction(new_tx, tx_desc=tx_label)
|
||||||
|
# TODO maybe save tx_label as label for new tx??
|
||||||
|
|
||||||
|
|
||||||
|
class BumpFeeDialog(_BaseRBFDialog):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
main_window: 'ElectrumWindow',
|
||||||
|
tx: PartialTransaction,
|
||||||
|
txid: str,
|
||||||
|
):
|
||||||
|
help_text = _("Increase your transaction's fee to improve its position in mempool.")
|
||||||
|
_BaseRBFDialog.__init__(
|
||||||
|
self,
|
||||||
|
main_window=main_window,
|
||||||
|
tx=tx,
|
||||||
|
txid=txid,
|
||||||
|
title=_('Bump Fee'),
|
||||||
|
help_text=help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
def rbf_func(self, fee_rate):
|
||||||
|
return self.wallet.bump_fee(
|
||||||
|
tx=self.tx,
|
||||||
|
txid=self.txid,
|
||||||
|
new_fee_rate=fee_rate,
|
||||||
|
coins=self.window.get_coins(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DSCancelDialog(_BaseRBFDialog):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
main_window: 'ElectrumWindow',
|
||||||
|
tx: PartialTransaction,
|
||||||
|
txid: str,
|
||||||
|
):
|
||||||
|
help_text = _(
|
||||||
|
"Cancel an unconfirmed RBF transaction by double-spending "
|
||||||
|
"its inputs back to your wallet with a higher fee.")
|
||||||
|
_BaseRBFDialog.__init__(
|
||||||
|
self,
|
||||||
|
main_window=main_window,
|
||||||
|
tx=tx,
|
||||||
|
txid=txid,
|
||||||
|
title=_('Cancel transaction'),
|
||||||
|
help_text=help_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
def rbf_func(self, fee_rate):
|
||||||
|
return self.wallet.dscancel(tx=self.tx, new_fee_rate=fee_rate)
|
||||||
Reference in New Issue
Block a user