|
|
@ -3235,7 +3235,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): |
|
|
|
return False |
|
|
|
return True |
|
|
|
|
|
|
|
def bump_fee_dialog(self, tx: Transaction): |
|
|
|
def _rbf_dialog(self, tx: Transaction, func, title, help_text): |
|
|
|
txid = tx.txid() |
|
|
|
assert txid |
|
|
|
if not isinstance(tx, PartialTransaction): |
|
|
@ -3247,107 +3247,71 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): |
|
|
|
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, _('Bump Fee')) |
|
|
|
d = WindowModalDialog(self, title) |
|
|
|
vbox = QVBoxLayout(d) |
|
|
|
vbox.addWidget(WWLabel(_("Increase your transaction's fee to improve its position in mempool."))) |
|
|
|
vbox.addWidget(WWLabel(help_text)) |
|
|
|
|
|
|
|
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) |
|
|
|
def on_textedit_rate(): |
|
|
|
fee_slider.deactivate() |
|
|
|
ok_button = OkButton(d) |
|
|
|
feerate_e = FeerateEdit(lambda: 0) |
|
|
|
feerate_e.setAmount(max(old_fee_rate * 1.5, old_fee_rate + 1)) |
|
|
|
feerate_e.textEdited.connect(on_textedit_rate) |
|
|
|
grid.addWidget(feerate_e, 2, 1) |
|
|
|
|
|
|
|
def on_slider_rate(dyn, pos, fee_rate): |
|
|
|
def on_feerate(): |
|
|
|
fee_rate = feerate_e.get_amount() |
|
|
|
ok_button.setEnabled(fee_rate is not None) |
|
|
|
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_rate) |
|
|
|
fee_slider = FeeSlider(self, self.config, on_slider) |
|
|
|
fee_combo = FeeComboBox(fee_slider) |
|
|
|
fee_slider.deactivate() |
|
|
|
grid.addWidget(fee_slider, 3, 1) |
|
|
|
grid.addWidget(fee_combo, 3, 2) |
|
|
|
|
|
|
|
vbox.addLayout(grid) |
|
|
|
cb = QCheckBox(_('Final')) |
|
|
|
vbox.addWidget(cb) |
|
|
|
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) |
|
|
|
if not d.exec_(): |
|
|
|
return |
|
|
|
is_final = cb.isChecked() |
|
|
|
new_fee_rate = feerate_e.get_amount() |
|
|
|
try: |
|
|
|
new_tx = self.wallet.bump_fee( |
|
|
|
tx=tx, |
|
|
|
txid=txid, |
|
|
|
new_fee_rate=new_fee_rate, |
|
|
|
coins=self.get_coins(), |
|
|
|
) |
|
|
|
except CannotBumpFee as e: |
|
|
|
self.show_error(str(e)) |
|
|
|
return |
|
|
|
if is_final: |
|
|
|
new_tx.set_rbf(False) |
|
|
|
self.show_transaction(new_tx, tx_desc=tx_label) |
|
|
|
|
|
|
|
def dscancel_dialog(self, tx: Transaction): |
|
|
|
txid = tx.txid() |
|
|
|
assert txid |
|
|
|
if not isinstance(tx, PartialTransaction): |
|
|
|
tx = PartialTransaction.from_tx(tx) |
|
|
|
if not self._add_info_to_tx_from_wallet_and_network(tx): |
|
|
|
return |
|
|
|
fee = tx.get_fee() |
|
|
|
assert fee is not None |
|
|
|
tx_size = tx.estimated_size() |
|
|
|
old_fee_rate = fee / tx_size # sat/vbyte |
|
|
|
d = WindowModalDialog(self, _('Cancel transaction')) |
|
|
|
vbox = QVBoxLayout(d) |
|
|
|
vbox.addWidget(WWLabel(_("Cancel an unconfirmed RBF transaction by double-spending " |
|
|
|
"its inputs back to your wallet with a higher fee."))) |
|
|
|
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) |
|
|
|
def on_textedit_rate(): |
|
|
|
fee_slider.deactivate() |
|
|
|
feerate_e = FeerateEdit(lambda: 0) |
|
|
|
feerate_e.setAmount(max(old_fee_rate * 1.5, old_fee_rate + 1)) |
|
|
|
feerate_e.textEdited.connect(on_textedit_rate) |
|
|
|
grid.addWidget(feerate_e, 2, 1) |
|
|
|
|
|
|
|
def on_slider_rate(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_rate) |
|
|
|
fee_combo = FeeComboBox(fee_slider) |
|
|
|
fee_slider.deactivate() |
|
|
|
grid.addWidget(fee_slider, 3, 1) |
|
|
|
grid.addWidget(fee_combo, 3, 2) |
|
|
|
|
|
|
|
vbox.addLayout(grid) |
|
|
|
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) |
|
|
|
cb = QCheckBox(_('Final')) |
|
|
|
vbox.addWidget(cb) |
|
|
|
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 = self.wallet.dscancel(tx=tx, new_fee_rate=new_fee_rate) |
|
|
|
except CannotDoubleSpendTx as e: |
|
|
|
new_tx = func(new_fee_rate) |
|
|
|
except Exception as e: |
|
|
|
self.show_error(str(e)) |
|
|
|
return |
|
|
|
self.show_transaction(new_tx) |
|
|
|
if is_final: |
|
|
|
new_tx.set_rbf(False) |
|
|
|
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): |
|
|
|
title = _('Cancel transaction') |
|
|
|
help_text = _( |
|
|
|
"Cancel an unconfirmed RBF transaction by double-spending " |
|
|
|
"its inputs back to your wallet with a higher fee.") |
|
|
|
def func(new_fee_rate): |
|
|
|
return self.wallet.dscancel(tx=tx, new_fee_rate=new_fee_rate) |
|
|
|
self._rbf_dialog(tx, func, title, help_text) |
|
|
|
|
|
|
|
def save_transaction_into_wallet(self, tx: Transaction): |
|
|
|
win = self.top_level_window() |
|
|
|