new.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2023 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
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. proto.btc.tx.new: Bitcoin new transaction class
  12. """
  13. import mmgen.tx.new as TxBase
  14. from .base import Base
  15. from ....opts import opt
  16. from ....obj import HexStr,MMGenTxID
  17. from ....util import msg,dmsg,vmsg,make_chksum_6,die
  18. class New(Base,TxBase.New):
  19. usr_fee_prompt = 'Enter transaction fee: '
  20. fee_fail_fs = 'Network fee estimation for {c} confirmations failed ({t})'
  21. no_chg_msg = 'Warning: Change address will be deleted as transaction produces no change'
  22. @property
  23. def relay_fee(self):
  24. kb_fee = self.proto.coin_amt(self.rpc.cached['networkinfo']['relayfee'])
  25. ret = kb_fee * self.estimate_size() / 1024
  26. vmsg('Relay fee: {} {c}/kB, for transaction: {} {c}'.format(kb_fee,ret,c=self.coin))
  27. return ret
  28. async def get_rel_fee_from_network(self):
  29. try:
  30. ret = await self.rpc.call(
  31. 'estimatesmartfee',
  32. opt.fee_estimate_confs,
  33. opt.fee_estimate_mode.upper() )
  34. fee_per_kb = ret['feerate'] if 'feerate' in ret else -2
  35. fe_type = 'estimatesmartfee'
  36. except:
  37. args = self.rpc.daemon.estimatefee_args(self.rpc)
  38. fee_per_kb = await self.rpc.call('estimatefee',*args)
  39. fe_type = 'estimatefee'
  40. return fee_per_kb,fe_type
  41. # given tx size, rel fee and units, return absolute fee
  42. def fee_rel2abs(self,tx_size,units,amt,unit):
  43. if tx_size:
  44. return self.proto.coin_amt(amt * tx_size * getattr(self.proto.coin_amt,units[unit]))
  45. else:
  46. return None
  47. # given network fee estimate in BTC/kB, return absolute fee using estimated tx size
  48. def fee_est2abs(self,fee_per_kb,fe_type=None):
  49. from decimal import Decimal
  50. tx_size = self.estimate_size()
  51. ret = self.proto.coin_amt(
  52. fee_per_kb * Decimal(opt.fee_adjust) * tx_size / 1024,
  53. from_decimal = True )
  54. if opt.verbose:
  55. msg(fmt(f"""
  56. {fe_type.upper()} fee for {opt.fee_estimate_confs} confirmations: {fee_per_kb} {self.coin}/kB
  57. TX size (estimated): {tx_size} bytes
  58. Fee adjustment factor: {opt.fee_adjust:.2f}
  59. Absolute fee (fee_per_kb * adj_factor * tx_size / 1024): {ret} {self.coin}
  60. """).strip())
  61. return ret
  62. def convert_and_check_fee(self,fee,desc):
  63. abs_fee = self.feespec2abs(fee,self.estimate_size())
  64. if abs_fee == None:
  65. raise ValueError(f'{fee}: cannot convert {self.rel_fee_desc} to {self.coin}'
  66. + ' because transaction size is unknown')
  67. if abs_fee == False:
  68. err = f'{fee!r}: invalid TX fee (not a {self.coin} amount or {self.rel_fee_desc} specification)'
  69. elif abs_fee > self.proto.max_tx_fee:
  70. err = f'{abs_fee} {self.coin}: {desc} fee too large (maximum fee: {self.proto.max_tx_fee} {self.coin})'
  71. elif abs_fee < self.relay_fee:
  72. err = f'{abs_fee} {self.coin}: {desc} fee too small (less than relay fee of {self.relay_fee} {self.coin})'
  73. else:
  74. return abs_fee
  75. msg(err)
  76. return False
  77. async def get_input_addrs_from_cmdline(self):
  78. # Bitcoin full node, call doesn't go to the network, so just call listunspent with addrs=[]
  79. return []
  80. def update_change_output(self,funds_left):
  81. if funds_left == 0: # TODO: test
  82. msg(self.no_chg_msg)
  83. self.outputs.pop(self.chg_idx)
  84. else:
  85. self.update_output_amt(
  86. self.chg_idx,
  87. self.proto.coin_amt(funds_left) )
  88. def check_fee(self):
  89. fee = self.sum_inputs() - self.sum_outputs()
  90. if fee > self.proto.max_tx_fee:
  91. c = self.proto.coin
  92. die( 'MaxFeeExceeded', f'Transaction fee of {fee} {c} too high! (> {self.proto.max_tx_fee} {c})' )
  93. def final_inputs_ok_msg(self,funds_left):
  94. return 'Transaction produces {} {} in change'.format(
  95. self.proto.coin_amt(funds_left).hl(),
  96. self.coin
  97. )
  98. async def create_serialized(self,locktime=None,bump=None):
  99. if not bump:
  100. self.inputs.sort_bip69()
  101. # Set all sequence numbers to the same value, in conformity with the behavior of most modern wallets:
  102. seqnum_val = self.proto.max_int - (2 if opt.rbf else 1 if locktime else 0)
  103. for i in self.inputs:
  104. i.sequence = seqnum_val
  105. self.outputs.sort_bip69()
  106. inputs_list = [{
  107. 'txid': e.txid,
  108. 'vout': e.vout,
  109. 'sequence': e.sequence
  110. } for e in self.inputs ]
  111. outputs_dict = {e.addr:e.amt for e in self.outputs}
  112. ret = await self.rpc.call( 'createrawtransaction', inputs_list, outputs_dict )
  113. if locktime and not bump:
  114. msg(f'Setting nLockTime to {self.strfmt_locktime(locktime)}!')
  115. assert isinstance(locktime,int), 'locktime value not an integer'
  116. self.locktime = locktime
  117. ret = ret[:-8] + bytes.fromhex('{:08x}'.format(locktime))[::-1].hex()
  118. # TxID is set only once!
  119. self.txid = MMGenTxID(make_chksum_6(bytes.fromhex(ret)).upper())
  120. self.update_serialized(ret)