proto.xmr.tw.view: add age sort, locked balance display
NOTE: Age sort is meaningful only for addresses with 10 or fewer confirmations.
This commit is contained in:
parent
ba732b4610
commit
be17d06708
5 changed files with 102 additions and 19 deletions
|
|
@ -1 +1 @@
|
|||
16.1.dev16
|
||||
16.1.dev17
|
||||
|
|
|
|||
|
|
@ -31,12 +31,13 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
nice_addr_w = {'addr': 20}
|
||||
|
||||
prompt_fs_in = [
|
||||
'Sort options: [a]mount, a[d]dr, [M]mgen addr, [r]everse',
|
||||
'Sort options: [a]mount, [A]ge, a[d]dr, [M]mgen addr, [r]everse',
|
||||
'Display options: r[e]draw screen',
|
||||
'View/Print: pager [v]iew, [w]ide pager view, [p]rint to file{s}',
|
||||
'Actions: [q]uit menu, add [l]abel, [R]efresh balances:']
|
||||
extra_key_mappings = {
|
||||
'R': 'a_sync_wallets'}
|
||||
'R': 'a_sync_wallets',
|
||||
'A': 's_age'}
|
||||
|
||||
sort_disp = {
|
||||
'addr': 'Addr',
|
||||
|
|
@ -47,7 +48,7 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
# NB: For account-based views, ALL sort keys MUST begin with acct_sort_key!
|
||||
sort_funcs = {
|
||||
'addr': lambda i: '{}:{}'.format(i.twmmid.obj.acct_sort_key, i.addr),
|
||||
'age': lambda i: i.twmmid.sort_key, # dummy (age sort not supported)
|
||||
'age': lambda i: '{}:{:020}'.format(i.twmmid.obj.acct_sort_key, 0 - i.confs),
|
||||
'amt': lambda i: '{}:{:050}'.format(i.twmmid.obj.acct_sort_key, i.amt.to_unit('atomic')),
|
||||
'twmmid': lambda i: i.twmmid.sort_key} # sort_key begins with acct_sort_key
|
||||
|
||||
|
|
@ -63,32 +64,36 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
addr = data['addr'],
|
||||
confs = data['confs'],
|
||||
comment = data['lbl'].comment,
|
||||
amt = data['amt'])
|
||||
amt = data['amt'],
|
||||
unlocked_amt = data['unlocked_amt'])
|
||||
for twmmid, data in rpc_data.items())
|
||||
|
||||
def get_disp_data(self):
|
||||
chk_fail_msg = 'For account-based views, ALL sort keys MUST begin with acct_sort_key!'
|
||||
ad = namedtuple('accts_data', ['idx', 'acct_idx', 'total', 'data'])
|
||||
ad = namedtuple('accts_data', ['idx', 'acct_idx', 'total', 'unlocked_total', 'data'])
|
||||
bd = namedtuple('accts_data_data', ['disp_data_idx', 'data'])
|
||||
def gen_accts_data():
|
||||
idx, acct_idx, total, d_acc = (None, None, 0, {})
|
||||
idx, acct_idx = (None, None)
|
||||
total, unlocked_total, d_acc = (0, 0, {})
|
||||
chk_acc = [] # check for out-of-order accounts (developer idiot-proofing)
|
||||
for n, d in enumerate(self.data):
|
||||
m = d.twmmid.obj
|
||||
if idx != m.idx or acct_idx != m.acct_idx:
|
||||
if idx:
|
||||
yield ad(idx, acct_idx, total, d_acc)
|
||||
yield ad(idx, acct_idx, total, unlocked_total, d_acc)
|
||||
chk_acc.append((m.idx, m.acct_idx))
|
||||
idx = m.idx
|
||||
acct_idx = m.acct_idx
|
||||
total = d.amt
|
||||
unlocked_total = d.unlocked_amt
|
||||
d_acc = {m.addr_idx: bd(n, d)}
|
||||
else:
|
||||
total += d.amt
|
||||
unlocked_total += d.unlocked_amt
|
||||
d_acc[m.addr_idx] = bd(n, d)
|
||||
if idx:
|
||||
assert len(set(chk_acc)) == len(chk_acc), chk_fail_msg
|
||||
yield ad(idx, acct_idx, total, d_acc)
|
||||
yield ad(idx, acct_idx, total, unlocked_total, d_acc)
|
||||
self.accts_data = tuple(gen_accts_data())
|
||||
return super().get_disp_data()
|
||||
|
||||
|
|
@ -129,7 +134,10 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
str(n + 1) + ')',
|
||||
d.idx.fmt(6, color=color),
|
||||
d.acct_idx.fmt(7, color=color),
|
||||
d.total.hl(color=color)).ljust(self.term_width)
|
||||
d.total.hl2(
|
||||
color = color,
|
||||
color_override = None if d.total == d.unlocked_total else 'orange'
|
||||
)).ljust(self.term_width)
|
||||
for v in d.data.values():
|
||||
yield fmt_method(None, v.data, cw, fs, color, None, None)
|
||||
|
||||
|
|
@ -138,7 +146,11 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
I = d.twmmid.obj.addr_idx.fmt(cw.addr_idx, color=color),
|
||||
a = d.addr.fmt(self.addr_view_pref, cw.addr, color=color),
|
||||
c = d.comment.fmt2(cw.comment, color=color, nullrepl='-'),
|
||||
A = d.amt.fmt(cw.iwidth, color=color, prec=self.disp_prec))
|
||||
A = d.amt.fmt2(
|
||||
cw.iwidth,
|
||||
color = color,
|
||||
color_override = None if d.amt == d.unlocked_amt else 'orange',
|
||||
prec = self.disp_prec))
|
||||
|
||||
async def get_idx_from_user(self):
|
||||
if res := await self.get_idx(f'{self.item_desc} number', self.accts_data):
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
proto.xmr.tw.view: Monero protocol base class for tracking wallet view classes
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from ....xmrwallet import op as xmrwallet_op
|
||||
from ....seed import SeedID
|
||||
from ....tw.view import TwView
|
||||
|
|
@ -32,33 +34,48 @@ class MoneroTwView:
|
|||
if wallets_data:
|
||||
self.sid = SeedID(sid=wallets_data[0]['seed_id'])
|
||||
|
||||
self.total = self.proto.coin_amt('0')
|
||||
self.total = self.unlocked_total = self.proto.coin_amt('0')
|
||||
|
||||
def gen_addrs():
|
||||
bd = namedtuple('address_balance_data', ['bal', 'unlocked_bal', 'blocks_to_unlock'])
|
||||
for wdata in wallets_data:
|
||||
bals_data = {i: {} for i in range(len(wdata['data'].accts_data['subaddress_accounts']))}
|
||||
|
||||
for d in wdata['data'].bals_data.get('per_subaddress', []):
|
||||
bals_data[d['account_index']].update({d['address_index']: d['unlocked_balance']})
|
||||
bals_data[d['account_index']].update({
|
||||
d['address_index']: bd(
|
||||
d['balance'],
|
||||
d['unlocked_balance'],
|
||||
d['blocks_to_unlock'])})
|
||||
|
||||
for acct_idx, acct_data in enumerate(wdata['data'].addrs_data):
|
||||
for addr_data in acct_data['addresses']:
|
||||
addr_idx = addr_data['address_index']
|
||||
self.total += (bal := self.proto.coin_amt(
|
||||
bals_data[acct_idx].get(addr_idx, 0),
|
||||
from_unit = 'atomic'))
|
||||
if self.include_empty or bal:
|
||||
addr_bals = bals_data[acct_idx].get(addr_idx)
|
||||
bal = self.proto.coin_amt(
|
||||
addr_bals.bal if addr_bals else 0,
|
||||
from_unit = 'atomic')
|
||||
unlocked_bal = self.proto.coin_amt(
|
||||
addr_bals.unlocked_bal if addr_bals else 0,
|
||||
from_unit = 'atomic')
|
||||
if bal or self.include_empty:
|
||||
self.total += bal
|
||||
self.unlocked_total += unlocked_bal
|
||||
mmid = '{}:M:{}-{}/{}'.format(
|
||||
wdata['seed_id'],
|
||||
wdata['wallet_num'],
|
||||
acct_idx,
|
||||
addr_idx)
|
||||
btu = addr_bals.blocks_to_unlock if addr_bals else 0
|
||||
if not btu and bal != unlocked_bal:
|
||||
btu = 12
|
||||
yield (TwMMGenID(self.proto, mmid), {
|
||||
'addr': addr_data['address'],
|
||||
'amt': bal,
|
||||
'unlocked_amt': unlocked_bal,
|
||||
'recvd': bal,
|
||||
'is_used': addr_data['used'],
|
||||
'confs': 1,
|
||||
'confs': 11 - btu,
|
||||
'lbl': TwLabel(self.proto, mmid + ' ' + addr_data['label'])})
|
||||
|
||||
return dict(gen_addrs())
|
||||
|
|
|
|||
|
|
@ -458,7 +458,13 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
make_timestr(self.rpc.cur_date))
|
||||
|
||||
if hasattr(self, 'total'):
|
||||
yield 'Total {}: {}'.format(self.proto.dcoin, self.total.hl(color=color))
|
||||
if hasattr(self, 'unlocked_total') and self.total != self.unlocked_total:
|
||||
yield 'Total {}: {} {}'.format(
|
||||
self.proto.dcoin,
|
||||
self.unlocked_total.hl(color=color),
|
||||
self.total.hl3(color_override='orange', encl='[]'))
|
||||
else:
|
||||
yield 'Total {}: {}'.format(self.proto.dcoin, self.total.hl(color=color))
|
||||
|
||||
yield from getattr(self, dt.subhdr_fmt_method)(cw, color)
|
||||
|
||||
|
|
|
|||
|
|
@ -518,6 +518,17 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
('fund_alice1b', 'sending funds to Alice (wallet #1)'),
|
||||
('mine_blocks_10', 'mining some blocks'),
|
||||
('alice_twview1', 'adding label to Alice’s tracking wallets (twview)'),
|
||||
('new_account_alice', 'adding an account to Alice’s wallet'),
|
||||
('new_address_alice', 'adding an address to Alice’s wallet'),
|
||||
('new_address_alice_label', 'adding an address to Alice’s wallet (with label)'),
|
||||
('alice_dump', 'dumping alice’s wallets to JSON format'),
|
||||
('fund_alice_sub1', 'sending funds to Alice’s subaddress #1 (wallet #2)'),
|
||||
('mine_blocks_1', 'mining a block'),
|
||||
('fund_alice_sub2', 'sending funds to Alice’s subaddress #2 (wallet #2)'),
|
||||
('mine_blocks_1', 'mining a block'),
|
||||
('fund_alice_sub3', 'sending funds to Alice’s subaddress #3 (wallet #2)'),
|
||||
('alice_twview2', 'viewing Alice’s tracking wallets (reload, sort options)'),
|
||||
('alice_twview3', 'viewing Alice’s tracking wallets (check balances)'),
|
||||
)
|
||||
|
||||
def __init__(self, cfg, trunner, cfgs, spawn):
|
||||
|
|
@ -537,6 +548,9 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
def create_watchonly_wallets(self):
|
||||
return self._create_wallets()
|
||||
|
||||
async def mine_blocks_1(self):
|
||||
return await self._mine_blocks(1)
|
||||
|
||||
async def mine_blocks_10(self):
|
||||
return await self._mine_blocks(10)
|
||||
|
||||
|
|
@ -544,6 +558,29 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
self.spawn(msg_only=True)
|
||||
return await self.mine(n)
|
||||
|
||||
def _new_addr_alice(self, *args):
|
||||
return self.new_addr_alice(*args, do_autosign=True)
|
||||
|
||||
async def alice_dump(self):
|
||||
t = self._xmr_autosign_op('dump')
|
||||
t.read()
|
||||
self.remove_device_online() # device was inserted by _xmr_autosign_op()
|
||||
return t
|
||||
|
||||
async def fund_alice_sub1(self):
|
||||
return await self._fund_alice(1, 9876543210)
|
||||
|
||||
async def fund_alice_sub2(self):
|
||||
return await self._fund_alice(2, 8765432109)
|
||||
|
||||
async def fund_alice_sub3(self):
|
||||
return await self._fund_alice(3, 7654321098)
|
||||
|
||||
async def _fund_alice(self, addr_num, amt):
|
||||
data = json.loads(read_from_file(self.alice_dump_file))
|
||||
addr_data = data['MoneroMMGenWalletDumpFile']['data']['wallet_metadata'][1]['addresses']
|
||||
return await self.fund_alice(addr=addr_data[addr_num-1]['address'], amt=amt)
|
||||
|
||||
def alice_listaddresses1(self):
|
||||
return self._alice_twops(
|
||||
'listaddresses',
|
||||
|
|
@ -563,6 +600,17 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
menu = 'R',
|
||||
expect_str = r'New Label.*2\.469135782468')
|
||||
|
||||
def alice_twview2(self):
|
||||
return self._alice_twops('twview', menu='RaAdMraAdMe')
|
||||
|
||||
def alice_twview3(self):
|
||||
return self._alice_twops(
|
||||
'twview',
|
||||
expect_arr = [
|
||||
'Total XMR: 3.722345649021 [3.729999970119]',
|
||||
'1 0.026296296417',
|
||||
'0.007654321098'])
|
||||
|
||||
def _alice_twops(
|
||||
self,
|
||||
op,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue