main_txbump.py 9.7 KB

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