txcreate.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
  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. txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen inputs
  20. and outputs
  21. """
  22. from mmgen.common import *
  23. from mmgen.tx import *
  24. from mmgen.tw import *
  25. pnm = g.proj_name
  26. txcreate_notes = """
  27. The transaction's outputs are specified on the command line, while its inputs
  28. are chosen from a list of the user's unpent outputs via an interactive menu.
  29. If the transaction fee is not specified by the user, it will be calculated
  30. using bitcoind's "estimatefee" function for the default (or user-specified)
  31. number of confirmations. If "estimatefee" fails, the global default fee of
  32. {g.tx_fee} BTC will be used.
  33. Dynamic fees will be multiplied by the value of '--tx-fee-adj', if specified.
  34. Ages of transactions are approximate based on an average block discovery
  35. interval of {g.mins_per_block} minutes.
  36. All addresses on the command line can be either Bitcoin addresses or {pnm}
  37. addresses of the form <seed ID>:<index>.
  38. To send the value of all inputs (minus TX fee) to a single output, specify
  39. one address with no amount on the command line.
  40. """.format(g=g,pnm=pnm)
  41. wmsg = {
  42. 'addr_in_addrfile_only': """
  43. Warning: output address {mmgenaddr} is not in the tracking wallet, which means
  44. its balance will not be tracked. You're strongly advised to import the address
  45. into your tracking wallet before broadcasting this transaction.
  46. """.strip(),
  47. 'addr_not_found': """
  48. No data for {pnm} address {mmgenaddr} could be found in either the tracking
  49. wallet or the supplied address file. Please import this address into your
  50. tracking wallet, or supply an address file for it on the command line.
  51. """.strip(),
  52. 'addr_not_found_no_addrfile': """
  53. No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
  54. Please import this address into your tracking wallet or supply an address file
  55. for it on the command line.
  56. """.strip(),
  57. 'non_mmgen_inputs': """
  58. NOTE: This transaction includes non-{pnm} inputs, which makes the signing
  59. process more complicated. When signing the transaction, keys for non-{pnm}
  60. inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file'
  61. option.
  62. Selected non-{pnm} inputs: %s
  63. """.strip().format(pnm=pnm,pnl=pnm.lower()),
  64. 'not_enough_btc': """
  65. Not enough BTC in the inputs for this transaction (%s BTC)
  66. """.strip(),
  67. 'throwaway_change': """
  68. ERROR: This transaction produces change (%s BTC); however, no change address
  69. was specified.
  70. """.strip(),
  71. }
  72. def select_unspent(unspent,prompt):
  73. while True:
  74. reply = my_raw_input(prompt).strip()
  75. if reply:
  76. selected = AddrIdxList(fmt_str=','.join(reply.split()),on_fail='return')
  77. if selected:
  78. if selected[-1] <= len(unspent):
  79. return selected
  80. msg('Unspent output number must be <= %s' % len(unspent))
  81. def mmaddr2baddr(c,mmaddr,ad_w,ad_f):
  82. # assume mmaddr has already been checked
  83. btc_addr = ad_w.mmaddr2btcaddr(mmaddr)
  84. if not btc_addr:
  85. if ad_f:
  86. btc_addr = ad_f.mmaddr2btcaddr(mmaddr)
  87. if btc_addr:
  88. msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
  89. if not keypress_confirm('Continue anyway?'):
  90. sys.exit(1)
  91. else:
  92. die(2,wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
  93. else:
  94. die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
  95. return BTCAddr(btc_addr)
  96. def get_fee_estimate():
  97. if 'tx_fee' in opt.set_by_user: # TODO
  98. return None
  99. else:
  100. ret = c.estimatefee(opt.tx_confs)
  101. if ret != -1:
  102. return BTCAmt(ret)
  103. else:
  104. m = """
  105. Fee estimation failed!
  106. Your possible courses of action (from best to worst):
  107. 1) Re-run script with a different '--tx-confs' parameter (now '{c}')
  108. 2) Re-run script with the '--tx-fee' option (specify fee manually)
  109. 3) Accept the global default fee of {f} BTC
  110. Accept the global default fee of {f} BTC?
  111. """.format(c=opt.tx_confs,f=opt.tx_fee).strip()
  112. if keypress_confirm(m):
  113. return None
  114. else:
  115. die(1,'Exiting at user request')
  116. def txcreate(opt,cmd_args,do_info=False,caller='txcreate'):
  117. tx = MMGenTX()
  118. if opt.comment_file: tx.add_comment(opt.comment_file)
  119. c = bitcoin_connection()
  120. if not do_info:
  121. from mmgen.addr import AddrList,AddrData
  122. addrfiles = [a for a in cmd_args if get_extension(a) == AddrList.ext]
  123. cmd_args = set(cmd_args) - set(addrfiles)
  124. ad_f = AddrData()
  125. for a in addrfiles:
  126. check_infile(a)
  127. ad_f.add(AddrList(a))
  128. ad_w = AddrData(source='tw')
  129. for a in cmd_args:
  130. if ',' in a:
  131. a1,a2 = a.split(',',1)
  132. if is_btc_addr(a1):
  133. btc_addr = BTCAddr(a1)
  134. elif is_mmgen_id(a1):
  135. btc_addr = mmaddr2baddr(c,a1,ad_w,ad_f)
  136. else:
  137. die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
  138. tx.add_output(btc_addr,BTCAmt(a2))
  139. elif is_mmgen_id(a) or is_btc_addr(a):
  140. if tx.change_addr:
  141. die(2,'ERROR: More than one change address specified: %s, %s' %
  142. (change_addr, a))
  143. tx.change_addr = mmaddr2baddr(c,a,ad_w,ad_f) if is_mmgen_id(a) else BTCAddr(a)
  144. tx.add_output(tx.change_addr,BTCAmt('0'))
  145. else:
  146. die(2,'%s: unrecognized argument' % a)
  147. if not tx.outputs:
  148. die(2,'At least one output must be specified on the command line')
  149. if opt.tx_fee > tx.max_fee:
  150. die(2,'Transaction fee too large: %s > %s' % (opt.tx_fee,tx.max_fee))
  151. fee_estimate = get_fee_estimate()
  152. tw = MMGenTrackingWallet(minconf=opt.minconf)
  153. tw.view_and_sort()
  154. tw.display_total()
  155. if do_info: sys.exit()
  156. tx.send_amt = tx.sum_outputs()
  157. msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt.hl())[bool(tx.send_amt)])
  158. while True:
  159. sel_nums = select_unspent(tw.unspent,
  160. 'Enter a range or space-separated list of outputs to spend: ')
  161. msg('Selected output%s: %s' % (
  162. ('s','')[len(sel_nums)==1],
  163. ' '.join(str(i) for i in sel_nums)
  164. ))
  165. sel_unspent = [tw.unspent[i-1] for i in sel_nums]
  166. non_mmaddrs = [i for i in sel_unspent if i.mmid == None]
  167. if non_mmaddrs and caller != 'txdo':
  168. msg(wmsg['non_mmgen_inputs'] % ', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs]))))
  169. if not keypress_confirm('Accept?'):
  170. continue
  171. tx.copy_inputs_from_tw(sel_unspent) # makes tx.inputs
  172. tx.calculate_size_and_fee(fee_estimate) # sets tx.size, tx.fee
  173. change_amt = tx.sum_inputs() - tx.send_amt - tx.fee
  174. if change_amt >= 0:
  175. prompt = 'Transaction produces %s BTC in change. OK?' % change_amt.hl()
  176. if keypress_confirm(prompt,default_yes=True):
  177. break
  178. else:
  179. msg(wmsg['not_enough_btc'] % change_amt)
  180. if change_amt > 0:
  181. change_amt = BTCAmt(change_amt)
  182. if not tx.change_addr:
  183. die(2,wmsg['throwaway_change'] % change_amt)
  184. tx.del_output(tx.change_addr)
  185. tx.add_output(BTCAddr(tx.change_addr),change_amt)
  186. elif tx.change_addr:
  187. msg('Warning: Change address will be unused as transaction produces no change')
  188. tx.del_output(tx.change_addr)
  189. if not tx.send_amt:
  190. tx.send_amt = change_amt
  191. dmsg('tx: %s' % tx)
  192. tx.add_comment() # edits an existing comment
  193. tx.create_raw(c) # creates tx.hex, tx.txid
  194. tx.add_mmaddrs_to_outputs(ad_w,ad_f)
  195. tx.add_timestamp()
  196. tx.add_blockcount(c)
  197. qmsg('Transaction successfully created')
  198. dmsg('TX (final): %s' % tx)
  199. tx.view_with_prompt('View decoded transaction?')
  200. return tx