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:
parent
662577aea6
commit
e123774ea8
|
@ -572,7 +572,9 @@ class ElectrumWindow(App):
|
||||||
def get_max_amount(self):
|
def get_max_amount(self):
|
||||||
inputs = self.wallet.get_spendable_coins(None)
|
inputs = self.wallet.get_spendable_coins(None)
|
||||||
addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
|
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())
|
return format_satoshis_plain(amount, self.decimal_point())
|
||||||
|
|
||||||
def format_amount(self, x, is_diff=False, whitespaces=False):
|
def format_amount(self, x, is_diff=False, whitespaces=False):
|
||||||
|
|
|
@ -994,27 +994,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
vbox.addWidget(self.invoices_label)
|
vbox.addWidget(self.invoices_label)
|
||||||
vbox.addWidget(self.invoice_list)
|
vbox.addWidget(self.invoice_list)
|
||||||
vbox.setStretchFactor(self.invoice_list, 1000)
|
vbox.setStretchFactor(self.invoice_list, 1000)
|
||||||
|
|
||||||
# Defer this until grid is parented to avoid ugly flash during startup
|
# Defer this until grid is parented to avoid ugly flash during startup
|
||||||
self.update_fee_edit()
|
self.update_fee_edit()
|
||||||
|
|
||||||
run_hook('create_send_tab', grid)
|
run_hook('create_send_tab', grid)
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
|
||||||
def spend_max(self):
|
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.is_max = True
|
||||||
|
self.do_update_fee()
|
||||||
|
|
||||||
def reset_max(self):
|
def reset_max(self):
|
||||||
self.is_max = False
|
self.is_max = False
|
||||||
|
@ -1034,14 +1021,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
'''
|
'''
|
||||||
freeze_fee = (self.fee_e.isModified()
|
freeze_fee = (self.fee_e.isModified()
|
||||||
and (self.fee_e.text() or self.fee_e.hasFocus()))
|
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 amount is None:
|
||||||
if not freeze_fee:
|
if not freeze_fee:
|
||||||
self.fee_e.setAmount(None)
|
self.fee_e.setAmount(None)
|
||||||
self.not_enough_funds = False
|
self.not_enough_funds = False
|
||||||
else:
|
else:
|
||||||
fee = self.fee_e.get_amount() if freeze_fee else None
|
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:
|
if not outputs:
|
||||||
_type, addr = self.get_payto_or_dummy()
|
_type, addr = self.get_payto_or_dummy()
|
||||||
outputs = [(_type, addr, amount)]
|
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)
|
fee = None if self.not_enough_funds else self.wallet.get_tx_fee(tx)
|
||||||
self.fee_e.setAmount(fee)
|
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):
|
def update_fee_edit(self):
|
||||||
b = self.config.get('dynamic_fees', True)
|
b = self.config.get('dynamic_fees', True)
|
||||||
self.fee_slider.setVisible(b)
|
self.fee_slider.setVisible(b)
|
||||||
|
@ -1132,7 +1125,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if errors:
|
if errors:
|
||||||
self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
|
self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
|
||||||
return
|
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:
|
if self.payto_e.is_alias and self.payto_e.validated is False:
|
||||||
alias = self.payto_e.toPlainText()
|
alias = self.payto_e.toPlainText()
|
||||||
|
@ -1174,7 +1167,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if not r:
|
if not r:
|
||||||
return
|
return
|
||||||
outputs, fee, tx_desc, coins = r
|
outputs, fee, tx_desc, coins = r
|
||||||
amount = sum(map(lambda x:x[2], outputs))
|
|
||||||
try:
|
try:
|
||||||
tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
|
tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
|
||||||
except NotEnoughFunds:
|
except NotEnoughFunds:
|
||||||
|
@ -1185,6 +1177,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))
|
||||||
|
|
||||||
use_rbf = self.rbf_checkbox.isChecked()
|
use_rbf = self.rbf_checkbox.isChecked()
|
||||||
if use_rbf:
|
if use_rbf:
|
||||||
tx.set_sequence(0)
|
tx.set_sequence(0)
|
||||||
|
|
|
@ -98,6 +98,8 @@ class PayToEdit(ScanQRTextEdit):
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def parse_amount(self, x):
|
def parse_amount(self, x):
|
||||||
|
if x.strip() == '!':
|
||||||
|
return '!'
|
||||||
p = pow(10, self.amount_edit.decimal_point())
|
p = pow(10, self.amount_edit.decimal_point())
|
||||||
return int(p * Decimal(x.strip()))
|
return int(p * Decimal(x.strip()))
|
||||||
|
|
||||||
|
@ -138,10 +140,17 @@ class PayToEdit(ScanQRTextEdit):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
outputs.append((_type, to_address, amount))
|
outputs.append((_type, to_address, amount))
|
||||||
|
if amount == '!':
|
||||||
|
self.win.is_max = True
|
||||||
|
else:
|
||||||
total += amount
|
total += amount
|
||||||
|
|
||||||
self.outputs = outputs
|
self.outputs = outputs
|
||||||
self.payto_address = None
|
self.payto_address = None
|
||||||
|
|
||||||
|
if self.win.is_max:
|
||||||
|
self.win.do_update_fee()
|
||||||
|
else:
|
||||||
self.amount_edit.setAmount(total if outputs else None)
|
self.amount_edit.setAmount(total if outputs else None)
|
||||||
self.win.lock_amount(total or len(lines)>1)
|
self.win.lock_amount(total or len(lines)>1)
|
||||||
|
|
||||||
|
@ -151,12 +160,13 @@ class PayToEdit(ScanQRTextEdit):
|
||||||
def get_recipient(self):
|
def get_recipient(self):
|
||||||
return self.payto_address
|
return self.payto_address
|
||||||
|
|
||||||
def get_outputs(self):
|
def get_outputs(self, is_max):
|
||||||
if self.payto_address:
|
if self.payto_address:
|
||||||
try:
|
if is_max:
|
||||||
|
amount = '!'
|
||||||
|
else:
|
||||||
amount = self.amount_edit.get_amount()
|
amount = self.amount_edit.get_amount()
|
||||||
except:
|
|
||||||
amount = None
|
|
||||||
_type, addr = self.payto_address
|
_type, addr = self.payto_address
|
||||||
self.outputs = [(_type, addr, amount)]
|
self.outputs = [(_type, addr, amount)]
|
||||||
|
|
||||||
|
|
|
@ -404,11 +404,7 @@ class Commands:
|
||||||
final_outputs = []
|
final_outputs = []
|
||||||
for address, amount in outputs:
|
for address, amount in outputs:
|
||||||
address = self._resolver(address)
|
address = self._resolver(address)
|
||||||
if amount == '!':
|
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:
|
|
||||||
amount = int(COIN*Decimal(amount))
|
amount = int(COIN*Decimal(amount))
|
||||||
final_outputs.append((TYPE_ADDRESS, address, amount))
|
final_outputs.append((TYPE_ADDRESS, address, amount))
|
||||||
|
|
||||||
|
|
|
@ -551,18 +551,6 @@ class Abstract_Wallet(PrintError):
|
||||||
def dummy_address(self):
|
def dummy_address(self):
|
||||||
return self.get_receiving_addresses()[0]
|
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):
|
def get_addresses(self):
|
||||||
out = []
|
out = []
|
||||||
out += self.get_receiving_addresses()
|
out += self.get_receiving_addresses()
|
||||||
|
@ -819,18 +807,24 @@ class Abstract_Wallet(PrintError):
|
||||||
# this method can be overloaded
|
# this method can be overloaded
|
||||||
return tx.get_fee()
|
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
|
# check outputs
|
||||||
for type, data, value in outputs:
|
i_max = None
|
||||||
if type == TYPE_ADDRESS:
|
for i, o in enumerate(outputs):
|
||||||
|
_type, data, value = o
|
||||||
|
if _type == TYPE_ADDRESS:
|
||||||
if not is_address(data):
|
if not is_address(data):
|
||||||
raise BaseException("Invalid bitcoin 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
|
# Avoid index-out-of-range with coins[0] below
|
||||||
if not coins:
|
if not inputs:
|
||||||
raise NotEnoughFunds()
|
raise NotEnoughFunds()
|
||||||
|
|
||||||
for item in coins:
|
for item in inputs:
|
||||||
self.add_input_info(item)
|
self.add_input_info(item)
|
||||||
|
|
||||||
# change address
|
# change address
|
||||||
|
@ -855,11 +849,21 @@ class Abstract_Wallet(PrintError):
|
||||||
else:
|
else:
|
||||||
fee_estimator = lambda size: fixed_fee
|
fee_estimator = lambda size: fixed_fee
|
||||||
|
|
||||||
|
if i_max is None:
|
||||||
# Let the coin chooser select the coins to spend
|
# Let the coin chooser select the coins to spend
|
||||||
max_change = self.max_change_outputs if self.multiple_change else 1
|
max_change = self.max_change_outputs if self.multiple_change else 1
|
||||||
coin_chooser = coinchooser.get_coin_chooser(config)
|
coin_chooser = coinchooser.get_coin_chooser(config)
|
||||||
tx = coin_chooser.make_tx(coins, outputs, change_addrs[:max_change],
|
tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change],
|
||||||
fee_estimator, self.dust_threshold())
|
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
|
# Sort the inputs and outputs deterministically
|
||||||
tx.BIP_LI01_sort()
|
tx.BIP_LI01_sort()
|
||||||
|
|
Loading…
Reference in New Issue