URI: 
       tconfirm_tx_dialog.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tconfirm_tx_dialog.py (10277B)
       ---
            1 #!/usr/bin/env python
            2 #
            3 # Electrum - lightweight Bitcoin client
            4 # Copyright (2019) The Electrum Developers
            5 #
            6 # Permission is hereby granted, free of charge, to any person
            7 # obtaining a copy of this software and associated documentation files
            8 # (the "Software"), to deal in the Software without restriction,
            9 # including without limitation the rights to use, copy, modify, merge,
           10 # publish, distribute, sublicense, and/or sell copies of the Software,
           11 # and to permit persons to whom the Software is furnished to do so,
           12 # subject to the following conditions:
           13 #
           14 # The above copyright notice and this permission notice shall be
           15 # included in all copies or substantial portions of the Software.
           16 #
           17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           24 # SOFTWARE.
           25 
           26 from decimal import Decimal
           27 from typing import TYPE_CHECKING, Optional, Union
           28 
           29 from PyQt5.QtWidgets import  QVBoxLayout, QLabel, QGridLayout, QPushButton, QLineEdit
           30 
           31 from electrum.i18n import _
           32 from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
           33 from electrum.plugin import run_hook
           34 from electrum.transaction import Transaction, PartialTransaction
           35 from electrum.simple_config import FEERATE_WARNING_HIGH_FEE, FEE_RATIO_HIGH_WARNING
           36 from electrum.wallet import InternalAddressCorruption
           37 
           38 from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton,
           39                    BlockingWaitingDialog, PasswordLineEdit)
           40 
           41 from .fee_slider import FeeSlider, FeeComboBox
           42 
           43 if TYPE_CHECKING:
           44     from .main_window import ElectrumWindow
           45 
           46 
           47 
           48 class TxEditor:
           49 
           50     def __init__(self, *, window: 'ElectrumWindow', make_tx,
           51                  output_value: Union[int, str] = None, is_sweep: bool):
           52         self.main_window = window
           53         self.make_tx = make_tx
           54         self.output_value = output_value
           55         self.tx = None  # type: Optional[PartialTransaction]
           56         self.config = window.config
           57         self.wallet = window.wallet
           58         self.not_enough_funds = False
           59         self.no_dynfee_estimates = False
           60         self.needs_update = False
           61         self.password_required = self.wallet.has_keystore_encryption() and not is_sweep
           62         self.main_window.gui_object.timer.timeout.connect(self.timer_actions)
           63 
           64     def timer_actions(self):
           65         if self.needs_update:
           66             self.update_tx()
           67             self.update()
           68             self.needs_update = False
           69 
           70     def fee_slider_callback(self, dyn, pos, fee_rate):
           71         if dyn:
           72             if self.config.use_mempool_fees():
           73                 self.config.set_key('depth_level', pos, False)
           74             else:
           75                 self.config.set_key('fee_level', pos, False)
           76         else:
           77             self.config.set_key('fee_per_kb', fee_rate, False)
           78         self.needs_update = True
           79 
           80     def get_fee_estimator(self):
           81         return None
           82 
           83     def update_tx(self, *, fallback_to_zero_fee: bool = False):
           84         fee_estimator = self.get_fee_estimator()
           85         try:
           86             self.tx = self.make_tx(fee_estimator)
           87             self.not_enough_funds = False
           88             self.no_dynfee_estimates = False
           89         except NotEnoughFunds:
           90             self.not_enough_funds = True
           91             self.tx = None
           92             if fallback_to_zero_fee:
           93                 try:
           94                     self.tx = self.make_tx(0)
           95                 except BaseException:
           96                     return
           97             else:
           98                 return
           99         except NoDynamicFeeEstimates:
          100             self.no_dynfee_estimates = True
          101             self.tx = None
          102             try:
          103                 self.tx = self.make_tx(0)
          104             except NotEnoughFunds:
          105                 self.not_enough_funds = True
          106                 return
          107             except BaseException:
          108                 return
          109         except InternalAddressCorruption as e:
          110             self.tx = None
          111             self.main_window.show_error(str(e))
          112             raise
          113         use_rbf = bool(self.config.get('use_rbf', True))
          114         self.tx.set_rbf(use_rbf)
          115 
          116     def have_enough_funds_assuming_zero_fees(self) -> bool:
          117         try:
          118             tx = self.make_tx(0)
          119         except NotEnoughFunds:
          120             return False
          121         else:
          122             return True
          123 
          124 
          125 
          126 
          127 class ConfirmTxDialog(TxEditor, WindowModalDialog):
          128     # set fee and return password (after pw check)
          129 
          130     def __init__(self, *, window: 'ElectrumWindow', make_tx, output_value: Union[int, str], is_sweep: bool):
          131 
          132         TxEditor.__init__(self, window=window, make_tx=make_tx, output_value=output_value, is_sweep=is_sweep)
          133         WindowModalDialog.__init__(self, window, _("Confirm Transaction"))
          134         vbox = QVBoxLayout()
          135         self.setLayout(vbox)
          136         grid = QGridLayout()
          137         vbox.addLayout(grid)
          138         self.amount_label = QLabel('')
          139         grid.addWidget(QLabel(_("Amount to be sent") + ": "), 0, 0)
          140         grid.addWidget(self.amount_label, 0, 1)
          141 
          142         msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
          143               + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
          144               + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
          145         self.fee_label = QLabel('')
          146         grid.addWidget(HelpLabel(_("Mining fee") + ": ", msg), 1, 0)
          147         grid.addWidget(self.fee_label, 1, 1)
          148 
          149         self.extra_fee_label = QLabel(_("Additional fees") + ": ")
          150         self.extra_fee_label.setVisible(False)
          151         self.extra_fee_value = QLabel('')
          152         self.extra_fee_value.setVisible(False)
          153         grid.addWidget(self.extra_fee_label, 2, 0)
          154         grid.addWidget(self.extra_fee_value, 2, 1)
          155 
          156         self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback)
          157         self.fee_combo = FeeComboBox(self.fee_slider)
          158         grid.addWidget(HelpLabel(_("Fee rate") + ": ", self.fee_combo.help_msg), 5, 0)
          159         grid.addWidget(self.fee_slider, 5, 1)
          160         grid.addWidget(self.fee_combo, 5, 2)
          161 
          162         self.message_label = QLabel(self.default_message())
          163         grid.addWidget(self.message_label, 6, 0, 1, -1)
          164         self.pw_label = QLabel(_('Password'))
          165         self.pw_label.setVisible(self.password_required)
          166         self.pw = PasswordLineEdit()
          167         self.pw.setVisible(self.password_required)
          168         grid.addWidget(self.pw_label, 8, 0)
          169         grid.addWidget(self.pw, 8, 1, 1, -1)
          170         self.preview_button = QPushButton(_('Advanced'))
          171         self.preview_button.clicked.connect(self.on_preview)
          172         grid.addWidget(self.preview_button, 0, 2)
          173         self.send_button = QPushButton(_('Send'))
          174         self.send_button.clicked.connect(self.on_send)
          175         self.send_button.setDefault(True)
          176         vbox.addLayout(Buttons(CancelButton(self), self.send_button))
          177         BlockingWaitingDialog(window, _("Preparing transaction..."), self.update_tx)
          178         self.update()
          179         self.is_send = False
          180 
          181     def default_message(self):
          182         return _('Enter your password to proceed') if self.password_required else _('Click Send to proceed')
          183 
          184     def on_preview(self):
          185         self.accept()
          186 
          187     def run(self):
          188         cancelled = not self.exec_()
          189         password = self.pw.text() or None
          190         return cancelled, self.is_send, password, self.tx
          191 
          192     def on_send(self):
          193         password = self.pw.text() or None
          194         if self.password_required:
          195             if password is None:
          196                 self.main_window.show_error(_("Password required"), parent=self)
          197                 return
          198             try:
          199                 self.wallet.check_password(password)
          200             except Exception as e:
          201                 self.main_window.show_error(str(e), parent=self)
          202                 return
          203         self.is_send = True
          204         self.accept()
          205 
          206     def toggle_send_button(self, enable: bool, *, message: str = None):
          207         if message is None:
          208             self.message_label.setStyleSheet(None)
          209             self.message_label.setText(self.default_message())
          210         else:
          211             self.message_label.setStyleSheet(ColorScheme.RED.as_stylesheet())
          212             self.message_label.setText(message)
          213         self.pw.setEnabled(enable)
          214         self.send_button.setEnabled(enable)
          215 
          216     def _update_amount_label(self):
          217         tx = self.tx
          218         if self.output_value == '!':
          219             if tx:
          220                 amount = tx.output_value()
          221                 amount_str = self.main_window.format_amount_and_units(amount)
          222             else:
          223                 amount_str = "max"
          224         else:
          225             amount = self.output_value
          226             amount_str = self.main_window.format_amount_and_units(amount)
          227         self.amount_label.setText(amount_str)
          228 
          229     def update(self):
          230         tx = self.tx
          231         self._update_amount_label()
          232 
          233         if self.not_enough_funds:
          234             text = self.main_window.get_text_not_enough_funds_mentioning_frozen()
          235             self.toggle_send_button(False, message=text)
          236             return
          237 
          238         if not tx:
          239             return
          240 
          241         fee = tx.get_fee()
          242         assert fee is not None
          243         self.fee_label.setText(self.main_window.format_amount_and_units(fee))
          244         x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
          245         if x_fee:
          246             x_fee_address, x_fee_amount = x_fee
          247             self.extra_fee_label.setVisible(True)
          248             self.extra_fee_value.setVisible(True)
          249             self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount))
          250 
          251         amount = tx.output_value() if self.output_value == '!' else self.output_value
          252         tx_size = tx.estimated_size()
          253         fee_warning_tuple = self.wallet.get_tx_fee_warning(
          254             invoice_amt=amount, tx_size=tx_size, fee=fee)
          255         if fee_warning_tuple:
          256             allow_send, long_warning, short_warning = fee_warning_tuple
          257             self.toggle_send_button(allow_send, message=long_warning)
          258         else:
          259             self.toggle_send_button(True)