Improve 'send all coins' function:

* do use coin chooser when sending all coins (fixes #2000)
* allow "!" syntax for multiple outputs (fixes #1698)
This commit is contained in:
ThomasV 2016-12-31 16:29:18 +01:00
parent 662577aea6
commit e123774ea8
5 changed files with 59 additions and 53 deletions

View File

@ -572,7 +572,9 @@ class ElectrumWindow(App):
def get_max_amount(self):
inputs = self.wallet.get_spendable_coins(None)
addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
amount, fee = self.wallet.get_max_amount(self.electrum_config, inputs, (TYPE_ADDRESS, addr), None)
outputs = [(TYPE_ADDRESS, addr, '!')]
tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
amount = tx.output_value()
return format_satoshis_plain(amount, self.decimal_point())
def format_amount(self, x, is_diff=False, whitespaces=False):

View File

@ -994,27 +994,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
vbox.addWidget(self.invoices_label)
vbox.addWidget(self.invoice_list)
vbox.setStretchFactor(self.invoice_list, 1000)
# Defer this until grid is parented to avoid ugly flash during startup
self.update_fee_edit()
run_hook('create_send_tab', grid)
return w
def spend_max(self):
inputs = self.get_coins()
sendable = sum(map(lambda x:x['value'], inputs))
fee = self.fee_e.get_amount() if self.fee_e.isModified() else None
r = self.get_payto_or_dummy()
amount, fee = self.wallet.get_max_amount(self.config, inputs, r, fee)
if not self.fee_e.isModified():
self.fee_e.setAmount(fee)
self.amount_e.setAmount(amount)
self.not_enough_funds = (fee + amount > sendable)
# emit signal for fiat_amount update
self.amount_e.textEdited.emit("")
self.is_max = True
self.do_update_fee()
def reset_max(self):
self.is_max = False
@ -1034,14 +1021,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
'''
freeze_fee = (self.fee_e.isModified()
and (self.fee_e.text() or self.fee_e.hasFocus()))
amount = self.amount_e.get_amount()
amount = '!' if self.is_max else self.amount_e.get_amount()
if amount is None:
if not freeze_fee:
self.fee_e.setAmount(None)
self.not_enough_funds = False
else:
fee = self.fee_e.get_amount() if freeze_fee else None
outputs = self.payto_e.get_outputs()
outputs = self.payto_e.get_outputs(self.is_max)
if not outputs:
_type, addr = self.get_payto_or_dummy()
outputs = [(_type, addr, amount)]
@ -1054,6 +1041,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
fee = None if self.not_enough_funds else self.wallet.get_tx_fee(tx)
self.fee_e.setAmount(fee)
if self.is_max:
amount = tx.output_value()
self.amount_e.setAmount(amount)
self.amount_e.textEdited.emit("")
def update_fee_edit(self):
b = self.config.get('dynamic_fees', True)
self.fee_slider.setVisible(b)
@ -1132,7 +1125,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if errors:
self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
return
outputs = self.payto_e.get_outputs()
outputs = self.payto_e.get_outputs(self.is_max)
if self.payto_e.is_alias and self.payto_e.validated is False:
alias = self.payto_e.toPlainText()
@ -1174,7 +1167,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if not r:
return
outputs, fee, tx_desc, coins = r
amount = sum(map(lambda x:x[2], outputs))
try:
tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
except NotEnoughFunds:
@ -1185,6 +1177,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_message(str(e))
return
amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))
use_rbf = self.rbf_checkbox.isChecked()
if use_rbf:
tx.set_sequence(0)

View File

@ -98,6 +98,8 @@ class PayToEdit(ScanQRTextEdit):
return script
def parse_amount(self, x):
if x.strip() == '!':
return '!'
p = pow(10, self.amount_edit.decimal_point())
return int(p * Decimal(x.strip()))
@ -138,12 +140,19 @@ class PayToEdit(ScanQRTextEdit):
continue
outputs.append((_type, to_address, amount))
total += amount
if amount == '!':
self.win.is_max = True
else:
total += amount
self.outputs = outputs
self.payto_address = None
self.amount_edit.setAmount(total if outputs else None)
self.win.lock_amount(total or len(lines)>1)
if self.win.is_max:
self.win.do_update_fee()
else:
self.amount_edit.setAmount(total if outputs else None)
self.win.lock_amount(total or len(lines)>1)
def get_errors(self):
return self.errors
@ -151,12 +160,13 @@ class PayToEdit(ScanQRTextEdit):
def get_recipient(self):
return self.payto_address
def get_outputs(self):
def get_outputs(self, is_max):
if self.payto_address:
try:
if is_max:
amount = '!'
else:
amount = self.amount_edit.get_amount()
except:
amount = None
_type, addr = self.payto_address
self.outputs = [(_type, addr, amount)]

View File

@ -404,11 +404,7 @@ class Commands:
final_outputs = []
for address, amount in outputs:
address = self._resolver(address)
if amount == '!':
assert len(outputs) == 1
inputs = self.wallet.get_spendable_coins(domain)
amount, fee = self.wallet.get_max_amount(self.config, inputs, (TYPE_ADDRESS, address), fee)
else:
if amount != '!':
amount = int(COIN*Decimal(amount))
final_outputs.append((TYPE_ADDRESS, address, amount))

View File

@ -551,18 +551,6 @@ class Abstract_Wallet(PrintError):
def dummy_address(self):
return self.get_receiving_addresses()[0]
def get_max_amount(self, config, inputs, recipient, fee):
sendable = sum(map(lambda x:x['value'], inputs))
if fee is None:
for i in inputs:
self.add_input_info(i)
_type, addr = recipient
outputs = [(_type, addr, sendable)]
dummy_tx = Transaction.from_io(inputs, outputs)
fee = self.estimate_fee(config, dummy_tx.estimated_size())
amount = max(0, sendable - fee)
return amount, fee
def get_addresses(self):
out = []
out += self.get_receiving_addresses()
@ -819,18 +807,24 @@ class Abstract_Wallet(PrintError):
# this method can be overloaded
return tx.get_fee()
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None):
def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, change_addr=None):
# check outputs
for type, data, value in outputs:
if type == TYPE_ADDRESS:
i_max = None
for i, o in enumerate(outputs):
_type, data, value = o
if _type == TYPE_ADDRESS:
if not is_address(data):
raise BaseException("Invalid bitcoin address:" + data)
if value == '!':
if i_max is not None:
raise BaseException("More than one output set to spend max")
i_max = i
# Avoid index-out-of-range with coins[0] below
if not coins:
if not inputs:
raise NotEnoughFunds()
for item in coins:
for item in inputs:
self.add_input_info(item)
# change address
@ -855,11 +849,21 @@ class Abstract_Wallet(PrintError):
else:
fee_estimator = lambda size: fixed_fee
# Let the coin chooser select the coins to spend
max_change = self.max_change_outputs if self.multiple_change else 1
coin_chooser = coinchooser.get_coin_chooser(config)
tx = coin_chooser.make_tx(coins, outputs, change_addrs[:max_change],
fee_estimator, self.dust_threshold())
if i_max is None:
# Let the coin chooser select the coins to spend
max_change = self.max_change_outputs if self.multiple_change else 1
coin_chooser = coinchooser.get_coin_chooser(config)
tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change],
fee_estimator, self.dust_threshold())
else:
sendable = sum(map(lambda x:x['value'], inputs))
_type, data, value = outputs[i_max]
outputs[i_max] = (_type, data, 0)
tx = Transaction.from_io(inputs, outputs[:])
fee = fee_estimator(tx.estimated_size())
amount = max(0, sendable - tx.output_value() - fee)
outputs[i_max] = (_type, data, amount)
tx = Transaction.from_io(inputs, outputs[:])
# Sort the inputs and outputs deterministically
tx.BIP_LI01_sort()