|
|
@ -38,7 +38,7 @@ from PyQt5.QtWidgets import * |
|
|
|
|
|
|
|
from electrum.util import bh2u, bfh |
|
|
|
|
|
|
|
from electrum import keystore |
|
|
|
from electrum import keystore, simple_config |
|
|
|
from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants |
|
|
|
from electrum.plugins import run_hook |
|
|
|
from electrum.i18n import _ |
|
|
@ -54,7 +54,7 @@ try: |
|
|
|
except: |
|
|
|
plot_history = None |
|
|
|
|
|
|
|
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit |
|
|
|
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit |
|
|
|
from .qrcodewidget import QRCodeWidget, QRDialog |
|
|
|
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit |
|
|
|
from .transaction_dialog import show_transaction |
|
|
@ -1073,19 +1073,30 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
self.fee_slider = FeeSlider(self, self.config, fee_cb) |
|
|
|
self.fee_slider.setFixedWidth(140) |
|
|
|
|
|
|
|
def on_fee_or_feerate(edit_changed, editing_finished): |
|
|
|
edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e |
|
|
|
if editing_finished: |
|
|
|
if not edit_changed.get_amount(): |
|
|
|
# This is so that when the user blanks the fee and moves on, |
|
|
|
# we go back to auto-calculate mode and put a fee back. |
|
|
|
edit_changed.setModified(False) |
|
|
|
else: |
|
|
|
# edit_changed was edited just now, so make sure we will |
|
|
|
# freeze the correct fee setting (this) |
|
|
|
edit_other.setModified(False) |
|
|
|
self.update_fee() |
|
|
|
|
|
|
|
self.size_e = AmountEdit(lambda: 'bytes') |
|
|
|
self.size_e.setReadOnly(True) |
|
|
|
self.feerate_e = AmountEdit(lambda: self.base_unit() + '/kB' if self.fee_unit else 'sat/bytes') |
|
|
|
self.feerate_e.textEdited.connect(self.update_fee) |
|
|
|
|
|
|
|
self.size_e.setFrozen(True) |
|
|
|
|
|
|
|
self.feerate_e = FeerateEdit(lambda: 2 if self.fee_unit else 0) |
|
|
|
self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False)) |
|
|
|
self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True)) |
|
|
|
|
|
|
|
self.fee_e = BTCAmountEdit(self.get_decimal_point) |
|
|
|
|
|
|
|
if not self.config.get('show_fee', False): |
|
|
|
self.fee_e.setVisible(False) |
|
|
|
self.fee_e.textEdited.connect(self.update_fee) |
|
|
|
# This is so that when the user blanks the fee and moves on, |
|
|
|
# we go back to auto-calculate mode and put a fee back. |
|
|
|
self.fee_e.editingFinished.connect(self.update_fee) |
|
|
|
self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False)) |
|
|
|
self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True)) |
|
|
|
|
|
|
|
self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e) |
|
|
|
|
|
|
|
#self.rbf_checkbox = QCheckBox(_('Replaceable')) |
|
|
@ -1095,13 +1106,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
#self.rbf_checkbox.setToolTip('<p>' + ' '.join(msg) + '</p>') |
|
|
|
#self.rbf_checkbox.setVisible(False) |
|
|
|
|
|
|
|
grid.addWidget(self.fee_e_label, 5, 0) |
|
|
|
grid.addWidget(self.feerate_e, 5, 1) |
|
|
|
grid.addWidget(self.size_e, 5, 2) |
|
|
|
grid.addWidget(self.fee_e, 5, 3) |
|
|
|
vbox_feelabel = QVBoxLayout() |
|
|
|
vbox_feelabel.addWidget(self.fee_e_label) |
|
|
|
vbox_feelabel.addStretch(1) |
|
|
|
grid.addLayout(vbox_feelabel, 5, 0) |
|
|
|
|
|
|
|
self.fee_adv_controls = QWidget() |
|
|
|
hbox = QHBoxLayout(self.fee_adv_controls) |
|
|
|
hbox.setContentsMargins(0, 0, 0, 0) |
|
|
|
hbox.addWidget(self.feerate_e) |
|
|
|
hbox.addWidget(self.size_e) |
|
|
|
hbox.addWidget(self.fee_e) |
|
|
|
|
|
|
|
self.size_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet()) |
|
|
|
|
|
|
|
grid.addWidget(self.fee_slider, 6, 1) |
|
|
|
#grid.addWidget(self.rbf_checkbox, 5, 3) |
|
|
|
vbox_feecontrol = QVBoxLayout() |
|
|
|
vbox_feecontrol.addWidget(self.fee_adv_controls) |
|
|
|
vbox_feecontrol.addWidget(self.fee_slider) |
|
|
|
|
|
|
|
grid.addLayout(vbox_feecontrol, 5, 1, 1, 3) |
|
|
|
|
|
|
|
if not self.config.get('show_fee', False): |
|
|
|
self.fee_adv_controls.setVisible(False) |
|
|
|
|
|
|
|
self.preview_button = EnterButton(_("Preview"), self.do_preview) |
|
|
|
self.preview_button.setToolTip(_('Display the details of your transactions before signing it.')) |
|
|
@ -1112,7 +1138,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
buttons.addWidget(self.clear_button) |
|
|
|
buttons.addWidget(self.preview_button) |
|
|
|
buttons.addWidget(self.send_button) |
|
|
|
grid.addLayout(buttons, 7, 1, 1, 3) |
|
|
|
grid.addLayout(buttons, 6, 1, 1, 3) |
|
|
|
|
|
|
|
self.amount_e.shortcut.connect(self.spend_max) |
|
|
|
self.payto_e.textChanged.connect(self.update_fee) |
|
|
@ -1126,26 +1152,40 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
|
|
|
|
def entry_changed(): |
|
|
|
text = "" |
|
|
|
|
|
|
|
amt_color = ColorScheme.DEFAULT |
|
|
|
fee_color = ColorScheme.DEFAULT |
|
|
|
feerate_color = ColorScheme.DEFAULT |
|
|
|
|
|
|
|
if self.not_enough_funds: |
|
|
|
amt_color, fee_color = ColorScheme.RED, ColorScheme.RED |
|
|
|
feerate_color = ColorScheme.RED |
|
|
|
text = _( "Not enough funds" ) |
|
|
|
c, u, x = self.wallet.get_frozen_balance() |
|
|
|
if c+u+x: |
|
|
|
text += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')' |
|
|
|
|
|
|
|
# blue color denotes auto-filled values |
|
|
|
elif self.fee_e.isModified(): |
|
|
|
amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.DEFAULT |
|
|
|
feerate_color = ColorScheme.BLUE |
|
|
|
elif self.feerate_e.isModified(): |
|
|
|
fee_color = ColorScheme.BLUE |
|
|
|
elif self.amount_e.isModified(): |
|
|
|
amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.BLUE |
|
|
|
fee_color = ColorScheme.BLUE |
|
|
|
feerate_color = ColorScheme.BLUE |
|
|
|
else: |
|
|
|
amt_color, fee_color = ColorScheme.BLUE, ColorScheme.BLUE |
|
|
|
amt_color = ColorScheme.BLUE |
|
|
|
fee_color = ColorScheme.BLUE |
|
|
|
feerate_color = ColorScheme.BLUE |
|
|
|
|
|
|
|
self.statusBar().showMessage(text) |
|
|
|
self.amount_e.setStyleSheet(amt_color.as_stylesheet()) |
|
|
|
self.fee_e.setStyleSheet(fee_color.as_stylesheet()) |
|
|
|
self.feerate_e.setStyleSheet(feerate_color.as_stylesheet()) |
|
|
|
|
|
|
|
self.amount_e.textChanged.connect(entry_changed) |
|
|
|
self.fee_e.textChanged.connect(entry_changed) |
|
|
|
self.feerate_e.textChanged.connect(entry_changed) |
|
|
|
|
|
|
|
self.invoices_label = QLabel(_('Invoices')) |
|
|
|
from .invoice_list import InvoiceList |
|
|
@ -1186,8 +1226,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
if not self.config.get('offline') and self.config.is_dynfee() and not self.config.has_fee_estimates(): |
|
|
|
self.statusBar().showMessage(_('Waiting for fee estimates...')) |
|
|
|
return False |
|
|
|
freeze_fee = (self.fee_e.isModified() |
|
|
|
and (self.fee_e.text() or self.fee_e.hasFocus())) |
|
|
|
freeze_fee = self.is_send_fee_frozen() |
|
|
|
freeze_feerate = self.is_send_feerate_frozen() |
|
|
|
amount = '!' if self.is_max else self.amount_e.get_amount() |
|
|
|
if amount is None: |
|
|
|
if not freeze_fee: |
|
|
@ -1195,7 +1235,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
self.not_enough_funds = False |
|
|
|
self.statusBar().showMessage('') |
|
|
|
else: |
|
|
|
fee = self.fee_e.get_amount() if freeze_fee else None |
|
|
|
fee_estimator = self.get_send_fee_estimator() |
|
|
|
outputs = self.payto_e.get_outputs(self.is_max) |
|
|
|
if not outputs: |
|
|
|
_type, addr = self.get_payto_or_dummy() |
|
|
@ -1203,7 +1243,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
try: |
|
|
|
is_sweep = bool(self.tx_external_keypairs) |
|
|
|
tx = self.wallet.make_unsigned_transaction( |
|
|
|
self.get_coins(), outputs, self.config, fee, is_sweep=is_sweep) |
|
|
|
self.get_coins(), outputs, self.config, |
|
|
|
fixed_fee=fee_estimator, is_sweep=is_sweep) |
|
|
|
self.not_enough_funds = False |
|
|
|
except NotEnoughFunds: |
|
|
|
self.not_enough_funds = True |
|
|
@ -1211,18 +1252,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
self.fee_e.setAmount(None) |
|
|
|
return |
|
|
|
except BaseException: |
|
|
|
traceback.print_exc(file=sys.stderr) |
|
|
|
return |
|
|
|
|
|
|
|
size = tx.estimated_size() |
|
|
|
self.size_e.setAmount(size) |
|
|
|
|
|
|
|
|
|
|
|
fee = tx.get_fee() |
|
|
|
if not freeze_fee: |
|
|
|
fee_rate = self.config.fee_per_kb() |
|
|
|
fee = None if self.not_enough_funds else tx.get_fee() |
|
|
|
fee = None if self.not_enough_funds else fee |
|
|
|
self.fee_e.setAmount(fee) |
|
|
|
elif fee: |
|
|
|
print(size, fee) |
|
|
|
fee_rate = fee // size |
|
|
|
if not freeze_feerate: |
|
|
|
fee_rate = fee // size if fee is not None else None |
|
|
|
self.feerate_e.setAmount(fee_rate) |
|
|
|
|
|
|
|
if self.is_max: |
|
|
@ -1307,6 +1348,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
return func(self, *args, **kwargs) |
|
|
|
return request_password |
|
|
|
|
|
|
|
def is_send_fee_frozen(self): |
|
|
|
return self.fee_e.isVisible() and self.fee_e.isModified() \ |
|
|
|
and (self.fee_e.text() or self.fee_e.hasFocus()) |
|
|
|
|
|
|
|
def is_send_feerate_frozen(self): |
|
|
|
return self.feerate_e.isVisible() and self.feerate_e.isModified() \ |
|
|
|
and (self.feerate_e.text() or self.feerate_e.hasFocus()) |
|
|
|
|
|
|
|
def get_send_fee_estimator(self): |
|
|
|
if self.is_send_fee_frozen(): |
|
|
|
fee_estimator = self.fee_e.get_amount() |
|
|
|
elif self.is_send_feerate_frozen(): |
|
|
|
amount = self.feerate_e.get_amount() |
|
|
|
amount = 0 if amount is None else float(amount) |
|
|
|
fee_estimator = partial( |
|
|
|
simple_config.SimpleConfig.estimate_fee_for_feerate, amount) |
|
|
|
else: |
|
|
|
fee_estimator = None |
|
|
|
return fee_estimator |
|
|
|
|
|
|
|
def read_send_tab(self): |
|
|
|
if self.payment_request and self.payment_request.has_expired(): |
|
|
|
self.show_error(_('Payment request has expired')) |
|
|
@ -1344,10 +1405,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
self.show_error(_('Invalid Amount')) |
|
|
|
return |
|
|
|
|
|
|
|
freeze_fee = self.fee_e.isVisible() and self.fee_e.isModified() and (self.fee_e.text() or self.fee_e.hasFocus()) |
|
|
|
fee = self.fee_e.get_amount() if freeze_fee else None |
|
|
|
fee_estimator = self.get_send_fee_estimator() |
|
|
|
coins = self.get_coins() |
|
|
|
return outputs, fee, label, coins |
|
|
|
return outputs, fee_estimator, label, coins |
|
|
|
|
|
|
|
def do_preview(self): |
|
|
|
self.do_send(preview = True) |
|
|
@ -1358,11 +1418,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
r = self.read_send_tab() |
|
|
|
if not r: |
|
|
|
return |
|
|
|
outputs, fee, tx_desc, coins = r |
|
|
|
outputs, fee_estimator, tx_desc, coins = r |
|
|
|
try: |
|
|
|
is_sweep = bool(self.tx_external_keypairs) |
|
|
|
tx = self.wallet.make_unsigned_transaction( |
|
|
|
coins, outputs, self.config, fee, is_sweep=is_sweep) |
|
|
|
coins, outputs, self.config, fixed_fee=fee_estimator, |
|
|
|
is_sweep=is_sweep) |
|
|
|
except NotEnoughFunds: |
|
|
|
self.show_message(_("Insufficient funds")) |
|
|
|
return |
|
|
@ -1581,7 +1642,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
self.not_enough_funds = False |
|
|
|
self.payment_request = None |
|
|
|
self.payto_e.is_pr = False |
|
|
|
for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, self.fee_e]: |
|
|
|
for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, |
|
|
|
self.fee_e, self.feerate_e, self.size_e]: |
|
|
|
e.setText('') |
|
|
|
e.setFrozen(False) |
|
|
|
self.set_pay_from([]) |
|
|
@ -2522,7 +2584,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): |
|
|
|
feebox_cb.setToolTip(_("Show fee edit box in send tab.")) |
|
|
|
def on_feebox(x): |
|
|
|
self.config.set_key('show_fee', x == Qt.Checked) |
|
|
|
self.fee_e.setVisible(bool(x)) |
|
|
|
self.fee_adv_controls.setVisible(bool(x)) |
|
|
|
feebox_cb.stateChanged.connect(on_feebox) |
|
|
|
fee_widgets.append((feebox_cb, None)) |
|
|
|
|
|
|
|