online.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. tx.online: online signed transaction class
  12. """
  13. import time, asyncio
  14. from ..obj import MMGenRange
  15. from ..util import msg, Msg, gmsg, ymsg, make_timestr, die
  16. from ..color import pink, yellow
  17. from .signed import Signed, AutomountSigned
  18. class SentTXRange(MMGenRange):
  19. min_idx = 0
  20. max_idx = 1_000_000
  21. class CreatedTXRange(SentTXRange):
  22. pass
  23. class OnlineSigned(Signed):
  24. @property
  25. def status(self):
  26. from . import _base_proto_subclass
  27. return _base_proto_subclass('Status', 'status', {'proto': self.proto})(self)
  28. def check_swap_expiry(self):
  29. from ..util2 import format_elapsed_hr
  30. expiry = self.swap_quote_expiry
  31. now = int(time.time())
  32. t_rem = expiry - now
  33. clr = yellow if t_rem < 0 else pink
  34. msg('Swap quote {a} {b} [{c}]'.format(
  35. a = clr('expired' if t_rem < 0 else 'expires'),
  36. b = clr(format_elapsed_hr(expiry, now=now, future_msg='from now')),
  37. c = make_timestr(expiry)))
  38. return t_rem >= 0
  39. def confirm_send(self, idxs):
  40. from ..ui import confirm_or_raise
  41. confirm_or_raise(
  42. cfg = self.cfg,
  43. message = '' if self.cfg.quiet else
  44. 'Once this transaction is sent, there’s no taking it back!',
  45. action = 'broadcast this transaction to the {} {} network'.format(
  46. self.proto.coin,
  47. self.proto.network.upper()),
  48. expect = 'YES')
  49. msg('Sending transaction')
  50. if len(idxs) > 1 and getattr(self, 'coin_txid2', None) and self.is_swap:
  51. ymsg('Warning: two transactions (approval and router) will be broadcast to the network')
  52. async def post_send(self, asi):
  53. from . import SentTX
  54. tx2 = await SentTX(cfg=self.cfg, data=self.__dict__, automount=bool(asi))
  55. tx2.add_sent_timestamp()
  56. tx2.add_blockcount()
  57. tx2.file.write(
  58. outdir = asi.txauto_dir if asi else None,
  59. ask_overwrite = False,
  60. ask_write = False)
  61. async def send(self, cfg, asi, batch=False):
  62. """
  63. returns integer exit val to system
  64. """
  65. status_exitval = None
  66. sent_status = None
  67. all_ok = True
  68. idxs = ['', '2']
  69. if cfg.txhex_idx:
  70. if getattr(self, 'coin_txid2', None):
  71. if cfg.txhex_idx in ('1', '2'):
  72. idxs = ['' if cfg.txhex_idx == '1' else cfg.txhex_idx]
  73. else:
  74. die(1, f'{cfg.txhex_idx}: invalid parameter for --txhex-idx (must be 1 or 2)')
  75. else:
  76. die(1, 'Transaction has only one part, so --txhex-idx makes no sense')
  77. if not (cfg.status or cfg.receipt or cfg.dump_hex or cfg.test):
  78. self.confirm_send(idxs)
  79. for idx in idxs:
  80. if coin_txid := getattr(self, f'coin_txid{idx}', None):
  81. txhex = getattr(self, f'serialized{idx}')
  82. if cfg.status:
  83. if cfg.verbose:
  84. await self.post_network_send(coin_txid)
  85. else:
  86. cfg._util.qmsg(f'{self.proto.coin} TxID: {coin_txid.hl()}')
  87. status_exitval = await self.status.display(idx=idx)
  88. elif cfg.receipt:
  89. if res := await self.get_receipt(coin_txid, receipt_only=True):
  90. import json
  91. Msg(json.dumps(res, indent=4))
  92. else:
  93. msg(f'Unable to get receipt for TX {coin_txid.hl()}')
  94. elif cfg.dump_hex:
  95. from ..fileutil import write_data_to_file
  96. write_data_to_file(
  97. cfg,
  98. cfg.dump_hex + idx,
  99. txhex + '\n',
  100. desc = 'serialized transaction hex data',
  101. ask_overwrite = False,
  102. ask_tty = False)
  103. elif cfg.tx_proxy:
  104. if idx != '' and not cfg.test_suite:
  105. await asyncio.sleep(2)
  106. from .tx_proxy import send_tx
  107. msg('{} TX: {}'.format('Testing' if cfg.test else 'Sending', coin_txid.hl()))
  108. if ret := send_tx(cfg, txhex):
  109. if ret != coin_txid:
  110. ymsg(f'Warning: txid mismatch (after sending) ({ret} != {coin_txid})')
  111. sent_status = 'confirm_post_send'
  112. if cfg.test:
  113. break
  114. elif cfg.test:
  115. if await self.test_sendable(txhex):
  116. gmsg('Transaction can be sent')
  117. else:
  118. ymsg('Transaction cannot be sent')
  119. else: # node send
  120. msg(f'Sending TX: {coin_txid.hl()}')
  121. if cfg.bogus_send:
  122. msg(f'BOGUS transaction NOT sent: {coin_txid.hl()}')
  123. else:
  124. if idx != '':
  125. await asyncio.sleep(1)
  126. ret = await self.send_with_node(txhex)
  127. msg(f'Transaction sent: {coin_txid.hl()}')
  128. if ret != coin_txid:
  129. die('TxIDMismatch', f'txid mismatch (after sending) ({ret} != {coin_txid})')
  130. sent_status = 'no_confirm_post_send'
  131. if cfg.wait and sent_status:
  132. res = await self.post_network_send(coin_txid)
  133. if all_ok:
  134. all_ok = res
  135. if not cfg.txhex_idx and sent_status and all_ok:
  136. from ..ui import keypress_confirm
  137. if sent_status == 'no_confirm_post_send' or not asi or keypress_confirm(
  138. cfg, 'Mark transaction as sent on removable device?'):
  139. await self.post_send(asi)
  140. if status_exitval is not None:
  141. if cfg.verbose:
  142. if batch:
  143. self.info.view(pause=False, terse=True)
  144. else:
  145. self.info.view_with_prompt('View transaction details?', pause=False)
  146. return status_exitval
  147. return 0
  148. class AutomountOnlineSigned(AutomountSigned, OnlineSigned):
  149. pass
  150. class Sent(OnlineSigned):
  151. desc = 'sent transaction'
  152. ext = 'subtx'
  153. class AutomountSent(AutomountOnlineSigned):
  154. desc = 'sent automount transaction'
  155. ext = 'asubtx'