Skip to content

Commit

Permalink
mmgen-tool listaddresses: fully reimplement
Browse files Browse the repository at this point in the history
- reimplemented using the new tracking wallet display framework
- command is now interactive, with the same UI as 'twview' and 'txhist'
- the new 'Used' column shows whether an address has received funds
- the new tristate 'showused' filter allows display of only unused, used
  or all used addresses
- adding, removal and editing of labels is supported

Testing/demo:

    # Run the regtest test partially, leaving coin daemon running:
    $ test/test.py -De regtest.label

    # Try out the interactive sorting, filtering and label editing features:
    $ PYTHONPATH=. MMGEN_TEST_SUITE=1 cmds/mmgen-tool --bob listaddresses interactive=1

    # When finished, gracefully shut down the daemon:
    $ test/stop-coin-daemons.py btc_rt
  • Loading branch information
mmgen committed Nov 9, 2022
1 parent 8e04c21 commit 1d392f1
Show file tree
Hide file tree
Showing 12 changed files with 507 additions and 298 deletions.
2 changes: 1 addition & 1 deletion mmgen/data/version
@@ -1 +1 @@
13.3.dev16
13.3.dev17
83 changes: 83 additions & 0 deletions mmgen/proto/btc/tw/addresses.py
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen

"""
proto.btc.tw.addresses: Bitcoin base protocol tracking wallet address list class
"""

from ....tw.addresses import TwAddresses
from ....tw.common import TwLabel,get_obj
from ....util import msg,msg_r
from ....addr import CoinAddr
from ....obj import NonNegativeInt
from .common import BitcoinTwCommon

class BitcoinTwAddresses(TwAddresses,BitcoinTwCommon):

has_age = True
prompt = """
Sort options: [a]mt, [A]ge, [M]mid, [r]everse
Column options: toggle [D]ays/date/confs/block
Filters: show [E]mpty addrs, [u]sed addrs, all [L]abels
View/Print: pager [v]iew, [w]ide view, [p]rint
Actions: [q]uit, r[e]draw, add [l]abel:
"""
key_mappings = {
'a':'s_amt',
'A':'s_age',
'M':'s_twmmid',
'r':'d_reverse',
'D':'d_days',
'e':'d_redraw',
'E':'d_showempty',
'u':'d_showused',
'L':'d_all_labels',
'q':'a_quit',
'v':'a_view',
'w':'a_view_detail',
'p':'a_print_detail',
'l':'a_comment_add' }

squeezed_fs_fs = ' {{n:>{nw}}} {{m:}} {{u:}}%s {{c:}} {{b:}} {{d:}}'
squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}}%s {{c:{cw}}} {{b:{bw}}} {{d:}}'
wide_fs_fs = ' {{n:>{nw}}} {{m:}} {{u:}} {{a:}} {{c:}} {{b:}} {{B:<{Bw}}} {{d:}}'
wide_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}} {{a:{aw}}} {{c:{cw}}} {{b:{bw}}} {{B:{Bw}}} {{d:}}'

async def get_rpc_data(self):

msg_r('Getting unspent outputs...')
addrs = await self.get_unspent_by_mmid(self.minconf)
msg('done')

amt0 = self.proto.coin_amt('0')
self.total = sum((v['amt'] for v in addrs.values()), start=amt0 )

msg_r('Getting labels and associated addresses...')
for label,addr in await self.get_addr_label_pairs():
if label and label.mmid not in addrs:
addrs[label.mmid] = {
'addr': addr,
'amt': amt0,
'recvd': amt0,
'confs': 0,
'lbl': label }
msg('done')

msg_r('Getting received funds data...')
# args: 1:minconf, 2:include_empty, 3:include_watchonly, 4:include_immature_coinbase
for d in await self.rpc.call( 'listreceivedbylabel', 1, False, True ):
label = get_obj( TwLabel, proto=self.proto, text=d['label'] )
if label:
assert label.mmid in addrs, f'{label.mmid!r} not found in addrlist!'
addrs[label.mmid]['recvd'] = d['amount']
addrs[label.mmid]['confs'] = d['confirmations']
msg('done')

return addrs
46 changes: 0 additions & 46 deletions mmgen/proto/btc/tw/addrs.py

This file was deleted.

16 changes: 5 additions & 11 deletions mmgen/proto/btc/tw/json.py
Expand Up @@ -66,20 +66,14 @@ class Export(TwJSON.Export,Base):
@property
async def addrlist(self):
if not hasattr(self,'_addrlist'):
from .addrs import TwAddrList
self._addrlist = await TwAddrList(
proto = self.proto,
usr_addr_list = None,
minconf = 0,
showempty = True,
showcoinaddrs = True,
all_labels = False )
from .addresses import TwAddresses
self._addrlist = await TwAddresses(self.proto,get_data=True)
return self._addrlist

async def get_entries(self):
async def get_entries(self): # TODO: include 'received' field
return sorted(
[self.entry_tuple(v['lbl'].mmid, v['addr'], v['amt'], v['lbl'].comment)
for v in (await self.addrlist).values()],
[self.entry_tuple(d.twmmid.obj, d.addr, d.amt, d.comment)
for d in (await self.addrlist).data],
key = lambda x: x.mmgen_id.sort_key )

@property
Expand Down
68 changes: 68 additions & 0 deletions mmgen/proto/eth/tw/addresses.py
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen
# https://gitlab.com/mmgen/mmgen

"""
proto.eth.tw.addresses: Ethereum base protocol tracking wallet address list class
"""

from ....tw.addresses import TwAddresses
from ....tw.ctl import TrackingWallet
from ....addr import CoinAddr
from .common import EthereumTwCommon

class EthereumTwAddresses(TwAddresses,EthereumTwCommon):

has_age = False
prompt = """
Sort options: [a]mt, [M]mid, [r]everse
Filters: show [E]mpty addrs, all [L]abels
View/Print: pager [v]iew, [w]ide view, [p]rint
Actions: [q]uit, r[e]draw, [D]elete address, add [l]abel:
"""
key_mappings = {
'a':'s_amt',
'M':'s_twmmid',
'r':'d_reverse',
'e':'d_redraw',
'E':'d_showempty',
'L':'d_all_labels',
'q':'a_quit',
'l':'a_comment_add',
'D':'a_addr_delete',
'v':'a_view',
'w':'a_view_detail',
'p':'a_print_detail' }

squeezed_fs_fs = ' {{n:>{nw}}} {{m:}}%s {{c:}} {{b:}}'
squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}}%s {{c:{cw}}} {{b:{bw}}}'
wide_fs_fs = ' {{n:>{nw}}} {{m:}} {{a:}} {{c:}} {{b:}}'
wide_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{a:{aw}}} {{c:{cw}}} {{b:{bw}}}'

async def get_rpc_data(self):

amt0 = self.proto.coin_amt('0')
self.total = amt0
self.minconf = None
addrs = {}

for label,addr in await self.get_addr_label_pairs():
bal = await self.wallet.get_balance(addr)
addrs[label.mmid] = {
'addr': addr,
'amt': bal,
'recvd': amt0,
'confs': 0,
'lbl': label }
self.total += bal

return addrs

class EthereumTokenTwAddresses(EthereumTwAddresses):
pass
59 changes: 0 additions & 59 deletions mmgen/proto/eth/tw/addrs.py

This file was deleted.

93 changes: 41 additions & 52 deletions mmgen/tool/rpc.py
Expand Up @@ -44,58 +44,6 @@ async def getbalance(self,
from ..tw.bal import TwGetBalance
return (await TwGetBalance(self.proto,minconf,quiet)).format()

async def listaddress(self,
mmgen_addr:str,
minconf: 'minimum number of confirmations' = 1,
showcoinaddr: 'display coin address in addition to MMGen ID' = True,
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs' ):
"list the specified MMGen address in the tracking wallet and its balance"

return await self.listaddresses(
mmgen_addrs = mmgen_addr,
minconf = minconf,
showcoinaddrs = showcoinaddr,
age_fmt = age_fmt )

async def listaddresses(self,
mmgen_addrs: 'hyphenated range or comma-separated list of addresses' = '',
minconf: 'minimum number of confirmations' = 1,
pager: 'send output to pager' = False,
showcoinaddrs:'display coin addresses in addition to MMGen IDs' = True,
showempty: 'show addresses with no balances' = True,
all_labels: 'show all addresses with labels' = False,
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs',
sort: 'address sort order ' + options_annot_str(['reverse','age']) = '' ):
"list MMGen addresses in the tracking wallet and their balances"

show_age = bool(age_fmt)

if sort:
sort = set(sort.split(','))
sort_params = {'reverse','age'}
if not sort.issubset( sort_params ):
from ..util import die
die(1,"The sort option takes the following parameters: '{}'".format( "','".join(sort_params) ))

usr_addr_list = []
if mmgen_addrs:
a = mmgen_addrs.rsplit(':',1)
if len(a) != 2:
from ..util import die
die(1,
f'{mmgen_addrs}: invalid address list argument ' +
'(must be in form <seed ID>:[<type>:]<idx list>)' )
from ..addr import MMGenID
from ..addrlist import AddrIdxList
usr_addr_list = [MMGenID(self.proto,f'{a[0]}:{i}') for i in AddrIdxList(a[1])]

from ..tw.addrs import TwAddrList
al = await TwAddrList( self.proto, usr_addr_list, minconf, showempty, showcoinaddrs, all_labels )
if not al:
from ..util import die
die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
return await al.format( showcoinaddrs, sort, show_age, age_fmt or 'confs' )

async def twops(self,
obj,pager,reverse,detail,sort,age_fmt,interactive,
**kwargs ):
Expand Down Expand Up @@ -148,6 +96,47 @@ async def txhist(self,
return await self.twops(
obj,pager,reverse,detail,sort,age_fmt,interactive )

async def listaddress(self,
mmgen_addr:str,
wide: 'display data in wide tabular format' = False,
minconf: 'minimum number of confirmations' = 1,
showcoinaddr: 'display coin address in addition to MMGen ID' = True,
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs' ):
"list the specified MMGen address in the tracking wallet and its balance"

return await self.listaddresses(
mmgen_addrs = mmgen_addr,
wide = wide,
minconf = minconf,
showcoinaddrs = showcoinaddr,
age_fmt = age_fmt )

async def listaddresses(self,
pager: 'send output to pager' = False,
reverse: 'reverse order of unspent outputs' = False,
wide: 'display data in wide tabular format' = False,
minconf: 'minimum number of confirmations' = 1,
sort: 'address sort order ' + options_annot_str(['reverse','mmid','addr','amt']) = '',
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs',
interactive: 'enable interactive operation' = False,
mmgen_addrs: 'hyphenated range or comma-separated list of addresses' = '',
showcoinaddrs:'display coin addresses in addition to MMGen IDs' = True,
showempty: 'show addresses with no balances' = True,
showused: 'show used addresses (tristate: 0=no, 1=yes, 2=all)' = 1,
all_labels: 'show all addresses with labels' = False ):
"list MMGen addresses in the tracking wallet and their balances"

assert showused in (0,1,2), f"‘showused’ must have a value of 0, 1 or 2"

from ..tw.addresses import TwAddresses
obj = await TwAddresses(self.proto,minconf=minconf,mmgen_addrs=mmgen_addrs)
return await self.twops(
obj,pager,reverse,wide,sort,age_fmt,interactive,
showcoinaddrs = showcoinaddrs,
showempty = showempty,
showused = showused,
all_labels = all_labels )

async def add_label(self,mmgen_or_coin_addr:str,label:str):
"add descriptive label for address in tracking wallet"
from ..tw.ctl import TrackingWallet
Expand Down

0 comments on commit 1d392f1

Please sign in to comment.