import copy import threading from decimal import Decimal from typing import Optional, TYPE_CHECKING from functools import partial from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.i18n import _ from electrum.transaction import PartialTxOutput, PartialTransaction, Transaction, TxOutpoint from electrum.util import NotEnoughFunds, profiler, quantize_feerate, UserFacingException from electrum.wallet import CannotBumpFee, CannotDoubleSpendTx, CannotCPFP, BumpFeeStrategy, sweep_preparations from electrum import keystore from electrum.plugin import run_hook from .qewallet import QEWallet from .qetypes import QEAmount from .util import QtEventListener, event_listener if TYPE_CHECKING: from electrum.simple_config import SimpleConfig class FeeSlider(QObject): def __init__(self, parent=None): super().__init__(parent) self._wallet = None # type: Optional[QEWallet] self._sliderSteps = 0 self._sliderPos = 0 self._method = -1 self._target = '' self._config = None # type: Optional[SimpleConfig] walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): return self._wallet @wallet.setter def wallet(self, wallet: QEWallet): if self._wallet != wallet: self._wallet = wallet self._config = self._wallet.wallet.config self.read_config() self.walletChanged.emit() sliderStepsChanged = pyqtSignal() @pyqtProperty(int, notify=sliderStepsChanged) def sliderSteps(self): return self._sliderSteps sliderPosChanged = pyqtSignal() @pyqtProperty(int, notify=sliderPosChanged) def sliderPos(self): return self._sliderPos @sliderPos.setter def sliderPos(self, sliderPos): if self._sliderPos != sliderPos: self._sliderPos = sliderPos self.save_config() self.sliderPosChanged.emit() methodChanged = pyqtSignal() @pyqtProperty(int, notify=methodChanged) def method(self): return self._method @method.setter def method(self, method): if self._method != method: self._method = method self.update_slider() self.methodChanged.emit() self.save_config() def get_method(self): dynfees = self._method > 0 mempool = self._method == 2 return dynfees, mempool targetChanged = pyqtSignal() @pyqtProperty(str, notify=targetChanged) def target(self): return self._target @target.setter def target(self, target): if self._target != target: self._target = target self.targetChanged.emit() def update_slider(self): dynfees, mempool = self.get_method() maxp, pos, fee_rate = self._config.get_fee_slider(dynfees, mempool) self._sliderSteps = maxp self._sliderPos = pos self.sliderStepsChanged.emit() self.sliderPosChanged.emit() def update_target(self): target, tooltip, dyn = self._config.get_fee_target() self.target = target def read_config(self): mempool = self._config.use_mempool_fees() dynfees = self._config.is_dynfee() self._method = (2 if mempool else 1) if dynfees else 0 self.update_slider() self.methodChanged.emit() self.update_target() self.update() def save_config(self): value = int(self._sliderPos) dynfees, mempool = self.get_method() self._config.FEE_EST_DYNAMIC = dynfees self._config.FEE_EST_USE_MEMPOOL = mempool if dynfees: if mempool: self._config.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS = value else: self._config.FEE_EST_DYNAMIC_ETA_SLIDERPOS = value else: self._config.FEE_EST_STATIC_FEERATE = self._config.static_fee(value) self.update_target() self.update() def update(self): raise NotImplementedError() class TxFeeSlider(FeeSlider): def __init__(self, parent=None): super().__init__(parent) self._fee = QEAmount() self._feeRate = '' self._rbf = False self._tx = None self._inputs = [] self._outputs = [] self._valid = False self._warning = '' feeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=feeChanged) def fee(self): return self._fee @fee.setter def fee(self, fee): if self._fee != fee: self._fee.copyFrom(fee) self.feeChanged.emit() feeRateChanged = pyqtSignal() @pyqtProperty(str, notify=feeRateChanged) def feeRate(self): return self._feeRate @feeRate.setter def feeRate(self, feeRate): if self._feeRate != feeRate: self._feeRate = feeRate self.feeRateChanged.emit() rbfChanged = pyqtSignal() @pyqtProperty(bool, notify=rbfChanged) def rbf(self): return self._rbf @rbf.setter def rbf(self, rbf): if self._rbf != rbf: self._rbf = rbf self.update() self.rbfChanged.emit() inputsChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=inputsChanged) def inputs(self): return self._inputs @inputs.setter def inputs(self, inputs): if self._inputs != inputs: self._inputs = inputs self.inputsChanged.emit() outputsChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=outputsChanged) def outputs(self): return self._outputs @outputs.setter def outputs(self, outputs): if self._outputs != outputs: self._outputs = outputs self.outputsChanged.emit() warningChanged = pyqtSignal() @pyqtProperty(str, notify=warningChanged) def warning(self): return self._warning @warning.setter def warning(self, warning): if self._warning != warning: self._warning = warning self.warningChanged.emit() validChanged = pyqtSignal() @pyqtProperty(bool, notify=validChanged) def valid(self): return self._valid @pyqtSlot() def doUpdate(self): self.update() def update_from_tx(self, tx): tx_size = tx.estimated_size() fee = tx.get_fee() feerate = Decimal(fee) / tx_size # sat/byte self.fee = QEAmount(amount_sat=int(fee)) self.feeRate = f'{feerate:.1f}' self.update_inputs_from_tx(tx) self.update_outputs_from_tx(tx) def update_inputs_from_tx(self, tx): inputs = [] for inp in tx.inputs(): # addr # addr = self.wallet.adb.get_txin_address(txin) addr = inp.address address_str = '
' if addr is None else addr txin_value = inp.value_sats() if inp.value_sats() else 0 #self.wallet.adb.get_txin_value(txin) inputs.append({ 'address': address_str, 'short_id': str(inp.short_id), 'value': QEAmount(amount_sat=txin_value), 'is_coinbase': inp.is_coinbase_input(), 'is_mine': self._wallet.wallet.is_mine(addr), 'is_change': self._wallet.wallet.is_change(addr), 'prevout_txid': inp.prevout.txid.hex() }) self.inputs = inputs def update_outputs_from_tx(self, tx): outputs = [] for idx, o in enumerate(tx.outputs()): outputs.append({ 'address': o.get_ui_address_str(), 'value': o.value, 'short_id': str(TxOutpoint(bytes.fromhex(tx.txid()), idx).short_name()) if tx.txid() else '', 'is_mine': self._wallet.wallet.is_mine(o.get_ui_address_str()), 'is_change': self._wallet.wallet.is_change(o.get_ui_address_str()), 'is_billing': self._wallet.wallet.is_billing_address(o.get_ui_address_str()) }) self.outputs = outputs def update_fee_warning_from_tx(self, *, tx: PartialTransaction, invoice_amt: Optional[int]): if invoice_amt is None: invoice_amt = sum([txo.value for txo in tx.outputs() if not txo.is_mine]) if invoice_amt == 0: invoice_amt = tx.output_value() fee_warning_tuple = self._wallet.wallet.get_tx_fee_warning( invoice_amt=invoice_amt, tx_size=tx.estimated_size(), fee=tx.get_fee()) if fee_warning_tuple: allow_send, long_warning, short_warning = fee_warning_tuple self.warning = _('Warning') + ': ' + long_warning else: self.warning = '' class QETxFinalizer(TxFeeSlider): _logger = get_logger(__name__) finished = pyqtSignal([bool, bool, bool], arguments=['signed', 'saved', 'complete']) signError = pyqtSignal([str], arguments=['message']) def __init__(self, parent=None, *, make_tx=None, accept=None): super().__init__(parent) self.f_make_tx = make_tx self.f_accept = accept self._address = '' self._amount = QEAmount() self._effectiveAmount = QEAmount() self._extraFee = QEAmount() self._canRbf = False addressChanged = pyqtSignal() @pyqtProperty(str, notify=addressChanged) def address(self): return self._address @address.setter def address(self, address): if self._address != address: self._address = address self.addressChanged.emit() amountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=amountChanged) def amount(self): return self._amount @amount.setter def amount(self, amount): if self._amount != amount: self._logger.debug(str(amount)) self._amount.copyFrom(amount) self.amountChanged.emit() effectiveAmountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=effectiveAmountChanged) def effectiveAmount(self): return self._effectiveAmount extraFeeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=extraFeeChanged) def extraFee(self): return self._extraFee @extraFee.setter def extraFee(self, extrafee): if self._extraFee != extrafee: self._extraFee.copyFrom(extrafee) self.extraFeeChanged.emit() canRbfChanged = pyqtSignal() @pyqtProperty(bool, notify=canRbfChanged) def canRbf(self): return self._canRbf @canRbf.setter def canRbf(self, canRbf): if self._canRbf != canRbf: self._canRbf = canRbf self.canRbfChanged.emit() self.rbf = self._canRbf # if we can RbF, we do RbF @profiler def make_tx(self, amount): self._logger.debug(f'make_tx amount={amount}') if self.f_make_tx: tx = self.f_make_tx(amount) else: # default impl coins = self._wallet.wallet.get_spendable_coins(None) outputs = [PartialTxOutput.from_address_and_value(self.address, amount)] tx = self._wallet.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=None, rbf=self._rbf) self._logger.debug('fee: %d, inputs: %d, outputs: %d' % (tx.get_fee(), len(tx.inputs()), len(tx.outputs()))) return tx def update(self): if not self._wallet: self._logger.debug('wallet not set, ignoring update()') return try: # make unsigned transaction tx = self.make_tx(amount='!' if self._amount.isMax else self._amount.satsInt) except NotEnoughFunds: self.warning = _("Not enough funds") self._valid = False self.validChanged.emit() return except Exception as e: self._logger.error(str(e)) self.warning = repr(e) self._valid = False self.validChanged.emit() return self._tx = tx amount = self._amount.satsInt if not self._amount.isMax else tx.output_value() self._effectiveAmount.satsInt = amount self.effectiveAmountChanged.emit() self.update_from_tx(tx) x_fee = run_hook('get_tx_extra_fee', self._wallet.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee self.extraFee = QEAmount(amount_sat=x_fee_amount) self.update_fee_warning_from_tx(tx=tx, invoice_amt=amount) self._valid = True self.validChanged.emit() @pyqtSlot() def saveOrShow(self): if not self._valid or not self._tx: self._logger.debug('no valid tx') return saved = False if self._tx.txid(): if self._wallet.save_tx(self._tx): saved = True self.finished.emit(False, saved, self._tx.is_complete()) @pyqtSlot() def signAndSend(self): if not self._valid or not self._tx: self._logger.debug('no valid tx') return if self.f_accept: self.f_accept(self._tx) return self._wallet.sign_and_broadcast(self._tx, on_success=partial(self.on_signed_tx, False), on_failure=self.on_sign_failed) @pyqtSlot() def sign(self): if not self._valid or not self._tx: self._logger.error('no valid tx') return self._wallet.sign(self._tx, on_success=partial(self.on_signed_tx, True), on_failure=self.on_sign_failed) def on_signed_tx(self, save: bool, tx: Transaction): self._logger.debug('on_signed_tx') saved = False if save and self._tx.txid(): if self._wallet.save_tx(self._tx): saved = True else: self._logger.error('Could not save tx') self.finished.emit(True, saved, tx.is_complete()) def on_sign_failed(self, msg: str = None): self._logger.debug('on_sign_failed') self.signError.emit(msg) @pyqtSlot(result='QVariantList') def getSerializedTx(self): txqr = self._tx.to_qr_data() return [str(self._tx), txqr[0], txqr[1]] class TxMonMixin(QtEventListener): """ mixin for watching an existing TX based on its txid for verified event. requires self._wallet to contain a QEWallet instance. exposes txid qt property. calls get_tx() once txid is set. calls tx_verified and emits txMined signal once tx is verified. """ txMined = pyqtSignal() def __init__(self, parent=None): self._logger.debug('TxMonMixin.__init__') self._txid = '' self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) def on_destroy(self): self.unregister_callbacks() @event_listener def on_event_verified(self, wallet, txid, info): if wallet == self._wallet.wallet and txid == self._txid: self._logger.debug('verified event for our txid %s' % txid) self.tx_verified() self.txMined.emit() txidChanged = pyqtSignal() @pyqtProperty(str, notify=txidChanged) def txid(self): return self._txid @txid.setter def txid(self, txid): if self._txid != txid: self._txid = txid self.get_tx() self.txidChanged.emit() # override def get_tx(self) -> None: pass # override def tx_verified(self) -> None: pass class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin): _logger = get_logger(__name__) def __init__(self, parent=None): super().__init__(parent) self._oldfee = QEAmount() self._oldfee_rate = 0 self._orig_tx = None self._rbf = True self._bump_method = BumpFeeStrategy.PRESERVE_PAYMENT.name self._bump_methods_available = [] oldfeeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=oldfeeChanged) def oldfee(self): return self._oldfee @oldfee.setter def oldfee(self, oldfee): if self._oldfee != oldfee: self._oldfee.copyFrom(oldfee) self.oldfeeChanged.emit() oldfeeRateChanged = pyqtSignal() @pyqtProperty(str, notify=oldfeeRateChanged) def oldfeeRate(self): return self._oldfee_rate @oldfeeRate.setter def oldfeeRate(self, oldfeerate): if self._oldfee_rate != oldfeerate: self._oldfee_rate = oldfeerate self.oldfeeRateChanged.emit() bumpMethodChanged = pyqtSignal() @pyqtProperty(str, notify=bumpMethodChanged) def bumpMethod(self): return self._bump_method @bumpMethod.setter def bumpMethod(self, bumpmethod: str) -> None: if self._bump_method != bumpmethod: self._bump_method = bumpmethod self.bumpMethodChanged.emit() self.update() bumpMethodsAvailableChanged = pyqtSignal() @pyqtProperty('QVariantList', notify=bumpMethodsAvailableChanged) def bumpMethodsAvailable(self): return self._bump_methods_available def get_tx(self): assert self._txid self._orig_tx = self._wallet.wallet.db.get_transaction(self._txid) assert self._orig_tx strategies, def_strat_idx = self._wallet.wallet.get_bumpfee_strategies_for_tx(tx=self._orig_tx) self._bump_methods_available = [{'value': strat.name, 'text': strat.text()} for strat in strategies] self.bumpMethodsAvailableChanged.emit() self.bumpMethod = strategies[def_strat_idx].name if not isinstance(self._orig_tx, PartialTransaction): self._orig_tx = PartialTransaction.from_tx(self._orig_tx) if not self._orig_tx.add_info_from_wallet_and_network(wallet=self._wallet.wallet, show_error=self._logger.error): return self.update_from_tx(self._orig_tx) self.oldfee = self.fee self.oldfeeRate = self.feeRate self.update() def update(self): if not self._txid or not self._orig_tx: # not initialized yet return fee_per_kb = self._config.fee_per_kb() if fee_per_kb is None: # dynamic method and no network self._logger.debug('no fee_per_kb') self.warning = _('Cannot determine dynamic fees, not connected') return new_fee_rate = fee_per_kb / 1000 if new_fee_rate <= float(self._oldfee_rate): self._valid = False self.validChanged.emit() self.warning = _("The new fee rate needs to be higher than the old fee rate.") return try: self._tx = self._wallet.wallet.bump_fee( tx=self._orig_tx, new_fee_rate=new_fee_rate, strategy=BumpFeeStrategy[self._bump_method], ) except CannotBumpFee as e: self._valid = False self.validChanged.emit() self._logger.error(str(e)) self.warning = str(e) return else: self.warning = '' self._tx.set_rbf(self.rbf) self.update_from_tx(self._tx) self.update_fee_warning_from_tx(tx=self._tx, invoice_amt=None) self._valid = True self.validChanged.emit() @pyqtSlot(result=str) def getNewTx(self): return str(self._tx) class QETxCanceller(TxFeeSlider, TxMonMixin): _logger = get_logger(__name__) def __init__(self, parent=None): super().__init__(parent) self._oldfee = QEAmount() self._oldfee_rate = 0 self._orig_tx = None self._txid = '' self._rbf = True oldfeeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=oldfeeChanged) def oldfee(self): return self._oldfee @oldfee.setter def oldfee(self, oldfee): if self._oldfee != oldfee: self._oldfee.copyFrom(oldfee) self.oldfeeChanged.emit() oldfeeRateChanged = pyqtSignal() @pyqtProperty(str, notify=oldfeeRateChanged) def oldfeeRate(self): return self._oldfee_rate @oldfeeRate.setter def oldfeeRate(self, oldfeerate): if self._oldfee_rate != oldfeerate: self._oldfee_rate = oldfeerate self.oldfeeRateChanged.emit() def get_tx(self): assert self._txid self._orig_tx = self._wallet.wallet.db.get_transaction(self._txid) assert self._orig_tx if not isinstance(self._orig_tx, PartialTransaction): self._orig_tx = PartialTransaction.from_tx(self._orig_tx) if not self._orig_tx.add_info_from_wallet_and_network(wallet=self._wallet.wallet, show_error=self._logger.error): return self.update_from_tx(self._orig_tx) self.oldfee = self.fee self.oldfeeRate = self.feeRate self.update() def update(self): if not self._txid or not self._orig_tx: # not initialized yet return fee_per_kb = self._config.fee_per_kb() if fee_per_kb is None: # dynamic method and no network self._logger.debug('no fee_per_kb') self.warning = _('Cannot determine dynamic fees, not connected') return new_fee_rate = fee_per_kb / 1000 if new_fee_rate <= float(self._oldfee_rate): self._valid = False self.validChanged.emit() self.warning = _("The new fee rate needs to be higher than the old fee rate.") return try: self._tx = self._wallet.wallet.dscancel( tx=self._orig_tx, new_fee_rate=new_fee_rate, ) except CannotDoubleSpendTx as e: self._valid = False self.validChanged.emit() self._logger.error(str(e)) self.warning = str(e) return else: self.warning = '' self._tx.set_rbf(self.rbf) self.update_from_tx(self._tx) self.update_fee_warning_from_tx(tx=self._tx, invoice_amt=None) self._valid = True self.validChanged.emit() @pyqtSlot(result=str) def getNewTx(self): return str(self._tx) class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): _logger = get_logger(__name__) def __init__(self, parent=None): super().__init__(parent) self._input_amount = QEAmount() self._output_amount = QEAmount() self._total_fee = QEAmount() self._total_fee_rate = 0 self._total_size = 0 self._parent_tx = None self._new_tx = None self._parent_tx_size = 0 self._parent_fee = 0 self._max_fee = 0 self._txid = '' self._rbf = True totalFeeChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=totalFeeChanged) def totalFee(self): return self._total_fee @totalFee.setter def totalFee(self, totalfee): if self._total_fee != totalfee: self._total_fee.copyFrom(totalfee) self.totalFeeChanged.emit() totalFeeRateChanged = pyqtSignal() @pyqtProperty(str, notify=totalFeeRateChanged) def totalFeeRate(self): return self._total_fee_rate @totalFeeRate.setter def totalFeeRate(self, totalfeerate): if self._total_fee_rate != totalfeerate: self._total_fee_rate = totalfeerate self.totalFeeRateChanged.emit() inputAmountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=inputAmountChanged) def inputAmount(self): return self._input_amount outputAmountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=outputAmountChanged) def outputAmount(self): return self._output_amount totalSizeChanged = pyqtSignal() @pyqtProperty(int, notify=totalSizeChanged) def totalSize(self): return self._total_size def get_tx(self): assert self._txid self._parent_tx = self._wallet.wallet.db.get_transaction(self._txid) assert self._parent_tx if isinstance(self._parent_tx, PartialTransaction): self._logger.error('unexpected PartialTransaction') return self._parent_tx_size = self._parent_tx.estimated_size() self._parent_fee = self._wallet.wallet.get_tx_info(self._parent_tx).fee if self._parent_fee is None: self._logger.error(_("Can't CPFP: unknown fee for parent transaction.")) self.warning = _("Can't CPFP: unknown fee for parent transaction.") return self._new_tx = self._wallet.wallet.cpfp(self._parent_tx, 0) self._total_size = self._parent_tx_size + self._new_tx.estimated_size() self.totalSizeChanged.emit() self._max_fee = self._new_tx.output_value() self._input_amount.satsInt = self._max_fee self.update() def get_child_fee_from_total_feerate(self, fee_per_kb: Optional[int]) -> Optional[int]: if fee_per_kb is None: return None fee = fee_per_kb * self._total_size / 1000 - self._parent_fee fee = round(fee) fee = min(self._max_fee, fee) fee = max(self._total_size, fee) # pay at least 1 sat/byte for combined size return fee def update(self): if not self._txid: # not initialized yet return assert self._parent_tx self._valid = False self.validChanged.emit() self.warning = '' fee_per_kb = self._config.fee_per_kb() if fee_per_kb is None: # dynamic method and no network self._logger.debug('no fee_per_kb') self.warning = _('Cannot determine dynamic fees, not connected') return if self._parent_fee is None: self._logger.error(_("Can't CPFP: unknown fee for parent transaction.")) self.warning = _("Can't CPFP: unknown fee for parent transaction.") return fee = self.get_child_fee_from_total_feerate(fee_per_kb=fee_per_kb) if fee is None: self._logger.warning('no fee') self.warning = _('No fee') return if fee > self._max_fee: self._logger.warning('max fee exceeded') self.warning = _('Max fee exceeded') return comb_fee = fee + self._parent_fee comb_feerate = comb_fee / self._total_size self._fee.satsInt = fee self._output_amount.satsInt = self._max_fee - fee self.outputAmountChanged.emit() self._total_fee.satsInt = fee + self._parent_fee self._total_fee_rate = str(quantize_feerate(comb_feerate)) try: self._new_tx = self._wallet.wallet.cpfp(self._parent_tx, fee) except CannotCPFP as e: self._logger.error(str(e)) self.warning = str(e) return child_feerate = fee / self._new_tx.estimated_size() self.feeRate = str(quantize_feerate(child_feerate)) self.update_inputs_from_tx(self._new_tx) self.update_outputs_from_tx(self._new_tx) self._valid = True self.validChanged.emit() @pyqtSlot(result=str) def getNewTx(self): return str(self._new_tx) class QETxSweepFinalizer(QETxFinalizer): _logger = get_logger(__name__) txinsRetrieved = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._private_keys = '' self._txins = None self._amount = QEAmount(is_max=True) self.txinsRetrieved.connect(self.update) privateKeysChanged = pyqtSignal() @pyqtProperty(str, notify=privateKeysChanged) def privateKeys(self): return self._private_keys @privateKeys.setter def privateKeys(self, private_keys): if self._private_keys != private_keys: self._private_keys = private_keys self.update_privkeys() self.privateKeysChanged.emit() def make_sweep_tx(self): address = self._wallet.wallet.get_receiving_address() assert self._wallet.wallet.is_mine(address) coins, keypairs = copy.deepcopy(self._txins) outputs = [PartialTxOutput.from_address_and_value(address, value='!')] tx = self._wallet.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=None, rbf=self._rbf, is_sweep=True) self._logger.debug('fee: %d, inputs: %d, outputs: %d' % (tx.get_fee(), len(tx.inputs()), len(tx.outputs()))) tx.sign(keypairs) return tx def update_privkeys(self): privkeys = keystore.get_private_keys(self._private_keys) def fetch_privkeys_info(): try: self._txins = self._wallet.wallet.network.run_from_another_thread(sweep_preparations(privkeys, self._wallet.wallet.network)) self._logger.debug(f'txins {self._txins!r}') except UserFacingException as e: self.warning = str(e) return self.txinsRetrieved.emit() threading.Thread(target=fetch_privkeys_info, daemon=True).start() def update(self): if not self._wallet: self._logger.debug('wallet not set, ignoring update()') return if not self._private_keys: self._logger.debug('private keys not set, ignoring update()') return try: # make unsigned transaction tx = self.make_sweep_tx() except Exception as e: self._logger.error(str(e)) self.warning = repr(e) self._valid = False self.validChanged.emit() return self._tx = tx amount = tx.output_value() self._effectiveAmount.satsInt = amount self.effectiveAmountChanged.emit() self.update_from_tx(tx) self.update_fee_warning_from_tx(tx=self._tx, invoice_amt=amount) self._valid = True self.validChanged.emit() self.on_signed_tx(False, tx) @pyqtSlot() def send(self): self._wallet.broadcast(self._tx) self._wallet.wallet.set_label(self._tx.txid(), _('Sweep transaction'))