status.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. proto.btc.tx.status: Bitcoin transaction status class
  12. """
  13. import time
  14. from ....tx import status as TxBase
  15. from ....util import msg, suf
  16. from ....util2 import format_elapsed_hr
  17. class Status(TxBase.Status):
  18. async def display(self, *, idx=''):
  19. def do_return(exitval, message):
  20. if message:
  21. msg(message)
  22. return exitval
  23. assert idx == '', f'multiple txhex not supported for {self.tx.proto}'
  24. tx = self.tx
  25. class r:
  26. pass
  27. async def is_in_wallet():
  28. try:
  29. ret = await tx.rpc.icall(
  30. 'gettransaction',
  31. txid = tx.coin_txid,
  32. include_watchonly = True,
  33. verbose = False)
  34. except:
  35. return False
  36. if ret.get('confirmations', 0) > 0:
  37. r.confs = ret['confirmations']
  38. return True
  39. else:
  40. return False
  41. async def is_in_utxos():
  42. try:
  43. return 'txid' in await tx.rpc.call('getrawtransaction', tx.coin_txid, True)
  44. except:
  45. return False
  46. async def is_in_mempool():
  47. try:
  48. await tx.rpc.call('getmempoolentry', tx.coin_txid)
  49. return True
  50. except:
  51. return False
  52. async def is_replaced():
  53. if await is_in_mempool():
  54. return False
  55. try:
  56. ret = await tx.rpc.icall(
  57. 'gettransaction',
  58. txid = tx.coin_txid,
  59. include_watchonly = True,
  60. verbose = False)
  61. except:
  62. return False
  63. else:
  64. if 'bip125-replaceable' in ret and ret.get('confirmations', 1) <= 0:
  65. r.replacing_confs = -ret['confirmations']
  66. r.replacing_txs = ret['walletconflicts']
  67. return True
  68. else:
  69. return False
  70. if await is_in_mempool():
  71. d = await tx.rpc.icall(
  72. 'gettransaction',
  73. txid = tx.coin_txid,
  74. include_watchonly = True,
  75. verbose = False)
  76. rep = ('' if d.get('bip125-replaceable') == 'yes' else 'NOT ') + 'replaceable'
  77. t = d['timereceived']
  78. if tx.cfg.quiet:
  79. msg('Transaction is in mempool')
  80. else:
  81. msg(f'TX status: in mempool, {rep}')
  82. msg('Sent {} ({})'.format(time.strftime('%c', time.gmtime(t)), format_elapsed_hr(t)))
  83. return do_return(0, '')
  84. elif await is_in_wallet():
  85. return do_return(0, f'Transaction has {r.confs} confirmation{suf(r.confs)}')
  86. elif await is_in_utxos():
  87. return do_return(4, 'ERROR: transaction is in the blockchain (but not in the tracking wallet)!')
  88. elif await is_replaced():
  89. msg('Transaction has been replaced')
  90. msg('Replacement transaction ' + (
  91. f'has {r.replacing_confs} confirmation{suf(r.replacing_confs)}'
  92. if r.replacing_confs else
  93. 'is in mempool'))
  94. if not tx.cfg.quiet:
  95. msg('Replacing transactions:')
  96. d = []
  97. for txid in r.replacing_txs:
  98. try:
  99. d.append(await tx.rpc.call('getmempoolentry', txid))
  100. except:
  101. d.append({})
  102. for txid, mp_entry in zip(r.replacing_txs, d):
  103. msg(f' {txid}' + (' in mempool' if 'height' in mp_entry else ''))
  104. return do_return(0, '')