commit e123774ea8d2b9f30c958c7710cfe797bb7cd4b2
parent 662577aea6557858320504b6670cae35df48eda6
Author: ThomasV <thomasv@electrum.org>
Date: Sat, 31 Dec 2016 16:29:18 +0100
Improve 'send all coins' function:
* do use coin chooser when sending all coins (fixes #2000)
* allow "!" syntax for multiple outputs (fixes #1698)
Diffstat:
5 files changed, 59 insertions(+), 53 deletions(-)
diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
@@ -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):
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -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)
diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
@@ -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)]
diff --git a/lib/commands.py b/lib/commands.py
@@ -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))
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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()