confirm_tx_dialog.py (5800B)
1 from decimal import Decimal 2 from typing import TYPE_CHECKING 3 4 from kivy.app import App 5 from kivy.factory import Factory 6 from kivy.properties import ObjectProperty 7 from kivy.lang import Builder 8 from kivy.uix.checkbox import CheckBox 9 from kivy.uix.label import Label 10 from kivy.uix.widget import Widget 11 from kivy.clock import Clock 12 13 from electrum.simple_config import FEERATE_WARNING_HIGH_FEE, FEE_RATIO_HIGH_WARNING 14 from electrum.gui.kivy.i18n import _ 15 from electrum.plugin import run_hook 16 from electrum.util import NotEnoughFunds 17 18 from .fee_dialog import FeeSliderDialog, FeeDialog 19 20 if TYPE_CHECKING: 21 from electrum.gui.kivy.main_window import ElectrumWindow 22 23 Builder.load_string(''' 24 <ConfirmTxDialog@Popup> 25 id: popup 26 title: _('Confirm Payment') 27 message: '' 28 warning: '' 29 extra_fee: '' 30 show_final: False 31 size_hint: 0.8, 0.8 32 pos_hint: {'top':0.9} 33 BoxLayout: 34 orientation: 'vertical' 35 BoxLayout: 36 orientation: 'horizontal' 37 size_hint: 1, 0.5 38 Label: 39 text: _('Amount to be sent:') 40 Label: 41 id: amount_label 42 text: '' 43 BoxLayout: 44 orientation: 'horizontal' 45 size_hint: 1, 0.5 46 Label: 47 text: _('Mining fee:') 48 Label: 49 id: fee_label 50 text: '' 51 BoxLayout: 52 orientation: 'horizontal' 53 size_hint: 1, (0.5 if root.extra_fee else 0.01) 54 Label: 55 text: _('Additional fees') if root.extra_fee else '' 56 Label: 57 text: root.extra_fee 58 BoxLayout: 59 orientation: 'horizontal' 60 size_hint: 1, 0.5 61 Label: 62 text: _('Fee target:') 63 Button: 64 id: fee_button 65 text: '' 66 background_color: (0,0,0,0) 67 bold: True 68 on_release: 69 root.on_fee_button() 70 Slider: 71 id: slider 72 range: 0, 4 73 step: 1 74 on_value: root.on_slider(self.value) 75 BoxLayout: 76 orientation: 'horizontal' 77 size_hint: 1, 0.2 78 Label: 79 text: _('Final') 80 opacity: int(root.show_final) 81 CheckBox: 82 id: final_cb 83 opacity: int(root.show_final) 84 disabled: not root.show_final 85 Label: 86 text: root.warning 87 text_size: self.width, None 88 Widget: 89 size_hint: 1, 0.5 90 BoxLayout: 91 orientation: 'horizontal' 92 size_hint: 1, 0.5 93 Button: 94 text: _('Cancel') 95 size_hint: 0.5, None 96 height: '48dp' 97 on_release: 98 popup.dismiss() 99 Button: 100 text: _('OK') 101 size_hint: 0.5, None 102 height: '48dp' 103 on_release: 104 root.pay() 105 popup.dismiss() 106 ''') 107 108 109 110 111 class ConfirmTxDialog(FeeSliderDialog, Factory.Popup): 112 113 def __init__(self, app: 'ElectrumWindow', invoice): 114 115 Factory.Popup.__init__(self) 116 FeeSliderDialog.__init__(self, app.electrum_config, self.ids.slider) 117 self.app = app 118 self.show_final = bool(self.config.get('use_rbf')) 119 self.invoice = invoice 120 self.update_slider() 121 self.update_text() 122 self.update_tx() 123 124 def update_tx(self): 125 outputs = self.invoice.outputs 126 try: 127 # make unsigned transaction 128 coins = self.app.wallet.get_spendable_coins(None) 129 tx = self.app.wallet.make_unsigned_transaction(coins=coins, outputs=outputs) 130 except NotEnoughFunds: 131 self.warning = _("Not enough funds") 132 return 133 except Exception as e: 134 self.logger.exception('') 135 self.app.show_error(repr(e)) 136 return 137 rbf = not bool(self.ids.final_cb.active) if self.show_final else False 138 tx.set_rbf(rbf) 139 amount = sum(map(lambda x: x.value, outputs)) if '!' not in [x.value for x in outputs] else tx.output_value() 140 tx_size = tx.estimated_size() 141 fee = tx.get_fee() 142 feerate = Decimal(fee) / tx_size # sat/byte 143 self.ids.fee_label.text = self.app.format_amount_and_units(fee) + f' ({feerate:.1f} sat/B)' 144 self.ids.amount_label.text = self.app.format_amount_and_units(amount) 145 x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx) 146 if x_fee: 147 x_fee_address, x_fee_amount = x_fee 148 self.extra_fee = self.app.format_amount_and_units(x_fee_amount) 149 else: 150 self.extra_fee = '' 151 fee_warning_tuple = self.app.wallet.get_tx_fee_warning( 152 invoice_amt=amount, tx_size=tx_size, fee=fee) 153 if fee_warning_tuple: 154 allow_send, long_warning, short_warning = fee_warning_tuple 155 self.warning = long_warning 156 else: 157 self.warning = '' 158 self.tx = tx 159 160 def on_slider(self, value): 161 self.save_config() 162 self.update_text() 163 Clock.schedule_once(lambda dt: self.update_tx()) 164 165 def update_text(self): 166 target, tooltip, dyn = self.config.get_fee_target() 167 self.ids.fee_button.text = target 168 169 def pay(self): 170 self.app.protected(_('Send payment?'), self.app.send_screen.send_tx, (self.tx, self.invoice)) 171 172 def on_fee_button(self): 173 fee_dialog = FeeDialog(self, self.config, self.after_fee_changed) 174 fee_dialog.open() 175 176 def after_fee_changed(self): 177 self.read_config() 178 self.update_slider() 179 self.update_text() 180 Clock.schedule_once(lambda dt: self.update_tx())