main_txbump.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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. #
  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-txbump: Create, and optionally send and sign, a replacement transaction
  20. on supporting networks
  21. """
  22. from .cfg import gc, Config
  23. from .util import msg, msg_r, die, async_run
  24. from .color import green
  25. opts_data = {
  26. 'filter_codes': ['-'],
  27. 'sets': [('yes', True, 'quiet', True)],
  28. 'text': {
  29. 'desc': """
  30. Create, and optionally send and sign, a replacement transaction
  31. on supporting networks
  32. """,
  33. 'usage2': (
  34. f'[opts] [{gc.proj_name} TX file] [seed source] ...',
  35. f'[opts] {{u_args}} [{gc.proj_name} TX file] [seed source] ...',
  36. ),
  37. 'options': """
  38. -- -h, --help Print this help message
  39. -- --, --longhelp Print help message for long (global) options
  40. -- -a, --autosign Bump the most recent transaction created and sent with
  41. + the --autosign option. The removable device is mounted
  42. + and unmounted automatically. The transaction file
  43. + argument must be omitted. Note that only sent trans-
  44. + actions may be bumped with this option. To redo an
  45. + unsent --autosign transaction, first delete it using
  46. + ‘mmgen-txsend --abort’ and then create a new one
  47. -- -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
  48. + brainwallet input
  49. -- -c, --comment-file= f Source the transaction's comment from file 'f'
  50. -- -d, --outdir= d Specify an alternate directory 'd' for output
  51. -- -e, --echo-passphrase Print passphrase to screen when typing it
  52. -- -f, --fee= f Transaction fee, as a decimal {cu} amount or as
  53. + {fu} (an integer followed by {fl!r}).
  54. + See FEE SPECIFICATION below.
  55. -- -H, --hidden-incog-input-params=f,o Read hidden incognito data from file
  56. + 'f' at offset 'o' (comma-separated)
  57. -- -i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
  58. -- -l, --seed-len= l Specify wallet seed length of 'l' bits. This option
  59. + is required only for brainwallet and incognito inputs
  60. + with non-standard (< {dsl}-bit) seed lengths.
  61. -- -k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
  62. -- -K, --keygen-backend=n Use backend 'n' for public key generation. Options
  63. + for {coin_id}: {kgs}
  64. -- -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
  65. + address file (output of '{pnl}-keygen'). Permits
  66. + online signing without an {pnm} seed source. The
  67. + key-address file is also used to verify {pnm}-to-{cu}
  68. + mappings, so the user should record its checksum.
  69. b- -o, --output-to-reduce=o Deduct the fee from output 'o' (an integer, or 'c'
  70. + for the transaction's change output, if present)
  71. -- -O, --old-incog-fmt Specify old-format incognito input
  72. -- -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
  73. + for password hashing (default: '{gc.dfl_hash_preset}')
  74. -- -P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
  75. -- -q, --quiet Suppress warnings; overwrite files without prompting
  76. -- -s, --send Sign and send the transaction (the default if seed
  77. + data is provided)
  78. -- -v, --verbose Produce more verbose output
  79. -- -W, --allow-non-wallet-swap Allow signing of swap transactions that send funds
  80. + to non-wallet addresses
  81. -- -x, --proxy=P Fetch the swap quote via SOCKS5 proxy ‘P’ (host:port)
  82. -- -y, --yes Answer 'yes' to prompts, suppress non-essential output
  83. -- -z, --show-hash-presets Show information on available hash presets
  84. """,
  85. 'notes': """
  86. With --autosign, the TX file argument is omitted, and the last submitted TX
  87. file on the removable device will be used.
  88. If no outputs are specified, the original outputs will be used for the
  89. replacement transaction, otherwise a new transaction will be created with the
  90. outputs listed on the command line. The syntax for the output arguments is
  91. identical to that of ‘mmgen-txcreate’.
  92. The user should take care to select a fee sufficient to ensure the original
  93. transaction is replaced in the mempool.
  94. When bumping a swap transaction, the swap protocol’s quote server on the
  95. Internet must be reachable either directly or via the SOCKS5 proxy specified
  96. with the --proxy option. To improve privacy, it’s recommended to proxy
  97. requests to the quote server via Tor or some other anonymity network.
  98. {e}
  99. {s}
  100. Seed source files must have the canonical extensions listed in the 'FileExt'
  101. column below:
  102. {f}
  103. """
  104. },
  105. 'code': {
  106. 'usage': lambda cfg, proto, help_notes, s: s.format(
  107. u_args = help_notes('txcreate_args')),
  108. 'options': lambda cfg, help_notes, proto, s: s.format(
  109. cfg = cfg,
  110. gc = gc,
  111. pnm = gc.proj_name,
  112. pnl = gc.proj_name.lower(),
  113. fu = help_notes('rel_fee_desc'),
  114. fl = help_notes('fee_spec_letters'),
  115. kgs = help_notes('keygen_backends'),
  116. coin_id = help_notes('coin_id'),
  117. dsl = help_notes('dfl_seed_len'),
  118. cu = proto.coin),
  119. 'notes': lambda help_mod, help_notes, s: s.format(
  120. e = help_notes('fee'),
  121. s = help_mod('txsign'),
  122. f = help_notes('fmt_codes')),
  123. }
  124. }
  125. cfg = Config(opts_data=opts_data)
  126. from .tx import CompletedTX, BumpTX, UnsignedTX, OnlineSignedTX
  127. from .tx.sign import txsign, get_seed_files, get_keyaddrlist, get_keylist
  128. seed_files = get_seed_files(
  129. cfg,
  130. cfg._args,
  131. ignore_dfl_wallet = not cfg.send,
  132. empty_ok = not cfg.send)
  133. if cfg.autosign:
  134. if cfg.send:
  135. die(1, '--send cannot be used together with --autosign')
  136. else:
  137. tx_file = cfg._args.pop()
  138. from .fileutil import check_infile
  139. check_infile(tx_file)
  140. from .ui import do_license_msg
  141. do_license_msg(cfg)
  142. silent = cfg.yes and cfg.fee is not None and cfg.output_to_reduce is not None
  143. async def main():
  144. if cfg.autosign:
  145. from .tx.util import mount_removable_device
  146. from .autosign import Signable
  147. asi = mount_removable_device(cfg)
  148. si = Signable.automount_transaction(asi)
  149. if si.unsigned or si.unsent:
  150. state = 'unsigned' if si.unsigned else 'unsent'
  151. die(1,
  152. 'Only sent transactions can be bumped with --autosign. Instead of bumping\n'
  153. f'your {state} transaction, abort it with ‘mmgen-txsend --abort’ and create\n'
  154. 'a new one.')
  155. orig_tx = await si.get_last_created()
  156. kal = kl = sign_and_send = None
  157. else:
  158. orig_tx = await CompletedTX(cfg=cfg, filename=tx_file)
  159. kal = get_keyaddrlist(cfg, orig_tx.proto)
  160. kl = get_keylist(cfg)
  161. sign_and_send = any([seed_files, kl, kal])
  162. if not silent:
  163. msg(green('ORIGINAL TRANSACTION'))
  164. msg(orig_tx.info.format(terse=True))
  165. from .tw.ctl import TwCtl
  166. tx = await BumpTX(
  167. cfg = cfg,
  168. data = orig_tx.__dict__,
  169. automount = cfg.autosign,
  170. check_sent = cfg.autosign or sign_and_send,
  171. new_outputs = bool(cfg._args),
  172. twctl = await TwCtl(cfg, orig_tx.proto) if orig_tx.proto.tokensym else None)
  173. if tx.new_outputs:
  174. await tx.create(cfg._args, caller='txdo' if sign_and_send else 'txcreate')
  175. else:
  176. await tx.create_feebump(silent=silent)
  177. if not silent:
  178. msg(green('\nREPLACEMENT TRANSACTION:'))
  179. msg_r(tx.info.format(terse=True))
  180. if sign_and_send:
  181. tx2 = UnsignedTX(cfg=cfg, data=tx.__dict__)
  182. tx3 = await txsign(cfg, tx2, seed_files, kl, kal)
  183. if tx3:
  184. tx4 = await OnlineSignedTX(cfg=cfg, data=tx3.__dict__)
  185. tx4.file.write(ask_write=False)
  186. if await tx4.send():
  187. tx4.file.write(ask_write=False)
  188. else:
  189. die(2, 'Transaction could not be signed')
  190. else:
  191. tx.file.write(
  192. outdir = asi.txauto_dir if cfg.autosign else None,
  193. ask_write = not cfg.yes,
  194. ask_write_default_yes = False,
  195. ask_overwrite = not cfg.yes)
  196. async_run(main())