Browse Source

Replace --aug1hf with --coin=bch, display 'BCH' for BCH amounts

* Deprecate the --aug1hf option
* Use --coin=bch option to select BCH chain; works with all MMGen commands
* All BCH amounts now correctly display with 'BCH' instead of 'BTC'
* use non-standard RPC ports (8442,18442) for BCH mainnet/testnet to allow
  dual-node operation.  Use 'mmlive-node-start' and '-stop' scripts
  (home.mmgen/bin/mmlive-node-{start,stop} in the MMGenLive repo)
  to start daemons with correct RPC ports; the --coin option automatically
  configures the correct ports for all MMGen scripts.

* mmgen-txsend: new --status option checks confirmation/mempool status of TXs
philemon 7 years ago
parent
commit
2873897f68
15 changed files with 186 additions and 128 deletions
  1. 7 3
      mmgen/globalvars.py
  2. 8 6
      mmgen/main_txbump.py
  3. 2 2
      mmgen/main_txcreate.py
  4. 10 3
      mmgen/main_txdo.py
  5. 8 1
      mmgen/main_txsend.py
  6. 8 2
      mmgen/main_txsign.py
  7. 4 2
      mmgen/obj.py
  8. 13 4
      mmgen/opts.py
  9. 2 5
      mmgen/rpc.py
  10. 5 5
      mmgen/tool.py
  11. 10 8
      mmgen/tw.py
  12. 55 46
      mmgen/tx.py
  13. 16 18
      mmgen/txcreate.py
  14. 8 8
      mmgen/txsign.py
  15. 30 15
      mmgen/util.py

+ 7 - 3
mmgen/globalvars.py

@@ -49,6 +49,10 @@ class g(object):
 	Cdates    = '2013-2017'
 	keywords  = 'Bitcoin, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin'
 
+	coin   = 'BTC'
+	coins  = 'BTC','BCH'
+	ports = { 'BTC': (8332,18332), 'BCH': (8442,18442) }
+
 	user_entropy   = ''
 	hash_preset    = '3'
 	usr_randchars  = 30
@@ -106,19 +110,19 @@ class g(object):
 	# User opt sets global var:
 	common_opts = (
 		'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password',
-		'bitcoin_data_dir','force_256_color','regtest'
+		'bitcoin_data_dir','force_256_color','regtest','coin'
 	)
 	required_opts = (
 		'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
 		'show_hash_presets','label','keep_passphrase','keep_hash_preset','yes',
-		'brain_params','b16','usr_randchars'
+		'brain_params','b16','usr_randchars','coin'
 	)
 	incompatible_opts = (
 		('quiet','verbose'),
 		('label','keep_label'),
 		('tx_id','info'),
 		('tx_id','terse_info'),
-		('aug1hf','rbf'),
+		('aug1hf','rbf'), # TODO: remove in 0.9.4
 		('batch','rescan')
 	)
 	cfg_file_opts = (

+ 8 - 6
mmgen/main_txbump.py

@@ -36,7 +36,7 @@ opts_data = {
 -c, --comment-file=  f Source the transaction's comment from file 'f'
 -d, --outdir=        d Specify an alternate directory 'd' for output
 -e, --echo-passphrase  Print passphrase to screen when typing it
--f, --tx-fee=        f Transaction fee, as a decimal BTC amount or in
+-f, --tx-fee=        f Transaction fee, as a decimal {cu} amount or in
                        satoshis per byte (an integer followed by 's')
 -H, --hidden-incog-input-params=f,o  Read hidden incognito data from file
                       'f' at offset 'o' (comma-separated)
@@ -51,7 +51,7 @@ opts_data = {
 -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
                        address file (output of '{pnl}-keygen'). Permits
                        online signing without an {pnm} seed source. The
-                       key-address file is also used to verify {pnm}-to-BTC
+                       key-address file is also used to verify {pnm}-to-{cu}
                        mappings, so the user should record its checksum.
 -o, --output-to-reduce=o Deduct the fee from output 'o' (an integer, or 'c'
                        for the transaction's change output, if present)
@@ -67,11 +67,13 @@ opts_data = {
 -z, --show-hash-presets Show information on available hash presets
 """.format(g=g,pnm=pnm,pnl=pnm.lower(),
 		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		kg=g.key_generator),
+		kg=g.key_generator,
+		cu=g.coin
+		),
 	'notes': '\n' + fee_notes + txsign_notes
 }
 
-cmd_args = opts.init(opts_data,add_opts=['aug1hf'])
+cmd_args = opts.init(opts_data)
 
 c = bitcoin_connection()
 
@@ -96,14 +98,14 @@ tx.set_min_fee()
 
 if not [o.amt for o in tx.outputs if o.amt >= tx.min_fee]:
 	die(1,'Transaction cannot be bumped.' +
-	'\nAll outputs have less than the minimum fee ({} BTC)'.format(tx.min_fee))
+	'\nAll outputs have less than the minimum fee ({} {})'.format(tx.min_fee,g.coin))
 
 msg('Creating new transaction')
 
 op_idx = tx.choose_output()
 
 if not silent:
-	msg('Minimum fee for new transaction: {} BTC'.format(tx.min_fee))
+	msg('Minimum fee for new transaction: {} {}'.format(tx.min_fee,g.coin))
 
 fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected')
 

+ 2 - 2
mmgen/main_txcreate.py

@@ -35,7 +35,7 @@ opts_data = {
 -c, --comment-file=f Source the transaction's comment from file 'f'
 -C, --tx-confs=    c Desired number of confirmations (default: {g.tx_confs})
 -d, --outdir=      d Specify an alternate directory 'd' for output
--f, --tx-fee=      f Transaction fee, as a decimal BTC amount or in satoshis
+-f, --tx-fee=      f Transaction fee, as a decimal {cu} amount or in satoshis
                      per byte (an integer followed by 's').  If omitted, fee
                      will be calculated using bitcoind's 'estimatefee' call
 -i, --info           Display unspent outputs and exit
@@ -45,7 +45,7 @@ opts_data = {
 -r, --rbf            Make transaction BIP 125 replaceable (replace-by-fee)
 -v, --verbose        Produce more verbose output
 -y, --yes            Answer 'yes' to prompts, suppress non-essential output
-""".format(g=g),
+""".format(g=g,cu=g.coin),
 	'notes': '\n' + txcreate_notes + fee_notes
 }
 

+ 10 - 3
mmgen/main_txdo.py

@@ -39,7 +39,7 @@ opts_data = {
 -C, --tx-confs=      c Desired number of confirmations (default: {g.tx_confs})
 -d, --outdir=        d Specify an alternate directory 'd' for output
 -e, --echo-passphrase  Print passphrase to screen when typing it
--f, --tx-fee=        f Transaction fee, as a decimal BTC amount or in
+-f, --tx-fee=        f Transaction fee, as a decimal {cu} amount or in
                        satoshis per byte (an integer followed by 's').
                        If omitted, bitcoind's 'estimatefee' will be used
                        to calculate the fee.
@@ -58,7 +58,7 @@ opts_data = {
 -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
                        address file (output of '{pnl}-keygen'). Permits
                        online signing without an {pnm} seed source. The
-                       key-address file is also used to verify {pnm}-to-BTC
+                       key-address file is also used to verify {pnm}-to-{cu}
                        mappings, so the user should record its checksum.
 -O, --old-incog-fmt    Specify old-format incognito input
 -p, --hash-preset=   p Use the scrypt hash parameters defined by preset 'p'
@@ -71,11 +71,18 @@ opts_data = {
 -z, --show-hash-presets Show information on available hash presets
 """.format(g=g,pnm=pnm,pnl=pnm.lower(),
 		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		kg=g.key_generator),
+		kg=g.key_generator,
+		cu=g.coin
+		),
 	'notes': '\n' + txcreate_notes + fee_notes + txsign_notes
 }
 
 cmd_args = opts.init(opts_data)
+
+if opt.aug1hf: # TODO: remove in 0.9.4
+	msg(yellow('The --aug1hf option is deprecated. Please use --coin=bch instead'))
+	g.coin = 'BCH'
+
 seed_files = get_seed_files(opt,cmd_args)
 c = bitcoin_connection()
 do_license_msg()

+ 8 - 1
mmgen/main_txsend.py

@@ -33,6 +33,7 @@ opts_data = {
 --, --longhelp  Print help message for long options (common options)
 -d, --outdir= d Specify an alternate directory 'd' for output
 -q, --quiet     Suppress warnings; overwrite files without prompting
+-s, --status    Get status of a sent transaction
 -y, --yes       Answer 'yes' to prompts, suppress non-essential output
 """
 }
@@ -43,7 +44,8 @@ if len(cmd_args) == 1:
 	infile = cmd_args[0]; check_infile(infile)
 else: opts.usage()
 
-do_license_msg()
+if not opt.status: do_license_msg()
+
 c = bitcoin_connection()
 tx = MMGenTX(infile) # sig check performed here
 qmsg("Signed transaction file '%s' is valid" % infile)
@@ -51,6 +53,11 @@ qmsg("Signed transaction file '%s' is valid" % infile)
 if not tx.marked_signed(c):
 	die(1,'Transaction is not signed!')
 
+if opt.status:
+	if tx.btc_txid: qmsg('{} txid: {}'.format(g.coin,tx.btc_txid.hl()))
+	tx.get_status(c,status=True)
+	sys.exit(0)
+
 if not opt.yes:
 	tx.view_with_prompt('View transaction data?')
 	if tx.add_comment(): # edits an existing comment, returns true if changed

+ 8 - 2
mmgen/main_txsign.py

@@ -52,7 +52,7 @@ opts_data = {
 -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
                       address file (output of '{pnl}-keygen'). Permits
                       online signing without an {pnm} seed source. The
-                      key-address file is also used to verify {pnm}-to-BTC
+                      key-address file is also used to verify {pnm}-to-{cu}
                       mappings, so the user should record its checksum.
 -P, --passwd-file= f  Get {pnm} wallet or bitcoind passphrase from file 'f'
 -q, --quiet           Suppress warnings; overwrite files without prompting
@@ -63,12 +63,18 @@ opts_data = {
 """.format(
 		g=g,pnm=pnm,pnl=pnm.lower(),
 		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		kg=g.key_generator),
+		kg=g.key_generator,
+		cu=g.coin
+		),
 	'notes': '\n' + txsign_notes
 }
 
 infiles = opts.init(opts_data,add_opts=['b16'])
 
+if opt.aug1hf: # TODO: remove in 0.9.4
+	msg(yellow('The --aug1hf option is deprecated. Please use --coin=bch instead'))
+	g.coin = 'BCH'
+
 if not infiles: opts.usage()
 for i in infiles: check_infile(i)
 

+ 4 - 2
mmgen/obj.py

@@ -296,9 +296,11 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 			m = "'%s': value cannot be converted to decimal" % num
 		else:
 			if me.normalize().as_tuple()[-1] < -cls.max_prec:
-				m = "'%s': too many decimal places in BTC amount" % num
+				from mmgen.globalvars import g
+				m = "'{}': too many decimal places in {} amount".format(num,g.coin)
 			elif me > cls.max_amt:
-				m = "'%s': BTC amount too large (>%s)" % (num,cls.max_amt)
+				from mmgen.globalvars import g
+				m = "'{}': {} amount too large (>{})".format(num,g.coin,cls.max_amt)
 #			elif me.as_tuple()[0]:
 #				m = "'%s': BTC amount cannot be negative" % num
 			else:

+ 13 - 4
mmgen/opts.py

@@ -47,6 +47,7 @@ def _show_hash_presets():
 
 # most, but not all, of these set the corresponding global var
 common_opts_data = """
+--, --coin=c              Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
 --, --color=0|1           Disable or enable color output
 --, --force-256-color     Force 256-color output when color is enabled
 --, --bitcoin-data-dir=d  Specify Bitcoin data directory location 'd'
@@ -60,7 +61,11 @@ common_opts_data = """
 --, --testnet=0|1         Disable or enable testnet
 --, --skip-cfg-file       Skip reading the configuration file
 --, --version             Print version information and exit
-""".format(pnm=g.proj_name)
+""".format(
+	pnm=g.proj_name,
+	cu_dfl=g.coin,
+	cu_all=' '.join(g.coins),
+	)
 
 def opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args):
 	d = (
@@ -102,6 +107,8 @@ def opt_postproc_initializations():
 
 	if g.platform == 'win': start_mscolor()
 
+	g.coin = g.coin.upper() # allow user to use lowercase
+
 def	set_data_dir_root():
 	g.data_dir_root = os.path.normpath(os.path.expanduser(opt.data_dir)) if opt.data_dir else \
 			os.path.join(g.home_dir,'.'+g.proj_name.lower())
@@ -273,10 +280,10 @@ def check_opts(usr_opts):       # Returns false if any check fails
 		from mmgen.tx import MMGenTX
 		ret = MMGenTX().convert_fee_spec(val,224,on_fail='return')
 		if ret == False:
-			msg("'{}': invalid {} (not a BTC amount or satoshis-per-byte specification)".format(
-					val,desc))
+			msg("'{}': invalid {} (not a {} amount or satoshis-per-byte specification)".format(
+					val,desc,g.coin.upper()))
 		elif ret != None and ret > g.max_tx_fee:
-			msg("'{}': invalid {} (greater than max_tx_fee ({} BTC))".format(val,desc,g.max_tx_fee))
+			msg("'{}': invalid {} (> max_tx_fee ({} {}))".format(val,desc,g.max_tx_fee,g.coin.upper()))
 		else:
 			return True
 		return False
@@ -386,6 +393,8 @@ def check_opts(usr_opts):       # Returns false if any check fails
 		elif key == 'key_generator':
 			if not opt_compares(val,'<=',len(g.key_generators),desc): return False
 			if not opt_compares(val,'>',0,desc): return False
+		elif key == 'coin':
+			if not opt_is_in_list(val.upper(),g.coins,'coin'): return False
 		else:
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 

+ 2 - 5
mmgen/rpc.py

@@ -28,11 +28,7 @@ from mmgen.obj import BTCAmt
 
 class BitcoinRPCConnection(object):
 
-	def __init__(
-				self,
-				host=g.rpc_host,port=(8332,18332)[g.testnet],
-				user=None,passwd=None,auth_cookie=None,
-			):
+	def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None):
 
 		dmsg('=== BitcoinRPCConnection.__init__() debug ===')
 		dmsg('    host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format(
@@ -154,6 +150,7 @@ class BitcoinRPCConnection(object):
 		'getblockcount',
 		'getblockhash',
 		'getmempoolentry',
+		'getnettotals',
 		'getnetworkinfo',
 		'getpeerinfo',
 		'getrawmempool',

+ 5 - 5
mmgen/tool.py

@@ -485,8 +485,8 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 			if usr_addr_list and (label.mmid not in usr_addr_list): continue
 			if label.mmid in addrs:
 				if addrs[label.mmid]['addr'] != d['address']:
-					die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
-							(d['address'], addrs[label.mmid]['addr'])))
+					die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
+							g.coin,d['address'],addrs[label.mmid]['addr']))
 			else:
 				addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':BTCAddr(d['address']) }
 			addrs[label.mmid]['amt'] += d['amount']
@@ -501,7 +501,7 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 		assert len(accts) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not of same length'
 		for a in acct_addrs:
 			if len(a) != 1:
-				die(2,"'{}': more than one BTC address in account!".format(a))
+				die(2,"'{}': more than one {} address in account!".format(a,g.coin))
 		for label,addr in zip(accts,[b[0] for b in acct_addrs]):
 			if usr_addr_list and (label.mmid not in usr_addr_list): continue
 			if label.mmid not in addrs:
@@ -543,7 +543,7 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 			cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
 			amt=addrs[mmid]['amt'].fmt('3.0',color=True)))
 
-	out.append('\nTOTAL: %s BTC' % total.hl(color=True))
+	out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin))
 	o = '\n'.join(out)
 	return do_pager(o) if pager else Msg(o)
 
@@ -567,7 +567,7 @@ def Getbalance(minconf=1):
 	Msg(fs.format('Wallet',
 		*[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
 	for key in sorted(accts.keys()):
-		Msg(fs.format(key+':', *[a.fmt(color=True,suf=' BTC') for a in accts[key]]))
+		Msg(fs.format(key+':', *[a.fmt(color=True,suf=' '+g.coin) for a in accts[key]]))
 	if 'SPENDABLE' in accts:
 		Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!'))
 

+ 10 - 8
mmgen/tw.py

@@ -157,13 +157,13 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 					if self.sort_key == k and getattr(a,k) == getattr(b,k):
 						b.skip = (k,'addr')[k=='twmmid']
 
-		hdr_fmt   = 'UNSPENT OUTPUTS (sort order: %s)  Total BTC: %s'
-		out  = [hdr_fmt % (' '.join(self.sort_info()), self.total.hl())]
+		hdr_fmt = 'UNSPENT OUTPUTS (sort order: {})  Total {}: {}'
+		out  = [hdr_fmt.format(' '.join(self.sort_info()),g.coin,self.total.hl())]
 		if g.chain in ('testnet','regtest'):
 			out += [green('Chain: {}'.format(g.chain.upper()))]
 		af = BTCAddr.fmtc('Address',width=addr_w+1)
 		cf = ('Conf.','Age(d)')[self.show_days]
-		out += [fs % ('Num','TX id'.ljust(tx_w - 5) + ' Vout','',af,'Amt(BTC) ',cf)]
+		out += [fs % ('Num','TX id'.ljust(tx_w - 5) + ' Vout','',af,'Amt({}) '.format(g.coin),cf)]
 
 		for n,i in enumerate(unsp):
 			addr_dots = '|' + '.'*33
@@ -195,7 +195,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 
 		fs  = ' %-4s %-67s %s %s %s %-8s %-6s %s'
 		out = [fs % ('Num','Tx ID,Vout','Address'.ljust(34),'MMGen ID'.ljust(15),
-			'Amount(BTC)','Conf.','Age(d)', 'Label')]
+			'Amount({})'.format(g.coin),'Conf.','Age(d)', 'Label')]
 
 		max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [1])
 		for n,i in enumerate(self.unspent):
@@ -209,17 +209,18 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 						TwComment.fmtc('',color=color,nullrepl='-',width=max_lbl_len))
 			out.append(s.rstrip())
 
-		fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'
+		fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal {}: {}\n'
 		self.fmt_print = fs.format(
 				make_timestr(),
 				' '.join(self.sort_info(include_group=False)),
 				'\n'.join(out),
+				g.coin,
 				self.total.hl(color=color))
 		return self.fmt_print
 
 	def display_total(self):
-		fs = '\nTotal unspent: %s BTC (%s outputs)'
-		msg(fs % (self.total.hl(),len(self.unspent)))
+		fs = '\nTotal unspent: {} {} ({} outputs)'
+		msg(fs.format(self.total.hl(),g.coin,len(self.unspent)))
 
 	def get_idx_and_label_from_user(self):
 		msg('')
@@ -246,7 +247,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 							return n,s
 
 	def view_and_sort(self,tx):
-		txos = 'Total to spend, excluding fees: {} BTC\n\n'.format(tx.sum_outputs().hl()) if tx.outputs else ''
+		fs = 'Total to spend, excluding fees: {} {}\n\n'
+		txos = fs.format(tx.sum_outputs().hl(),g.coin) if tx.outputs else ''
 		prompt = """
 {}Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen

+ 55 - 46
mmgen/tx.py

@@ -319,7 +319,7 @@ class MMGenTX(MMGenObject):
 	def get_relay_fee(self):
 		assert self.estimate_size()
 		kb_fee = BTCAmt(bitcoin_connection().getnetworkinfo()['relayfee'])
-		vmsg('Relay fee: {} BTC/kB'.format(kb_fee))
+		vmsg('Relay fee: {} {}/kB'.format(kb_fee,g.coin))
 		return kb_fee * self.estimate_size() / 1024
 
 	def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'):
@@ -339,16 +339,20 @@ class MMGenTX(MMGenObject):
 	def get_usr_fee(self,tx_fee,desc='Missing description'):
 		btc_fee = self.convert_fee_spec(tx_fee,self.estimate_size(),on_fail='return')
 		if btc_fee == None:
-			msg("'{}': cannot convert satoshis-per-byte to BTC because transaction size is unknown".format(tx_fee))
-			assert False  # because we shouldn't be calling this if tx size is unknown
+			# we shouldn't be calling this if tx size is unknown
+			m = "'{}': cannot convert satoshis-per-byte to {} because transaction size is unknown"
+			assert False, m.format(tx_fee,g.coin)
 		elif btc_fee == False:
-			msg("'{}': invalid TX fee (not a BTC amount or satoshis-per-byte specification)".format(tx_fee))
+			m = "'{}': invalid TX fee (not a {} amount or satoshis-per-byte specification)"
+			msg(m.format(tx_fee,g.coin))
 			return False
 		elif btc_fee > g.max_tx_fee:
-			msg('{} BTC: {} fee too large (maximum fee: {} BTC)'.format(btc_fee,desc,g.max_tx_fee))
+			m = '{} {c}: {} fee too large (maximum fee: {} {c})'
+			msg(m.format(btc_fee,desc,g.max_tx_fee,c=g.coin))
 			return False
 		elif btc_fee < self.get_relay_fee():
-			msg('{} BTC: {} fee too small (below relay fee of {} BTC)'.format(str(btc_fee),desc,str(self.get_relay_fee())))
+			m = '{} {c}: {} fee too small (below relay fee of {} {c})'
+			msg(m.format(str(btc_fee),desc,str(self.get_relay_fee()),c=g.coin))
 			return False
 		else:
 			return btc_fee
@@ -360,8 +364,8 @@ class MMGenTX(MMGenObject):
 				btc_fee = self.get_usr_fee(tx_fee,desc)
 			if btc_fee:
 				m = ('',' (after {}x adjustment)'.format(opt.tx_fee_adj))[opt.tx_fee_adj != 1]
-				p = '{} TX fee{}: {} BTC ({} satoshis per byte)'.format(desc,m,
-					btc_fee.hl(),pink(str(self.btc2spb(btc_fee))))
+				p = '{} TX fee{}: {} {} ({} satoshis per byte)'.format(desc,m,
+					btc_fee.hl(),g.coin,pink(str(self.btc2spb(btc_fee))))
 				if opt.yes or keypress_confirm(p+'.  OK?',default_yes=True):
 					if opt.yes: msg(p)
 					return btc_fee
@@ -444,8 +448,8 @@ class MMGenTX(MMGenObject):
 
 		self.die_if_incorrect_chain()
 
-		if opt.aug1hf and self.has_segwit_inputs():
-			die(2,yellow("'--aug1hf' option is incompatible with Segwit transaction inputs!"))
+		if g.coin == 'BCH' and self.has_segwit_inputs():
+			die(2,yellow("Segwit inputs cannot be spent on BCH chain!"))
 
 		if not keys:
 			msg('No keys. Cannot sign!')
@@ -468,15 +472,15 @@ class MMGenTX(MMGenObject):
 
 		from mmgen.bitcoin import hash256
 		msg_r('Signing transaction{}...'.format(tx_num_str))
-		ht = ('ALL','ALL|FORKID')[bool(opt.aug1hf)] # sighashtype defaults to 'ALL'
+		ht = ('ALL','ALL|FORKID')[g.coin=='BCH'] # sighashtype defaults to 'ALL'
 		ret = c.signrawtransaction(self.hex,sig_data,keys.values(),ht,on_fail='return')
 
 		from mmgen.rpc import rpc_error,rpc_errmsg
 		if rpc_error(ret):
 			errmsg = rpc_errmsg(ret)
 			if 'Invalid sighash param' in errmsg:
-				m  = 'This chain does not support the Aug. 1 2017 UAHF.'
-				m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' without the --aug1hf option."
+				m  = 'This is not the BCH chain.'
+				m += "\nRe-run the script without the --aug1hf or --coin=bch option."
 			else:
 				m = errmsg
 			msg(yellow(m))
@@ -549,6 +553,17 @@ class MMGenTX(MMGenObject):
 	def is_in_utxos(self,c):
 		return 'txid' in c.getrawtransaction(self.btc_txid,True,on_fail='silent')
 
+	def get_status(self,c,status=False):
+		if self.is_in_mempool(c):
+			msg(('Warning: transaction is in mempool!','Transaction is in mempool')[status])
+		elif self.is_in_wallet(c):
+			die(1,'Transaction has been confirmed{}'.format('' if status else '!'))
+		elif self.is_in_utxos(c):
+			die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
+		ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed
+		if ret:
+			die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
+
 	def send(self,c,prompt_user=True):
 
 		self.die_if_incorrect_chain()
@@ -562,16 +577,7 @@ class MMGenTX(MMGenObject):
 		if self.get_fee() > g.max_tx_fee:
 			die(2,'Transaction fee ({}) greater than max_tx_fee ({})!'.format(self.get_fee(),g.max_tx_fee))
 
-		if self.is_in_mempool(c):
-			msg('Warning: transaction is in mempool!')
-		elif self.is_in_wallet(c):
-			die(1,'Transaction has been confirmed!')
-		elif self.is_in_utxos(c):
-			die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
-
-		ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed
-		if ret:
-			die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
+		self.get_status(c)
 
 		if prompt_user:
 			m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)]
@@ -592,10 +598,10 @@ class MMGenTX(MMGenObject):
 			errmsg = rpc_errmsg(ret)
 			if 'Signature must use SIGHASH_FORKID' in errmsg:
 				m  = 'The Aug. 1 2017 UAHF has activated on this chain.'
-				m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' with the --aug1hf option."
+				m += "\nRe-run the script with the --coin=bch option."
 			elif 'Illegal use of SIGHASH_FORKID' in errmsg:
 				m  = 'The Aug. 1 2017 UAHF is not yet active on this chain.'
-				m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' without the --aug1hf option."
+				m += "\nRe-run the script without the --aug1hf or --coin=bch option."
 			else:
 				m = errmsg
 			msg(yellow(m))
@@ -667,74 +673,77 @@ class MMGenTX(MMGenObject):
 			blockcount = None
 
 		hdr_fs = (
-			'TRANSACTION DATA\n\nHeader: [ID:{}] [{} BTC] [{} UTC] [RBF:{}] [Signed:{}]\n',
-			'Transaction {} {} BTC ({} UTC) RBF={} Signed={}\n'
+			'TRANSACTION DATA\n\n[ID:{}] [{} {}] [{} UTC] [RBF:{}] [Signed:{}]\n',
+			'Transaction {} {} {} ({} UTC) RBF={} Signed={}\n'
 		)[bool(terse)]
 
-		out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),self.timestamp,
+		out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),g.coin,self.timestamp,
 				self.is_rbf(color=True),self.marked_signed(color=True))
 
 		enl = ('\n','')[bool(terse)]
 		if self.chain in ('testnet','regtest'): out += green('Chain: {}\n'.format(self.chain.upper()))
-		if self.btc_txid: out += 'Bitcoin TxID: {}\n'.format(self.btc_txid.hl())
+		if self.btc_txid: out += '{} TxID: {}\n'.format(g.coin,self.btc_txid.hl())
 		out += enl
 
 		if self.label:
 			out += 'Comment: %s\n%s' % (self.label.hl(),enl)
 		out += 'Inputs:\n' + enl
 
-		nonmm_str = '(non-{pnm} address){s}  '.format(pnm=g.proj_name,s=('',' ')[terse])
+		nonmm_str = '(non-{pnm} address)'.format(pnm=g.proj_name)
+		max_mmwid = max(max(len(i.mmid) for i in self.inputs if i.mmid)+len('()'),len(nonmm_str))
 		for n,e in enumerate(sorted(self.inputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
 			if blockcount:
 				confs = e.confs + blockcount - self.blockcount
 				days = int(confs * g.mins_per_block / (60*24))
-			mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True) if e.mmid \
-						else MMGenID.hlc(nonmm_str)
+			mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True) if e.mmid else MMGenID.hlc(nonmm_str)
 			if terse:
-				out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl())
+				out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
 			else:
 				for d in (
 	(n+1, 'tx,vout:',       '%s,%s' % (e.txid, e.vout)),
 	('',  'address:',       e.addr.fmt(color=True) + ' ' + mmid_fmt),
 	('',  'comment:',       e.label.hl() if e.label else ''),
-	('',  'amount:',        '%s BTC' % e.amt.hl()),
+	('',  'amount:',        '{} {}'.format(e.amt.hl(),g.coin)),
 	('',  'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '')
 				):
 					if d[2]: out += ('%3s %-8s %s\n' % d)
 			out += '\n'
 
 		out += 'Outputs:\n' + enl
+		max_mmwid = max((len(o.mmid),len(o.mmid)+len(' (chg)'))[bool(o.is_chg)] for o in self.outputs if o.mmid)
+		max_mmwid = max(max_mmwid+len('()'),len(nonmm_str))
 		for n,e in enumerate(sorted(self.outputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
 			if e.mmid:
 				app=('',' (chg)')[bool(e.is_chg and terse)]
-				mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True,
+				mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True,
 										app=app,appcolor='green')
 			else:
 				mmid_fmt = MMGenID.hlc(nonmm_str)
 			if terse:
-				out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl())
+				out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
 			else:
 				for d in (
 						(n+1, 'address:',  e.addr.fmt(color=True) + ' ' + mmid_fmt),
 						('',  'comment:',  e.label.hl() if e.label else ''),
-						('',  'amount:',   '%s BTC' % e.amt.hl()),
+						('',  'amount:',   '{} {}'.format(e.amt.hl(),g.coin)),
 						('',  'change:',   green('True') if e.is_chg else '')
 					):
 					if d[2]: out += ('%3s %-8s %s\n' % d)
 			out += '\n'
 
 		fs = (
-			'Total input:  %s BTC\nTotal output: %s BTC\nTX fee:       %s BTC (%s satoshis per byte)\n',
-			'In %s BTC - Out %s BTC - Fee %s BTC (%s satoshis/byte)\n'
+			'Total input:  {} {c}\nTotal output: {} {c}\nTX fee:       {} {c} ({} satoshis per byte)\n',
+			'In {} {c} - Out {} {c} - Fee {} {c} ({} satoshis/byte)\n'
 		)[bool(terse)]
 
 		total_in  = self.sum_inputs()
 		total_out = self.sum_outputs()
-		out += fs % (
+		out += fs.format(
 			total_in.hl(),
 			total_out.hl(),
 			(total_in-total_out).hl(),
 			pink(str(self.btc2spb(total_in-total_out))),
+			c=g.coin
 		)
 		if opt.verbose:
 			ts = len(self.hex)/2 if self.hex else 'unknown'
@@ -851,10 +860,10 @@ class MMGenBumpTX(MMGenTX):
 				else:
 					o_amt = self.outputs[idx].amt
 					cs = ('',' (change output)')[chg_idx == idx]
-					p = 'Fee will be deducted from output {}{} ({} BTC)'.format(idx+1,cs,o_amt)
+					p = 'Fee will be deducted from output {}{} ({} {})'.format(idx+1,cs,o_amt,g.coin)
 					if o_amt < self.min_fee:
-						msg('Minimum fee ({} BTC) is greater than output amount ({} BTC)'.format(
-							self.min_fee,o_amt))
+						msg('Minimum fee ({} {c}) is greater than output amount ({} {c})'.format(
+							self.min_fee,o_amt,c=g.coin))
 					elif opt.yes or keypress_confirm(p+'.  OK?',default_yes=True):
 						if opt.yes: msg(p)
 						self.bump_output_idx = idx
@@ -866,11 +875,11 @@ class MMGenBumpTX(MMGenTX):
 	def get_usr_fee(self,tx_fee,desc):
 		ret = super(type(self),self).get_usr_fee(tx_fee,desc)
 		if ret < self.min_fee:
-			msg('{} BTC: {} fee too small. Minimum fee: {} BTC ({} satoshis per byte)'.format(
-				ret,desc,self.min_fee,self.btc2spb(self.min_fee)))
+			msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} satoshis per byte)'.format(
+				ret,desc,self.min_fee,self.btc2spb(self.min_fee,c=g.coin)))
 			return False
 		output_amt = self.outputs[self.bump_output_idx].amt
 		if ret >= output_amt:
-			msg('{} BTC: {} fee too large. Maximum fee: <{} BTC'.format(ret,desc,output_amt))
+			msg('{} {c}: {} fee too large. Maximum fee: <{} {c}'.format(ret,desc,output_amt,c=g.coin))
 			return False
 		return ret

+ 16 - 18
mmgen/txcreate.py

@@ -51,10 +51,10 @@ one address with no amount on the command line.
 
 fee_notes = """
 FEE SPECIFICATION: Transaction fees, both on the command line and at the
-interactive prompt, may be specified as either absolute BTC amounts, using a
-plain decimal number, or as satoshis per byte, using an integer followed by
+interactive prompt, may be specified as either absolute {} amounts, using
+a plain decimal number, or as satoshis per byte, using an integer followed by
 the letter 's'.
-"""
+""".format(g.coin)
 
 wmsg = {
 	'addr_in_addrfile_only': """
@@ -77,19 +77,15 @@ NOTE: This transaction includes non-{pnm} inputs, which makes the signing
 process more complicated.  When signing the transaction, keys for non-{pnm}
 inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file'
 option.
-Selected non-{pnm} inputs: %s
+Selected non-{pnm} inputs: {{}}
 """.strip().format(pnm=pnm,pnl=pnm.lower()),
 	'not_enough_btc': """
-Selected outputs insufficient to fund this transaction (%s BTC needed)
-""".strip(),
-	'throwaway_change': """
-ERROR: This transaction produces change (%s BTC); however, no change address
-was specified.
-""".strip(),
+Selected outputs insufficient to fund this transaction ({{}} {} needed)
+""".strip().format(g.coin),
 	'no_change_output': """
 ERROR: No change address specified.  If you wish to create a transaction with
-only one output, specify a single output address with no BTC amount
-""".strip(),
+only one output, specify a single output address with no {} amount
+""".strip().format(g.coin),
 }
 
 def select_unspent(unspent,prompt):
@@ -139,7 +135,7 @@ def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]):
 		else:
 			start_fee = BTCAmt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024
 			if opt.verbose:
-				msg('{} fee ({} confs): {} BTC/kB'.format(desc,opt.tx_confs,ret))
+				msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin))
 				msg('TX size (estimated): {}'.format(tx.estimate_size()))
 
 	return tx.get_usr_fee_interactive(start_fee,desc=desc)
@@ -195,12 +191,12 @@ def get_inputs_from_user(tw,tx,caller):
 
 		t_inputs = sum(s.amt for s in sel_unspent)
 		if t_inputs < tx.send_amt:
-			msg(wmsg['not_enough_btc'] % (tx.send_amt - t_inputs))
+			msg(wmsg['not_enough_btc'].format(tx.send_amt-t_inputs))
 			continue
 
 		non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen']
 		if non_mmaddrs and caller != 'txdo':
-			msg(wmsg['non_mmgen_inputs'] % ', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs]))))
+			msg(wmsg['non_mmgen_inputs'].format(', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs])))))
 			if not keypress_confirm('Accept?'):
 				continue
 
@@ -209,12 +205,12 @@ def get_inputs_from_user(tw,tx,caller):
 		change_amt = tx.sum_inputs() - tx.send_amt - get_fee_from_estimate_or_user(tx)
 
 		if change_amt >= 0:
-			p = 'Transaction produces %s BTC in change' % change_amt.hl()
+			p = 'Transaction produces {} {} in change'.format(change_amt.hl(),g.coin)
 			if opt.yes or keypress_confirm(p+'.  OK?',default_yes=True):
 				if opt.yes: msg(p)
 				return change_amt
 		else:
-			msg(wmsg['not_enough_btc'] % abs(change_amt))
+			msg(wmsg['not_enough_btc'].format(abs(change_amt)))
 
 def txcreate(cmd_args,do_info=False,caller='txcreate'):
 
@@ -234,7 +230,9 @@ def txcreate(cmd_args,do_info=False,caller='txcreate'):
 
 	tx.send_amt = tx.sum_outputs()
 
-	msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt.hl())[bool(tx.send_amt)])
+	msg('Total amount to spend: {}'.format(
+		('Unknown','{} {}'.format(tx.send_amt.hl(),g.coin))[bool(tx.send_amt)]
+	))
 
 	change_amt = get_inputs_from_user(tw,tx,caller)
 

+ 8 - 8
mmgen/txsign.py

@@ -57,12 +57,12 @@ column below:
 
 wmsg = {
 	'mapping_error': """
-{pnm} -> BTC address mappings differ!
-%-23s %s -> %s
-%-23s %s -> %s
-""".strip().format(pnm=pnm),
+{pnm} -> {c} address mappings differ!
+{{:<23}} {{}} -> {{}}
+{{:<23}} {{}} -> {{}}
+""".strip().format(pnm=pnm,c=g.coin),
 	'missing_keys_error': """
-ERROR: a key file must be supplied for the following non-{pnm} address%s:\n    %s
+ERROR: a key file must be supplied for the following non-{pnm} address{{}}:\n    {{}}
 """.format(pnm=pnm).strip()
 }
 
@@ -106,7 +106,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
 	if not need_keys: return []
 	desc,m1 = ('key-address file','From key-address file:') if keyaddr_list else \
 					('seed(s)','Generated from seed:')
-	qmsg('Checking {} -> BTC address mappings for {} (from {})'.format(pnm,src,desc))
+	qmsg('Checking {} -> {} address mappings for {} (from {})'.format(pnm,g.coin,src,desc))
 	d = keyaddr_list.flat_list() if keyaddr_list else \
 		generate_keys_for_mmgen_addrs([e.mmid for e in need_keys],infiles,saved_seeds)
 	new_keys = []
@@ -118,7 +118,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
 					if src == 'inputs':
 						new_keys.append((f.addr,f.wif))
 				else:
-					die(3,wmsg['mapping_error'] % (m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr))
+					die(3,wmsg['mapping_error'].format(m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr))
 	if new_keys:
 		vmsg('Added %s wif key%s from %s' % (len(new_keys),suf(new_keys,'s'),desc))
 	return new_keys
@@ -165,7 +165,7 @@ def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''):
 		tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False)
 		tmp.add_wifs(kl)
 		m = tmp.list_missing('wif')
-		if m: die(2,wmsg['missing_keys_error'] % (suf(m,'es'),'\n    '.join(m)))
+		if m: die(2,wmsg['missing_keys_error'].format(suf(m,'es'),'\n    '.join(m)))
 		keys += tmp.get_addr_wif_pairs()
 
 	if opt.mmgen_keys_from_file:

+ 30 - 15
mmgen/util.py

@@ -58,7 +58,8 @@ def Die(ev=0,s=''):
 	sys.exit(ev)
 
 def rdie(ev=0,s=''): die(ev,red(s))
-def ydie(ev=0,s=''): die(ev,yellow(s))
+def wdie(ev=0,s=''): die(ev,yellow(s))
+def hi(): sys.stdout.write(yellow('hi'))
 
 def pformat(d):
 	import pprint
@@ -773,20 +774,16 @@ def get_bitcoind_auth_cookie():
 		return ''
 
 def bitcoin_connection():
-	cfg = get_bitcoind_cfg_options(('rpcuser','rpcpassword'))
-	import mmgen.rpc
-	c = mmgen.rpc.BitcoinRPCConnection(
-				g.rpc_host or 'localhost',
-				g.rpc_port or (8332,18332)[g.testnet],
-				g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override bitcoind's
-				g.rpc_password or cfg['rpcpassword'],
-				auth_cookie=get_bitcoind_auth_cookie())
-	# do an RPC call so we exit immediately if we can't connect
-	if not g.bitcoind_version:
-		g.bitcoind_version = int(c.getnetworkinfo()['version'])
-		g.chain = c.getblockchaininfo()['chain']
-		if g.chain != 'regtest': g.chain += 'net'
-		assert g.chain in g.chains
+
+	def	check_coin_mismatch(c):
+		fb = '00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148'
+		err = []
+		if int(c.getblockchaininfo()['blocks']) <= 478558 or c.getblockhash(478559) == fb:
+			if g.coin == 'BCH': err = 'BCH','BTC'
+		elif g.coin == 'BTC': err = 'BTC','BCH'
+		if err: wdie(2,"'{}' requested, but this is the {} chain!".format(*err))
+
+	def	check_chain_mismatch():
 		err = None
 		if g.regtest and g.chain != 'regtest':
 			err = '--regtest option'
@@ -797,4 +794,22 @@ def bitcoin_connection():
 			err = 'mainnet'
 		if err:
 			die(1,'{} selected but chain is {}'.format(err,g.chain))
+
+	cfg = get_bitcoind_cfg_options(('rpcuser','rpcpassword'))
+	import mmgen.rpc
+	c = mmgen.rpc.BitcoinRPCConnection(
+				g.rpc_host or 'localhost',
+				g.rpc_port or g.ports[g.coin][g.testnet],
+				g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override bitcoind's
+				g.rpc_password or cfg['rpcpassword'],
+				auth_cookie=get_bitcoind_auth_cookie())
+
+	if not g.bitcoind_version: # First call
+		g.bitcoind_version = int(c.getnetworkinfo()['version'])
+		g.chain = c.getblockchaininfo()['chain']
+		if g.chain != 'regtest':
+			g.chain += 'net'
+		assert g.chain in g.chains
+		if g.chain == 'mainnet':
+			check_coin_mismatch(c)
 	return c