Ethereum twview support
This commit is contained in:
parent
a512423f7f
commit
854d6532dc
3 changed files with 109 additions and 35 deletions
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
101
mmgen/tw.py
101
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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue