support Replace-By-Fee in GUI
This commit is contained in:
parent
8c42c6d39f
commit
bc1bef60a0
|
@ -117,11 +117,16 @@ class HistoryWidget(MyTreeWidget):
|
||||||
if not tx_hash:
|
if not tx_hash:
|
||||||
return
|
return
|
||||||
tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
|
tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
|
||||||
if not tx_URL:
|
conf, timestamp = self.wallet.get_confirmations(tx_hash)
|
||||||
return
|
tx = self.wallet.transactions.get(tx_hash)
|
||||||
|
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
|
||||||
|
rbf = is_mine and (conf == 0) and tx and not tx.is_final()
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash))
|
menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash))
|
||||||
menu.addAction(_("Details"), lambda: self.parent.show_transaction(self.wallet.transactions.get(tx_hash)))
|
menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
|
||||||
menu.addAction(_("Edit description"), lambda: self.editItem(item, self.editable_columns[0]))
|
menu.addAction(_("Edit description"), lambda: self.editItem(item, self.editable_columns[0]))
|
||||||
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
|
if rbf:
|
||||||
|
menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx))
|
||||||
|
if tx_URL:
|
||||||
|
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
|
||||||
menu.exec_(self.viewport().mapToGlobal(position))
|
menu.exec_(self.viewport().mapToGlobal(position))
|
||||||
|
|
|
@ -2785,6 +2785,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
qr_combo.currentIndexChanged.connect(on_video_device)
|
qr_combo.currentIndexChanged.connect(on_video_device)
|
||||||
gui_widgets.append((qr_label, qr_combo))
|
gui_widgets.append((qr_label, qr_combo))
|
||||||
|
|
||||||
|
rbf_cb = QCheckBox(_('Enable Replace-By-Fee'))
|
||||||
|
rbf_cb.setChecked(self.wallet.use_rbf)
|
||||||
|
if not self.config.is_modifiable('use_rbf'):
|
||||||
|
rbf_cb.setEnabled(False)
|
||||||
|
def on_rbf(x):
|
||||||
|
rbf_result = x == Qt.Checked
|
||||||
|
if self.wallet.use_rbf != rbf_result:
|
||||||
|
self.wallet.use_rbf = rbf_result
|
||||||
|
self.wallet.storage.put('use_rbf', self.wallet.use_rbf)
|
||||||
|
rbf_cb.stateChanged.connect(on_rbf)
|
||||||
|
rbf_cb.setToolTip(_('Enable RBF'))
|
||||||
|
tx_widgets.append((rbf_cb, None))
|
||||||
|
|
||||||
usechange_cb = QCheckBox(_('Use change addresses'))
|
usechange_cb = QCheckBox(_('Use change addresses'))
|
||||||
usechange_cb.setChecked(self.wallet.use_change)
|
usechange_cb.setChecked(self.wallet.use_change)
|
||||||
if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
|
if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
|
||||||
|
@ -2796,6 +2809,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
multiple_cb.setEnabled(self.wallet.use_change)
|
multiple_cb.setEnabled(self.wallet.use_change)
|
||||||
usechange_cb.stateChanged.connect(on_usechange)
|
usechange_cb.stateChanged.connect(on_usechange)
|
||||||
usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
|
usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
|
||||||
|
tx_widgets.append((usechange_cb, None))
|
||||||
|
|
||||||
def on_multiple(x):
|
def on_multiple(x):
|
||||||
multiple = x == Qt.Checked
|
multiple = x == Qt.Checked
|
||||||
|
@ -2812,7 +2826,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
]))
|
]))
|
||||||
multiple_cb.setChecked(multiple_change)
|
multiple_cb.setChecked(multiple_change)
|
||||||
multiple_cb.stateChanged.connect(on_multiple)
|
multiple_cb.stateChanged.connect(on_multiple)
|
||||||
tx_widgets.append((usechange_cb, None))
|
|
||||||
tx_widgets.append((multiple_cb, None))
|
tx_widgets.append((multiple_cb, None))
|
||||||
|
|
||||||
showtx_cb = QCheckBox(_('View transaction before signing'))
|
showtx_cb = QCheckBox(_('View transaction before signing'))
|
||||||
|
@ -2973,20 +2986,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
|
|
||||||
def show_account_details(self, k):
|
def show_account_details(self, k):
|
||||||
account = self.wallet.accounts[k]
|
account = self.wallet.accounts[k]
|
||||||
|
|
||||||
d = WindowModalDialog(self, _('Account Details'))
|
d = WindowModalDialog(self, _('Account Details'))
|
||||||
|
|
||||||
vbox = QVBoxLayout(d)
|
vbox = QVBoxLayout(d)
|
||||||
name = self.wallet.get_account_name(k)
|
name = self.wallet.get_account_name(k)
|
||||||
label = QLabel('Name: ' + name)
|
label = QLabel('Name: ' + name)
|
||||||
vbox.addWidget(label)
|
vbox.addWidget(label)
|
||||||
|
|
||||||
vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
|
vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
|
||||||
|
|
||||||
vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
|
vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
|
||||||
|
|
||||||
vbox.addWidget(QLabel(_('Master Public Key:')))
|
vbox.addWidget(QLabel(_('Master Public Key:')))
|
||||||
|
|
||||||
text = QTextEdit()
|
text = QTextEdit()
|
||||||
text.setReadOnly(True)
|
text.setReadOnly(True)
|
||||||
text.setMaximumHeight(170)
|
text.setMaximumHeight(170)
|
||||||
|
@ -2995,3 +3002,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
text.setText(mpk_text)
|
text.setText(mpk_text)
|
||||||
vbox.addLayout(Buttons(CloseButton(d)))
|
vbox.addLayout(Buttons(CloseButton(d)))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
def bump_fee_dialog(self, tx):
|
||||||
|
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
|
||||||
|
fee = -fee
|
||||||
|
d = WindowModalDialog(self, _('Bump Fee'))
|
||||||
|
vbox = QVBoxLayout(d)
|
||||||
|
vbox.addWidget(QLabel(_('Current fee') + ': %s'% self.format_amount(fee) + ' ' + self.base_unit()))
|
||||||
|
vbox.addWidget(QLabel(_('New Fee') + ': '))
|
||||||
|
e = BTCAmountEdit(self.get_decimal_point)
|
||||||
|
e.setAmount(fee + self.wallet.relayfee())
|
||||||
|
vbox.addWidget(e)
|
||||||
|
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
|
||||||
|
if not d.exec_():
|
||||||
|
return
|
||||||
|
new_fee = e.get_amount()
|
||||||
|
delta = new_fee - fee
|
||||||
|
if delta < 0:
|
||||||
|
self.show_error("fee too low")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
new_tx = self.wallet.bump_fee(tx, delta)
|
||||||
|
except BaseException as e:
|
||||||
|
self.show_error(e)
|
||||||
|
return
|
||||||
|
self.show_transaction(new_tx)
|
||||||
|
|
|
@ -172,15 +172,17 @@ class Abstract_Wallet(PrintError):
|
||||||
self.gap_limit_for_change = 6 # constant
|
self.gap_limit_for_change = 6 # constant
|
||||||
# saved fields
|
# saved fields
|
||||||
self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
|
self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
|
||||||
self.use_change = storage.get('use_change',True)
|
self.use_change = storage.get('use_change', True)
|
||||||
self.multiple_change = storage.get('multiple_change', False)
|
self.multiple_change = storage.get('multiple_change', False)
|
||||||
self.use_encryption = storage.get('use_encryption', False)
|
self.use_encryption = storage.get('use_encryption', False)
|
||||||
|
self.use_rbf = storage.get('use_rbf', False)
|
||||||
self.seed = storage.get('seed', '') # encrypted
|
self.seed = storage.get('seed', '') # encrypted
|
||||||
self.labels = storage.get('labels', {})
|
self.labels = storage.get('labels', {})
|
||||||
self.frozen_addresses = set(storage.get('frozen_addresses',[]))
|
self.frozen_addresses = set(storage.get('frozen_addresses',[]))
|
||||||
self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode)
|
self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode)
|
||||||
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
||||||
|
|
||||||
|
|
||||||
# imported_keys is deprecated. The GUI should call convert_imported_keys
|
# imported_keys is deprecated. The GUI should call convert_imported_keys
|
||||||
self.imported_keys = self.storage.get('imported_keys',{})
|
self.imported_keys = self.storage.get('imported_keys',{})
|
||||||
|
|
||||||
|
@ -988,6 +990,7 @@ class Abstract_Wallet(PrintError):
|
||||||
|
|
||||||
def add_input_info(self, txin):
|
def add_input_info(self, txin):
|
||||||
address = txin['address']
|
address = txin['address']
|
||||||
|
txin['sequence'] = 0 if self.use_rbf else 0xffffffff
|
||||||
account_id, sequence = self.get_address_index(address)
|
account_id, sequence = self.get_address_index(address)
|
||||||
account = self.accounts[account_id]
|
account = self.accounts[account_id]
|
||||||
redeemScript = account.redeem_script(*sequence)
|
redeemScript = account.redeem_script(*sequence)
|
||||||
|
@ -998,7 +1001,6 @@ class Abstract_Wallet(PrintError):
|
||||||
txin['pubkeys'] = list(pubkeys)
|
txin['pubkeys'] = list(pubkeys)
|
||||||
txin['x_pubkeys'] = list(x_pubkeys)
|
txin['x_pubkeys'] = list(x_pubkeys)
|
||||||
txin['signatures'] = [None] * len(pubkeys)
|
txin['signatures'] = [None] * len(pubkeys)
|
||||||
|
|
||||||
if redeemScript:
|
if redeemScript:
|
||||||
txin['redeemScript'] = redeemScript
|
txin['redeemScript'] = redeemScript
|
||||||
txin['num_sig'] = account.m
|
txin['num_sig'] = account.m
|
||||||
|
@ -1176,6 +1178,23 @@ class Abstract_Wallet(PrintError):
|
||||||
age = tx_age
|
age = tx_age
|
||||||
return age > age_limit
|
return age > age_limit
|
||||||
|
|
||||||
|
def bump_fee(self, tx, delta):
|
||||||
|
if tx.is_final():
|
||||||
|
raise BaseException("cannot bump fee: transaction is final")
|
||||||
|
inputs = copy.deepcopy(tx.inputs())
|
||||||
|
outputs = copy.deepcopy(tx.outputs())
|
||||||
|
for txin in inputs:
|
||||||
|
txin['signatures'] = [None] * len(txin['signatures'])
|
||||||
|
for i, o in enumerate(outputs):
|
||||||
|
otype, address, value = o
|
||||||
|
if self.is_mine(address) and value >= delta:
|
||||||
|
outputs[i] = otype, address, value - delta
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise BaseException("cannot bump fee")
|
||||||
|
new_tx = Transaction.from_io(inputs, outputs)
|
||||||
|
return new_tx
|
||||||
|
|
||||||
def can_sign(self, tx):
|
def can_sign(self, tx):
|
||||||
if self.is_watching_only():
|
if self.is_watching_only():
|
||||||
return False
|
return False
|
||||||
|
|
Loading…
Reference in New Issue