You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

177 lines
6.4 KiB

# 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.QtCore import Qt
from PyQt5.QtWidgets import (QCheckBox, QLabel, QVBoxLayout, QGridLayout, QWidget,
QPushButton, QHBoxLayout, QComboBox)
from .amountedit import FeerateEdit
from .fee_slider import FeeSlider, FeeComboBox
from .util import (ColorScheme, WindowModalDialog, Buttons,
OkButton, WWLabel, CancelButton)
from electrum.i18n import _
from electrum.transaction import PartialTransaction
from electrum.wallet import CannotRBFTx, BumpFeeStrategy
if TYPE_CHECKING:
from .main_window import ElectrumWindow
from .confirm_tx_dialog import ConfirmTxDialog, TxEditor, TxSizeLabel, HelpLabel
class _BaseRBFDialog(TxEditor):
def __init__(
self,
*,
main_window: 'ElectrumWindow',
tx: PartialTransaction,
title: str):
self.wallet = main_window.wallet
self.old_tx = tx
self.message = ''
self.old_fee = self.old_tx.get_fee()
self.old_tx_size = tx.estimated_size()
self.old_fee_rate = old_fee_rate = self.old_fee / self.old_tx_size # sat/vbyte
TxEditor.__init__(
self,
window=main_window,
title=title,
make_tx=self.rbf_func)
self.fee_e.setFrozen(True) # disallow setting absolute fee for now, as wallet.bump_fee can only target feerate
new_fee_rate = self.old_fee_rate + max(1, self.old_fee_rate // 20)
self.feerate_e.setAmount(new_fee_rate)
self.update()
self.fee_slider.deactivate()
def create_grid(self):
self.method_label = QLabel(_('Method') + ':')
self.method_combo = QComboBox()
self._strategies, def_strat_idx = self.wallet.get_bumpfee_strategies_for_tx(tx=self.old_tx)
self.method_combo.addItems([strat.text() for strat in self._strategies])
self.method_combo.setCurrentIndex(def_strat_idx)
self.method_combo.currentIndexChanged.connect(self.trigger_update)
self.method_combo.setFocusPolicy(Qt.NoFocus)
old_size_label = TxSizeLabel()
old_size_label.setAlignment(Qt.AlignCenter)
old_size_label.setAmount(self.old_tx_size)
old_size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
current_fee_hbox = QHBoxLayout()
current_fee_hbox.addWidget(QLabel(self.main_window.format_fee_rate(1000 * self.old_fee_rate)))
current_fee_hbox.addWidget(old_size_label)
current_fee_hbox.addWidget(QLabel(self.main_window.format_amount_and_units(self.old_fee)))
current_fee_hbox.addStretch()
grid = QGridLayout()
grid.addWidget(self.method_label, 0, 0)
grid.addWidget(self.method_combo, 0, 1)
grid.addWidget(QLabel(_('Current fee') + ':'), 1, 0)
grid.addLayout(current_fee_hbox, 1, 1, 1, 3)
grid.addWidget(QLabel(_('New fee') + ':'), 2, 0)
grid.addLayout(self.fee_hbox, 2, 1, 1, 3)
grid.addWidget(HelpLabel(_("Fee target") + ": ", self.fee_combo.help_msg), 4, 0)
grid.addLayout(self.fee_target_hbox, 4, 1, 1, 3)
grid.setColumnStretch(4, 1)
# locktime
grid.addWidget(self.locktime_label, 5, 0)
grid.addWidget(self.locktime_e, 5, 1, 1, 2)
return grid
def run(self) -> None:
if not self.exec_():
return
if self.is_preview:
self.main_window.show_transaction(self.tx)
return
def sign_done(success):
if success:
self.main_window.broadcast_or_show(self.tx)
self.main_window.sign_tx(
self.tx,
callback=sign_done,
external_keypairs={})
def update_tx(self):
fee_rate = self.feerate_e.get_amount()
if fee_rate is None:
self.tx = None
self.error = _('No fee rate')
elif fee_rate <= self.old_fee_rate:
self.tx = None
self.error = _("The new fee rate needs to be higher than the old fee rate.")
else:
try:
self.tx = self.make_tx(fee_rate)
except CannotRBFTx as e:
self.tx = None
self.error = str(e)
def get_messages(self):
messages = super().get_messages()
if not self.tx:
return
delta = self.tx.get_fee() - self.old_tx.get_fee()
if self._strategies[self.method_combo.currentIndex()] == BumpFeeStrategy.PRESERVE_PAYMENT:
msg = _("You will pay {} more.").format(self.main_window.format_amount_and_units(delta))
elif self._strategies[self.method_combo.currentIndex()] == BumpFeeStrategy.DECREASE_PAYMENT:
msg = _("The recipient will receive {} less.").format(self.main_window.format_amount_and_units(delta))
else:
raise Exception(f"unknown strategy: {self=}")
messages.insert(0, msg)
return messages
class BumpFeeDialog(_BaseRBFDialog):
help_text = _("Increase your transaction's fee to improve its position in mempool.")
def __init__(
self,
*,
main_window: 'ElectrumWindow',
tx: PartialTransaction,
):
_BaseRBFDialog.__init__(
self,
main_window=main_window,
tx=tx,
title=_('Bump Fee'))
def rbf_func(self, fee_rate, *, confirmed_only=False):
return self.wallet.bump_fee(
tx=self.old_tx,
new_fee_rate=fee_rate,
coins=self.main_window.get_coins(nonlocal_only=True, confirmed_only=confirmed_only),
strategy=self._strategies[self.method_combo.currentIndex()],
)
class DSCancelDialog(_BaseRBFDialog):
help_text = _(
"Cancel an unconfirmed transaction by replacing it with "
"a higher-fee transaction that spends back to your wallet.")
def __init__(
self,
*,
main_window: 'ElectrumWindow',
tx: PartialTransaction,
):
_BaseRBFDialog.__init__(
self,
main_window=main_window,
tx=tx,
title=_('Cancel transaction'))
self.method_label.setVisible(False)
self.method_combo.setVisible(False)
def rbf_func(self, fee_rate, *, confirmed_only=False):
return self.wallet.dscancel(tx=self.old_tx, new_fee_rate=fee_rate)