Browse Source

Format the transaction window fee rate with 1 decimal place (#4286)

* Fix format_satoshi to properly handle non-integer values

Handling the integer and fraction parts together via string formatting
simplifies the initial composition because the default behavior manages
the - sign, and the incorporation of the fractional part.

* Limit fee rate output to one decimal place

Via a new precision arg

* Introduce format_fee_satoshis and use it for all fee display
master
Ben Woosley 8 years ago committed by ghost43
parent
commit
53320470f5
  1. 2
      gui/kivy/main_window.py
  2. 10
      gui/qt/main_window.py
  3. 8
      lib/simple_config.py
  4. 38
      lib/tests/test_util.py
  5. 21
      lib/util.py
  6. 7
      lib/wallet.py

2
gui/kivy/main_window.py

@ -671,7 +671,7 @@ class ElectrumWindow(App):
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):
return format_satoshis(x, is_diff, 0, self.decimal_point(), whitespaces) return format_satoshis(x, 0, self.decimal_point(), is_diff=is_diff, whitespaces=whitespaces)
def format_amount_and_units(self, x): def format_amount_and_units(self, x):
return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit

10
gui/qt/main_window.py

@ -44,8 +44,8 @@ from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS
from electrum import constants from electrum import constants
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import (format_time, format_satoshis, PrintError, from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
format_satoshis_plain, NotEnoughFunds, format_satoshis_plain, NotEnoughFunds, PrintError,
UserCancelled, NoDynamicFeeEstimates, profiler, UserCancelled, NoDynamicFeeEstimates, profiler,
export_meta, import_meta, bh2u, bfh, InvalidPassword) export_meta, import_meta, bh2u, bfh, InvalidPassword)
from electrum import Transaction from electrum import Transaction
@ -639,7 +639,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.require_fee_update = False self.require_fee_update = False
def format_amount(self, x, is_diff=False, whitespaces=False): def format_amount(self, x, is_diff=False, whitespaces=False):
return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces) return format_satoshis(x, self.num_zeros, self.decimal_point, is_diff=is_diff, whitespaces=whitespaces)
def format_amount_and_units(self, amount): def format_amount_and_units(self, amount):
text = self.format_amount(amount) + ' '+ self.base_unit() text = self.format_amount(amount) + ' '+ self.base_unit()
@ -649,7 +649,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return text return text
def format_fee_rate(self, fee_rate): def format_fee_rate(self, fee_rate):
return format_satoshis(fee_rate/1000, False, self.num_zeros, 0, False) + ' sat/byte' return format_fee_satoshis(fee_rate/1000, self.num_zeros) + ' sat/byte'
def get_decimal_point(self): def get_decimal_point(self):
return self.decimal_point return self.decimal_point
@ -3192,5 +3192,3 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.need_update.set() self.need_update.set()
self.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), _("Transaction added to wallet history")) self.msg_box(QPixmap(":icons/offline_tx.png"), None, _('Success'), _("Transaction added to wallet history"))
return True return True

8
lib/simple_config.py

@ -7,7 +7,7 @@ import stat
from copy import deepcopy from copy import deepcopy
from .util import (user_dir, print_error, PrintError, from .util import (user_dir, print_error, PrintError,
NoDynamicFeeEstimates, format_satoshis) NoDynamicFeeEstimates, format_fee_satoshis)
from .i18n import _ from .i18n import _
FEE_ETA_TARGETS = [25, 10, 5, 2] FEE_ETA_TARGETS = [25, 10, 5, 2]
@ -367,7 +367,11 @@ class SimpleConfig(PrintError):
text is what we target: static fee / num blocks to confirm in / mempool depth text is what we target: static fee / num blocks to confirm in / mempool depth
tooltip is the corresponding estimate (e.g. num blocks for a static fee) tooltip is the corresponding estimate (e.g. num blocks for a static fee)
""" """
rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown' if fee_rate is None:
rate_str = 'unknown'
else:
rate_str = format_fee_satoshis(fee_rate/1000) + ' sat/byte'
if dyn: if dyn:
if mempool: if mempool:
depth = self.depth_target(pos) depth = self.depth_target(pos)

38
lib/tests/test_util.py

@ -8,6 +8,43 @@ class TestUtil(unittest.TestCase):
expected = "0.00001234" expected = "0.00001234"
self.assertEqual(expected, result) self.assertEqual(expected, result)
def test_format_satoshis_negative(self):
result = format_satoshis(-1234)
expected = "-0.00001234"
self.assertEqual(expected, result)
def test_format_fee(self):
result = format_satoshis(1700/1000, 0, 0)
expected = "1.7"
self.assertEqual(expected, result)
def test_format_fee_precision(self):
result = format_satoshis(1666/1000, 0, 0, precision=6)
expected = "1.666"
self.assertEqual(expected, result)
result = format_satoshis(1666/1000, 0, 0, precision=1)
expected = "1.7"
self.assertEqual(expected, result)
def test_format_satoshis_whitespaces(self):
result = format_satoshis(12340, whitespaces=True)
expected = " 0.0001234 "
self.assertEqual(expected, result)
result = format_satoshis(1234, whitespaces=True)
expected = " 0.00001234"
self.assertEqual(expected, result)
def test_format_satoshis_whitespaces_negative(self):
result = format_satoshis(-12340, whitespaces=True)
expected = " -0.0001234 "
self.assertEqual(expected, result)
result = format_satoshis(-1234, whitespaces=True)
expected = " -0.00001234"
self.assertEqual(expected, result)
def test_format_satoshis_diff_positive(self): def test_format_satoshis_diff_positive(self):
result = format_satoshis(1234, is_diff=True) result = format_satoshis(1234, is_diff=True)
expected = "+0.00001234" expected = "+0.00001234"
@ -67,4 +104,3 @@ class TestUtil(unittest.TestCase):
def test_parse_URI_parameter_polution(self): def test_parse_URI_parameter_polution(self):
self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0') self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0')

21
lib/util.py

@ -412,20 +412,18 @@ def format_satoshis_plain(x, decimal_point = 8):
return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.') return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespaces=False): def format_satoshis(x, num_zeros=0, decimal_point=8, precision=None, is_diff=False, whitespaces=False):
from locale import localeconv from locale import localeconv
if x is None: if x is None:
return 'unknown' return 'unknown'
x = int(x) # Some callers pass Decimal if precision is None:
scale_factor = pow (10, decimal_point) precision = decimal_point
integer_part = "{:d}".format(int(abs(x) / scale_factor)) decimal_format = ".0" + str(precision) if precision > 0 else ""
if x < 0: if is_diff:
integer_part = '-' + integer_part decimal_format = '+' + decimal_format
elif is_diff: result = ("{:" + decimal_format + "f}").format(x / pow (10, decimal_point)).rstrip('0')
integer_part = '+' + integer_part integer_part, fract_part = result.split(".")
dp = localeconv()['decimal_point'] dp = localeconv()['decimal_point']
fract_part = ("{:0" + str(decimal_point) + "}").format(abs(x) % scale_factor)
fract_part = fract_part.rstrip('0')
if len(fract_part) < num_zeros: if len(fract_part) < num_zeros:
fract_part += "0" * (num_zeros - len(fract_part)) fract_part += "0" * (num_zeros - len(fract_part))
result = integer_part + dp + fract_part result = integer_part + dp + fract_part
@ -434,6 +432,9 @@ def format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespa
result = " " * (15 - len(result)) + result result = " " * (15 - len(result)) + result
return result return result
def format_fee_satoshis(fee, num_zeros=0):
return format_satoshis(fee, num_zeros, 0, precision=1)
def timestamp_to_datetime(timestamp): def timestamp_to_datetime(timestamp):
if timestamp is None: if timestamp is None:
return None return None

7
lib/wallet.py

@ -45,8 +45,8 @@ import sys
from .i18n import _ from .i18n import _
from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
format_satoshis, NoDynamicFeeEstimates, TimeoutException, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
WalletFileException, BitcoinException) TimeoutException, WalletFileException, BitcoinException)
from .bitcoin import * from .bitcoin import *
from .version import * from .version import *
@ -1169,7 +1169,7 @@ class Abstract_Wallet(PrintError):
if fee is not None: if fee is not None:
size = tx.estimated_size() size = tx.estimated_size()
fee_per_byte = fee / size fee_per_byte = fee / size
extra.append('%.1f sat/b'%(fee_per_byte)) extra.append(format_fee_satoshis(fee_per_byte) + ' sat/b')
if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \ if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
and self.network and self.network.config.has_fee_mempool(): and self.network and self.network.config.has_fee_mempool():
exp_n = self.network.config.fee_to_depth(fee_per_byte) exp_n = self.network.config.fee_to_depth(fee_per_byte)
@ -2362,4 +2362,3 @@ class Wallet(object):
if wallet_type in wallet_constructors: if wallet_type in wallet_constructors:
return wallet_constructors[wallet_type] return wallet_constructors[wallet_type]
raise RuntimeError("Unknown wallet type: " + str(wallet_type)) raise RuntimeError("Unknown wallet type: " + str(wallet_type))

Loading…
Cancel
Save