main_addrimport.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 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. mmgen-addrimport: Import addresses into a MMGen coin daemon tracking wallet
  20. """
  21. from collections import namedtuple
  22. import mmgen.opts as opts
  23. from .cfg import gc,Config
  24. from .util import msg,suf,die,fmt,async_run
  25. from .addrlist import AddrList,KeyAddrList
  26. from .tw.shared import TwLabel
  27. opts_data = {
  28. 'text': {
  29. 'desc': f'Import addresses into an {gc.proj_name} tracking wallet',
  30. 'usage':'[opts] [MMGen address file]',
  31. 'options': """
  32. -h, --help Print this help message
  33. --, --longhelp Print help message for long options (common options)
  34. -a, --address=a Import the single coin address 'a'
  35. -b, --batch Import all addresses in one RPC call
  36. -l, --addrlist Address source is a flat list of non-MMGen coin addresses
  37. -k, --keyaddr-file Address source is a key-address file
  38. -q, --quiet Suppress warnings
  39. -r, --rescan Update address balances by selectively rescanning the
  40. blockchain for unspent outputs that include the imported
  41. address(es). Required if any of the imported addresses
  42. are already in the blockchain and have a balance.
  43. -t, --token-addr=A Import addresses for ERC20 token with address 'A'
  44. """,
  45. 'notes': """
  46. This command can also be used to update the comment fields or balances of
  47. addresses already in the tracking wallet.
  48. Rescanning now uses the ‘scantxoutset’ RPC call and a selective scan of
  49. blocks containing the relevant UTXOs for much faster performance than the
  50. previous implementation. The rescan operation typically takes around two
  51. minutes total, independent of the number of addresses imported.
  52. Bear in mind that the UTXO scan will not find historical transactions: to add
  53. them to the tracking wallet, you must perform a full or partial rescan of the
  54. blockchain with the ‘mmgen-tool rescan_blockchain’ utility. A full rescan of
  55. the blockchain may take up to several hours.
  56. It’s recommended to use ‘--rpc-backend=aio’ with ‘--rescan’.
  57. """
  58. }
  59. }
  60. addrimport_msgs = {
  61. 'rescan': """
  62. WARNING: You’ve chosen the ‘--rescan’ option. Rescanning the blockchain is
  63. necessary only if an address you’re importing is already in an output or
  64. outputs in the blockchain but not all transactions involving the address
  65. are known to the tracking wallet.
  66. Rescanning is performed via the UTXO method, which is only minimally affected
  67. by the number of addresses imported and typically takes just a few minutes.
  68. """,
  69. 'bad_args': f"""
  70. You must specify either an {gc.proj_name} address file, a single address with
  71. the ‘--address’ option, or a flat list of non-{gc.proj_name} addresses with
  72. the ‘--addrlist’ option.
  73. """
  74. }
  75. def parse_cmd_args(rpc,cmd_args):
  76. def import_mmgen_list(infile):
  77. al = (AddrList,KeyAddrList)[bool(cfg.keyaddr_file)](cfg,proto,infile)
  78. if al.al_id.mmtype in ('S','B'):
  79. if not rpc.info('segwit_is_active'):
  80. die(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
  81. return al
  82. if len(cmd_args) == 1:
  83. infile = cmd_args[0]
  84. from .fileutil import check_infile,get_lines_from_file
  85. check_infile(infile)
  86. if cfg.addrlist:
  87. al = AddrList(
  88. cfg = cfg,
  89. proto = proto,
  90. addrlist = get_lines_from_file(
  91. cfg,
  92. infile,
  93. f'non-{gc.proj_name} addresses',
  94. trim_comments = True ) )
  95. else:
  96. al = import_mmgen_list(infile)
  97. elif len(cmd_args) == 0 and cfg.address:
  98. al = AddrList( cfg, proto=proto, addrlist=[cfg.address] )
  99. infile = 'command line'
  100. else:
  101. die(1,addrimport_msgs['bad_args'])
  102. return al,infile
  103. def check_opts(twctl):
  104. batch = bool(cfg.batch)
  105. rescan = bool(cfg.rescan)
  106. if rescan and not 'rescan' in twctl.caps:
  107. msg(f"‘--rescan’ ignored: not supported by {type(twctl).__name__}")
  108. rescan = False
  109. if rescan and not cfg.quiet:
  110. from .ui import keypress_confirm
  111. if not keypress_confirm(
  112. cfg,
  113. '\n{}\n\nContinue?'.format(addrimport_msgs['rescan']),
  114. default_yes = True ):
  115. die(1,'Exiting at user request')
  116. if batch and not 'batch' in twctl.caps:
  117. msg(f"‘--batch’ ignored: not supported by {type(twctl).__name__}")
  118. batch = False
  119. return batch,rescan
  120. async def main():
  121. from .tw.ctl import TwCtl
  122. if cfg.token_addr:
  123. proto.tokensym = 'foo' # hack to trigger 'Token' in proto.base_proto_subclass()
  124. twctl = await TwCtl(
  125. cfg = cfg,
  126. proto = proto,
  127. token_addr = cfg.token_addr,
  128. mode = 'i' )
  129. if cfg.token or cfg.token_addr:
  130. msg(f'Importing for token {twctl.token.hl()} ({twctl.token.hlc(proto.tokensym)})')
  131. from .rpc import rpc_init
  132. twctl.rpc = await rpc_init(cfg,proto)
  133. for k,v in addrimport_msgs.items():
  134. addrimport_msgs[k] = fmt(v,indent=' ',strip_char='\t').rstrip()
  135. al,infile = parse_cmd_args(twctl.rpc,cfg._args)
  136. cfg._util.qmsg(
  137. f'OK. {al.num_addrs} addresses'
  138. + (f' from Seed ID {al.al_id.sid}' if hasattr(al.al_id,'sid') else '') )
  139. msg(
  140. f'Importing {len(al.data)} address{suf(al.data,"es")} from {infile}'
  141. + (' (batch mode)' if cfg.batch else '') )
  142. batch,rescan = check_opts(twctl)
  143. def gen_args_list(al):
  144. _d = namedtuple('import_data',['addr','twmmid','comment'])
  145. for num,e in enumerate(al.data,1):
  146. yield _d(
  147. addr = e.addr,
  148. twmmid = f'{al.al_id}:{e.idx}' if e.idx else f'{proto.base_coin.lower()}:{e.addr}',
  149. comment = e.comment )
  150. args_list = list(gen_args_list(al))
  151. await twctl.import_address_common( args_list, batch=batch )
  152. if rescan:
  153. await twctl.rescan_addresses({a.addr for a in args_list})
  154. del twctl
  155. cfg = Config( opts_data=opts_data, need_amt=False )
  156. proto = cfg._proto
  157. async_run(main())