Browse Source

Ethereum twview support

MMGen 6 years ago
parent
commit
854d6532dc
3 changed files with 109 additions and 35 deletions
  1. 37 1
      mmgen/altcoins/eth/tw.py
  2. 4 1
      mmgen/obj.py
  3. 68 33
      mmgen/tw.py

+ 37 - 1
mmgen/altcoins/eth/tw.py

@@ -23,7 +23,7 @@ altcoins.eth.tw: ETH tracking wallet functions and methods for the MMGen suite
 import json
 from mmgen.common import *
 from mmgen.obj import *
-from mmgen.tw import TrackingWallet,TwAddrList
+from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs
 from mmgen.addr import AddrData
 
 # No file locking - 2 processes accessing the wallet at the same time will corrupt it
@@ -100,6 +100,42 @@ class EthereumTrackingWallet(TrackingWallet):
 		else: # emulate RPC library
 			return ('rpcfail',(None,2,"Address '{}' not found in tracking wallet".format(coinaddr)))
 
+# Use consistent naming, even though Ethereum doesn't have unspent outputs
+class EthereumTwUnspentOutputs(TwUnspentOutputs):
+
+	show_tx = False
+	can_group = False
+	prompt = """
+Sort options: [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
+Display options: show [D]ays, show [m]mgen addr, r[e]draw screen
+"""
+
+	def do_sort(self,key=None,reverse=False):
+		if key == 'txid': return
+		super(type(self),self).do_sort(key=key,reverse=reverse)
+
+	class MMGenTwUnspentOutput(MMGenListItem):
+	#	attrs = 'txid','vout','amt','label','twmmid','addr','confs','days','skip'
+		txid   = MMGenImmutableAttr('txid',str,typeconv=False)
+		vout   = MMGenImmutableAttr('vout',str,typeconv=False)
+		amt    = MMGenImmutableAttr('amt',g.proto.coin_amt.__name__)
+		label  = MMGenListItemAttr('label','TwComment',reassign_ok=True)
+		twmmid = MMGenImmutableAttr('twmmid','TwMMGenID')
+		addr   = MMGenImmutableAttr('addr','CoinAddr')
+		confs  = MMGenImmutableAttr('confs',int,typeconv=False)
+		days   = MMGenListItemAttr('days',int,typeconv=False)
+		skip   = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True)
+
+	def get_unspent_rpc(self):
+		rpc_init()
+		return map(lambda d: {
+				'txid': 'N/A',
+				'vout': '',
+				'account': TwLabel(d['mmid']+' '+d['comment'],on_fail='raise'),
+				'address': d['addr'],
+				'amount': ETHAmt(int(g.rpch.eth_getBalance('0x'+d['addr']),16),fromWei=True),
+				'confirmations': 0, # TODO
+				}, EthereumTrackingWallet().sorted_list())
 
 class EthereumTwAddrList(TwAddrList):
 

+ 4 - 1
mmgen/obj.py

@@ -308,6 +308,7 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 	max_prec = 8
 	max_amt = 21000000
 	min_coin_unit = Decimal('0.00000001') # satoshi
+	amt_fs = '4.8'
 
 	def __new__(cls,num,on_fail='die'):
 		if type(num) == cls: return num
@@ -328,7 +329,8 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 	def fmtc(cls):
 		raise NotImplementedError
 
-	def fmt(self,fs='4.8',color=False,suf=''):
+	def fmt(self,fs=None,color=False,suf=''):
+		if fs == None: fs = self.amt_fs
 		s = str(int(self)) if int(self) == self else self.normalize().__format__('f')
 		if '.' in fs:
 			p1,p2 = map(int,fs.split('.',1))
@@ -376,6 +378,7 @@ class ETHAmt(BTCAmt):
 	max_prec = 18
 	max_amt = 999999999 # TODO
 	min_coin_unit = Decimal('0.000000000000000001') # wei
+	amt_fs = '4.18'
 
 	def __new__(cls,num,on_fail='die',fromWei=False):
 		if type(num) == cls: return num

+ 68 - 33
mmgen/tw.py

@@ -28,6 +28,14 @@ CUR_HOME,ERASE_ALL = '\033[H','\033[0J'
 
 class TwUnspentOutputs(MMGenObject):
 
+	txid_w = 64
+	show_tx = True
+	can_group = True
+	prompt = """
+Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
+Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
+"""
+
 	class MMGenTwOutputList(list,MMGenObject): pass
 
 	class MMGenTwUnspentOutput(MMGenListItem):
@@ -50,6 +58,12 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 """.strip().format(g.proj_name.lower())
 	}
 
+	def __new__(cls,*args,**kwargs):
+		if g.coin == 'ETH':
+			from mmgen.altcoins.eth.tw import EthereumTwUnspentOutputs
+			cls = EthereumTwUnspentOutputs
+		return MMGenObject.__new__(cls,*args,**kwargs)
+
 	def __init__(self,minconf=1):
 		self.unspent      = self.MMGenTwOutputList()
 		self.fmt_display  = ''
@@ -68,11 +82,14 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 	def get_total_coin(self):
 		return sum(i.amt for i in self.unspent)
 
+	def get_unspent_rpc(self):
+		return g.rpch.listunspent(self.minconf)
+
 	def get_unspent_data(self):
 		if g.bogus_wallet_data: # for debugging purposes only
 			us_rpc = eval(get_data_from_file(g.bogus_wallet_data)) # testing, so ok
 		else:
-			us_rpc = g.rpch.listunspent(self.minconf)
+			us_rpc = self.get_unspent_rpc()
 #		write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
 #		sys.exit(0)
 
@@ -130,7 +147,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 			my_raw_input((m1+m2).format(g.min_screen_width))
 
 	def display(self):
-		if not opt.no_blank: msg(CUR_HOME+ERASE_ALL)
+		if not opt.no_blank: msg_r(CUR_HOME+ERASE_ALL)
 		msg(self.format_for_display())
 
 	def format_for_display(self):
@@ -148,8 +165,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 		acct_w = min(max_acct_w, max(24,addr_w-10))
 		btaddr_w = addr_w - acct_w - 1
 		label_w = acct_w - mmid_w - 1
-		tx_w = min(64,self.cols-addr_w-28-col1_w) # min=7
-		txdots = ('','..')[tx_w < 64]
+		tx_w = min(self.txid_w,self.cols-addr_w-28-col1_w) # min=7
+		txdots = ('','..')[tx_w < self.txid_w]
 
 		for i in unsp: i.skip = ''
 		if self.group and (self.sort_key in ('addr','txid','twmmid')):
@@ -158,16 +175,21 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 					if self.sort_key == k and getattr(a,k) == getattr(b,k):
 						b.skip = (k,'addr')[k=='twmmid']
 
-		hdr_fmt = 'UNSPENT OUTPUTS (sort order: {})  Total {}: {}'
+		hdr_fmt = 'UNSPENT OUTPUTS (sort order: {}) Total {}: {}'
 		out  = [hdr_fmt.format(' '.join(self.sort_info()),g.coin,self.total.hl())]
 		if g.chain in ('testnet','regtest'):
 			out += [green('Chain: {}'.format(g.chain.upper()))]
-		fs = u' {:%s} {:%s} {:2} {} {} {:<}' % (col1_w,tx_w)
-		out += [fs.format('Num',
-				'TXid'.ljust(tx_w - 5) + ' Vout', '',
-				'Address'.ljust(addr_w),
-				'Amt({})'.format(g.coin).ljust(12),
-				('Confs','Age(d)')[self.show_days])]
+		if self.show_tx:
+			fs = u' {n:%s} {t:%s} {v:2} {a} {A} {c:<}' % (col1_w,tx_w)
+		else:
+			fs = u' {n:%s} {a} {A} {c:<}' % col1_w
+		out += [fs.format(
+				n='Num',
+				t='TXid'.ljust(tx_w - 5) + ' Vout',
+				v='',
+				a='Address'.ljust(addr_w),
+				A='Amt({})'.format(g.coin).ljust(g.proto.coin_amt.max_prec+4),
+				c=('Confs','Age(d)')[self.show_days])]
 
 		for n,i in enumerate(unsp):
 			addr_dots = '|' + '.'*(addr_w-1)
@@ -187,8 +209,13 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 			tx = ' ' * (tx_w-4) + '|...' if i.skip == 'txid' \
 					else i.txid[:tx_w-len(txdots)]+txdots
 
-			out.append(fs.format(str(n+1)+')',tx,i.vout,addr_out,i.amt.fmt(color=True),
-						i.days if self.show_days else i.confs))
+			out.append(fs.format(
+						n=str(n+1)+')',
+						t=tx,
+						v=i.vout,
+						a=addr_out,
+						A=i.amt.fmt(color=True),
+						c=i.days if self.show_days else i.confs))
 
 		self.fmt_display = '\n'.join(out) + '\n'
 #		unsp.pdie()
@@ -198,29 +225,38 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 
 		addr_w = max(len(i.addr) for i in self.unspent)
 		mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in self.unspent) or 12 # DEADBEEF:S:1
-		fs  = ' {:4} {:67} {} {} {:12} {:<8} {:<6} {}'
-		out = [fs.format('Num','Tx ID,Vout',
-				'Address'.ljust(addr_w),
-				'MMGen ID'.ljust(mmid_w+1),
-				'Amount({})'.format(g.coin),
-				'Confs','Age(d)',
-				'Label')]
+		if self.show_tx:
+			fs  = ' {n:4} {t:%s} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (self.txid_w+3,g.proto.coin_amt.max_prec+4)
+		else:
+			fs  = ' {n:4} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (g.proto.coin_amt.max_prec+4)
+		out = [fs.format(
+				n='Num',
+				t='Tx ID,Vout',
+				a='Address'.ljust(addr_w),
+				m='MMGen ID'.ljust(mmid_w+1),
+				A='Amount({})'.format(g.coin),
+				c='Confs',
+				g='Age(d)',
+				l='Label')]
 
 		max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [1])
 		for n,i in enumerate(self.unspent):
 			addr = '|'+'.' * addr_w if i.skip == 'addr' and self.group else i.addr.fmt(color=color,width=addr_w)
 			tx = '|'+'.' * 63 if i.skip == 'txid' and self.group else str(i.txid)
 			out.append(
-				fs.format(str(n+1)+')', tx+','+str(i.vout),
-					addr,
-					MMGenID.fmtc(i.twmmid if i.twmmid.type=='mmgen'
+				fs.format(
+					n=str(n+1)+')',
+					t=tx+','+str(i.vout),
+					a=addr,
+					m=MMGenID.fmtc(i.twmmid if i.twmmid.type=='mmgen'
 						else 'Non-{}'.format(g.proj_name),width=mmid_w,color=color),
-					i.amt.fmt(color=color),
-					i.confs,i.days,
-					i.label.hl(color=color) if i.label else
+					A=i.amt.fmt(color=color),
+					c=i.confs,
+					g=i.days,
+					l=i.label.hl(color=color) if i.label else
 						TwComment.fmtc('',color=color,nullrepl='-',width=max_lbl_len)).rstrip())
 
-		fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal {}: {}\n'
+		fs = 'Unspent outputs ({} UTC)\nSort order: {}\n{}\n\nTotal {}: {}\n'
 		self.fmt_print = fs.format(
 				make_timestr(),
 				' '.join(self.sort_info(include_group=False)),
@@ -259,23 +295,22 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 	def view_and_sort(self,tx):
 		fs = 'Total to spend, excluding fees: {} {}\n\n'
 		txos = fs.format(tx.sum_outputs().hl(),g.coin) if tx.outputs else ''
-		prompt = """
-{}Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
-Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
-	""".format(txos).strip()
+		prompt = txos + self.prompt.strip()
 		self.display()
 		msg(prompt)
 
 		from mmgen.term import get_char
 		p = "'q'=quit view, 'p'=print to file, 'v'=pager view, 'w'=wide view, 'l'=add label:\b"
 		while True:
-			reply = get_char(p, immed_chars='atDdAMrgmeqpvw')
+			reply = get_char(p,immed_chars='atDdAMrgmeqpvw')
 			if   reply == 'a': self.do_sort('amt')
 			elif reply == 'A': self.do_sort('age')
 			elif reply == 'd': self.do_sort('addr')
 			elif reply == 'D': self.show_days = not self.show_days
 			elif reply == 'e': msg('\n{}\n{}\n{}'.format(self.fmt_display,prompt,p))
-			elif reply == 'g': self.group = not self.group
+			elif reply == 'g':
+				if self.can_group:
+					self.group = not self.group
 			elif reply == 'l':
 				idx,lbl = self.get_idx_and_label_from_user()
 				if idx: