Ethereum twview support

This commit is contained in:
The MMGen Project 2018-05-26 20:50:21 +00:00
commit 854d6532dc
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 109 additions and 35 deletions

View file

@ -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):

View file

@ -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

View file

@ -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: