follow-up prev: some clean-ups
re https://github.com/spesmilo/electrum/pull/7492
This commit is contained in:
@@ -18,7 +18,7 @@ from electrum.plugin import run_hook
|
|||||||
from electrum import util
|
from electrum import util
|
||||||
from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
|
from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
|
||||||
format_satoshis, format_satoshis_plain, format_fee_satoshis,
|
format_satoshis, format_satoshis_plain, format_fee_satoshis,
|
||||||
maybe_extract_bolt11_invoice)
|
maybe_extract_bolt11_invoice, parse_max_spend)
|
||||||
from electrum.invoices import PR_PAID, PR_FAILED
|
from electrum.invoices import PR_PAID, PR_FAILED
|
||||||
from electrum import blockchain
|
from electrum import blockchain
|
||||||
from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed
|
from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed
|
||||||
@@ -988,8 +988,8 @@ class ElectrumWindow(App, Logger):
|
|||||||
def format_amount_and_units(self, x) -> str:
|
def format_amount_and_units(self, x) -> str:
|
||||||
if x is None:
|
if x is None:
|
||||||
return 'none'
|
return 'none'
|
||||||
if x == '!':
|
if parse_max_spend(x):
|
||||||
return 'max'
|
return f'max({x})'
|
||||||
# FIXME this is using format_satoshis_plain instead of config.format_amount
|
# FIXME this is using format_satoshis_plain instead of config.format_amount
|
||||||
# as we sometimes convert the returned string back to numbers,
|
# as we sometimes convert the returned string back to numbers,
|
||||||
# via self.get_amount()... the need for converting back should be removed
|
# via self.get_amount()... the need for converting back should be removed
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ from electrum.util import (format_time,
|
|||||||
UserFacingException,
|
UserFacingException,
|
||||||
get_new_wallet_name, send_exception_to_crash_reporter,
|
get_new_wallet_name, send_exception_to_crash_reporter,
|
||||||
InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds,
|
InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds,
|
||||||
NoDynamicFeeEstimates, MultipleSpendMaxTxOutputs,
|
NoDynamicFeeEstimates,
|
||||||
AddTransactionException, BITCOIN_BIP21_URI_SCHEME,
|
AddTransactionException, BITCOIN_BIP21_URI_SCHEME,
|
||||||
InvoiceError, parse_max_spend)
|
InvoiceError, parse_max_spend)
|
||||||
from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING, Invoice
|
from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING, Invoice
|
||||||
@@ -1351,8 +1351,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||||
self.payto_e = PayToEdit(self)
|
self.payto_e = PayToEdit(self)
|
||||||
self.payto_e.addPasteButton(self.app)
|
self.payto_e.addPasteButton(self.app)
|
||||||
msg = _('Recipient of the funds.') + '\n\n'\
|
msg = (_("Recipient of the funds.") + "\n\n"
|
||||||
+ _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
|
+ _("You may enter a Bitcoin address, a label from your list of contacts "
|
||||||
|
"(a list of completions will be proposed), "
|
||||||
|
"or an alias (email-like address that forwards to a Bitcoin address)") + ". "
|
||||||
|
+ _("Lightning invoices are also supported.") + "\n\n"
|
||||||
|
+ _("You can also pay to many outputs in a single transaction, "
|
||||||
|
"specifying one output per line.") + "\n" + _("Format: address, amount") + "\n"
|
||||||
|
+ _("To set the amount to 'max', use the '!' special character.") + "\n"
|
||||||
|
+ _("Integers weights can also be used in conjunction with '!', "
|
||||||
|
"e.g. set one amount to '2!' and another to '3!' to split your coins 40-60."))
|
||||||
payto_label = HelpLabel(_('Pay to'), msg)
|
payto_label = HelpLabel(_('Pay to'), msg)
|
||||||
grid.addWidget(payto_label, 1, 0)
|
grid.addWidget(payto_label, 1, 0)
|
||||||
grid.addWidget(self.payto_e, 1, 1, 1, -1)
|
grid.addWidget(self.payto_e, 1, 1, 1, -1)
|
||||||
@@ -1451,10 +1459,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
# Check if we had enough funds excluding fees,
|
# Check if we had enough funds excluding fees,
|
||||||
# if so, still provide opportunity to set lower fees.
|
# if so, still provide opportunity to set lower fees.
|
||||||
tx = make_tx(0)
|
tx = make_tx(0)
|
||||||
except MultipleSpendMaxTxOutputs as e:
|
|
||||||
self.max_button.setChecked(False)
|
|
||||||
self.show_error(str(e))
|
|
||||||
return
|
|
||||||
except NotEnoughFunds as e:
|
except NotEnoughFunds as e:
|
||||||
self.max_button.setChecked(False)
|
self.max_button.setChecked(False)
|
||||||
text = self.get_text_not_enough_funds_mentioning_frozen()
|
text = self.get_text_not_enough_funds_mentioning_frozen()
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class OnchainInvoice(Invoice):
|
|||||||
if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN):
|
if not (0 <= value <= TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN):
|
||||||
raise InvoiceError(f"amount is out-of-bounds: {value!r} sat")
|
raise InvoiceError(f"amount is out-of-bounds: {value!r} sat")
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
if value != "!":
|
if value != '!':
|
||||||
raise InvoiceError(f"unexpected amount: {value!r}")
|
raise InvoiceError(f"unexpected amount: {value!r}")
|
||||||
else:
|
else:
|
||||||
raise InvoiceError(f"unexpected amount: {value!r}")
|
raise InvoiceError(f"unexpected amount: {value!r}")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import copy
|
|||||||
|
|
||||||
from . import ecc, bitcoin, constants, segwit_addr, bip32
|
from . import ecc, bitcoin, constants, segwit_addr, bip32
|
||||||
from .bip32 import BIP32Node
|
from .bip32 import BIP32Node
|
||||||
from .util import profiler, to_bytes, bh2u, bfh, chunks, is_hex_str
|
from .util import profiler, to_bytes, bh2u, bfh, chunks, is_hex_str, parse_max_spend
|
||||||
from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
||||||
hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr,
|
hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr,
|
||||||
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
|
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
|
||||||
@@ -109,7 +109,9 @@ class TxOutput:
|
|||||||
|
|
||||||
def __init__(self, *, scriptpubkey: bytes, value: Union[int, str]):
|
def __init__(self, *, scriptpubkey: bytes, value: Union[int, str]):
|
||||||
self.scriptpubkey = scriptpubkey
|
self.scriptpubkey = scriptpubkey
|
||||||
self.value = value # str when the output is set to max: '!' # in satoshis
|
if not (isinstance(value, int) or parse_max_spend(value) is not None):
|
||||||
|
raise ValueError(f"bad txout value: {value!r}")
|
||||||
|
self.value = value # int in satoshis; or spend-max-like str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_address_and_value(cls, address: str, value: Union[int, str]) -> Union['TxOutput', 'PartialTxOutput']:
|
def from_address_and_value(cls, address: str, value: Union[int, str]) -> Union['TxOutput', 'PartialTxOutput']:
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ def base_unit_name_to_decimal_point(unit_name: str) -> int:
|
|||||||
def parse_max_spend(amt: Any) -> Optional[int]:
|
def parse_max_spend(amt: Any) -> Optional[int]:
|
||||||
"""Checks if given amount is "spend-max"-like.
|
"""Checks if given amount is "spend-max"-like.
|
||||||
Returns None or the positive integer weight for "max". Never raises.
|
Returns None or the positive integer weight for "max". Never raises.
|
||||||
|
|
||||||
When creating invoices and on-chain txs, the user can specify to send "max".
|
When creating invoices and on-chain txs, the user can specify to send "max".
|
||||||
This is done by setting the amount to '!'. Splitting max between multiple
|
This is done by setting the amount to '!'. Splitting max between multiple
|
||||||
tx outputs is also possible, and custom weights (positive ints) can also be used.
|
tx outputs is also possible, and custom weights (positive ints) can also be used.
|
||||||
@@ -143,11 +144,6 @@ class NoDynamicFeeEstimates(Exception):
|
|||||||
return _('Dynamic fee estimates not available')
|
return _('Dynamic fee estimates not available')
|
||||||
|
|
||||||
|
|
||||||
class MultipleSpendMaxTxOutputs(Exception):
|
|
||||||
def __str__(self):
|
|
||||||
return _('At most one output can be set to spend max')
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidPassword(Exception):
|
class InvalidPassword(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Incorrect password")
|
return _("Incorrect password")
|
||||||
@@ -658,8 +654,8 @@ def format_satoshis_plain(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Display a satoshi amount scaled. Always uses a '.' as a decimal
|
"""Display a satoshi amount scaled. Always uses a '.' as a decimal
|
||||||
point and has no thousands separator"""
|
point and has no thousands separator"""
|
||||||
if x == '!':
|
if parse_max_spend(x):
|
||||||
return 'max'
|
return f'max({x})'
|
||||||
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
||||||
scale_factor = pow(10, decimal_point)
|
scale_factor = pow(10, decimal_point)
|
||||||
return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
|
return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
|
||||||
@@ -688,7 +684,7 @@ def format_satoshis(
|
|||||||
if x is None:
|
if x is None:
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
if parse_max_spend(x):
|
if parse_max_spend(x):
|
||||||
return f'max ({x}) '
|
return f'max({x})'
|
||||||
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
||||||
# lose redundant precision
|
# lose redundant precision
|
||||||
x = Decimal(x).quantize(Decimal(10) ** (-precision))
|
x = Decimal(x).quantize(Decimal(10) ** (-precision))
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ from .crypto import sha256
|
|||||||
from . import util
|
from . import util
|
||||||
from .util import (NotEnoughFunds, UserCancelled, profiler,
|
from .util import (NotEnoughFunds, UserCancelled, profiler,
|
||||||
format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
|
format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
|
||||||
WalletFileException, BitcoinException, MultipleSpendMaxTxOutputs,
|
WalletFileException, BitcoinException,
|
||||||
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
|
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
|
||||||
Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex, parse_max_spend)
|
Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex, parse_max_spend)
|
||||||
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
|
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
|
||||||
@@ -1342,7 +1342,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
weight = parse_max_spend(o.value)
|
weight = parse_max_spend(o.value)
|
||||||
if weight:
|
if weight:
|
||||||
i_max_sum += weight
|
i_max_sum += weight
|
||||||
i_max.append((weight,i))
|
i_max.append((weight, i))
|
||||||
|
|
||||||
if fee is None and self.config.fee_per_kb() is None:
|
if fee is None and self.config.fee_per_kb() is None:
|
||||||
raise NoDynamicFeeEstimates()
|
raise NoDynamicFeeEstimates()
|
||||||
@@ -1412,8 +1412,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
if amount < 0:
|
if amount < 0:
|
||||||
raise NotEnoughFunds()
|
raise NotEnoughFunds()
|
||||||
distr_amount = 0
|
distr_amount = 0
|
||||||
for (x,i) in i_max:
|
for (weight, i) in i_max:
|
||||||
val = int((amount/i_max_sum)*x)
|
val = int((amount/i_max_sum) * weight)
|
||||||
outputs[i].value = val
|
outputs[i].value = val
|
||||||
distr_amount += val
|
distr_amount += val
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user