main_addrimport.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2026 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. from .cfg import gc, Config
  23. from .util import msg, suf, die, fmt, async_run
  24. from .addrlist import AddrList, KeyAddrList
  25. opts_data = {
  26. 'filter_codes': ['-'],
  27. 'text': {
  28. 'desc': f'Import addresses into an {gc.proj_name} tracking wallet',
  29. 'usage': '{u_args}',
  30. 'options': """
  31. -- -h, --help Print this help message
  32. -- --, --longhelp Print help message for long (global) options
  33. m- -a, --autosign Import addresses from pre-created key-address file on the
  34. + removable device. The removable device is mounted and
  35. + unmounted automatically. See notes below.
  36. R- -A, --address=ADDR Import the single coin address ADDR
  37. R- -b, --batch Import all addresses in one RPC call (where applicable)
  38. R- -l, --addrlist Address source is a flat list of non-MMGen coin addresses
  39. R- -k, --keyaddr-file Address source is a key-address file
  40. -- -q, --quiet Suppress warnings
  41. b- -r, --rescan Update address balances by selectively rescanning the
  42. + blockchain for unspent outputs that include the imported
  43. + address(es). Required if any of the imported addresses
  44. + are already in the blockchain and have a balance.
  45. e- -t, --token-addr=ADDR Import addresses for ERC20 token with address ADDR
  46. """,
  47. 'notes': '{notes}',
  48. },
  49. 'code': {
  50. 'usage': lambda help_notes, s: s.format(
  51. u_args = help_notes('addrimport_args')),
  52. 'notes': lambda help_mod, s: s.format(
  53. notes = help_mod('addrimport'))
  54. }
  55. }
  56. addrimport_msgs = {
  57. 'rescan': """
  58. WARNING: You’ve chosen the ‘--rescan’ option. Rescanning the blockchain is
  59. necessary only if an address you’re importing is already in an output or
  60. outputs in the blockchain but not all transactions involving the address
  61. are known to the tracking wallet.
  62. Rescanning is performed via the UTXO method, which is only minimally affected
  63. by the number of addresses imported and typically takes just a few minutes.
  64. """,
  65. 'bad_args': f"""
  66. You must specify either an {gc.proj_name} address file, a single address with
  67. the ‘--address’ option, or a flat list of non-{gc.proj_name} addresses with
  68. the ‘--addrlist’ option.
  69. """
  70. }
  71. def parse_cmd_args(cmd_args):
  72. def import_mmgen_list(infile):
  73. return (AddrList, KeyAddrList)[bool(cfg.keyaddr_file)](cfg, proto, infile=infile)
  74. match cmd_args:
  75. case [infile]:
  76. from .fileutil import check_infile, get_lines_from_file
  77. check_infile(infile)
  78. if cfg.addrlist:
  79. return (
  80. AddrList(
  81. cfg = cfg,
  82. proto = proto,
  83. addrlist = get_lines_from_file(
  84. cfg,
  85. infile,
  86. desc = f'non-{gc.proj_name} addresses',
  87. trim_comments = True)),
  88. infile)
  89. else:
  90. return (import_mmgen_list(infile), infile)
  91. case [] if cfg.address:
  92. return (AddrList(cfg, proto=proto, addrlist=[cfg.address]), 'command line')
  93. case _:
  94. die(1, addrimport_msgs['bad_args'])
  95. def check_opts(twctl):
  96. batch = bool(cfg.batch)
  97. rescan = bool(cfg.rescan)
  98. if rescan and not 'rescan' in twctl.caps:
  99. msg(f"‘--rescan’ ignored: not supported by {type(twctl).__name__}")
  100. rescan = False
  101. if rescan and not cfg.quiet:
  102. from .ui import keypress_confirm
  103. keypress_confirm(
  104. cfg,
  105. f'\n{addrimport_msgs["rescan"]}\n\nContinue?',
  106. default_yes = True,
  107. do_exit = True)
  108. if batch and not 'batch' in twctl.caps:
  109. msg(f"‘--batch’ ignored: not supported by {type(twctl).__name__}")
  110. batch = False
  111. return batch, rescan
  112. def check_xmr_args():
  113. if not cfg.autosign:
  114. die(1, 'For XMR address import, --autosign is required')
  115. if cfg._args:
  116. die(1, 'Address file arg not supported with --autosign')
  117. async def main():
  118. if cfg._proto.base_coin == 'XMR':
  119. from .tx.util import mount_removable_device
  120. from .xmrwallet import op as xmrwallet_op
  121. check_xmr_args()
  122. mount_removable_device(cfg)
  123. op = xmrwallet_op('create', cfg, None, None, compat_call=True)
  124. if op.to_process:
  125. await op.restart_wallet_daemon()
  126. await op.main()
  127. return
  128. from .tw.ctl import TwCtl
  129. twctl = await TwCtl(
  130. cfg = cfg,
  131. proto = proto,
  132. token_addr = cfg.token_addr,
  133. mode = 'i')
  134. if cfg.token or cfg.token_addr:
  135. msg(f'Importing for token {twctl.token.hl(0)} ({twctl.token.hlc(proto.tokensym)})')
  136. for k, v in addrimport_msgs.items():
  137. addrimport_msgs[k] = fmt(v, indent=' ', strip_char='\t').rstrip()
  138. al, infile = parse_cmd_args(cfg._args)
  139. cfg._util.qmsg(
  140. f'OK. {al.num_addrs} addresses'
  141. + (f' from Seed ID {al.al_id.sid.hl()}' if hasattr(al.al_id, 'sid') else ''))
  142. msg(
  143. f'Importing {len(al.data)} address{suf(al.data, "es")} from {infile}'
  144. + (' (batch mode)' if cfg.batch else ''))
  145. batch, rescan = check_opts(twctl)
  146. def gen_args_list(al):
  147. _d = namedtuple('import_data', ['addr', 'twmmid', 'comment'])
  148. for e in al.data:
  149. yield _d(
  150. addr = e.addr,
  151. twmmid = f'{al.al_id}:{e.idx}' if e.idx else f'{proto.base_coin.lower()}:{e.addr}',
  152. comment = e.comment)
  153. args_list = list(gen_args_list(al))
  154. await twctl.import_address_common(args_list, batch=batch)
  155. if rescan:
  156. await twctl.rescan_addresses({a.addr for a in args_list})
  157. del twctl
  158. cfg = Config(opts_data=opts_data, need_amt=False)
  159. proto = cfg._proto
  160. async_run(cfg, main)