diff --git a/electrum/commands.py b/electrum/commands.py index 6bc172eb7..b54a41b14 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -36,7 +36,7 @@ import inspect from collections import defaultdict from functools import wraps from decimal import Decimal, InvalidOperation -from typing import Optional, TYPE_CHECKING, Dict, List, Any +from typing import Optional, TYPE_CHECKING, Dict, List, Any, Union import os import re @@ -101,8 +101,14 @@ def satoshis(amount): return int(COIN*to_decimal(amount)) if amount is not None else None -def format_satoshis(x): - return str(to_decimal(x)/COIN) if x is not None else None +def format_satoshis(x: Union[str, float, int, Decimal, None]) -> Optional[str]: + """ + input: satoshis as a Number + output: str formatted as bitcoin amount + """ + if x is None: + return None + return util.format_satoshis_plain(x, is_max_allowed=False) class Command: @@ -476,7 +482,7 @@ class Commands(Logger): for txin in wallet.get_utxos(): d = txin.to_json() v = d.pop("value_sats") - d["value"] = str(to_decimal(v)/COIN) if v is not None else None + d["value"] = format_satoshis(v) coins.append(d) return coins @@ -719,13 +725,13 @@ class Commands(Logger): """Return the balance of your wallet. """ c, u, x = wallet.get_balance() l = wallet.lnworker.get_balance() if wallet.lnworker else None - out = {"confirmed": str(to_decimal(c)/COIN)} + out = {"confirmed": format_satoshis(c)} if u: - out["unconfirmed"] = str(to_decimal(u)/COIN) + out["unconfirmed"] = format_satoshis(u) if x: - out["unmatured"] = str(to_decimal(x)/COIN) + out["unmatured"] = format_satoshis(x) if l: - out["lightning"] = str(to_decimal(l)/COIN) + out["lightning"] = format_satoshis(l) return out @command('n') @@ -738,8 +744,8 @@ class Commands(Logger): """ sh = bitcoin.address_to_scripthash(address) out = await self.network.get_balance_for_scripthash(sh) - out["confirmed"] = str(to_decimal(out["confirmed"])/COIN) - out["unconfirmed"] = str(to_decimal(out["unconfirmed"])/COIN) + out["confirmed"] = format_satoshis(out["confirmed"]) + out["unconfirmed"] = format_satoshis(out["unconfirmed"]) return out @command('n') @@ -1183,7 +1189,7 @@ class Commands(Logger): if labels or balance: item = (item,) if balance: - item += (util.format_satoshis(sum(wallet.get_addr_balance(addr))),) + item += (format_satoshis(sum(wallet.get_addr_balance(addr))),) if labels: item += (repr(wallet.get_label_for_address(addr)),) out.append(item) diff --git a/electrum/util.py b/electrum/util.py index 033283e9a..915f7ce21 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -793,10 +793,11 @@ def format_satoshis_plain( x: Union[int, float, Decimal, str], # amount in satoshis, *, decimal_point: int = 8, # how much to shift decimal point to left (default: sat->BTC) + is_max_allowed: bool = True, ) -> str: """Display a satoshi amount scaled. Always uses a '.' as a decimal point and has no thousands separator""" - if parse_max_spend(x): + if is_max_allowed and parse_max_spend(x): return f'max({x})' assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number" scale_factor = pow(10, decimal_point) diff --git a/tests/test_commands.py b/tests/test_commands.py index 2a93d099d..607a691a7 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -171,6 +171,21 @@ class TestCommands(ElectrumTestCase): with self.assertRaises(binascii.Error): # perhaps it should raise some nice UserFacingException instead await cmds.decrypt(pubkey, ciphertext+"trailinggarbage", wallet=wallet) + def test_format_satoshis(self): + format_satoshis = electrum.commands.format_satoshis + # input type is highly polymorphic: + self.assertEqual(format_satoshis(None), None) + self.assertEqual(format_satoshis(1), "0.00000001") + self.assertEqual(format_satoshis(1.0), "0.00000001") + self.assertEqual(format_satoshis(Decimal(1)), "0.00000001") + # trailing zeroes are cut + self.assertEqual(format_satoshis(51000), "0.00051") + self.assertEqual(format_satoshis(123456_12345670), "123456.1234567") + # sub-satoshi precision is rounded + self.assertEqual(format_satoshis(Decimal(123.456)), "0.00000123") + self.assertEqual(format_satoshis(Decimal(123.5)), "0.00000124") + self.assertEqual(format_satoshis(Decimal(123.789)), "0.00000124") + class TestCommandsTestnet(ElectrumTestCase): TESTNET = True