Browse Source

tracking wallet classes: various cleanups

The MMGen Project 2 years ago
parent
commit
03712efbfb

+ 4 - 4
mmgen/obj.py

@@ -273,12 +273,12 @@ class Int(int,Hilite,InitErrors):
 			return cls.init_fail(e,n)
 
 	@classmethod
-	def fmtc(cls,*args,**kwargs):
-		cls.method_not_implemented()
+	def fmtc(cls,n,*args,**kwargs):
+		return super().fmtc(str(n),*args,**kwargs)
 
 	@classmethod
-	def colorize(cls,n,**kwargs):
-		return super().colorize(repr(n),**kwargs)
+	def colorize(cls,n,*args,**kwargs):
+		return super().colorize(str(n),*args,**kwargs)
 
 class NonNegativeInt(Int):
 	min_val = 0

+ 4 - 4
mmgen/proto/btc/tw/addresses.py

@@ -45,10 +45,10 @@ Actions: [q]uit, r[e]draw, add [l]abel:
 		'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:}}'
+	squeezed_fs_fs     = ' {{n:>{nw}}} {{m}} {{u}}%s {{c}} {{A}} {{d}}'
+	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}}%s {{c:{cw}}} {{A:{Aw}}} {{d}}'
+	wide_fs_fs         = ' {{n:>{nw}}} {{m}} {{u}} {{a}} {{c}} {{A}} {{b:<{bw}}} {{D}}'
+	wide_hdr_fs_fs     = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}} {{b:{bw}}} {{D}}'
 
 	async def get_rpc_data(self):
 

+ 4 - 4
mmgen/proto/btc/tw/txhistory.py

@@ -129,11 +129,11 @@ class BitcoinTwTransaction(BitcoinTwCommon):
 
 	def age_disp(self,age_fmt,width,color):
 		if age_fmt == 'confs':
-			ret_str = str(self.confirmations).rjust(width)
+			ret_str = str(self.confirmations).ljust(width)
 			return gray(ret_str) if self.confirmations < 0 and color else ret_str
 		elif age_fmt == 'block':
 			ret = (self.rpc.blockcount - (abs(self.confirmations) - 1)) * (-1 if self.confirmations < 0 else 1)
-			ret_str = str(ret).rjust(width)
+			ret_str = str(ret).ljust(width)
 			return gray(ret_str) if ret < 0 and color else ret_str
 		else:
 			return self.parent.date_formatter[age_fmt](self.rpc,self.tx.get('blocktime',0))
@@ -253,8 +253,8 @@ Actions: [q]uit, r[e]draw:
 		'p':'a_print_squeezed',
 		'P':'a_print_detail' }
 
-	squeezed_fs_fs     = ' {{n:>{nw}}} {{d:>{dw}}} {txid_fs}{{a1}} {{A}} {{a2}} {{l}}'
-	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{d:{dw}}} {txid_fs}{{a1:{aw}}} {{A}} {{a2:{a2w}}} {{l}}'
+	squeezed_fs_fs     = ' {{n:>{nw}}} {{d:>{dw}}} {txid_fs}{{i}} {{A}} {{o}} {{c}}'
+	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{d:{dw}}} {txid_fs}{{i:{iw}}} {{A}} {{o:{ow}}} {{c}}'
 
 	async def get_rpc_data(self):
 		blockhash = (

+ 5 - 5
mmgen/proto/eth/tw/addresses.py

@@ -17,7 +17,7 @@ from ....tw.ctl import TrackingWallet
 from ....addr import CoinAddr
 from .common import EthereumTwCommon
 
-class EthereumTwAddresses(TwAddresses,EthereumTwCommon):
+class EthereumTwAddresses(EthereumTwCommon,TwAddresses):
 
 	has_age = False
 	prompt = """
@@ -40,10 +40,10 @@ Actions: [q]uit, r[e]draw, [D]elete address, add [l]abel:
 		'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}}}'
+	squeezed_fs_fs     = ' {{n:>{nw}}} {{m:}}%s {{c:}} {{A:}}'
+	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}}%s {{c:{cw}}} {{A:{Aw}}}'
+	wide_fs_fs         = ' {{n:>{nw}}} {{m:}} {{a:}} {{c:}} {{A:}}'
+	wide_hdr_fs_fs     = ' {{n:>{nw}}} {{m:{mw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}}'
 
 	async def get_rpc_data(self):
 

+ 19 - 2
mmgen/proto/eth/tw/common.py

@@ -11,12 +11,29 @@
 """
 proto.eth.tw.common: Ethereum base protocol tracking wallet dependency classes
 """
-
+from ....globalvars import g
 from ....tw.ctl import TrackingWallet
+from ....tw.common import TwCommon
 from ....addr import CoinAddr
 from ....tw.common import TwLabel
 
-class EthereumTwCommon:
+class EthereumTwCommon(TwCommon):
+
+	def age_disp(self,o,age_fmt): # TODO
+		pass
+
+	def get_disp_prec(self,wide):
+		return self.proto.coin_amt.max_prec if wide else 8
+
+	def subheader(self,color):
+		ret = ''
+		if self.disp_prec == 8:
+			ret += 'Balances truncated to 8 decimal points\n'
+		if g.cached_balances:
+			from ....color import nocolor,yellow
+			ret += (nocolor,yellow)[color](
+				'WARNING: Using cached balances. These may be out of date!') + '\n'
+		return ret
 
 	async def get_addr_label_pairs(self,twmmid=None):
 		wallet = (

+ 1 - 1
mmgen/proto/eth/tw/ctl.py

@@ -27,7 +27,7 @@ from ....amt import ETHAmt
 from ..contract import Token,TokenResolve
 from .common import EthereumTwCommon
 
-class EthereumTrackingWallet(TrackingWallet,EthereumTwCommon):
+class EthereumTrackingWallet(EthereumTwCommon,TrackingWallet):
 
 	caps = ('batch',)
 	data_key = 'accounts'

+ 0 - 4
mmgen/proto/eth/tw/unspent.py

@@ -100,10 +100,6 @@ class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
 		await super().__init__(proto,*args,**kwargs)
 		self.proto.tokensym = self.wallet.symbol
 
-	@property
-	def disp_prec(self):
-		return 10 # truncate precision for narrow display
-
 	async def get_data(self,*args,**kwargs):
 		await super().get_data(*args,**kwargs)
 		for e in self.data:

+ 26 - 27
mmgen/tw/addresses.py

@@ -12,12 +12,10 @@
 tw.addresses: Tracking wallet listaddresses class for the MMGen suite
 """
 
-from collections import namedtuple
-
 from ..util import suf
 from ..base_obj import AsyncInit
 from ..objmethods import MMGenObject
-from ..obj import MMGenList,MMGenListItem,ImmutableAttr,ListItemAttr,TwComment,NonNegativeInt
+from ..obj import MMGenListItem,ImmutableAttr,ListItemAttr,TwComment,NonNegativeInt
 from ..rpc import rpc_init
 from ..addr import CoinAddr,MMGenID
 from ..color import red,green
@@ -30,7 +28,6 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 	item_desc = 'address'
 	txid_w = 64
 	sort_key = 'twmmid'
-	age_fmts_interactive = ('confs','block','days','date','date_time')
 	update_widths_on_age_toggle = True
 	print_output_types = ('detail',)
 	filters = ('showempty','showused','all_labels')
@@ -133,15 +130,17 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 				'mmid': max(len(d.twmmid.disp) for d in data),
 				'used': 4,
 				'amt':  self.disp_prec + 5,
-				'date': self.age_w,
+				'date': self.age_w if self.has_age else 0,
+				'block': self.age_col_params['block'][0] if wide and self.has_age else 0,
+				'date_time': self.age_col_params['date_time'][0] if wide and self.has_age else 0,
 				'spc':  7, # 6 spaces between cols + 1 leading space in fs
 			},
 			maxws = { # expandable cols
-				'addr':    max(len(d.addr) for d in data),
+				'addr':    max(len(d.addr) for d in data) if self.showcoinaddrs else 0,
 				'comment': max(d.comment.screen_width for d in data),
 			},
 			minws = {
-				'addr': 12,
+				'addr': 12 if self.showcoinaddrs else 0,
 				'comment': len('Comment'),
 			},
 			maxws_nice = {'addr': 18},
@@ -162,12 +161,12 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 			'uw': cw.used,
 			'aw': cw.addr,
 			'cw': cw.comment,
-			'bw': cw.amt,
+			'Aw': cw.amt,
 			'dw': cw.date
 		}
 
 		hdr_fs = (self.squeezed_hdr_fs_fs % ('',' {{a:{aw}}}')[self.showcoinaddrs]).format(**fs_parms)
-		fs = (self.squeezed_fs_fs % ('',' {{a:}}')[self.showcoinaddrs]).format(**fs_parms)
+		fs = (self.squeezed_fs_fs % ('',' {{a}}')[self.showcoinaddrs]).format(**fs_parms)
 
 		yield hdr_fs.format(
 			n  = '',
@@ -175,7 +174,7 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 			u  = 'Used',
 			a  = 'Address',
 			c  = 'Comment',
-			b  = 'Balance',
+			A  = 'Balance',
 			d  = self.age_hdr )
 
 		yes,no = (red('Yes '),green('No  ')) if color else ('Yes ','No  ')
@@ -187,11 +186,11 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 				yield ''
 			yield fs.format(
 				n = str(n) + ')',
-				m = MMGenID.fmtc(d.twmmid.disp,width=cw.mmid,color=True),
+				m = d.twmmid.fmt(width=cw.mmid,color=color),
 				u = yes if d.recvd else no,
-				a = d.addr.fmt(color=True,width=cw.addr),
-				c = d.comment.fmt(width=cw.comment,color=True,nullrepl='-'),
-				b = d.amt.fmt(color=True),
+				a = d.addr.fmt(color=color,width=cw.addr),
+				c = d.comment.fmt(width=cw.comment,color=color,nullrepl='-'),
+				A = d.amt.fmt(color=color,prec=self.disp_prec),
 				d = self.age_disp( d, self.age_fmt )
 			)
 
@@ -203,9 +202,9 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 			'uw': cw.used,
 			'aw': cw.addr,
 			'cw': cw.comment,
-			'bw': cw.amt,
-			'Bw': self.age_col_params['block'][0],
-			'dw': self.age_col_params['date_time'][0],
+			'Aw': cw.amt,
+			'bw': self.age_col_params['block'][0],
+			'Dw': self.age_col_params['date_time'][0],
 		}
 
 		hdr_fs = self.wide_hdr_fs_fs.format(**fs_parms)
@@ -217,9 +216,9 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 			u  = 'Used',
 			a  = 'Address',
 			c  = 'Comment',
-			b  = 'Balance',
-			B  = 'Block',
-			d  = 'Date' )
+			A  = 'Balance',
+			b  = 'Block',
+			D  = 'Date/Time' ).rstrip()
 
 		yes,no = (red('Yes '),green('No  ')) if color else ('Yes ','No  ')
 		id_save = data[0].al_id
@@ -230,14 +229,14 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 				yield ''
 			yield fs.format(
 				n = str(n) + ')',
-				m = MMGenID.fmtc(d.twmmid.disp,width=fs_parms['mw'],color=color),
+				m = d.twmmid.fmt(width=cw.mmid,color=color),
 				u = yes if d.recvd else no,
-				a = d.addr.fmt(color=color,width=fs_parms['aw']),
-				c = d.comment.fmt(width=fs_parms['cw'],color=color,nullrepl='-'),
-				b = d.amt.fmt(color=color),
-				B = self.age_disp( d, 'block' ),
-				d = self.age_disp( d, 'date_time' ),
-			)
+				a = d.addr.fmt(color=color,width=cw.addr),
+				c = d.comment.fmt(width=cw.comment,color=color,nullrepl='-'),
+				A = d.amt.fmt(color=color,prec=self.disp_prec),
+				b = self.age_disp( d, 'block' ),
+				D = self.age_disp( d, 'date_time' ),
+			).rstrip()
 
 	async def set_dates(self,addrs):
 		if not self.dates_set:

+ 8 - 3
mmgen/tw/common.py

@@ -44,7 +44,7 @@ class TwCommon:
 
 	age_fmts = ('confs','block','days','date','date_time')
 	age_fmts_date_dependent = ('days','date','date_time')
-	age_fmts_interactive = ('confs','block','days','date')
+	age_fmts_interactive = ('confs','block','days','date','date_time')
 	_age_fmt = 'confs'
 
 	age_col_params = {
@@ -138,8 +138,7 @@ class TwCommon:
 				f'{val!r}: invalid age format for {op_desc} operation (must be one of {ok_vals!r})' )
 		self._age_fmt = val
 
-	@property
-	def disp_prec(self):
+	def get_disp_prec(self,wide):
 		return self.proto.coin_amt.max_prec
 
 	def get_term_columns(self,min_cols):
@@ -277,6 +276,8 @@ class TwCommon:
 
 			dt = getattr(self.display_type,display_type)
 
+			self.disp_prec = self.get_disp_prec(wide=dt.detail)
+
 			if self.has_age and (self.age_fmt in self.age_fmts_date_dependent or dt.detail):
 				await self.set_dates(self.data)
 
@@ -509,6 +510,10 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
 		me.proto = proto
 		return me
 
+	@classmethod
+	def fmtc(cls,twmmid,*args,**kwargs):
+		return super().fmtc(twmmid.disp,*args,**kwargs)
+
 # non-displaying container for TwMMGenID,TwComment
 class TwLabel(str,InitErrors,MMGenObject):
 	exc = 'BadTwLabel'

+ 46 - 47
mmgen/tw/txhistory.py

@@ -36,7 +36,6 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 	show_txid = False
 	show_unconfirmed = False
 	show_total_amt = False
-	age_fmts_interactive = ('confs','block','days','date','date_time')
 	update_widths_on_age_toggle = True
 	print_output_types = ('squeezed','detail')
 	filters = ('show_unconfirmed',)
@@ -56,18 +55,18 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 
 	def get_column_widths(self,data,wide=False):
 
-		# var cols: addr1 addr2 comment [txid]
+		# var cols: inputs outputs comment [txid]
 		if not hasattr(self,'varcol_maxwidths'):
 			self.varcol_maxwidths = {
-				'addr1': max(len(d.vouts_disp('inputs',width=None,color=False)) for d in data),
-				'addr2': max(len(d.vouts_disp('outputs',width=None,color=False)) for d in data),
+				'inputs': max(len(d.vouts_disp('inputs',width=None,color=False)) for d in data),
+				'outputs': max(len(d.vouts_disp('outputs',width=None,color=False)) for d in data),
 				'comment': max(len(d.comment) for d in data),
 			}
 
 		maxws = self.varcol_maxwidths.copy()
 		minws = {
-			'addr1': 15,
-			'addr2': 15,
+			'inputs': 15,
+			'outputs': 15,
 			'comment': len('Comment'),
 		}
 		if self.show_txid:
@@ -75,11 +74,13 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 			minws['txid'] = 8
 			maxws_nice = {'txid': 20}
 		else:
+			maxws['txid'] = 0
+			minws['txid'] = 0
 			maxws_nice = {}
 
 		widths = { # fixed cols
 			'num': max(2,len(str(len(data)))+1),
-			'age': self.age_w,
+			'date': self.age_w,
 			'amt': self.disp_prec + 5,
 			'spc': 6 + self.show_txid, # 5(6) spaces between cols + 1 leading space in fs
 		}
@@ -92,8 +93,8 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 			yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'
 		yield 'Only wallet-related outputs are shown'
 		yield 'Comment is from first wallet address in outputs or inputs'
-		if (cw.addr1 < self.varcol_maxwidths['addr1'] or
-			cw.addr2 < self.varcol_maxwidths['addr2'] ):
+		if (cw.inputs < self.varcol_maxwidths['inputs'] or
+			cw.outputs < self.varcol_maxwidths['outputs'] ):
 			yield 'Due to screen width limitations, not all addresses could be displayed'
 		yield ''
 
@@ -101,8 +102,8 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 			nw = cw.num,
 			dw = self.age_w,
 			txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '',
-			aw = cw.addr1,
-			a2w = cw.addr2 )
+			iw = cw.inputs,
+			ow = cw.outputs )
 
 		fs = self.squeezed_fs_fs.format(
 			nw = cw.num,
@@ -110,58 +111,56 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 			txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '' )
 
 		yield hdr_fs.format(
-			n  = '',
-			i  = 'TxID',
-			d  = self.age_hdr,
-			a1 = 'Inputs',
-			A  = 'Amt({})'.format('TX' if self.show_total_amt else 'Wallet').ljust(cw.amt),
-			a2 = 'Outputs',
-			l  = 'Comment' ).rstrip()
+			n = '',
+			t = 'TxID',
+			d = self.age_hdr,
+			i = 'Inputs',
+			A = 'Amt({})'.format('TX' if self.show_total_amt else 'Wallet'),
+			o = 'Outputs',
+			c = 'Comment' ).rstrip()
 
 		for n,d in enumerate(data,1):
 			yield fs.format(
-				n  = str(n) + ')',
-				i  = d.txid_disp( width=cw.txid, color=color ) if hasattr(cw,'txid') else None,
-				d  = d.age_disp( self.age_fmt, width=self.age_w, color=color ),
-				a1 = d.vouts_disp( 'inputs', width=cw.addr1, color=color ),
-				A  = d.amt_disp(self.show_total_amt).fmt( prec=self.disp_prec, color=color ),
-				a2 = d.vouts_disp( 'outputs', width=cw.addr2, color=color ),
-				l  = d.comment.fmt( width=cw.comment, color=color ) ).rstrip()
+				n = str(n) + ')',
+				t = d.txid_disp( width=cw.txid, color=color ) if hasattr(cw,'txid') else None,
+				d = d.age_disp( self.age_fmt, width=self.age_w, color=color ),
+				i = d.vouts_disp( 'inputs', width=cw.inputs, color=color ),
+				A = d.amt_disp(self.show_total_amt).fmt( prec=self.disp_prec, color=color ),
+				o = d.vouts_disp( 'outputs', width=cw.outputs, color=color ),
+				c = d.comment.fmt( width=cw.comment, color=color ) ).rstrip()
 
 	def gen_detail_display(self,data,cw,color):
 
-		yield (
-			(f'Displaying transactions since block {self.sinceblock.hl(color=color)}\n'
-				if self.sinceblock else '')
-			+ 'Only wallet-related outputs are shown'
-		)
+		if self.sinceblock:
+			yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'
+		yield 'Only wallet-related outputs are shown'
 
 		fs = fmt("""
 		{n}
 		    Block:        [{d}] {b}
-		    TxID:         [{D}] {i}
-		    Value:        {A1}
-		    Wallet Value: {A2}
+		    TxID:         [{D}] {t}
+		    Value:        {A}
+		    Wallet Value: {B}
 		    Fee:          {f}
 		    Inputs:
-		        {a1}
-		    Outputs ({oc}):
-		        {a2}
+		        {i}
+		    Outputs ({N}):
+		        {o}
 		""",strip_char='\t').strip()
 
 		for n,d in enumerate(data,1):
 			yield fs.format(
-				n  = str(n) + ')',
-				d  = d.age_disp( 'date_time', width=None, color=None ),
-				b  = d.blockheight_disp(color=color),
-				D  = d.txdate_disp( 'date_time' ),
-				i  = d.txid_disp( width=None, color=color ),
-				A1 = d.amt_disp(True).hl( color=color ),
-				A2 = d.amt_disp(False).hl( color=color ),
-				f  = d.fee_disp( color=color ),
-				a1 = d.vouts_list_disp( 'inputs', color=color, indent=' '*8 ),
-				oc = d.nOutputs,
-				a2 = d.vouts_list_disp( 'outputs', color=color, indent=' '*8 ),
+				n = str(n) + ')',
+				d = d.age_disp( 'date_time', width=None, color=None ),
+				b = d.blockheight_disp(color=color),
+				D = d.txdate_disp( 'date_time' ),
+				t = d.txid_disp( width=None, color=color ),
+				A = d.amt_disp(True).hl( color=color ),
+				B = d.amt_disp(False).hl( color=color ),
+				f = d.fee_disp( color=color ),
+				i = d.vouts_list_disp( 'inputs', color=color, indent=' '*8 ),
+				N = d.nOutputs,
+				o = d.vouts_list_disp( 'outputs', color=color, indent=' '*8 ),
 			)
 
 	sort_disp = {

+ 11 - 5
mmgen/tw/unspent.py

@@ -20,15 +20,21 @@
 twuo: Tracking wallet unspent outputs class for the MMGen suite
 """
 
-import asyncio
 from collections import namedtuple
 
 from ..globalvars import g
-from ..color import red,yellow
-from ..util import msg,die,capfirst,suf,fmt
+from ..util import msg,suf,fmt
 from ..base_obj import AsyncInit
 from ..objmethods import MMGenObject
-from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,TwComment,get_obj,HexStr,CoinTxID,MMGenList
+from ..obj import (
+	ImmutableAttr,
+	ListItemAttr,
+	MMGenListItem,
+	TwComment,
+	get_obj,
+	HexStr,
+	CoinTxID,
+	NonNegativeInt )
 from ..addr import CoinAddr,MMGenID
 from ..rpc import rpc_init
 from .common import TwCommon,TwMMGenID,get_tw_label
@@ -48,7 +54,7 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
 
 	class MMGenTwUnspentOutput(MMGenListItem):
 		txid         = ListItemAttr(CoinTxID)
-		vout         = ListItemAttr(int,typeconv=False)
+		vout         = ListItemAttr(NonNegativeInt)
 		amt          = ImmutableAttr(None)
 		amt2         = ListItemAttr(None) # the ETH balance for token account
 		comment      = ListItemAttr(TwComment,reassign_ok=True)

+ 11 - 2
mmgen/tx/base.py

@@ -14,13 +14,22 @@ tx.base: base transaction class
 
 from ..globalvars import *
 from ..objmethods import MMGenObject
-from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,MMGenTxComment,TwComment,CoinTxID,HexStr
+from ..obj import (
+	ImmutableAttr,
+	ListItemAttr,
+	MMGenListItem,
+	MMGenTxComment,
+	TwComment,
+	CoinTxID,
+	HexStr,
+	NonNegativeInt
+)
 from ..addr import MMGenID,CoinAddr
 from ..util import msg,ymsg,fmt,remove_dups,make_timestamp,die
 from ..opts import opt
 
 class MMGenTxIO(MMGenListItem):
-	vout     = ListItemAttr(int,typeconv=False)
+	vout     = ListItemAttr(NonNegativeInt)
 	amt      = ImmutableAttr(None)
 	comment  = ListItemAttr(TwComment,reassign_ok=True)
 	mmid     = ListItemAttr(MMGenID,include_proto=True)