tw.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2018 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. altcoins.eth.tw: ETH tracking wallet functions and methods for the MMGen suite
  20. """
  21. import json
  22. from mmgen.common import *
  23. from mmgen.obj import *
  24. from mmgen.tw import TrackingWallet,TwAddrList,TwUnspentOutputs
  25. from mmgen.addr import AddrData
  26. # No file locking - 2 processes accessing the wallet at the same time will corrupt it
  27. class EthereumTrackingWallet(TrackingWallet):
  28. data_dir = os.path.join(g.altcoin_data_dir,'eth')
  29. tw_file = os.path.join(data_dir,'tracking-wallet.json')
  30. def __init__(self):
  31. check_or_create_dir(self.data_dir)
  32. try:
  33. self.data = json.loads(get_data_from_file(self.tw_file,silent=True))
  34. except:
  35. try: os.stat(self.tw_file)
  36. except: self.data = {}
  37. else: die(2,"File '{}' exists but does not contain valid json data")
  38. else:
  39. for d in self.data:
  40. self.data[d]['mmid'] = TwMMGenID(self.data[d]['mmid'],on_fail='raise')
  41. self.data[d]['comment'] = TwComment(self.data[d]['comment'],on_fail='raise')
  42. def import_address(self,addr,label):
  43. if addr in self.data:
  44. if not self.data[addr]['mmid'] and label.mmid:
  45. msg("Warning: MMGen ID '{}' was missing in tracking wallet!".format(label.mmid))
  46. elif self.data[addr]['mmid'] != label.mmid:
  47. die(3,"MMGen ID '{}' does not match tracking wallet!".format(label.mmid))
  48. self.data[addr] = { 'mmid': label.mmid, 'comment': label.comment }
  49. def write(self):
  50. write_data_to_file(
  51. self.tw_file,
  52. json.dumps(self.data),
  53. 'Ethereum address data',
  54. ask_overwrite=False,
  55. silent=True)
  56. def delete_all(self):
  57. self.data = {}
  58. self.write()
  59. def delete(self,addr):
  60. if is_coin_addr(addr):
  61. have_match = lambda k: k == addr
  62. elif is_mmgen_id(addr):
  63. have_match = lambda k: self.data[k]['mmid'] == addr
  64. else:
  65. die(1,"'{}' is not an Ethereum address or MMGen ID".format(addr))
  66. for k in self.data:
  67. if have_match(k):
  68. del self.data[k]
  69. break
  70. else:
  71. die(1,"Address '{}' not found in tracking wallet".format(addr))
  72. self.write()
  73. def sorted_list(self):
  74. return sorted(
  75. map(lambda x: {'addr':x[0], 'mmid':x[1]['mmid'], 'comment':x[1]['comment'] }, self.data.items()),
  76. key=lambda x: x['mmid'].sort_key+x['addr']
  77. )
  78. def mmid_ordered_dict(self):
  79. from collections import OrderedDict
  80. return OrderedDict(map(lambda x: (x['mmid'],{'addr':x['addr'],'comment':x['comment']}), self.sorted_list()))
  81. def import_label(self,coinaddr,lbl):
  82. for addr,d in self.data.items():
  83. if addr == coinaddr:
  84. d['comment'] = lbl.comment
  85. self.write()
  86. return None
  87. else: # emulate RPC library
  88. return ('rpcfail',(None,2,"Address '{}' not found in tracking wallet".format(coinaddr)))
  89. # Use consistent naming, even though Ethereum doesn't have unspent outputs
  90. class EthereumTwUnspentOutputs(TwUnspentOutputs):
  91. show_txid = False
  92. can_group = False
  93. hdr_fmt = 'TRACKED ACCOUNTS (sort order: {})\nTotal {}: {}'
  94. prompt = """
  95. Sort options: [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
  96. Display options: show [D]ays, show [m]mgen addr, r[e]draw screen
  97. """
  98. def do_sort(self,key=None,reverse=False):
  99. if key == 'txid': return
  100. super(type(self),self).do_sort(key=key,reverse=reverse)
  101. def get_unspent_rpc(self):
  102. rpc_init()
  103. return map(lambda d: {
  104. 'txid': '0'*64, # bogus value, not displayed
  105. 'vout': 0, # ""
  106. 'account': TwLabel(d['mmid']+' '+d['comment'],on_fail='raise'),
  107. 'address': d['addr'],
  108. 'amount': ETHAmt(int(g.rpch.eth_getBalance('0x'+d['addr']),16),'wei'),
  109. 'confirmations': 0, # TODO
  110. }, EthereumTrackingWallet().sorted_list())
  111. class EthereumTwAddrList(TwAddrList):
  112. def __init__(self,usr_addr_list,minconf,showempty,showbtcaddrs,all_labels):
  113. tw = EthereumTrackingWallet().mmid_ordered_dict()
  114. self.total = g.proto.coin_amt('0')
  115. rpc_init()
  116. # cur_blk = int(g.rpch.eth_blockNumber(),16)
  117. for mmid,d in tw.items():
  118. # if d['confirmations'] < minconf: continue
  119. label = TwLabel(mmid+' '+d['comment'],on_fail='raise')
  120. if usr_addr_list and (label.mmid not in usr_addr_list): continue
  121. bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+d['addr']),16),'wei')
  122. if bal == 0 and not showempty:
  123. if not label.comment: continue
  124. if not all_labels: continue
  125. self[label.mmid] = {'amt': g.proto.coin_amt('0'), 'lbl': label }
  126. if showbtcaddrs:
  127. self[label.mmid]['addr'] = CoinAddr(d['addr'])
  128. self[label.mmid]['lbl'].mmid.confs = 9999 # TODO
  129. self[label.mmid]['amt'] += bal
  130. self.total += bal
  131. class EthereumAddrData(AddrData):
  132. @classmethod
  133. def get_tw_data(cls):
  134. vmsg('Getting address data from tracking wallet')
  135. tw = EthereumTrackingWallet().mmid_ordered_dict()
  136. # emulate the output of RPC 'listaccounts' and 'getaddressesbyaccount'
  137. return [(mmid+' '+d['comment'],[d['addr']]) for mmid,d in tw.items()]