main_txsend.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
  20. """
  21. import sys
  22. from .cfg import gc, Config
  23. from .util import msg, async_run, die
  24. opts_data = {
  25. 'sets': [
  26. ('yes', True, 'quiet', True),
  27. ('abort', True, 'autosign', True),
  28. ],
  29. 'text': {
  30. 'desc': f'Send a signed {gc.proj_name} cryptocoin transaction',
  31. 'usage2': [
  32. '[opts] <signed transaction file>',
  33. '[opts] --autosign',
  34. '[opts] --autosign (--status | --receipt) [index or range]',
  35. ],
  36. 'options': """
  37. -h, --help Print this help message
  38. --, --longhelp Print help message for long (global) options
  39. -a, --autosign Send an autosigned transaction created by ‘mmgen-txcreate
  40. --autosign’. The removable device is mounted and unmounted
  41. automatically. The transaction file argument must be omitted
  42. when using this option
  43. -A, --abort Abort an unsent transaction created by ‘mmgen-txcreate
  44. --autosign’ and delete it from the removable device. The
  45. transaction may be signed or unsigned.
  46. -d, --outdir=d Specify an alternate directory 'd' for output
  47. -H, --dump-hex=F Instead of sending to the network, dump the transaction hex
  48. to file ‘F’. Use filename ‘-’ to dump to standard output.
  49. -m, --mark-sent Mark the transaction as sent by adding it to the removable
  50. device. Used in combination with --autosign when a trans-
  51. action has been successfully sent out-of-band.
  52. -n, --tx-proxy=P Send transaction via public TX proxy ‘P’ (supported proxies:
  53. {tx_proxies}). This is done via a publicly accessible web
  54. page, so no API key or registration is required.
  55. -q, --quiet Suppress warnings; overwrite files without prompting
  56. -r, --receipt Print the receipt of the sent transaction (Ethereum only)
  57. -s, --status Get status of a sent transaction (or current transaction,
  58. whether sent or unsent, when used with --autosign)
  59. -t, --test Test whether the transaction can be sent without sending it
  60. -T, --txhex-idx=N Send only part ‘N’ of a multi-part transaction. Indexing
  61. begins with one.
  62. -v, --verbose Be more verbose
  63. -w, --wait Wait for transaction confirmation (Ethereum only)
  64. -x, --proxy=P Connect to TX proxy via SOCKS5h proxy ‘P’ (host:port).
  65. Use special value ‘env’ to honor *_PROXY environment vars
  66. instead.
  67. -y, --yes Answer 'yes' to prompts, suppress non-essential output
  68. """,
  69. 'notes': """
  70. With --autosign, combined with --status or --receipt, the optional index or
  71. range arg represents an index or range into the list of sent transaction files
  72. on the removable device, in reverse chronological order. ‘0’ (the default)
  73. specifies the last sent transaction, ‘1’ the next-to-last, and so on. Hyphen-
  74. separated ranges are also supported. For example, specifying a range ‘0-3’
  75. would output data for the last four sent transactions, beginning with the most
  76. recent.
  77. """
  78. },
  79. 'code': {
  80. 'options': lambda cfg, proto, help_notes, s: s.format(
  81. tx_proxies = help_notes('tx_proxies'))
  82. }
  83. }
  84. cfg = Config(opts_data=opts_data)
  85. if cfg.autosign and cfg.outdir:
  86. die(1, '--outdir cannot be used in combination with --autosign')
  87. if cfg.mark_sent and not cfg.autosign:
  88. die(1, '--mark-sent is used only in combination with --autosign')
  89. if cfg.test and cfg.dump_hex:
  90. die(1, '--test cannot be used in combination with --dump-hex')
  91. if cfg.dump_hex and cfg.dump_hex != '-':
  92. from .fileutil import check_outfile_dir
  93. check_outfile_dir(cfg.dump_hex)
  94. post_send_op = cfg.status or cfg.receipt
  95. asi, tx_range = (None, None)
  96. def init_autosign(arg):
  97. global asi, si, infile, tx_range
  98. from .tx.util import mount_removable_device
  99. from .tx.online import SentTXRange
  100. from .autosign import Signable
  101. asi = mount_removable_device(cfg)
  102. si = Signable.automount_transaction(asi)
  103. if cfg.abort:
  104. si.shred_abortable() # prompts user, then raises exception or exits
  105. elif post_send_op and (si.unsent or si.unsigned):
  106. die(1, 'Transaction is {}'.format('unsent' if si.unsent else 'unsigned'))
  107. elif post_send_op:
  108. try:
  109. tx_range = SentTXRange(arg)
  110. except:
  111. die(2, f'{arg}: invalid transaction index arg '
  112. '(must be a non-negative integer or hyphen-separated range)')
  113. else:
  114. infile = si.get_unsent()
  115. cfg._util.qmsg(f'Got signed transaction file ‘{infile}’')
  116. match cfg._args:
  117. case [arg] if cfg.autosign and post_send_op:
  118. init_autosign(arg)
  119. case [] if cfg.autosign:
  120. init_autosign('0')
  121. case [infile]:
  122. from .fileutil import check_infile
  123. check_infile(infile)
  124. case _:
  125. cfg._usage()
  126. if not cfg.status:
  127. from .ui import do_license_msg
  128. do_license_msg(cfg)
  129. from .tx import OnlineSignedTX
  130. batch = tx_range and (tx_range.last != tx_range.first)
  131. def do_sep():
  132. if batch:
  133. msg('-' * 74)
  134. async def process_tx(tx):
  135. do_sep()
  136. cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
  137. if tx.is_compat:
  138. return await tx.compat_send()
  139. txcfg = Config({'_clone': cfg, 'proto': tx.proto, 'coin': tx.proto.coin})
  140. if not post_send_op:
  141. if cfg.tx_proxy:
  142. from .tx.tx_proxy import check_client
  143. check_client(txcfg)
  144. from .rpc import rpc_init
  145. tx.rpc = await rpc_init(txcfg)
  146. if not post_send_op:
  147. if cfg.mark_sent:
  148. await tx.post_send(asi)
  149. return 0
  150. if tx.is_swap and not tx.check_swap_expiry():
  151. die(1, 'Swap quote has expired. Please re-create the transaction')
  152. if not cfg.yes:
  153. tx.info.view_with_prompt('View transaction details?')
  154. if tx.add_comment(): # edits an existing comment, returns true if changed
  155. if not cfg.autosign:
  156. tx.file.write(ask_write_default_yes=True)
  157. return await tx.send(txcfg, asi, batch=batch)
  158. async def main():
  159. if cfg.autosign and post_send_op:
  160. exitvals = [await process_tx(tx)
  161. for tx in reversed(await si.get_last_sent(tx_range=tx_range))]
  162. do_sep()
  163. return max(exitvals)
  164. else:
  165. return await process_tx(await OnlineSignedTX(
  166. cfg = cfg,
  167. filename = infile,
  168. automount = cfg.autosign,
  169. quiet_open = True))
  170. sys.exit(async_run(cfg, main))