From 015cb4aa20210bc693f6b6cc82dffb41349672c2 Mon Sep 17 00:00:00 2001 From: Wukong Date: Wed, 11 Aug 2021 15:44:36 -0700 Subject: [PATCH] If there is unavailable fund, display 2 balances: total balance and unlocked balance. For frozen balances, display a label "FROZEN" in the status notes. For unconfirmed balances, display a label "PENDING" in the status notes. Both FROZEN and LOCKED balances are considered unavailable balances, while PENDING balance is considered available balance. Because it is possible to broadcast a transaction that spends unconfirmed balance, while a user has to manually unfreeze the balance or wait until the timelock has expired before they can use FROZEN or LOCKED balances. --- jmclient/jmclient/wallet_utils.py | 55 +++++++++++++++++++++++++++---- scripts/joinmarket-qt.py | 5 ++- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index 5ec17d7..4379a1e 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -142,8 +142,16 @@ class WalletViewBase(object): raise NotImplementedError("Separate conf/unconf balances not impl.") return sum([x.get_balance() for x in self.children]) + def get_available_balance(self): + return sum([x.get_available_balance() for x in self.children]) + def get_fmt_balance(self, include_unconf=True): - return "{0:.08f}".format(self.get_balance(include_unconf)) + available_balance = self.get_available_balance() + total_balance = self.get_balance(include_unconf) + if available_balance != total_balance: + return "{0:.08f} ({1:.08f})".format(available_balance, total_balance) + else: + return "{0:.08f}".format(total_balance) class WalletViewEntry(WalletViewBase): def __init__(self, wallet_path_repr, account, address_type, aindex, addr, amounts, @@ -166,6 +174,12 @@ class WalletViewEntry(WalletViewBase): self.status = status self.label = label + def is_locked(self): + return "[LOCKED]" in self.status + + def is_frozen(self): + return "[FROZEN]" in self.status + def get_balance(self, include_unconf=True): """Overwrites base class since no children """ @@ -173,6 +187,9 @@ class WalletViewEntry(WalletViewBase): raise NotImplementedError("Separate conf/unconf balances not impl.") return self.unconfirmed_amount/1e8 + def get_available_balance(self, include_unconf=True): + return 0 if self.is_locked() or self.is_frozen() else self.get_balance() + def serialize(self): left = self.serialize_wallet_position() addr = self.serialize_address() @@ -435,6 +452,22 @@ def wallet_showutxos(wallet_service, showprivkey): return json.dumps(unsp, indent=4, ensure_ascii=False) +def get_utxo_status_string(utxos, utxos_enabled, path): + has_frozen_utxo = False + has_pending_utxo = False + for utxo, utxodata in utxos.items(): + if path == utxodata["path"]: + if not utxo in utxos_enabled: + has_frozen_utxo = True + if utxodata['confs'] <= 0: + has_pending_utxo = True + + utxo_status_string = "" + if has_frozen_utxo: + utxo_status_string += ' [FROZEN]' + if has_pending_utxo: + utxo_status_string += ' [PENDING]' + return utxo_status_string def wallet_display(wallet_service, showprivkey, displayall=False, serialized=True, summarized=False, mixdepth=None, jsonified=False): @@ -442,13 +475,15 @@ def wallet_display(wallet_service, showprivkey, displayall=False, then return its serialization directly if serialized, else return the WalletView object. """ - def get_addr_status(addr_path, utxos, is_new, is_internal): + def get_addr_status(addr_path, utxos, utxos_enabled, is_new, is_internal): addr_balance = 0 status = [] + for utxo, utxodata in utxos.items(): if addr_path != utxodata['path']: continue addr_balance += utxodata['value'] + #TODO it is a failure of abstraction here that # the bitcoin core interface is used directly #the function should either be removed or added to bci @@ -472,12 +507,15 @@ def wallet_display(wallet_service, showprivkey, displayall=False, elif len(status) == 1: out_status = status[0] + out_status += get_utxo_status_string(utxos, utxos_enabled, addr_path) + return addr_balance, out_status acctlist = [] - # TODO - either optionally not show disabled utxos, or - # mark them differently in display (labels; colors) - utxos = wallet_service.get_utxos_by_mixdepth(include_disabled=True) + + utxos = wallet_service.get_utxos_by_mixdepth(include_disabled=True, includeconfs=True) + utxos_enabled = wallet_service.get_utxos_by_mixdepth() + if mixdepth: md_range = range(mixdepth, mixdepth + 1) else: @@ -502,7 +540,7 @@ def wallet_display(wallet_service, showprivkey, displayall=False, gap_addrs.append(addr) label = wallet_service.get_address_label(addr) balance, status = get_addr_status( - path, utxos[m], k >= unused_index, address_type) + path, utxos[m], utxos_enabled[m], k >= unused_index, address_type) if showprivkey: privkey = wallet_service.get_wif_path(path) else: @@ -536,10 +574,13 @@ def wallet_display(wallet_service, showprivkey, displayall=False, label = wallet_service.get_address_label(addr) timelock = datetime.utcfromtimestamp(0) + timedelta(seconds=path[-1]) - balance = sum([utxodata["value"] for utxo, utxodata in + balance = sum([utxodata["value"] for _, utxodata in utxos[m].items() if path == utxodata["path"]]) + status = timelock.strftime("%Y-%m-%d") + " [" + ( "LOCKED" if datetime.now() < timelock else "UNLOCKED") + "]" + status += get_utxo_status_string(utxos[m], utxos_enabled[m], path) + privkey = "" if showprivkey: privkey = wallet_service.get_wif_path(path) diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index 357e1cf..cff12b9 100755 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -1556,9 +1556,8 @@ class JMWalletTab(QWidget): # if expansion states existed, reinstate them: if len(previous_expand_states) == max_mixdepth_count: m_item.setExpanded(previous_expand_states[mixdepth][0]) - # by default, if the mixdepth is 0 or balance of the mixdepth is - # greater than 0, expand it - elif mixdepth == 0 or float(mdbalance) > 0: + # we expand at the mixdepth level by default + else: m_item.setExpanded(True) for address_type in [