Browse Source

Config API, Part I

This patch eliminates the global configuration variables `opt` and `g`, making
all functions and class instances locally configurable.  Configuration data is
passed to functions and constructors via the `cfg` parameter and made available
to methods in `self.cfg`.

Local configuration free from dependence on the command line will enable the
creation of multiple, independently configured instances of MMGen’s data
objects within a single process.

Potential applications include testing (tracking wallets configured to interact
with spawned processes, for example) and the use of MMGen as a library for
other projects.

This patch completes most of the work required to enable the API.  The full
implementation will appear in a forthcoming commit.
The MMGen Project 1 year ago
parent
commit
c7adb56e38
197 changed files with 2218 additions and 2078 deletions
  1. 4 6
      examples/halving-calculator.py
  2. 8 6
      mmgen/addrdata.py
  3. 19 20
      mmgen/addrfile.py
  4. 6 6
      mmgen/addrgen.py
  5. 16 15
      mmgen/addrlist.py
  6. 6 6
      mmgen/altcoin.py
  7. 2 3
      mmgen/baseconv.py
  8. 17 14
      mmgen/cfgfile.py
  9. 0 1
      mmgen/common.py
  10. 50 51
      mmgen/crypto.py
  11. 24 21
      mmgen/daemon.py
  12. 1 1
      mmgen/data/version
  13. 33 30
      mmgen/fileutil.py
  14. 6 21
      mmgen/globalvars.py
  15. 7 7
      mmgen/help.py
  16. 9 9
      mmgen/keygen.py
  17. 20 21
      mmgen/main_addrgen.py
  18. 22 20
      mmgen/main_addrimport.py
  19. 38 38
      mmgen/main_autosign.py
  20. 18 18
      mmgen/main_msg.py
  21. 21 19
      mmgen/main_passgen.py
  22. 5 3
      mmgen/main_regtest.py
  23. 23 24
      mmgen/main_seedjoin.py
  24. 17 18
      mmgen/main_split.py
  25. 10 9
      mmgen/main_tool.py
  26. 21 20
      mmgen/main_txbump.py
  27. 15 19
      mmgen/main_txcreate.py
  28. 17 20
      mmgen/main_txdo.py
  29. 12 12
      mmgen/main_txsend.py
  30. 21 19
      mmgen/main_txsign.py
  31. 30 23
      mmgen/main_wallet.py
  32. 22 16
      mmgen/main_xmrwallet.py
  33. 9 8
      mmgen/mn_entry.py
  34. 28 12
      mmgen/msg.py
  35. 0 1
      mmgen/objmethods.py
  36. 125 121
      mmgen/opts.py
  37. 6 4
      mmgen/passwdlist.py
  38. 1 2
      mmgen/proto/btc/addrdata.py
  39. 4 5
      mmgen/proto/btc/daemon.py
  40. 3 4
      mmgen/proto/btc/misc.py
  41. 17 17
      mmgen/proto/btc/regtest.py
  42. 16 16
      mmgen/proto/btc/rpc.py
  43. 5 6
      mmgen/proto/btc/tw/ctl.py
  44. 2 1
      mmgen/proto/btc/tw/json.py
  45. 3 4
      mmgen/proto/btc/tw/txhistory.py
  46. 3 4
      mmgen/proto/btc/tx/base.py
  47. 9 10
      mmgen/proto/btc/tx/new.py
  48. 3 4
      mmgen/proto/btc/tx/online.py
  49. 2 2
      mmgen/proto/btc/tx/signed.py
  50. 2 3
      mmgen/proto/btc/tx/status.py
  51. 7 8
      mmgen/proto/btc/tx/unsigned.py
  52. 2 3
      mmgen/proto/eth/addrdata.py
  53. 9 8
      mmgen/proto/eth/contract.py
  54. 9 9
      mmgen/proto/eth/misc.py
  55. 3 2
      mmgen/proto/eth/msg.py
  56. 1 2
      mmgen/proto/eth/params.py
  57. 3 2
      mmgen/proto/eth/rpc.py
  58. 3 3
      mmgen/proto/eth/tw/bal.py
  59. 4 4
      mmgen/proto/eth/tw/ctl.py
  60. 1 2
      mmgen/proto/eth/tw/view.py
  61. 1 2
      mmgen/proto/eth/tx/base.py
  62. 12 13
      mmgen/proto/eth/tx/new.py
  63. 3 5
      mmgen/proto/eth/tx/online.py
  64. 3 3
      mmgen/proto/eth/tx/unsigned.py
  65. 3 4
      mmgen/proto/secp256k1/keygen.py
  66. 9 6
      mmgen/proto/xmr/daemon.py
  67. 8 8
      mmgen/proto/xmr/keygen.py
  68. 5 4
      mmgen/proto/xmr/rpc.py
  69. 1 1
      mmgen/proto/zec/keygen.py
  70. 1 2
      mmgen/proto/zec/params.py
  71. 21 15
      mmgen/protocol.py
  72. 16 10
      mmgen/rpc.py
  73. 6 6
      mmgen/seed.py
  74. 18 13
      mmgen/seedsplit.py
  75. 4 4
      mmgen/share/Opts.py
  76. 5 5
      mmgen/subseed.py
  77. 8 4
      mmgen/term.py
  78. 13 13
      mmgen/tool/api.py
  79. 6 6
      mmgen/tool/coin.py
  80. 8 8
      mmgen/tool/common.py
  81. 5 5
      mmgen/tool/file.py
  82. 6 6
      mmgen/tool/filecrypt.py
  83. 7 9
      mmgen/tool/fileutil.py
  84. 5 5
      mmgen/tool/help.py
  85. 5 6
      mmgen/tool/mnemonic.py
  86. 14 14
      mmgen/tool/rpc.py
  87. 9 9
      mmgen/tool/util.py
  88. 14 13
      mmgen/tool/wallet.py
  89. 4 5
      mmgen/tw/addresses.py
  90. 3 3
      mmgen/tw/bal.py
  91. 16 15
      mmgen/tw/ctl.py
  92. 15 14
      mmgen/tw/json.py
  93. 1 1
      mmgen/tw/prune.py
  94. 2 2
      mmgen/tw/txhistory.py
  95. 2 3
      mmgen/tw/unspent.py
  96. 14 12
      mmgen/tw/view.py
  97. 6 5
      mmgen/tx/__init__.py
  98. 7 6
      mmgen/tx/base.py
  99. 4 5
      mmgen/tx/bump.py
  100. 2 2
      mmgen/tx/completed.py
  101. 11 10
      mmgen/tx/file.py
  102. 1 2
      mmgen/tx/info.py
  103. 35 35
      mmgen/tx/new.py
  104. 3 3
      mmgen/tx/online.py
  105. 28 24
      mmgen/tx/sign.py
  106. 16 15
      mmgen/ui.py
  107. 56 57
      mmgen/util.py
  108. 5 5
      mmgen/util2.py
  109. 10 9
      mmgen/wallet/__init__.py
  110. 10 10
      mmgen/wallet/base.py
  111. 10 11
      mmgen/wallet/brain.py
  112. 10 12
      mmgen/wallet/dieroll.py
  113. 16 17
      mmgen/wallet/enc.py
  114. 15 17
      mmgen/wallet/incog_base.py
  115. 14 13
      mmgen/wallet/incog_hidden.py
  116. 17 19
      mmgen/wallet/mmgen.py
  117. 4 4
      mmgen/wallet/mmhex.py
  118. 7 8
      mmgen/wallet/mnemonic.py
  119. 1 1
      mmgen/wallet/plainhex.py
  120. 4 4
      mmgen/wallet/seed.py
  121. 3 3
      mmgen/wallet/unenc.py
  122. 44 33
      mmgen/xmrwallet.py
  123. 3 3
      scripts/compute-file-chksum.py
  124. 18 20
      scripts/create-token.py
  125. 4 4
      scripts/tx-v2-to-v3.py
  126. 5 5
      scripts/uninstall-mmgen.py
  127. 55 50
      test/gentest.py
  128. 5 1
      test/hashfunc.py
  129. 24 23
      test/include/coin_daemon_control.py
  130. 46 18
      test/include/common.py
  131. 22 24
      test/include/pexpect.py
  132. 13 13
      test/misc/cfg.py
  133. 9 10
      test/misc/get_passphrase.py
  134. 14 7
      test/misc/input_func.py
  135. 1 1
      test/misc/oneshot_warning.py
  136. 12 8
      test/misc/opts.py
  137. 10 10
      test/misc/term.py
  138. 6 6
      test/misc/term_ni.py
  139. 5 2
      test/misc/tool_api_test.py
  140. 3 3
      test/misc/utf8_output.py
  141. 15 11
      test/objattrtest.py
  142. 4 3
      test/objattrtest_py_d/oat_btc_mainnet.py
  143. 4 4
      test/objattrtest_py_d/oat_common.py
  144. 25 22
      test/objtest.py
  145. 4 3
      test/objtest_py_d/ot_btc_mainnet.py
  146. 2 1
      test/objtest_py_d/ot_btc_testnet.py
  147. 0 1
      test/objtest_py_d/ot_common.py
  148. 2 1
      test/objtest_py_d/ot_ltc_mainnet.py
  149. 2 1
      test/objtest_py_d/ot_ltc_testnet.py
  150. 1 0
      test/overlay/fakemods/mmgen/proto/btc/tw/unspent.py
  151. 16 11
      test/scrambletest.py
  152. 94 88
      test/test.py
  153. 3 2
      test/test_py_d/cfg.py
  154. 7 6
      test/test_py_d/common.py
  155. 5 6
      test/test_py_d/ts_autosign.py
  156. 5 7
      test/test_py_d/ts_base.py
  157. 3 3
      test/test_py_d/ts_cfgfile.py
  158. 0 2
      test/test_py_d/ts_chainsplit.py
  159. 33 28
      test/test_py_d/ts_ethdev.py
  160. 3 3
      test/test_py_d/ts_input.py
  161. 26 25
      test/test_py_d/ts_main.py
  162. 3 3
      test/test_py_d/ts_misc.py
  163. 17 20
      test/test_py_d/ts_opts.py
  164. 2 4
      test/test_py_d/ts_ref.py
  165. 4 6
      test/test_py_d/ts_ref_3seed.py
  166. 2 3
      test/test_py_d/ts_ref_altcoin.py
  167. 24 25
      test/test_py_d/ts_regtest.py
  168. 0 2
      test/test_py_d/ts_seedsplit.py
  169. 0 2
      test/test_py_d/ts_shared.py
  170. 2 2
      test/test_py_d/ts_tool.py
  171. 2 3
      test/test_py_d/ts_wallet.py
  172. 15 11
      test/test_py_d/ts_xmrwallet.py
  173. 43 39
      test/tooltest.py
  174. 44 40
      test/tooltest2.py
  175. 14 13
      test/unit_tests.py
  176. 3 3
      test/unit_tests_d/__init__.py
  177. 11 9
      test/unit_tests_d/ut_addrlist.py
  178. 2 1
      test/unit_tests_d/ut_addrparse.py
  179. 4 3
      test/unit_tests_d/ut_baseconv.py
  180. 2 1
      test/unit_tests_d/ut_bip39.py
  181. 4 3
      test/unit_tests_d/ut_daemon.py
  182. 2 2
      test/unit_tests_d/ut_dep.py
  183. 4 2
      test/unit_tests_d/ut_devtools.py
  184. 1 0
      test/unit_tests_d/ut_flags.py
  185. 7 6
      test/unit_tests_d/ut_gen.py
  186. 1 0
      test/unit_tests_d/ut_indexed_dict.py
  187. 1 0
      test/unit_tests_d/ut_lockable.py
  188. 4 3
      test/unit_tests_d/ut_mn_entry.py
  189. 9 8
      test/unit_tests_d/ut_msg.py
  190. 1 0
      test/unit_tests_d/ut_obj.py
  191. 23 20
      test/unit_tests_d/ut_rpc.py
  192. 11 10
      test/unit_tests_d/ut_scrypt.py
  193. 7 6
      test/unit_tests_d/ut_seedsplit.py
  194. 10 9
      test/unit_tests_d/ut_subseed.py
  195. 6 5
      test/unit_tests_d/ut_tx.py
  196. 10 10
      test/unit_tests_d/ut_tx_deserialize.py
  197. 2 1
      test/unit_tests_d/ut_xmrseed.py

+ 4 - 6
examples/halving-calculator.py

@@ -15,10 +15,9 @@ examples.halving-calculator.py: Demonstrate use of the MMGen asyncio/aiohttp JSO
 import time
 
 import mmgen.opts as opts
-from mmgen.opts import opt
 from mmgen.util import async_run
 
-opts.init({
+cfg = opts.init({
 	'text': {
 		'desc': 'Estimate date of next block subsidy halving',
 		'usage':'[opts]',
@@ -55,16 +54,15 @@ def time_diff_warning(t_diff):
 
 async def main():
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
+	proto = cfg._proto
 
 	from mmgen.rpc import rpc_init
-	c = await rpc_init(proto)
+	c = await rpc_init(cfg,proto)
 
 	tip = await c.call('getblockcount')
 	assert tip > 1, 'block tip must be > 1'
 	remaining = proto.halving_interval - tip % proto.halving_interval
-	sample_size = int(opt.sample_size) if opt.sample_size else min(tip-1,max(remaining,144))
+	sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1,max(remaining,144))
 
 	# aiohttp backend will perform these two calls concurrently:
 	cur,old = await c.gathered_call('getblockstats',((tip,),(tip - sample_size,)))

+ 8 - 6
mmgen/addrdata.py

@@ -20,7 +20,8 @@
 addrdata: MMGen AddrData and related classes
 """
 
-from .util import vmsg,fmt,die
+from .globalvars import gc
+from .util import fmt,die
 from .base_obj import AsyncInit
 from .obj import MMGenObject,MMGenDict,get_obj
 from .addr import MMGenID,AddrListID
@@ -68,16 +69,16 @@ class AddrData(MMGenObject):
 
 class TwAddrData(AddrData,metaclass=AsyncInit):
 
-	def __new__(cls,proto,*args,**kwargs):
+	def __new__(cls,cfg,proto,*args,**kwargs):
 		return MMGenObject.__new__(proto.base_proto_subclass(cls,'addrdata'))
 
-	async def __init__(self,proto,twctl=None):
+	async def __init__(self,cfg,proto,twctl=None):
 		from .rpc import rpc_init
 		from .tw.shared import TwLabel
-		from .globalvars import gc
 		from .seed import SeedID
+		self.cfg = cfg
 		self.proto = proto
-		self.rpc = await rpc_init(proto)
+		self.rpc = await rpc_init(cfg,proto)
 		self.al_ids = {}
 		twd = await self.get_tw_data(twctl)
 		out,i = {},0
@@ -96,10 +97,11 @@ class TwAddrData(AddrData,metaclass=AsyncInit):
 				out[al_id].append(AddrListEntry(self.proto,idx=obj.idx,addr=addr_array[0],comment=l.comment))
 				i += 1
 
-		vmsg(f'{i} {gc.proj_name} addresses found, {len(twd)} accounts total')
+		self.cfg._util.vmsg(f'{i} {gc.proj_name} addresses found, {len(twd)} accounts total')
 
 		for al_id in out:
 			self.add(AddrList(
+				self.cfg,
 				self.proto,
 				al_id = al_id,
 				adata = AddrListData(sorted( out[al_id], key=lambda a: a.idx ))

+ 19 - 20
mmgen/addrfile.py

@@ -20,8 +20,8 @@
 addrfile: Address and password file classes for the MMGen suite
 """
 
-from .globalvars import g
-from .util import msg,qmsg,qmsg_r,die,capfirst
+from .globalvars import gc
+from .util import msg,die,capfirst
 from .protocol import init_proto
 from .obj import MMGenObject,TwComment,WalletPassword,MMGenPWIDString
 from .seed import SeedID,is_seed_id
@@ -44,13 +44,13 @@ class AddrFile(MMGenObject):
 """
 
 	def __init__(self,parent):
-
 		self.parent = parent
+		self.cfg    = parent.cfg
 		self.infile = None
 
 	def encrypt(self):
 		from .crypto import Crypto
-		self.fmt_data = Crypto().mmgen_encrypt(
+		self.fmt_data = Crypto(self.cfg).mmgen_encrypt(
 			data = self.fmt_data.encode(),
 			desc = f'new {self.parent.desc} list' )
 		self.ext += f'.{Crypto.mmenc_ext}'
@@ -63,13 +63,13 @@ class AddrFile(MMGenObject):
 			self.ext )
 
 	def write(self,fn=None,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
-		from .opts import opt
 		from .fileutil import write_data_to_file
 		write_data_to_file(
+			self.cfg,
 			fn or self.filename,
 			self.fmt_data,
 			desc or self.desc,
-			ask_tty = self.parent.has_keys and not opt.quiet,
+			ask_tty = self.parent.has_keys and not self.cfg.quiet,
 			binary = binary )
 
 	def make_label(self):
@@ -87,8 +87,7 @@ class AddrFile(MMGenObject):
 			self.file_header_mn.format(p.pw_fmt.upper())
 				if p.gen_passwds and p.pw_fmt in ('bip39','xmrseed') else
 			self.file_header ).strip()
-		from .globalvars import gc
-		out = [fh.format(pnm=gc.proj_name,n=TwComment.max_screen_width) + '\n']
+		out = [fh.format( pnm=gc.proj_name, n=TwComment.max_screen_width ) + '\n']
 
 		if p.chksum:
 			out.append(f'# {capfirst(p.desc)} data checksum for {p.id_str}: {p.chksum}')
@@ -108,8 +107,7 @@ class AddrFile(MMGenObject):
 			else: # First line with idx
 				out.append(fs.format(e.idx,e.addr,c))
 				if p.has_keys:
-					from .opts import opt
-					if opt.b16:
+					if self.cfg.b16:
 						out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c ))
 					out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
 					for k in ('viewkey','wallet_passwd'):
@@ -160,21 +158,21 @@ class AddrFile(MMGenObject):
 
 			def verify_keys():
 				from .addrgen import KeyGenerator,AddrGenerator
-				kg = KeyGenerator(p.proto,p.al_id.mmtype.pubkey_type)
-				ag = AddrGenerator(p.proto,p.al_id.mmtype)
+				kg = KeyGenerator( self.cfg, p.proto, p.al_id.mmtype.pubkey_type )
+				ag = AddrGenerator( self.cfg, p.proto, p.al_id.mmtype )
 				llen = len(ret)
+				qmsg_r = p.cfg._util.qmsg_r
 				for n,e in enumerate(ret):
 					qmsg_r(f'\rVerifying keys {n+1}/{llen}')
 					assert e.addr == ag.to_addr(kg.gen_data(e.sec)),(
 						f'Key doesn’t match address!\n  {e.sec.wif}\n  {e.addr}')
-				qmsg(' - done')
+				p.cfg._util.qmsg(' - done')
 
-			from .opts import opt
-			if opt.yes or p.ka_validity_chk == True:
+			if self.cfg.yes or p.ka_validity_chk == True:
 				verify_keys()
 			else:
 				from .ui import keypress_confirm
-				if keypress_confirm('Check key-to-address validity?'):
+				if keypress_confirm( p.cfg, 'Check key-to-address validity?' ):
 					verify_keys()
 
 		return ret
@@ -216,7 +214,7 @@ class AddrFile(MMGenObject):
 			else:            # only component is coin
 				coin,mmtype_key = ( lbl, None )
 
-			proto = init_proto(coin=coin,network=network)
+			proto = init_proto( p.cfg, coin=coin, network=network )
 
 			if mmtype_key == None:
 				mmtype_key = proto.mmtypes[0]
@@ -224,8 +222,9 @@ class AddrFile(MMGenObject):
 			return ( proto, proto.addr_type(mmtype_key) )
 
 		p = self.parent
+
 		from .fileutil import get_lines_from_file
-		lines = get_lines_from_file(fn,p.desc+' data',trim_comments=True)
+		lines = get_lines_from_file( p.cfg, fn, p.desc+' data', trim_comments=True )
 
 		try:
 			assert len(lines) >= 3, f'Too few lines in address file ({len(lines)})'
@@ -246,12 +245,12 @@ class AddrFile(MMGenObject):
 				modname,funcname = p.pw_info[p.pw_fmt].chk_func.split('.')
 				import importlib
 				p.chk_func = getattr(importlib.import_module('mmgen.'+modname),funcname)
-				proto = init_proto('btc') # FIXME: dummy protocol
+				proto = init_proto( p.cfg, 'btc' ) # FIXME: dummy protocol
 				mmtype = MMGenPasswordType(proto,'P')
 			elif len(ls) == 1:
 				proto,mmtype = parse_addrfile_label(ls[0])
 			elif len(ls) == 0:
-				proto = init_proto('btc')
+				proto = init_proto( p.cfg, 'btc' )
 				mmtype = proto.addr_type('L')
 			else:
 				raise ValueError(f'{lines[0]}: Invalid first line for {p.gen_desc} file {fn!r}')

+ 6 - 6
mmgen/addrgen.py

@@ -36,7 +36,7 @@ class addr_generator:
 
 	class base:
 
-		def __init__(self,proto,addr_type):
+		def __init__(self,cfg,proto,addr_type):
 			self.proto = proto
 			self.pubkey_type = addr_type.pubkey_type
 			self.compressed = addr_type.compressed
@@ -44,12 +44,12 @@ class addr_generator:
 
 	class keccak(base):
 
-		def __init__(self,proto,addr_type):
-			super().__init__(proto,addr_type)
+		def __init__(self,cfg,proto,addr_type):
+			super().__init__(cfg,proto,addr_type)
 			from .util2 import get_keccak
-			self.keccak_256 = get_keccak()
+			self.keccak_256 = get_keccak(cfg)
 
-def AddrGenerator(proto,addr_type):
+def AddrGenerator(cfg,proto,addr_type):
 	"""
 	factory function returning an address generator for the specified address type
 	"""
@@ -76,4 +76,4 @@ def AddrGenerator(proto,addr_type):
 	import importlib
 	return getattr(
 		importlib.import_module(f'mmgen.proto.{package_map[addr_type.name]}.addrgen'),
-		addr_type.name )(proto,addr_type)
+		addr_type.name )(cfg,proto,addr_type)

+ 16 - 15
mmgen/addrlist.py

@@ -20,8 +20,7 @@
 addrlist: Address list classes for the MMGen suite
 """
 
-from .globalvars import g
-from .util import qmsg,qmsg_r,suf,make_chksum_N,Msg,die
+from .util import suf,make_chksum_N,Msg,die
 from .objmethods import MMGenObject,Hilite,InitErrors
 from .obj import MMGenListItem,ListItemAttr,MMGenDict,TwComment,WalletPassword
 from .key import PrivKey
@@ -158,6 +157,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 
 	def __init__(
 			self,
+			cfg,
 			proto,
 			addrfile  = '',
 			al_id     = '',
@@ -173,12 +173,13 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 			skip_chksum_msg = False,
 			add_p2pkh = False ):
 
+		self.cfg = cfg
 		self.ka_validity_chk = key_address_validity_check
 		self.add_p2pkh = add_p2pkh
 		self.proto = proto
 		do_chksum = False
 
-		if not g.debug_addrlist:
+		if not cfg.debug_addrlist:
 			self.dmsg_sc = self.noop
 
 		if seed and addr_idxs:   # data from seed + idxs
@@ -230,7 +231,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 	def do_chksum_msg(self,record):
 		chk = 'Check this value against your records'
 		rec = f'Record this checksum: it will be used to verify the {self.desc} file in the future'
-		qmsg(
+		self.cfg._util.qmsg(
 			f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
 			(chk,rec)[record] )
 
@@ -246,23 +247,23 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 
 		if self.gen_addrs:
 			from .addrgen import KeyGenerator,AddrGenerator
-			kg = KeyGenerator( self.proto, mmtype.pubkey_type )
-			ag = AddrGenerator( self.proto, mmtype )
+			kg = KeyGenerator( self.cfg, self.proto, mmtype.pubkey_type )
+			ag = AddrGenerator( self.cfg, self.proto, mmtype )
 			if self.add_p2pkh:
-				ag2 = AddrGenerator( self.proto, 'compressed' )
+				ag2 = AddrGenerator( self.cfg, self.proto, 'compressed' )
 
-		from .globalvars import g
 		from .derive import derive_coin_privkey_bytes
 
 		t_addrs = len(addr_idxs)
 		le = self.entry_type
 		out = AddrListData()
-		CR = '\n' if g.debug_addrlist else '\r'
+		CR = '\n' if self.cfg.debug_addrlist else '\r'
 
 		for pk_bytes in derive_coin_privkey_bytes(seed,addr_idxs):
 
-			if not g.debug:
-				qmsg_r(f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})')
+			if not self.cfg.debug:
+				self.cfg._util.qmsg_r(
+					f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})' )
 
 			e = le( proto=self.proto, idx=pk_bytes.idx )
 
@@ -286,7 +287,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 
 			out.append(e)
 
-		qmsg('{}{}: {} {}{} generated{}'.format(
+		self.cfg._util.qmsg('{}{}: {} {}{} generated{}'.format(
 			CR,
 			self.al_id.hl(),
 			t_addrs,
@@ -316,7 +317,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 		if self.proto.testnet:
 			scramble_key += ':' + self.proto.network
 		self.dmsg_sc('str',scramble_key)
-		return Crypto().scramble_seed(seed,scramble_key.encode())
+		return Crypto(self.cfg).scramble_seed(seed,scramble_key.encode())
 
 	def idxs(self):
 		return [e.idx for e in self.data]
@@ -371,8 +372,8 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 		def gen_addr(pk,t):
 			at = self.proto.addr_type(t)
 			from .addrgen import KeyGenerator,AddrGenerator
-			kg = KeyGenerator(self.proto,at.pubkey_type)
-			ag = AddrGenerator(self.proto,at)
+			kg = KeyGenerator( self.cfg, self.proto, at.pubkey_type )
+			ag = AddrGenerator( self.cfg, self.proto, at )
 			return ag.to_addr(kg.gen_data(pk))
 
 		compressed_types = set(self.proto.mmtypes) - {'L','E'}

+ 6 - 6
mmgen/altcoin.py

@@ -434,14 +434,14 @@ class CoinInfo(object):
 					test_equal('P2SH leading symbol',vn_info[1],ret,*cdata)
 
 	@classmethod
-	def verify_core_coin_data(cls,quiet=False,verbose=False):
+	def verify_core_coin_data(cls,cfg,quiet=False,verbose=False):
 		from .protocol import CoinProtocol,init_proto
 
 		for network in ('mainnet','testnet'):
 			for coin in gc.core_coins:
 				e = cls.get_entry(coin,network)
 				if e:
-					proto = init_proto(coin,testnet=network=='testnet')
+					proto = init_proto( cfg, coin, network=network )
 					cdata = (network,coin,e,type(proto).__name__,verbose)
 					if not quiet:
 						msg(f'Verifying {coin.upper()} {network}')
@@ -791,11 +791,11 @@ if __name__ == '__main__':
 		}
 	}
 
-	from mmgen.opts import init,opt
-	init( opts_data )
+	from mmgen.opts import init
+	cfg = init( opts_data, need_amt=False )
 
 	msg('Checking CoinInfo WIF/P2PKH/P2SH version numbers and trust levels against protocol.py')
-	CoinInfo.verify_core_coin_data( quiet=opt.quiet, verbose=opt.verbose )
+	CoinInfo.verify_core_coin_data( cfg, cfg.quiet, cfg.verbose )
 
 	msg('Checking CoinInfo address leading symbols')
-	CoinInfo.verify_leading_symbols( quiet=opt.quiet, verbose=opt.verbose )
+	CoinInfo.verify_leading_symbols( cfg.quiet, cfg.verbose )

+ 2 - 3
mmgen/baseconv.py

@@ -100,14 +100,13 @@ class baseconv(object):
 		from hashlib import sha256
 		return sha256( ' '.join(self.digits).encode() ).hexdigest()[:8]
 
-	def check_wordlist(self):
+	def check_wordlist(self,cfg):
 
 		wl = self.digits
-		from .util import qmsg,compare_chksums
 		ret = f'Wordlist: {self.wl_id}\nLength: {len(wl)} words'
 		new_chksum = self.get_wordlist_chksum()
 
-		compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True )
+		cfg._util.compare_chksums( new_chksum, 'generated', self.wl_chksum, 'saved', die_on_fail=True )
 
 		if tuple(sorted(wl)) == wl:
 			return ret + '\nList is sorted'

+ 17 - 14
mmgen/cfgfile.py

@@ -23,11 +23,10 @@ cfgfile: API for the MMGen runtime configuration file and related files
 import os,re
 from collections import namedtuple
 
-from .globalvars import g,gc
 from .util import msg,ymsg,suf,fmt,fmt_list,oneshot_warning,strip_comment,capfirst
 
-def mmgen_cfg_file(id_str):
-	return cfg_file.get_cls_by_id(id_str)()
+def mmgen_cfg_file(cfg,id_str):
+	return cfg_file.get_cls_by_id(id_str)(cfg)
 
 class cfg_file:
 	cur_ver = 2
@@ -53,7 +52,7 @@ class cfg_file:
 
 	def copy_system_data(self,fn):
 		assert self.write_ok, f'writing to file {fn!r} not allowed!'
-		src = mmgen_cfg_file('sys')
+		src = mmgen_cfg_file(self.cfg,'sys')
 		if src.data:
 			data = src.data + src.make_metadata() if self.write_metadata else src.data
 			try:
@@ -169,8 +168,9 @@ class CfgFileUsr(cfg_file):
 	warn_missing = False
 	write_ok = True
 
-	def __init__(self):
-		self.fn = os.path.join(g.data_dir_root,self.fn_base)
+	def __init__(self,cfg):
+		self.cfg = cfg
+		self.fn = os.path.join(cfg.data_dir_root,self.fn_base)
 		self.data = self.get_data(self.fn)
 		if not self.data:
 			self.copy_system_data(self.fn)
@@ -179,14 +179,16 @@ class CfgFileSampleSys(cfg_file_sample):
 	desc = 'system sample configuration file'
 	test_fn_subdir = 'usr.local.share'
 
-	def __init__(self):
-		if g.test_suite_cfgtest:
-			self.fn = os.path.join(g.data_dir_root,self.test_fn_subdir,self.fn_base)
+	def __init__(self,cfg):
+		self.cfg = cfg
+		if self.cfg.test_suite_cfgtest:
+			self.fn = os.path.join(cfg.data_dir_root,self.test_fn_subdir,self.fn_base)
 			with open(self.fn) as fp:
 				self.data = fp.read().splitlines()
 		else:
 			# self.fn is used for error msgs only, so file need not exist on filesystem
 			self.fn = os.path.join(os.path.dirname(__file__),'data',self.fn_base)
+			from .globalvars import gc
 			self.data = gc.get_mmgen_data_file(self.fn_base).splitlines()
 
 	def make_metadata(self):
@@ -202,11 +204,12 @@ class CfgFileSampleUsr(cfg_file_sample):
 	out_of_date_fs = 'File {!r} is out of date - replacing'
 	altered_by_user_fs = 'File {!r} was altered by user - replacing'
 
-	def __init__(self):
-		self.fn = os.path.join(g.data_dir_root,f'{self.fn_base}.sample')
+	def __init__(self,cfg):
+		self.cfg = cfg
+		self.fn = os.path.join(cfg.data_dir_root,f'{self.fn_base}.sample')
 		self.data = self.get_data(self.fn)
 
-		src = mmgen_cfg_file('sys')
+		src = mmgen_cfg_file(cfg,'sys')
 
 		if not src.data:
 			return
@@ -255,7 +258,7 @@ class CfgFileSampleUsr(cfg_file_sample):
 				opts = fmt_list([i.name for i in data],fmt='bare')
 				msg(f'  The following option{suf(data,verb="has")} been {desc}:\n    {opts}\n')
 				if desc == 'removed' and data:
-					uc = mmgen_cfg_file('usr')
+					uc = mmgen_cfg_file(self.cfg,'usr')
 					usr_names = [i.name for i in uc.get_lines()]
 					rm_names = [i.name for i in data]
 					bad = sorted(set(usr_names).intersection(rm_names))
@@ -269,7 +272,7 @@ class CfgFileSampleUsr(cfg_file_sample):
 
 		from .ui import keypress_confirm,do_pager
 		while True:
-			if not keypress_confirm(self.details_confirm_prompt,no_nl=True):
+			if not keypress_confirm( self.cfg, self.details_confirm_prompt, no_nl=True ):
 				return
 
 			def get_details():

+ 0 - 1
mmgen/common.py

@@ -23,5 +23,4 @@ common: Common imports for all MMGen scripts
 import sys,os
 from .globalvars import *
 import mmgen.opts as opts
-from .opts import opt
 from .util import *

+ 50 - 51
mmgen/crypto.py

@@ -23,23 +23,16 @@ crypto: Random number, password hashing and symmetric encryption routines for th
 import os
 from collections import namedtuple
 
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .util import (
 	msg,
 	msg_r,
-	dmsg,
-	vmsg,
-	vmsg_r,
-	qmsg,
 	fmt,
 	die,
 	make_chksum_8,
-	compare_chksums,
 	oneshot_warning,
 )
 
-
 class Crypto:
 
 	mmenc_ext = 'mmenc'
@@ -72,6 +65,10 @@ class Crypto:
 		def __init__(self,fn):
 			oneshot_warning.__init__(self,div=fn,fmt_args=[fn],reverse=True)
 
+	def __init__(self,cfg):
+		self.cfg = cfg
+		self.util = cfg._util
+
 	def get_hash_params(self,hash_preset):
 		if hash_preset in self.hash_presets:
 			return self.hash_presets[hash_preset] # N,r,p
@@ -87,7 +84,7 @@ class Crypto:
 	def scramble_seed(self,seed,scramble_key):
 		import hmac
 		step1 = hmac.digest(seed,scramble_key,'sha256')
-		if g.debug:
+		if self.cfg.debug:
 			msg(f'Seed:  {seed.hex()!r}\nScramble key: {scramble_key}\nScrambled seed: {step1.hex()}\n')
 		return self.sha256_rounds(step1)
 
@@ -95,56 +92,56 @@ class Crypto:
 		return self.encrypt_data(data,key,desc=desc)
 
 	def decrypt_seed(self,enc_seed,key,seed_id,key_id):
-		vmsg_r('Checking key...')
+		self.util.vmsg_r('Checking key...')
 		chk1 = make_chksum_8(key)
 		if key_id:
-			if not compare_chksums(key_id,'key ID',chk1,'computed'):
+			if not self.util.compare_chksums(key_id,'key ID',chk1,'computed'):
 				msg('Incorrect passphrase or hash preset')
 				return False
 
 		dec_seed = self.decrypt_data(enc_seed,key,desc='seed')
 		chk2     = make_chksum_8(dec_seed)
 		if seed_id:
-			if compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'):
-				qmsg('Passphrase is OK')
+			if self.util.compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'):
+				self.util.qmsg('Passphrase is OK')
 			else:
-				if not opt.debug:
+				if not self.cfg.debug:
 					msg_r('Checking key ID...')
-					if compare_chksums(key_id,'key ID',chk1,'computed'):
+					if self.util.compare_chksums(key_id,'key ID',chk1,'computed'):
 						msg('Key ID is correct but decryption of seed failed')
 					else:
 						msg('Incorrect passphrase or hash preset')
-				vmsg('')
+				self.util.vmsg('')
 				return False
 
-		dmsg(f'Decrypted seed: {dec_seed.hex()}')
+		self.util.dmsg(f'Decrypted seed: {dec_seed.hex()}')
 		return dec_seed
 
 	def encrypt_data(self,data,key,iv=aesctr_dfl_iv,desc='data',verify=True,silent=False):
 		from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes
 		from cryptography.hazmat.backends import default_backend
 		if not silent:
-			vmsg(f'Encrypting {desc}')
+			self.util.vmsg(f'Encrypting {desc}')
 		c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend())
 		encryptor = c.encryptor()
 		enc_data = encryptor.update(data) + encryptor.finalize()
 
 		if verify:
-			vmsg_r(f'Performing a test decryption of the {desc}...')
+			self.util.vmsg_r(f'Performing a test decryption of the {desc}...')
 			c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend())
 			encryptor = c.encryptor()
 			dec_data = encryptor.update(enc_data) + encryptor.finalize()
 			if dec_data != data:
 				die(2,f'ERROR.\nDecrypted {desc} doesn’t match original {desc}')
 			if not silent:
-				vmsg('done')
+				self.util.vmsg('done')
 
 		return enc_data
 
 	def decrypt_data(self,enc_data,key,iv=aesctr_dfl_iv,desc='data'):
 		from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes
 		from cryptography.hazmat.backends import default_backend
-		vmsg_r(f'Decrypting {desc} with key...')
+		self.util.vmsg_r(f'Decrypting {desc} with key...')
 		c = Cipher(algorithms.AES(key),modes.CTR(iv),backend=default_backend())
 		encryptor = c.encryptor()
 		return encryptor.update(enc_data) + encryptor.finalize()
@@ -184,7 +181,7 @@ class Crypto:
 
 		# hashlib.scrypt doesn't support N > 14 (hash preset > 3)
 		ret = (
-			do_standalone_scrypt() if ps.N > 14 or g.force_standalone_scrypt_module else
+			do_standalone_scrypt() if ps.N > 14 or self.cfg.force_standalone_scrypt_module else
 			do_hashlib_scrypt() )
 
 		if int(hash_preset) > 3:
@@ -193,17 +190,17 @@ class Crypto:
 		return ret
 
 	def make_key(self,passwd,salt,hash_preset,desc='encryption key',from_what='passphrase',verbose=False):
-		if opt.verbose or verbose:
+		if self.cfg.verbose or verbose:
 			msg_r(f"Generating {desc}{' from ' + from_what if from_what else ''}...")
 		key = self.scrypt_hash_passphrase(passwd,salt,hash_preset)
-		if opt.verbose or verbose: msg('done')
-		dmsg(f'Key: {key.hex()}')
+		if self.cfg.verbose or verbose: msg('done')
+		self.util.dmsg(f'Key: {key.hex()}')
 		return key
 
 	def _get_random_data_from_user(self,uchars=None,desc='data'):
 
 		if uchars is None:
-			uchars = opt.usr_randchars
+			uchars = self.cfg.usr_randchars
 
 		info1 = f"""
 			Now we're going to gather some additional input from the keyboard to further
@@ -225,7 +222,7 @@ class Crypto:
 			on the screen.
 		"""
 
-		msg(f'Enter {uchars} random symbols' if opt.quiet else
+		msg(f'Enter {uchars} random symbols' if self.cfg.quiet else
 			'\n' + fmt(info1,indent='  ') +
 			'\n' + fmt(info2) )
 
@@ -238,7 +235,7 @@ class Crypto:
 			key_data += get_char_raw(f'\rYou may begin typing.  {uchars-i} symbols left: ')
 			time_data.append(time.time())
 
-		msg_r( '\r' if opt.quiet else f'\rThank you.  That’s enough.{" "*18}\n\n' )
+		msg_r( '\r' if self.cfg.quiet else f'\rThank you.  That’s enough.{" "*18}\n\n' )
 
 		time_data = [f'{t:.22f}'.rstrip('0') for t in time_data]
 
@@ -249,11 +246,11 @@ class Crypto:
 
 		ret = key_data + '\n' + '\n'.join(time_data)
 
-		if g.debug:
+		if self.cfg.debug:
 			msg(f'USER ENTROPY (user input + keystroke timings):\n{ret}')
 
 		from .ui import line_input
-		line_input( 'User random data successfully acquired.  Press ENTER to continue: ' )
+		line_input( self.cfg, 'User random data successfully acquired.  Press ENTER to continue: ' )
 
 		return ret.encode()
 
@@ -274,7 +271,7 @@ class Crypto:
 
 		assert type(rand_bytes) == bytes, 'add_user_random_chk1'
 
-		if opt.usr_randchars:
+		if self.cfg.usr_randchars:
 
 			if not urand['data']:
 				from hashlib import sha256
@@ -310,7 +307,7 @@ class Crypto:
 
 		from .ui import line_input
 		while True:
-			ret = line_input( prompt )
+			ret = line_input( self.cfg, prompt )
 			if ret:
 				if ret in self.hash_presets:
 					return ret
@@ -329,29 +326,30 @@ class Crypto:
 		if passwd_file:
 			from .fileutil import get_words_from_file
 			pw = ' '.join(get_words_from_file(
+				cfg    = self.cfg,
 				infile = passwd_file,
 				desc   = f'{pw_desc} for {data_desc}',
 				quiet  = self.pwfile_reuse_warning(passwd_file).warning_shown ))
 		else:
-			qmsg('\n'+fmt(message,indent='  '))
+			self.util.qmsg('\n'+fmt(message,indent='  '))
 			from .ui import get_words_from_user
-			if opt.echo_passphrase:
-				pw = ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' ))
+			if self.cfg.echo_passphrase:
+				pw = ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' ))
 			else:
 				for i in range(gc.passwd_max_tries):
-					pw = ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' ))
-					pw_chk = ' '.join(get_words_from_user( f'Repeat {pw_desc}: ' ))
-					dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
+					pw = ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' ))
+					pw_chk = ' '.join(get_words_from_user( self.cfg, f'Repeat {pw_desc}: ' ))
+					self.util.dmsg(f'Passphrases: [{pw}] [{pw_chk}]')
 					if pw == pw_chk:
-						vmsg('Passphrases match')
+						self.util.vmsg('Passphrases match')
 						break
 					else:
 						msg('Passphrases do not match.  Try again.')
 				else:
-					die(2,f'User failed to duplicate passphrase in {g.passwd_max_tries} attempts')
+					die(2,f'User failed to duplicate passphrase in {gc.passwd_max_tries} attempts')
 
 		if pw == '':
-			qmsg('WARNING: Empty passphrase')
+			self.util.qmsg('WARNING: Empty passphrase')
 
 		return pw
 
@@ -359,48 +357,49 @@ class Crypto:
 		if passwd_file:
 			from .fileutil import get_words_from_file
 			return ' '.join(get_words_from_file(
+				cfg    = self.cfg,
 				infile = passwd_file,
 				desc   = f'{pw_desc} for {data_desc}',
 				quiet  = self.pwfile_reuse_warning(passwd_file).warning_shown ))
 		else:
 			from .ui import get_words_from_user
-			return ' '.join(get_words_from_user( f'Enter {pw_desc} for {data_desc}: ' ))
+			return ' '.join(get_words_from_user( self.cfg, f'Enter {pw_desc} for {data_desc}: ' ))
 
 	def mmgen_encrypt(self,data,desc='data',hash_preset=None):
 		salt  = self.get_random(self.mmenc_salt_len)
 		iv    = self.get_random(self.aesctr_iv_len)
 		nonce = self.get_random(self.mmenc_nonce_len)
-		hp    = hash_preset or opt.hash_preset or self.get_hash_preset_from_user(data_desc=desc)
+		hp    = hash_preset or self.cfg.hash_preset or self.get_hash_preset_from_user(data_desc=desc)
 		m     = ('user-requested','default')[hp=='3']
-		vmsg(f'Encrypting {desc}')
-		qmsg(f'Using {m} hash preset of {hp!r}')
+		self.util.vmsg(f'Encrypting {desc}')
+		self.util.qmsg(f'Using {m} hash preset of {hp!r}')
 		passwd = self.get_new_passphrase(
 			data_desc = desc,
 			hash_preset = hp,
-			passwd_file = opt.passwd_file )
+			passwd_file = self.cfg.passwd_file )
 		key    = self.make_key(passwd,salt,hp)
 		from hashlib import sha256
 		enc_d  = self.encrypt_data( sha256(nonce+data).digest() + nonce + data, key, iv, desc=desc )
 		return salt+iv+enc_d
 
 	def mmgen_decrypt(self,data,desc='data',hash_preset=None):
-		vmsg(f'Preparing to decrypt {desc}')
+		self.util.vmsg(f'Preparing to decrypt {desc}')
 		dstart = self.mmenc_salt_len + self.aesctr_iv_len
 		salt   = data[:self.mmenc_salt_len]
 		iv     = data[self.mmenc_salt_len:dstart]
 		enc_d  = data[dstart:]
-		hp     = hash_preset or opt.hash_preset or self.get_hash_preset_from_user(data_desc=desc)
+		hp     = hash_preset or self.cfg.hash_preset or self.get_hash_preset_from_user(data_desc=desc)
 		m  = ('user-requested','default')[hp=='3']
-		qmsg(f'Using {m} hash preset of {hp!r}')
+		self.util.qmsg(f'Using {m} hash preset of {hp!r}')
 		passwd = self.get_passphrase(
 			data_desc = desc,
-			passwd_file = opt.passwd_file )
+			passwd_file = self.cfg.passwd_file )
 		key    = self.make_key(passwd,salt,hp)
 		dec_d  = self.decrypt_data( enc_d, key, iv, desc )
 		sha256_len = 32
 		from hashlib import sha256
 		if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
-			vmsg('OK')
+			self.util.vmsg('OK')
 			return dec_d[sha256_len+self.mmenc_nonce_len:]
 		else:
 			msg('Incorrect passphrase or hash preset')

+ 24 - 21
mmgen/daemon.py

@@ -24,7 +24,7 @@ import os,time,importlib
 from subprocess import run,PIPE,CompletedProcess
 from collections import namedtuple
 
-from .globalvars import g,gc
+from .globalvars import gc
 from .color import set_vt100
 from .util import msg,Msg_r,ymsg,die,remove_dups,oneshot_warning
 from .flags import *
@@ -49,8 +49,9 @@ class Daemon(Lockable):
 	avail_flags = () # like opts, but can be set or unset after instantiation
 	_reset_ok = ('debug','wait','pids')
 
-	def __init__(self,opts=None,flags=None):
+	def __init__(self,cfg,opts=None,flags=None):
 
+		self.cfg = cfg
 		self.platform = gc.platform
 		if self.platform == 'win':
 			self.use_pidfile = False
@@ -237,8 +238,8 @@ class RPCDaemon(Daemon):
 
 	avail_opts = ('no_daemonize',)
 
-	def __init__(self):
-		super().__init__()
+	def __init__(self,cfg):
+		super().__init__(cfg)
 		self.desc = '{} {} {}RPC daemon'.format(
 			self.rpc_type,
 			getattr(self.proto.network_names,self.proto.network),
@@ -280,13 +281,13 @@ class CoinDaemon(Daemon):
 		message = 'blacklisted daemon: {!r}'
 
 	@classmethod
-	def get_daemon_ids(cls,coin):
+	def get_daemon_ids(cls,cfg,coin):
 
 		ret = cls.coins[coin].daemon_ids
-		if 'erigon' in ret and not g.enable_erigon:
+		if 'erigon' in ret and not cfg.enable_erigon:
 			ret.remove('erigon')
-		if g.blacklisted_daemons:
-			blacklist = g.blacklisted_daemons.split()
+		if cfg.blacklisted_daemons:
+			blacklist = cfg.blacklisted_daemons.split()
 			def gen():
 				for daemon_id in ret:
 					if daemon_id in blacklist:
@@ -297,21 +298,21 @@ class CoinDaemon(Daemon):
 		return ret
 
 	@classmethod
-	def get_daemon(cls,coin,daemon_id,proto=None):
+	def get_daemon(cls,cfg,coin,daemon_id,proto=None):
 		if not proto:
 			from .protocol import init_proto
-			proto = init_proto(coin)
+			proto = init_proto( cfg, coin )
 		return getattr(
 			importlib.import_module(f'mmgen.proto.{proto.base_proto_coin.lower()}.daemon'),
 			daemon_id+'_daemon' )
 
 	@classmethod
-	def get_network_ids(cls):
+	def get_network_ids(cls,cfg):
 		from .protocol import CoinProtocol
 		def gen():
 			for coin in cls.coins:
-				for daemon_id in cls.get_daemon_ids(coin):
-					for network in cls.get_daemon(coin,daemon_id).networks:
+				for daemon_id in cls.get_daemon_ids(cfg,coin):
+					for network in cls.get_daemon( cfg, coin, daemon_id ).networks:
 						yield CoinProtocol.Base.create_network_id(coin,network)
 		return remove_dups(list(gen()),quiet=True)
 
@@ -329,6 +330,7 @@ class CoinDaemon(Daemon):
 			return ( res[0] if len(res) == 1 else [s for s in res if 'ersion' in s][0] ).strip()
 
 	def __new__(cls,
+			cfg,
 			network_id = None,
 			proto      = None,
 			opts       = None,
@@ -349,19 +351,19 @@ class CoinDaemon(Daemon):
 		else:
 			network_id = network_id.lower()
 			from .protocol import CoinProtocol,init_proto
-			proto = init_proto(network_id=network_id)
+			proto = init_proto( cfg, network_id=network_id )
 			coin,network = CoinProtocol.Base.parse_network_id(network_id)
 			coin = coin.upper()
 
-		daemon_ids = cls.get_daemon_ids(coin)
+		daemon_ids = cls.get_daemon_ids(cfg,coin)
 		if not daemon_ids:
 			die(1,f'No configured daemons for coin {coin}!')
-		daemon_id = daemon_id or g.daemon_id or daemon_ids[0]
+		daemon_id = daemon_id or cfg.daemon_id or daemon_ids[0]
 
 		if daemon_id not in daemon_ids:
 			die(1,f'{daemon_id!r}: invalid daemon_id - valid choices: {fmt_list(daemon_ids)}')
 
-		me = Daemon.__new__(cls.get_daemon( None, daemon_id, proto=proto ))
+		me = Daemon.__new__(cls.get_daemon( cfg, None, daemon_id, proto=proto ))
 
 		assert network in me.networks, f'{network!r}: unsupported network for daemon {daemon_id}'
 		me.network_id = network_id
@@ -373,6 +375,7 @@ class CoinDaemon(Daemon):
 		return me
 
 	def __init__(self,
+			cfg,
 			network_id = None,
 			proto      = None,
 			opts       = None,
@@ -385,7 +388,7 @@ class CoinDaemon(Daemon):
 
 		self.test_suite = test_suite
 
-		super().__init__(opts=opts,flags=flags)
+		super().__init__(cfg=cfg,opts=opts,flags=flags)
 
 		self._set_ok += ('shared_args','usr_coind_args')
 		self.shared_args = []
@@ -400,8 +403,8 @@ class CoinDaemon(Daemon):
 			'test suite ' if test_suite else '' )
 
 		# user-set values take precedence
-		self.datadir = os.path.abspath(datadir or g.daemon_data_dir or self.init_datadir())
-		self.non_dfl_datadir = bool(datadir or g.daemon_data_dir or test_suite or self.network == 'regtest')
+		self.datadir = os.path.abspath(datadir or cfg.daemon_data_dir or self.init_datadir())
+		self.non_dfl_datadir = bool(datadir or cfg.daemon_data_dir or test_suite or self.network == 'regtest')
 
 		# init_datadir() may have already initialized logdir
 		self.logdir = os.path.abspath(getattr(self,'logdir',self.datadir))
@@ -409,7 +412,7 @@ class CoinDaemon(Daemon):
 		ps_adj = (port_shift or 0) + (self.test_suite_port_shift if test_suite else 0)
 
 		# user-set values take precedence
-		self.rpc_port = (g.rpc_port or 0) + (port_shift or 0) if g.rpc_port else ps_adj + self.get_rpc_port()
+		self.rpc_port = (cfg.rpc_port or 0) + (port_shift or 0) if cfg.rpc_port else ps_adj + self.get_rpc_port()
 		self.p2p_port = (
 			p2p_port or (
 				self.get_p2p_port() + ps_adj if self.get_p2p_port() and (test_suite or ps_adj) else None

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.3.dev41
+13.3.dev42

+ 33 - 30
mmgen/fileutil.py

@@ -22,12 +22,10 @@ fileutil: Routines that read, write, execute or stat files
 
 import sys,os
 
-from .globalvars import g,gc
+from .globalvars import gc
 from .color import set_vt100
 from .util import (
 	msg,
-	qmsg,
-	dmsg,
 	die,
 	get_extension,
 	is_utf8,
@@ -114,15 +112,16 @@ def check_outfile(f,blkdev_ok=False):
 def check_outdir(f):
 	return _check_file_type_and_access(f,'output directory')
 
-def get_seed_file(wallets,nargs,invoked_as=None):
+def get_seed_file(cfg,nargs,wallets=None,invoked_as=None):
+
+	wallets = wallets or cfg._args
 
-	from .opts import opt
 	from .filename import find_file_in_dir
 	from .wallet.mmgen import wallet
 
-	wf = find_file_in_dir(wallet,g.data_dir)
+	wf = find_file_in_dir(wallet,cfg.data_dir)
 
-	wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?
+	wd_from_opt = bool(cfg.hidden_incog_input_params or cfg.in_fmt) # have wallet data from opt?
 
 	import mmgen.opts as opts
 	if len(wallets) + (wd_from_opt or bool(wf)) < nargs:
@@ -132,7 +131,7 @@ def get_seed_file(wallets,nargs,invoked_as=None):
 	elif len(wallets) > nargs:
 		opts.usage()
 	elif len(wallets) == nargs and wf and invoked_as != 'gen':
-		qmsg('Warning: overriding default wallet with user-supplied wallet')
+		cfg._util.qmsg('Warning: overriding default wallet with user-supplied wallet')
 
 	if wallets or wf:
 		check_infile(wallets[0] if wallets else wf)
@@ -150,6 +149,7 @@ def _open_or_die(filename,mode,silent=False):
 			))
 
 def write_data_to_file(
+		cfg,
 		outfile,
 		data,
 		desc                  = 'data',
@@ -165,25 +165,24 @@ def write_data_to_file(
 		check_data            = False,
 		cmp_data              = None):
 
-	from .opts import opt
-
 	if quiet:
 		ask_tty = ask_overwrite = False
 
-	if opt.quiet:
+	if cfg.quiet:
 		ask_overwrite = False
 
 	if ask_write_default_yes == False or ask_write_prompt:
 		ask_write = True
 
 	def do_stdout():
-		qmsg('Output to STDOUT requested')
-		if g.stdin_tty:
+		cfg._util.qmsg('Output to STDOUT requested')
+		if cfg.stdin_tty:
 			if no_tty:
 				die(2,f'Printing {desc} to screen is not allowed')
-			if (ask_tty and not opt.quiet) or binary:
+			if (ask_tty and not cfg.quiet) or binary:
 				from .ui import confirm_or_raise
 				confirm_or_raise(
+					cfg,
 					message = '',
 					action  = f'output {desc} to screen' )
 		else:
@@ -194,9 +193,10 @@ def write_data_to_file(
 				if of[:5] == 'pipe:':
 					if no_tty:
 						die(2,f'Writing {desc} to pipe is not allowed')
-					if ask_tty and not opt.quiet:
+					if ask_tty and not cfg.quiet:
 						from .ui import confirm_or_raise
 						confirm_or_raise(
+							cfg,
 							message = '',
 							action  = f'output {desc} to pipe' )
 						msg('')
@@ -216,14 +216,15 @@ def write_data_to_file(
 			os.write(1,data if isinstance(data,bytes) else data.encode())
 
 	def do_file(outfile,ask_write_prompt):
-		if opt.outdir and not ignore_opt_outdir and not os.path.isabs(outfile):
-			outfile = make_full_path(opt.outdir,outfile)
+		if cfg.outdir and not ignore_opt_outdir and not os.path.isabs(outfile):
+			outfile = make_full_path(cfg.outdir,outfile)
 
 		if ask_write:
 			if not ask_write_prompt:
 				ask_write_prompt = f'Save {desc}?'
 			from .ui import keypress_confirm
 			if not keypress_confirm(
+					cfg,
 					ask_write_prompt,
 					default_yes = ask_write_default_yes ):
 				die(1,f'{capfirst(desc)} not saved')
@@ -232,6 +233,7 @@ def write_data_to_file(
 		if os.path.lexists(outfile) and ask_overwrite:
 			from .ui import confirm_or_raise
 			confirm_or_raise(
+				cfg,
 				message = '',
 				action  = f'File {outfile!r} already exists\nOverwrite?' )
 			msg(f'Overwriting file {outfile!r}')
@@ -262,17 +264,17 @@ def write_data_to_file(
 
 		return True
 
-	if opt.stdout or outfile in ('','-'):
+	if cfg.stdout or outfile in ('','-'):
 		do_stdout()
 	elif sys.stdin.isatty() and not sys.stdout.isatty():
 		do_stdout()
 	else:
 		do_file(outfile,ask_write_prompt)
 
-def get_words_from_file(infile,desc,quiet=False):
+def get_words_from_file(cfg,infile,desc,quiet=False):
 
 	if not quiet:
-		qmsg(f'Getting {desc} from file {infile!r}')
+		cfg._util.qmsg(f'Getting {desc} from file {infile!r}')
 
 	with _open_or_die(infile, 'rb') as fp:
 		data = fp.read()
@@ -282,11 +284,12 @@ def get_words_from_file(infile,desc,quiet=False):
 	except:
 		die(1,f'{capfirst(desc)} data must be UTF-8 encoded.')
 
-	dmsg('Sanitized input: [{}]'.format(' '.join(words)))
+	cfg._util.dmsg('Sanitized input: [{}]'.format(' '.join(words)))
 
 	return words
 
 def get_data_from_file(
+		cfg,
 		infile,
 		desc   = 'data',
 		dash   = False,
@@ -294,26 +297,26 @@ def get_data_from_file(
 		binary = False,
 		quiet  = False ):
 
-	from .opts import opt
-	if not (opt.quiet or silent or quiet):
-		qmsg(f'Getting {desc} from file {infile!r}')
+	if not (cfg.quiet or silent or quiet):
+		cfg._util.qmsg(f'Getting {desc} from file {infile!r}')
 
 	with _open_or_die(
 			(0 if dash and infile == '-' else infile),
 			'rb',
 			silent=silent) as fp:
-		data = fp.read(g.max_input_size+1)
+		data = fp.read(cfg.max_input_size+1)
 
 	if not binary:
 		data = data.decode()
 
-	if len(data) == g.max_input_size + 1:
+	if len(data) == cfg.max_input_size + 1:
 		die( 'MaxInputSizeExceeded',
 			f'Too much input data!  Max input data size: {f.max_input_size} bytes' )
 
 	return data
 
 def get_lines_from_file(
+		cfg,
 		fn,
 		desc          = 'data',
 		trim_comments = False,
@@ -321,17 +324,17 @@ def get_lines_from_file(
 		silent        = False ):
 
 	def decrypt_file_maybe():
-		data = get_data_from_file( fn, desc=desc, binary=True, quiet=quiet, silent=silent )
+		data = get_data_from_file( cfg, fn, desc=desc, binary=True, quiet=quiet, silent=silent )
 		from .crypto import Crypto
 		have_enc_ext = get_extension(fn) == Crypto.mmenc_ext
 		if have_enc_ext or not is_utf8(data):
 			m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
-			qmsg(f'{m} {desc} {fn!r}')
-			data = Crypto().mmgen_decrypt_retry(data,desc)
+			cfg._util.qmsg(f'{m} {desc} {fn!r}')
+			data = Crypto(cfg).mmgen_decrypt_retry(data,desc)
 		return data
 
 	lines = decrypt_file_maybe().decode().splitlines()
 	if trim_comments:
 		lines = strip_comments(lines)
-	dmsg(f'Got {len(lines)} lines from file {fn!r}')
+	cfg._util.dmsg(f'Got {len(lines)} lines from file {fn!r}')
 	return lines

+ 6 - 21
mmgen/globalvars.py

@@ -104,7 +104,7 @@ class GlobalVars:
 
 gv = GlobalVars()
 
-class GlobalConfig(Lockable):
+class Config(Lockable):
 	"""
 	These values are configurable - RHS values are defaults
 	Globals are overridden with the following precedence:
@@ -113,12 +113,13 @@ class GlobalConfig(Lockable):
 	  3 - config file
 	"""
 	_autolock = False
-	_set_ok = ()
-	_reset_ok = ('accept_defaults',)
+	_set_ok = ('usr_randchars','_proto')
+	_reset_ok = ('accept_defaults','quiet','verbose','yes')
 	_use_class_attr = True
+	_default_to_none = True
 
 	# general
-	coin        = ''
+	coin        = 'BTC'
 	token       = ''
 	outdir      = ''
 	passwd_file = ''
@@ -189,7 +190,7 @@ class GlobalConfig(Lockable):
 	bob          = False
 	alice        = False
 	carol        = False
-	regtest_user = None
+	regtest_user = ''
 
 	# test suite:
 	bogus_send               = False
@@ -216,20 +217,6 @@ class GlobalConfig(Lockable):
 	_proto = None
 	pager = False
 
-	# global var sets user opt:
-	global_sets_opt = (
-		'autochg_ignore_labels',
-		'debug',
-		'minconf',
-		'quiet',
-		'fee_estimate_confs',
-		'fee_adjust',
-		'use_internal_keccak_module',
-		'usr_randchars' )
-
-	# user opt sets global var:
-	opt_sets_global = ( 'cached_balances', )
-
 	# 'long' opts (subset of common_opts_data):
 	common_opts = (
 		'accept_defaults',
@@ -415,5 +402,3 @@ class GlobalConfig(Lockable):
 				'mainnet': (self.data_dir_root,),
 			}[self.network] ))
 			return self._data_dir
-
-g = GlobalConfig()

+ 7 - 7
mmgen/help.py

@@ -22,7 +22,7 @@ help: help notes for MMGen suite commands
 
 from .globalvars import gc
 
-def help_notes_func(proto,opt,k):
+def help_notes_func(proto,cfg,k):
 
 	def fee_spec_letters(use_quotes=False):
 		cu = proto.coin_amt.units
@@ -36,7 +36,7 @@ def help_notes_func(proto,opt,k):
 	def coind_exec():
 		from .daemon import CoinDaemon
 		return (
-			CoinDaemon(proto.coin).exec_fn if proto.coin in CoinDaemon.coins else 'bitcoind' )
+			CoinDaemon(cfg,proto.coin).exec_fn if proto.coin in CoinDaemon.coins else 'bitcoind' )
 
 	class help_notes:
 
@@ -92,7 +92,7 @@ def help_notes_func(proto,opt,k):
 			from .keygen import get_backends
 			from .addr import MMGenAddrType
 			backends = get_backends(
-				MMGenAddrType(proto,opt.type or proto.dfl_mmtype).pubkey_type
+				MMGenAddrType(proto,cfg.type or proto.dfl_mmtype).pubkey_type
 			)
 			return ' '.join( f'{n}:{k}{" [default]" if n==1 else ""}' for n,k in enumerate(backends,1) )
 
@@ -102,11 +102,11 @@ def help_notes_func(proto,opt,k):
 		def coin_daemon_network_ids():
 			from .daemon import CoinDaemon
 			from .util import fmt_list
-			return fmt_list(CoinDaemon.get_network_ids(),fmt='bare')
+			return fmt_list(CoinDaemon.get_network_ids(cfg),fmt='bare')
 
 		def rel_fee_desc():
 			from .tx import BaseTX
-			return BaseTX(proto=proto).rel_fee_desc
+			return BaseTX(cfg=cfg,proto=proto).rel_fee_desc
 
 		def fee_spec_letters():
 			return fee_spec_letters()
@@ -121,7 +121,7 @@ be specified as either absolute {c} amounts, using a plain decimal number, or
 as {r}, using an integer followed by '{l}', for {u}.
 """.format(
 	c = proto.coin,
-	r = BaseTX(proto=proto).rel_fee_desc,
+	r = BaseTX(cfg=cfg,proto=proto).rel_fee_desc,
 	l = fee_spec_letters(use_quotes=True),
 	u = fee_spec_names() )
 
@@ -147,7 +147,7 @@ seed, the same seed length and hash preset parameters must always be used.
 
 			mmtype = 'S' if 'segwit' in proto.caps else 'C'
 			from .tool.coin import tool_cmd
-			t = tool_cmd(mmtype=mmtype)
+			t = tool_cmd(cfg,mmtype=mmtype)
 			sample_addr = t.privhex2addr('bead'*16)
 
 			return f"""

+ 9 - 9
mmgen/keygen.py

@@ -71,9 +71,9 @@ def get_pubkey_type_cls(pubkey_type):
 		importlib.import_module(f'mmgen.proto.{backend_data[pubkey_type]["package"]}.keygen'),
 		'backend' )
 
-def _check_backend(backend,pubkey_type,desc='keygen backend'):
+def _check_backend(cfg,backend,pubkey_type,desc='keygen backend'):
 
-	from .util import is_int,qmsg,die
+	from .util import is_int,die
 
 	assert is_int(backend), f'illegal value for {desc} (must be an integer)'
 
@@ -86,21 +86,22 @@ def _check_backend(backend,pubkey_type,desc='keygen backend'):
 			' '.join( f'{n}:{k}' for n,k in enumerate(backends,1) )
 		)
 
-	qmsg(f'Using backend {backends[int(backend)-1]!r} for public key generation')
+	cfg._util.qmsg(f'Using backend {backends[int(backend)-1]!r} for public key generation')
 
 	return True
 
-def check_backend(proto,backend,addr_type):
+def check_backend(cfg,proto,backend,addr_type):
 
 	from .addr import MMGenAddrType
 	pubkey_type = MMGenAddrType(proto,addr_type or proto.dfl_mmtype).pubkey_type
 
 	return  _check_backend(
+		cfg,
 		backend,
 		pubkey_type,
 		desc = '--keygen-backend parameter' )
 
-def KeyGenerator(proto,pubkey_type,backend=None,silent=False):
+def KeyGenerator(cfg,proto,pubkey_type,backend=None,silent=False):
 	"""
 	factory function returning a key generator backend for the specified pubkey type
 	"""
@@ -108,11 +109,10 @@ def KeyGenerator(proto,pubkey_type,backend=None,silent=False):
 
 	pubkey_type_cls = get_pubkey_type_cls(pubkey_type)
 
-	from .opts import opt
-	backend = backend or opt.keygen_backend
+	backend = backend or cfg.keygen_backend
 
 	if backend:
-		_check_backend(backend,pubkey_type)
+		_check_backend( cfg, backend, pubkey_type )
 
 	backend_id = backend_data[pubkey_type]['backends'][int(backend) - 1 if backend else 0]
 
@@ -121,4 +121,4 @@ def KeyGenerator(proto,pubkey_type,backend=None,silent=False):
 		backend_id.replace('-','_')
 			).test_avail(silent=silent)
 
-	return getattr(pubkey_type_cls,backend_clsname)()
+	return getattr(pubkey_type_cls,backend_clsname)(cfg)

+ 20 - 21
mmgen/main_addrgen.py

@@ -22,8 +22,7 @@ mmgen-addrgen: Generate a series or range of addresses from an MMGen
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .addr import MMGenAddrType
 from .addrfile import AddrFile
 from .wallet import Wallet
@@ -74,7 +73,7 @@ opts_data = {
 -P, --passwd-file= f  Get wallet passphrase from file 'f'
 -q, --quiet           Produce quieter output; suppress some warnings
 -r, --usr-randchars=n Get 'n' characters of additional randomness from user
-                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+                      (min={cfg.min_urandchars}, max={cfg.max_urandchars}, default={cfg.usr_randchars})
 -S, --stdout          Print {what} to stdout
 -t, --type=t          Choose address type. Options: see ADDRESS TYPES below
                       (default: {dmat})
@@ -106,14 +105,14 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda proto,help_notes,s: s.format(
+		'options': lambda proto,help_notes,cfg,s: s.format(
 			dmat=help_notes('dfl_mmtype'),
 			kgs=help_notes('keygen_backends'),
 			coin_id=help_notes('coin_id'),
 			dsl=help_notes('dfl_seed_len'),
 			pnm=gc.proj_name,
 			what=gen_what,
-			g=g,
+			cfg=cfg,
 			gc=gc,
 		),
 		'notes': lambda help_notes,s: s.format(
@@ -127,39 +126,39 @@ FMT CODES:
 	}
 }
 
-cmd_args = opts.init(opts_data,opt_filter=opt_filter)
+cfg = opts.init(opts_data,opt_filter=opt_filter,need_amt=False)
 
-from .protocol import init_proto_from_opts
-proto = init_proto_from_opts()
+proto = cfg._proto
 
 addr_type = MMGenAddrType(
 	proto = proto,
-	id_str = opt.type or proto.dfl_mmtype,
-	errmsg = f'{opt.type!r}: invalid parameter for --type option' )
+	id_str = cfg.type or proto.dfl_mmtype,
+	errmsg = f'{cfg.type!r}: invalid parameter for --type option' )
 
-if len(cmd_args) < 1:
+if len(cfg._args) < 1:
 	opts.usage()
 
-if opt.keygen_backend:
+if cfg.keygen_backend:
 	from .keygen import check_backend
-	check_backend( proto, opt.keygen_backend, opt.type )
+	check_backend( cfg, proto, cfg.keygen_backend, cfg.type )
 
-idxs = mmgen.addrlist.AddrIdxList( fmt_str=cmd_args.pop() )
+idxs = mmgen.addrlist.AddrIdxList( fmt_str=cfg._args.pop() )
 
 from .fileutil import get_seed_file
-sf = get_seed_file(cmd_args,1)
+sf = get_seed_file(cfg,1)
 
 from .ui import do_license_msg
-do_license_msg()
+do_license_msg(cfg)
 
-ss = Wallet(sf)
+ss = Wallet(cfg,sf)
 
-ss_seed = ss.seed if opt.subwallet is None else ss.seed.subseed(opt.subwallet,print_msg=True)
+ss_seed = ss.seed if cfg.subwallet is None else ss.seed.subseed(cfg.subwallet,print_msg=True)
 
-if opt.no_addresses:
+if cfg.no_addresses:
 	gen_clsname = 'KeyList'
 
 al = getattr( mmgen.addrlist, gen_clsname )(
+	cfg       = cfg,
 	proto     = proto,
 	seed      = ss_seed,
 	addr_idxs = idxs,
@@ -169,11 +168,11 @@ af = al.get_file()
 
 af.format()
 
-if al.gen_addrs and opt.print_checksum:
+if al.gen_addrs and cfg.print_checksum:
 	Die(0,al.checksum)
 
 from .ui import keypress_confirm
-if al.gen_keys and keypress_confirm('Encrypt key list?'):
+if al.gen_keys and keypress_confirm( cfg, 'Encrypt key list?' ):
 	af.encrypt()
 	af.write(
 		binary = True,

+ 22 - 20
mmgen/main_addrimport.py

@@ -24,8 +24,7 @@ from collections import namedtuple
 
 import mmgen.opts as opts
 from .globalvars import gc
-from .opts import opt
-from .util import msg,qmsg,suf,die,fmt,async_run
+from .util import msg,suf,die,fmt,async_run
 from .addrlist import AddrList,KeyAddrList
 from .tw.shared import TwLabel
 
@@ -87,7 +86,7 @@ addrimport_msgs = {
 def parse_cmd_args(rpc,cmd_args):
 
 	def import_mmgen_list(infile):
-		al = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](proto,infile)
+		al = (AddrList,KeyAddrList)[bool(cfg.keyaddr_file)](cfg,proto,infile)
 		if al.al_id.mmtype in ('S','B'):
 			if not rpc.info('segwit_is_active'):
 				die(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
@@ -97,17 +96,19 @@ def parse_cmd_args(rpc,cmd_args):
 		infile = cmd_args[0]
 		from .fileutil import check_infile,get_lines_from_file
 		check_infile(infile)
-		if opt.addrlist:
+		if cfg.addrlist:
 			al = AddrList(
-				proto = proto,
+				cfg      = cfg,
+				proto    = proto,
 				addrlist = get_lines_from_file(
+					cfg,
 					infile,
 					f'non-{gc.proj_name} addresses',
 					trim_comments = True ) )
 		else:
 			al = import_mmgen_list(infile)
-	elif len(cmd_args) == 0 and opt.address:
-		al = AddrList(proto=proto,addrlist=[opt.address])
+	elif len(cmd_args) == 0 and cfg.address:
+		al = AddrList( cfg, proto=proto, addrlist=[cfg.address] )
 		infile = 'command line'
 	else:
 		die(1,addrimport_msgs['bad_args'])
@@ -115,16 +116,17 @@ def parse_cmd_args(rpc,cmd_args):
 	return al,infile
 
 def check_opts(twctl):
-	batch = bool(opt.batch)
-	rescan = bool(opt.rescan)
+	batch = bool(cfg.batch)
+	rescan = bool(cfg.rescan)
 
 	if rescan and not 'rescan' in twctl.caps:
 		msg(f"‘--rescan’ ignored: not supported by {type(twctl).__name__}")
 		rescan = False
 
-	if rescan and not opt.quiet:
+	if rescan and not cfg.quiet:
 		from .ui import keypress_confirm
 		if not keypress_confirm(
+				cfg,
 				'\n{}\n\nContinue?'.format(addrimport_msgs['rescan']),
 				default_yes = True ):
 			die(1,'Exiting at user request')
@@ -137,32 +139,33 @@ def check_opts(twctl):
 
 async def main():
 	from .tw.ctl import TwCtl
-	if opt.token_addr:
+	if cfg.token_addr:
 		proto.tokensym = 'foo' # hack to trigger 'Token' in proto.base_proto_subclass()
 
 	twctl = await TwCtl(
+		cfg        = cfg,
 		proto      = proto,
-		token_addr = opt.token_addr,
+		token_addr = cfg.token_addr,
 		mode       = 'i' )
 
-	if opt.token or opt.token_addr:
+	if cfg.token or cfg.token_addr:
 		msg(f'Importing for token {twctl.token.hl()} ({twctl.token.hlc(proto.tokensym)})')
 
 	from .rpc import rpc_init
-	twctl.rpc = await rpc_init(proto)
+	twctl.rpc = await rpc_init(cfg,proto)
 
 	for k,v in addrimport_msgs.items():
 		addrimport_msgs[k] = fmt(v,indent='  ',strip_char='\t').rstrip()
 
-	al,infile = parse_cmd_args(twctl.rpc,cmd_args)
+	al,infile = parse_cmd_args(twctl.rpc,cfg._args)
 
-	qmsg(
+	cfg._util.qmsg(
 		f'OK. {al.num_addrs} addresses'
 		+ (f' from Seed ID {al.al_id.sid}' if hasattr(al.al_id,'sid') else '') )
 
 	msg(
 		f'Importing {len(al.data)} address{suf(al.data,"es")} from {infile}'
-		+ (' (batch mode)' if opt.batch else '') )
+		+ (' (batch mode)' if cfg.batch else '') )
 
 	batch,rescan = check_opts(twctl)
 
@@ -183,9 +186,8 @@ async def main():
 
 	del twctl
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data,need_amt=False)
 
-from .protocol import init_proto_from_opts
-proto = init_proto_from_opts()
+proto = cfg._proto
 
 async_run(main())

+ 38 - 38
mmgen/main_autosign.py

@@ -26,9 +26,7 @@ from collections import namedtuple
 from stat import *
 
 import mmgen.opts as opts
-from .globalvars import g
-from .opts import opt
-from .util import msg,msg_r,vmsg,qmsg,ymsg,rmsg,gmsg,bmsg,die,suf,fmt_list,async_run,exit_if_mswin
+from .util import msg,msg_r,ymsg,rmsg,gmsg,bmsg,die,suf,fmt_list,async_run,exit_if_mswin
 from .color import yellow,red,orange
 
 mountpoint   = '/mnt/tx'
@@ -122,7 +120,7 @@ This command is currently available only on Linux-based platforms.
 	}
 }
 
-cmd_args = opts.init(
+cfg = opts.init(
 	opts_data,
 	add_opts = ['outdir','passwd_file'], # in _set_ok, so must be set
 	init_opts = {
@@ -133,14 +131,16 @@ cmd_args = opts.init(
 		'label': 'Autosign Wallet',
 	})
 
-type(opt)._set_ok += ('outdir','passwd_file')
+cmd_args = cfg._args
+
+type(cfg)._set_ok += ('outdir','passwd_file')
 
 exit_if_mswin('autosigning')
 
-if opt.mnemonic_fmt:
-	if opt.mnemonic_fmt not in mn_fmts:
+if cfg.mnemonic_fmt:
+	if cfg.mnemonic_fmt not in mn_fmts:
 		die(1,'{!r}: invalid mnemonic format (must be one of: {})'.format(
-			opt.mnemonic_fmt,
+			cfg.mnemonic_fmt,
 			fmt_list(mn_fmts,fmt='no_spc') ))
 
 from .wallet import Wallet
@@ -149,32 +149,32 @@ from .tx.sign import txsign
 from .protocol import init_proto
 from .rpc import rpc_init
 
-if opt.mountpoint:
-	mountpoint = opt.mountpoint
+if cfg.mountpoint:
+	mountpoint = cfg.mountpoint
 
 keyfile = os.path.join(mountpoint,'autosign.key')
 msg_dir = os.path.join(mountpoint,'msg')
 tx_dir  = os.path.join(mountpoint,'tx')
 
-opt.outdir = tx_dir
-opt.passwd_file = keyfile
+cfg.outdir = tx_dir
+cfg.passwd_file = keyfile
 
 async def check_daemons_running():
-	if opt.coin:
+	if cfg.coin != type(cfg).coin:
 		die(1,'--coin option not supported with this command.  Use --coins instead')
-	if opt.coins:
-		coins = opt.coins.upper().split(',')
+	if cfg.coins:
+		coins = cfg.coins.upper().split(',')
 	else:
 		ymsg('Warning: no coins specified, defaulting to BTC')
 		coins = ['BTC']
 
 	for coin in coins:
-		proto = init_proto( coin, testnet=g.network=='testnet', need_amt=True )
+		proto = init_proto( cfg,  coin, testnet=cfg.network=='testnet', need_amt=True )
 		if proto.sign_mode == 'daemon':
-			vmsg(f'Checking {coin} daemon')
+			cfg._util.vmsg(f'Checking {coin} daemon')
 			from .exception import SocketError
 			try:
-				await rpc_init(proto)
+				await rpc_init(cfg,proto)
 			except SocketError as e:
 				die(2,f'{coin} daemon not running or not listening on port {proto.rpc_port}')
 
@@ -213,10 +213,10 @@ def do_umount():
 async def sign_object(d,fn):
 	try:
 		if d.desc == 'transaction':
-			tx1 = UnsignedTX(filename=fn)
+			tx1 = UnsignedTX(cfg=cfg,filename=fn)
 			if tx1.proto.sign_mode == 'daemon':
-				tx1.rpc = await rpc_init(tx1.proto)
-			tx2 = await txsign(tx1,wfs[:],None,None)
+				tx1.rpc = await rpc_init(cfg,tx1.proto)
+			tx2 = await txsign(cfg,tx1,wfs[:],None,None)
 			if tx2:
 				tx2.file.write(ask_write=False)
 				return tx2
@@ -224,9 +224,9 @@ async def sign_object(d,fn):
 				return False
 		elif d.desc == 'message file':
 			from .msg import UnsignedMsg,SignedMsg
-			m = UnsignedMsg(infile=fn)
+			m = UnsignedMsg(cfg,infile=fn)
 			await m.sign(wallet_files=wfs[:])
-			m = SignedMsg(data=m.__dict__)
+			m = SignedMsg(cfg,data=m.__dict__)
 			m.write_to_file(
 				outdir = os.path.abspath(msg_dir),
 				ask_overwrite = False )
@@ -259,12 +259,12 @@ async def sign(target):
 				ok.append(ret)
 			else:
 				bad.append(fn)
-			qmsg('')
+			cfg._util.qmsg('')
 		await asyncio.sleep(0.3)
 		msg(f'{len(ok)} {d.desc}{suf(ok)} signed')
 		if bad:
 			rmsg(f'{len(bad)} {d.desc}{suf(bad)} failed to {d.fail_desc}')
-		if ok and not opt.no_summary:
+		if ok and not cfg.no_summary:
 			print_summary(d,ok)
 		if bad:
 			msg('')
@@ -285,11 +285,11 @@ async def sign(target):
 		return True
 
 def decrypt_wallets():
-	msg(f'Unlocking wallet{suf(wfs)} with key from {opt.passwd_file!r}')
+	msg(f'Unlocking wallet{suf(wfs)} with key from {cfg.passwd_file!r}')
 	fails = 0
 	for wf in wfs:
 		try:
-			Wallet(wf,ignore_in_fmt=True)
+			Wallet(cfg,wf,ignore_in_fmt=True)
 		except SystemExit as e:
 			if e.code != 0:
 				fails += 1
@@ -304,7 +304,7 @@ def print_summary(d,signed_objects):
 			gmsg('  ' + os.path.join(msg_dir,m.signed_filename) )
 		return
 
-	if opt.full_summary:
+	if cfg.full_summary:
 		bmsg('\nAutosign summary:\n')
 		def gen():
 			for tx in signed_objects:
@@ -340,23 +340,23 @@ def print_summary(d,signed_objects):
 		msg('No non-MMGen outputs')
 
 async def do_sign():
-	if not opt.stealth_led:
+	if not cfg.stealth_led:
 		led.set('busy')
 	do_mount()
 	key_ok = decrypt_wallets()
 	if key_ok:
-		if opt.stealth_led:
+		if cfg.stealth_led:
 			led.set('busy')
 		ret1 = await sign('tx')
 		ret2 = await sign('msg') if have_msg_dir else True
 		ret = ret1 and ret2
 		do_umount()
-		led.set(('standby','off','error')[(not ret)*2 or bool(opt.stealth_led)])
+		led.set(('standby','off','error')[(not ret)*2 or bool(cfg.stealth_led)])
 		return ret
 	else:
 		msg('Password is incorrect!')
 		do_umount()
-		if not opt.stealth_led:
+		if not cfg.stealth_led:
 			led.set('error')
 		return False
 
@@ -366,7 +366,7 @@ def wipe_existing_key():
 	else:
 		from .fileutil import shred_file
 		msg(f'\nShredding existing key {keyfile!r}')
-		shred_file( keyfile, verbose=opt.verbose )
+		shred_file( keyfile, verbose=cfg.verbose )
 
 def create_key():
 	kdata = os.urandom(32).hex()
@@ -404,12 +404,12 @@ def create_wallet_dir():
 def setup():
 	remove_wallet_dir()
 	gen_key(no_unmount=True)
-	ss_in = Wallet(in_fmt=mn_fmts[opt.mnemonic_fmt or mn_fmt_dfl])
-	ss_out = Wallet(ss=ss_in)
+	ss_in = Wallet(cfg,in_fmt=mn_fmts[cfg.mnemonic_fmt or mn_fmt_dfl])
+	ss_out = Wallet(cfg,ss=ss_in)
 	ss_out.write_to_file(desc='autosign wallet',outdir=wallet_dir)
 
 def get_insert_status():
-	if opt.no_insert_check:
+	if cfg.no_insert_check:
 		return True
 	try: os.stat(os.path.join('/dev/disk/by-label',part_label))
 	except: return False
@@ -417,7 +417,7 @@ def get_insert_status():
 
 async def do_loop():
 	n,prev_status = 0,False
-	if not opt.stealth_led:
+	if not cfg.stealth_led:
 		led.set('standby')
 	while True:
 		status = get_insert_status()
@@ -459,7 +459,7 @@ signal.signal(signal.SIGINT,handler)
 
 from .led import LEDControl
 led = LEDControl(
-	enabled = opt.led,
+	enabled = cfg.led,
 	simulate = os.getenv('MMGEN_TEST_SUITE_AUTOSIGN_LED_SIMULATE') )
 led.set('off')
 

+ 18 - 18
mmgen/main_msg.py

@@ -14,10 +14,8 @@ mmgen-msg: Message signing operations for the MMGen suite
 
 import sys
 import mmgen.opts as opts
-from .globalvars import g
-from .opts import opt
 from .base_obj import AsyncInit
-from .util import msg,suf,async_run,stdout_or_pager
+from .util import msg,suf,async_run
 from .msg import (
 	NewMsg,
 	CompletedMsg,
@@ -33,30 +31,29 @@ class MsgOps:
 	class create:
 
 		def __init__(self,msg,addr_specs):
-			from .protocol import init_proto_from_opts
-			proto = init_proto_from_opts()
 			NewMsg(
-				coin      = proto.coin,
-				network   = proto.network,
+				cfg       = cfg,
+				coin      = cfg._proto.coin,
+				network   = cfg._proto.network,
 				message   = msg,
 				addrlists = addr_specs,
-				msghash_type = opt.msghash_type
+				msghash_type = cfg.msghash_type
 			).write_to_file( ask_overwrite=False )
 
 	class sign(metaclass=AsyncInit):
 
 		async def __init__(self,msgfile,wallet_files):
 
-			m = UnsignedMsg( infile=msgfile )
+			m = UnsignedMsg( cfg, infile=msgfile )
 
 			if not wallet_files:
 				from .filename import find_file_in_dir
 				from .wallet import get_wallet_cls
-				wallet_files = [find_file_in_dir( get_wallet_cls('mmgen'), g.data_dir )]
+				wallet_files = [find_file_in_dir( get_wallet_cls('mmgen'), cfg.data_dir )]
 
 			await m.sign(wallet_files)
 
-			m = SignedMsg( data=m.__dict__ )
+			m = SignedMsg( cfg, data=m.__dict__ )
 
 			m.write_to_file( ask_overwrite=False )
 
@@ -67,18 +64,18 @@ class MsgOps:
 
 		async def __init__(self,msgfile,addr=None):
 			try:
-				m = SignedOnlineMsg( infile=msgfile )
+				m = SignedOnlineMsg( cfg, infile=msgfile )
 			except:
-				m = ExportedMsgSigs( infile=msgfile )
+				m = ExportedMsgSigs( cfg, infile=msgfile )
 
 			nSigs = await m.verify(addr)
 
 			summary = f'{nSigs} signature{suf(nSigs)} verified'
 
-			if opt.quiet:
+			if cfg.quiet:
 				msg(summary)
 			else:
-				stdout_or_pager(m.format(addr) + '\n\n' + summary + '\n')
+				cfg._util.stdout_or_pager(m.format(addr) + '\n\n' + summary + '\n')
 
 			if m.data.get('failed_sids'):
 				sys.exit(1)
@@ -89,8 +86,9 @@ class MsgOps:
 
 			from .fileutil import write_data_to_file
 			write_data_to_file(
+				cfg     = cfg,
 				outfile = 'signatures.json',
-				data    = SignedOnlineMsg( infile=msgfile ).get_json_for_export( addr ),
+				data    = SignedOnlineMsg( cfg, infile=msgfile ).get_json_for_export( addr ),
 				desc    = 'signature data' )
 
 opts_data = {
@@ -205,14 +203,16 @@ $ mmgen-msg verify signatures.json
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data,need_amt=False)
+
+cmd_args = cfg._args
 
 if len(cmd_args) < 2:
 	opts.usage()
 
 op = cmd_args.pop(0)
 
-if opt.msghash_type and op != 'create':
+if cfg.msghash_type and op != 'create':
 	die(1,'--msghash-type option may only be used with the "create" command')
 
 async def main():

+ 21 - 19
mmgen/main_passgen.py

@@ -22,8 +22,7 @@ mmgen-passgen: Generate a series or range of passwords from an MMGen
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .addrlist import AddrIdxList
 from .passwdlist import PasswordList
 from .wallet import Wallet
@@ -62,7 +61,7 @@ opts_data = {
 -P, --passwd-file= f  Get wallet passphrase from file 'f'
 -q, --quiet           Produce quieter output; suppress some warnings
 -r, --usr-randchars=n Get 'n' characters of additional randomness from user
-                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+                      (min={cfg.min_urandchars}, max={cfg.max_urandchars}, default={cfg.usr_randchars})
 -S, --stdout          Print passwords to stdout
 -v, --verbose         Produce more verbose output
 """,
@@ -115,14 +114,15 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda help_notes,s: s.format(
-			g=g,pnm=gc.proj_name,
+		'options': lambda cfg,help_notes,s: s.format(
+			pnm=gc.proj_name,
 			dsl=help_notes('dfl_seed_len'),
 			dpf=PasswordList.dfl_pw_fmt,
+			cfg=cfg,
 			gc=gc,
 		),
-		'notes': lambda help_notes,s: s.format(
-				o=opts,g=g,i58=pwi['b58'],i32=pwi['b32'],i39=pwi['bip39'],
+		'notes': lambda cfg,help_notes,s: s.format(
+				o=opts,cfg=cfg,i58=pwi['b58'],i32=pwi['b32'],i39=pwi['bip39'],
 				ml=MMGenPWIDString.max_len,
 				fs="', '".join(MMGenPWIDString.forbidden),
 				n_pw=help_notes('passwd'),
@@ -134,24 +134,25 @@ FMT CODES:
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-if len(cmd_args) < 2: opts.usage()
+if len(cfg._args) < 2: opts.usage()
 
-pw_idxs = AddrIdxList(fmt_str=cmd_args.pop())
+pw_idxs = AddrIdxList(fmt_str=cfg._args.pop())
 
-pw_id_str = cmd_args.pop()
+pw_id_str = cfg._args.pop()
 
 from .fileutil import get_seed_file
-sf = get_seed_file(cmd_args,1)
+sf = get_seed_file(cfg,1)
 
-pw_fmt = opt.passwd_fmt or PasswordList.dfl_pw_fmt
-pw_len = pwi[pw_fmt].dfl_len // 2 if opt.passwd_len in ('h','H') else opt.passwd_len
+pw_fmt = cfg.passwd_fmt or PasswordList.dfl_pw_fmt
+pw_len = pwi[pw_fmt].dfl_len // 2 if cfg.passwd_len in ('h','H') else cfg.passwd_len
 
 from .protocol import init_proto
-proto = init_proto('btc') # TODO: get rid of dummy proto
+proto = init_proto( cfg, 'btc' ) # TODO: get rid of dummy proto
 
 PasswordList(
+	cfg             = cfg,
 	proto           = proto,
 	pw_id_str       = pw_id_str,
 	pw_len          = pw_len,
@@ -159,11 +160,12 @@ PasswordList(
 	chk_params_only = True )
 
 from .ui import do_license_msg
-do_license_msg()
+do_license_msg(cfg)
 
-ss = Wallet(sf)
+ss = Wallet(cfg,sf)
 
 al = PasswordList(
+	cfg       = cfg,
 	proto     = proto,
 	seed      = ss.seed,
 	pw_idxs   = pw_idxs,
@@ -176,11 +178,11 @@ af = al.get_file()
 af.format()
 
 from .ui import keypress_confirm
-if keypress_confirm('Encrypt password list?'):
+if keypress_confirm( cfg, 'Encrypt password list?' ):
 	af.encrypt()
 	af.write(binary=True,desc='encrypted password list')
 else:
-	if g.test_suite_popen_spawn and gc.platform == 'win':
+	if cfg.test_suite_popen_spawn and gc.platform == 'win':
 		import time
 		time.sleep(0.1)
 	af.write(desc='password list')

+ 5 - 3
mmgen/main_regtest.py

@@ -22,7 +22,7 @@ mmgen-regtest: Coin daemon regression test mode setup and operations for the MMG
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
+from .globalvars import gc
 from .util import die,async_run
 
 opts_data = {
@@ -57,7 +57,9 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+cmd_args = cfg._args
 
 from .proto.btc.regtest import MMGenRegtest
 
@@ -82,6 +84,6 @@ elif cmd_args[0] not in ('cli','wallet_cli','balances'):
 	check_num_args()
 
 async def main():
-	await MMGenRegtest(g.coin).cmd(cmd_args)
+	await MMGenRegtest(cfg,cfg.coin).cmd(cmd_args)
 
 async_run(main())

+ 23 - 24
mmgen/main_seedjoin.py

@@ -22,9 +22,8 @@ mmgen-seedjoin: Regenerate an MMGen deterministic wallet from seed shares
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
-from .util import msg,msg_r,qmsg,die
+from .globalvars import gc
+from .util import msg,msg_r,die
 from .color import yellow
 from .obj import MMGenWalletLabel
 from .seed import Seed
@@ -58,7 +57,7 @@ opts_data = {
 -P, --passwd-file= f  Get wallet passphrase from file 'f'
 -q, --quiet           Produce quieter output; suppress some warnings
 -r, --usr-randchars=n Get 'n' characters of additional randomness from user
-                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+                      (min={cfg.min_urandchars}, max={cfg.max_urandchars}, default={cfg.usr_randchars})
 -S, --stdout          Write wallet data to stdout instead of file
 -v, --verbose         Produce more verbose output
 """,
@@ -82,13 +81,13 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda s: s.format(
+		'options': lambda cfg,s: s.format(
 			ms_min=MasterShareIdx.min_val,
 			ms_max=MasterShareIdx.max_val,
-			g=g,
+			cfg=cfg,
 			gc=gc,
 		),
-		'notes': lambda help_notes,s: s.format(
+		'notes': lambda cfg,help_notes,s: s.format(
 			f=help_notes('fmt_codes'),
 			n_pw=help_notes('passwd'),
 		)
@@ -97,7 +96,7 @@ FMT CODES:
 
 def print_shares_info():
 	si,out = 0,'\nComputed shares:\n'
-	if opt.master_share:
+	if cfg.master_share:
 		fs = '{:3}: {}->{} ' + yellow('(master share #{}, split id ') + '{}' + yellow(', share count {})\n')
 		out += fs.format(
 				1,
@@ -109,36 +108,36 @@ def print_shares_info():
 		si = 1
 	for n,s in enumerate(shares[si:],si+1):
 		out += f'{n:3}: {s.sid}\n'
-	qmsg(out)
+	cfg._util.qmsg(out)
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-if len(cmd_args) + bool(opt.hidden_incog_input_params) < 2:
+if len(cfg._args) + bool(cfg.hidden_incog_input_params) < 2:
 	opts.usage()
 
-if opt.master_share:
-	master_idx = MasterShareIdx(opt.master_share)
-	id_str = SeedSplitIDString(opt.id_str or 'default')
+if cfg.master_share:
+	master_idx = MasterShareIdx(cfg.master_share)
+	id_str = SeedSplitIDString(cfg.id_str or 'default')
 
-if opt.id_str and not opt.master_share:
+if cfg.id_str and not cfg.master_share:
 	die(1,'--id-str option meaningless in context of non-master-share join')
 
 from .fileutil import check_infile
 from .wallet import check_wallet_extension
-for arg in cmd_args:
+for arg in cfg._args:
 	check_wallet_extension(arg)
 	check_infile(arg)
 
 from .ui import do_license_msg
-do_license_msg()
+do_license_msg(cfg)
 
-qmsg('Input files:\n  {}\n'.format('\n  '.join(cmd_args)))
+cfg._util.qmsg('Input files:\n  {}\n'.format('\n  '.join(cfg._args)))
 
-shares = [Wallet().seed] if opt.hidden_incog_input_params else []
-shares += [Wallet(fn).seed for fn in cmd_args]
+shares = [Wallet(cfg).seed] if cfg.hidden_incog_input_params else []
+shares += [Wallet(cfg,fn).seed for fn in cfg._args]
 
-if opt.master_share:
-	share1 = SeedShareMasterJoining(master_idx,shares[0],id_str,len(shares)).derived_seed
+if cfg.master_share:
+	share1 = SeedShareMasterJoining( cfg, master_idx, shares[0], id_str, len(shares) ).derived_seed
 else:
 	share1 = shares[0]
 
@@ -146,8 +145,8 @@ print_shares_info()
 
 msg_r('Joining {n}-of-{n} XOR split...'.format(n=len(shares)))
 
-seed_out = Seed.join_shares([share1]+shares[1:])
+seed_out = Seed.join_shares( cfg, [share1] + shares[1:] )
 
 msg(f'OK\nJoined Seed ID: {seed_out.sid.hl()}')
 
-Wallet(seed=seed_out).write_to_file()
+Wallet(cfg,seed=seed_out).write_to_file()

+ 17 - 18
mmgen/main_split.py

@@ -86,24 +86,23 @@ transaction reconfirmed before the timelock expires. Use at your own risk.
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data,need_amt=False)
 
-from .protocol import init_proto_from_opts
-proto = init_proto_from_opts()
+proto = cfg._proto
 
 die(1,'This command is disabled')
 
 # the following code is broken:
-opt.other_coin = opt.other_coin.upper() if opt.other_coin else proto.forks[-1][2].upper()
-if opt.other_coin.lower() not in [e[2] for e in proto.forks if e[3] == True]:
-	die(1,f'{opt.other_coin!r}: not a replayable fork of {proto.coin} chain')
+cfg.other_coin = cfg.other_coin.upper() if cfg.other_coin else proto.forks[-1][2].upper()
+if cfg.other_coin.lower() not in [e[2] for e in proto.forks if e[3] == True]:
+	die(1,f'{cfg.other_coin!r}: not a replayable fork of {proto.coin} chain')
 
-if len(cmd_args) != 2:
+if len(cfg._args) != 2:
 	die(1,f'This command requires exactly two {gc.proj_name} addresses as arguments')
 
 from .addr import MMGenID
 try:
-	mmids = [MMGenID(a) for a in cmd_args]
+	mmids = [MMGenID(a) for a in cfg._args]
 except:
 	die(1,'Command line arguments must be valid MMGen IDs')
 
@@ -113,18 +112,18 @@ if mmids[0] == mmids[1]:
 from .tx import MMGenSplitTX
 from .protocol import init_proto
 
-if opt.tx_fees:
-	for idx,g_coin in ((1,opt.other_coin),(0,proto.coin)):
-		proto = init_proto(g_coin)
-		opt.fee = opt.tx_fees.split(',')[idx]
-		opts.opt_is_tx_fee('foo',opt.fee,'transaction fee') # raises exception on error
+if cfg.tx_fees:
+	for idx,g_coin in ((1,cfg.other_coin),(0,proto.coin)):
+		proto = init_proto( cfg, g_coin )
+		cfg.fee = cfg.tx_fees.split(',')[idx]
+		opts.opt_is_tx_fee('foo',cfg.fee,'transaction fee') # raises exception on error
 
 tx1 = MMGenSplitTX()
-opt.no_blank = True
+cfg.no_blank = True
 
 async def main():
 	gmsg(f'Creating timelocked transaction for long chain ({proto.coin})')
-	locktime = int(opt.locktime)
+	locktime = int(cfg.locktime)
 	if not locktime:
 		rpc = rpc_init(proto)
 		locktime = rpc.call('getblockcount')
@@ -133,9 +132,9 @@ async def main():
 	tx1.format()
 	tx1.create_fn()
 
-	gmsg(f'\nCreating transaction for short chain ({opt.other_coin})')
+	gmsg(f'\nCreating transaction for short chain ({cfg.other_coin})')
 
-	proto = init_proto(opt.other_coin)
+	proto = init_proto( self.cfg, cfg.other_coin )
 
 	tx2 = MMGenSplitTX()
 	tx2.inputs = tx1.inputs
@@ -145,4 +144,4 @@ async def main():
 
 	for tx,desc in ((tx1,'Long chain (timelocked)'),(tx2,'Short chain')):
 		tx.desc = desc + ' transaction'
-		tx.file.write(ask_write=False,ask_overwrite=not opt.yes,ask_write_default_yes=False)
+		tx.file.write(ask_write=False,ask_overwrite=not cfg.yes,ask_write_default_yes=False)

+ 10 - 9
mmgen/main_tool.py

@@ -23,8 +23,7 @@ mmgen-tool:  Perform various MMGen- and cryptocoin-related operations.
 
 import sys,os,importlib
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .util import msg,Msg,die,capfirst,suf,async_run
 
 opts_data = {
@@ -45,7 +44,7 @@ opts_data = {
 -P, --passwd-file= f   Get passphrase from file 'f'.
 -q, --quiet            Produce quieter output
 -r, --usr-randchars=n  Get 'n' characters of additional randomness from
-                       user (min={g.min_urandchars}, max={g.max_urandchars})
+                       user (min={cfg.min_urandchars}, max={cfg.max_urandchars})
 -t, --type=t           Specify address type (valid choices: 'legacy',
                        'compressed', 'segwit', 'bech32', 'zcash_z')
 -v, --verbose          Produce more verbose output
@@ -61,13 +60,13 @@ Type ‘{pn} help <command>’ for help on a particular command
 """
 	},
 	'code': {
-		'options': lambda s, help_notes: s.format(
+		'options': lambda cfg,s, help_notes: s.format(
 			kgs=help_notes('keygen_backends'),
 			coin_id=help_notes('coin_id'),
-			g=g,
+			cfg=cfg,
 			gc=gc,
 		),
-		'notes': lambda s, help_notes: s.format(
+		'notes': lambda cfg,s, help_notes: s.format(
 			ch=help_notes('tool_help'),
 			pn=gc.prog_name)
 	}
@@ -346,7 +345,7 @@ def get_cmd_cls(cmd):
 def get_mod_cls(modname):
 	return getattr(importlib.import_module(f'mmgen.tool.{modname}'),'tool_cmd')
 
-if gc.prog_name == 'mmgen-tool' and not opt._lock:
+if gc.prog_name == 'mmgen-tool':
 
 	po = opts.init( opts_data, parse_only=True )
 
@@ -372,18 +371,20 @@ if gc.prog_name == 'mmgen-tool' and not opt._lock:
 	if not cls:
 		die(1,f'{cmd!r}: no such command')
 
-	cmd,*args = opts.init(
+	cfg = opts.init(
 		opts_data,
 		parsed_opts = po,
 		need_proto  = cls.need_proto,
 		init_opts   = {'rpc_backend':'aiohttp'} if cmd == 'twimport' else None )
 
+	cmd,*args = cfg._args
+
 	if cmd in ('help','usage') and args:
 		args[0] = 'command_name=' + args[0]
 
 	args,kwargs = process_args(cmd,args,cls)
 
-	ret = getattr(cls(cmdname=cmd),cmd)(*args,**kwargs)
+	ret = getattr(cls(cfg,cmdname=cmd),cmd)(*args,**kwargs)
 
 	if type(ret).__name__ == 'coroutine':
 		ret = async_run(ret)

+ 21 - 20
mmgen/main_txbump.py

@@ -23,8 +23,7 @@ mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen
 
 import mmgen.opts as opts
 from .globalvars import gc
-from .opts import opt
-from .util import msg,msg_r,qmsg,die,async_run
+from .util import msg,msg_r,die,async_run
 from .color import green
 from .wallet import Wallet
 
@@ -86,7 +85,8 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda help_notes,proto,s: s.format(
+		'options': lambda cfg,help_notes,proto,s: s.format(
+			cfg=cfg,
 			gc=gc,
 			pnm=gc.proj_name,
 			pnl=gc.proj_name.lower(),
@@ -103,9 +103,9 @@ FMT CODES:
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-tx_file = cmd_args.pop(0)
+tx_file = cfg._args.pop(0)
 
 from .fileutil import check_infile
 check_infile(tx_file)
@@ -113,33 +113,34 @@ check_infile(tx_file)
 from .tx import CompletedTX,BumpTX,UnsignedTX,OnlineSignedTX
 from .tx.sign import txsign,get_seed_files,get_keyaddrlist,get_keylist
 
-seed_files = get_seed_files(opt,cmd_args) if (cmd_args or opt.send) else None
+seed_files = get_seed_files(cfg,cfg._args) if (cfg._args or cfg.send) else None
 
 from .ui import do_license_msg
-do_license_msg()
+do_license_msg(cfg)
 
-silent = opt.yes and opt.fee != None and opt.output_to_reduce != None
+silent = cfg.yes and cfg.fee != None and cfg.output_to_reduce != None
 
 async def main():
 
-	orig_tx = await CompletedTX(filename=tx_file)
+	orig_tx = await CompletedTX(cfg=cfg,filename=tx_file)
 
 	if not silent:
 		msg(green('ORIGINAL TRANSACTION'))
 		msg(orig_tx.info.format(terse=True))
 
-	kal = get_keyaddrlist(orig_tx.proto,opt)
-	kl = get_keylist(orig_tx.proto,opt)
+	kal = get_keyaddrlist(cfg,orig_tx.proto)
+	kl = get_keylist(cfg,orig_tx.proto)
 	sign_and_send = bool(seed_files or kl or kal)
 
 	from .tw.ctl import TwCtl
 	tx = await BumpTX(
+		cfg  = cfg,
 		data = orig_tx.__dict__,
 		send = sign_and_send,
-		twctl = await TwCtl(orig_tx.proto) if orig_tx.proto.tokensym else None )
+		twctl = await TwCtl(cfg,orig_tx.proto) if orig_tx.proto.tokensym else None )
 
 	from .rpc import rpc_init
-	tx.rpc = await rpc_init(tx.proto)
+	tx.rpc = await rpc_init(cfg,tx.proto)
 
 	msg('Creating replacement transaction')
 
@@ -150,13 +151,13 @@ async def main():
 	if not silent:
 		msg(f'Minimum fee for new transaction: {tx.min_fee.hl()} {tx.proto.coin}')
 
-	tx.usr_fee = tx.get_usr_fee_interactive(fee=opt.fee,desc='User-selected')
+	tx.usr_fee = tx.get_usr_fee_interactive(fee=cfg.fee,desc='User-selected')
 
 	tx.bump_fee(output_idx,tx.usr_fee)
 
 	assert tx.fee <= tx.proto.max_tx_fee
 
-	if not opt.yes:
+	if not cfg.yes:
 		tx.add_comment()   # edits an existing comment
 
 	await tx.create_serialized(bump=True)
@@ -164,23 +165,23 @@ async def main():
 	tx.add_timestamp()
 	tx.add_blockcount()
 
-	qmsg('Fee successfully increased')
+	cfg._util.qmsg('Fee successfully increased')
 
 	if not silent:
 		msg(green('\nREPLACEMENT TRANSACTION:'))
 		msg_r(tx.info.format(terse=True))
 
 	if sign_and_send:
-		tx2 = UnsignedTX(data=tx.__dict__)
-		tx3 = await txsign(tx2,seed_files,kl,kal)
+		tx2 = UnsignedTX(cfg=cfg,data=tx.__dict__)
+		tx3 = await txsign(cfg,tx2,seed_files,kl,kal)
 		if tx3:
-			tx4 = await OnlineSignedTX(data=tx3.__dict__)
+			tx4 = await OnlineSignedTX(cfg=cfg,data=tx3.__dict__)
 			tx4.file.write(ask_write=False)
 			await tx4.send(exit_on_fail=True)
 			tx4.file.write(ask_write=False)
 		else:
 			die(2,'Transaction could not be signed')
 	else:
-		tx.file.write(ask_write=not opt.yes,ask_write_default_yes=False,ask_overwrite=not opt.yes)
+		tx.file.write(ask_write=not cfg.yes,ask_write_default_yes=False,ask_overwrite=not cfg.yes)
 
 async_run(main())

+ 15 - 19
mmgen/main_txcreate.py

@@ -22,8 +22,7 @@ mmgen-txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .util import fmt_list,async_run
 
 opts_data = {
@@ -38,7 +37,7 @@ opts_data = {
 -B, --no-blank        Don't blank screen before displaying unspent outputs
 -c, --comment-file=f  Source the transaction's comment from file 'f'
 -C, --fee-estimate-confs=c Desired number of confirmations for fee estimation
-                      (default: {g.fee_estimate_confs})
+                      (default: {cfg.fee_estimate_confs})
 -d, --outdir=      d  Specify an alternate directory 'd' for output
 -D, --contract-data=D Path to hex-encoded contract data (ETH only)
 -E, --fee-estimate-mode=M Specify the network fee estimate mode.  Choices:
@@ -66,41 +65,38 @@ opts_data = {
 		'notes': '\n{c}\n{F}\n{x}',
 	},
 	'code': {
-		'options': lambda proto,help_notes,s: s.format(
+		'options': lambda cfg,proto,help_notes,s: s.format(
 			fu=help_notes('rel_fee_desc'),
 			fl=help_notes('fee_spec_letters'),
-			fe_all=fmt_list(g.autoset_opts['fee_estimate_mode'].choices,fmt='no_spc'),
-			fe_dfl=g.autoset_opts['fee_estimate_mode'].choices[0],
+			fe_all=fmt_list(cfg.autoset_opts['fee_estimate_mode'].choices,fmt='no_spc'),
+			fe_dfl=cfg.autoset_opts['fee_estimate_mode'].choices[0],
 			cu=proto.coin,
-			g=g),
-		'notes': lambda help_notes,s: s.format(
+			cfg=cfg),
+		'notes': lambda cfg,help_notes,s: s.format(
 			c = help_notes('txcreate'),
 			F = help_notes('fee'),
 			x = help_notes('txcreate_examples') )
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 async def main():
 
-	from .protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
-
 	from .tx import NewTX
-	tx1 = await NewTX(proto=proto)
+	tx1 = await NewTX(cfg=cfg,proto=cfg._proto)
 
 	from .rpc import rpc_init
-	tx1.rpc = await rpc_init(proto)
+	tx1.rpc = await rpc_init(cfg,cfg._proto)
 
 	tx2 = await tx1.create(
-		cmd_args = cmd_args,
-		locktime = int(opt.locktime or 0),
-		do_info  = opt.info )
+		cmd_args = cfg._args,
+		locktime = int(cfg.locktime or 0),
+		do_info  = cfg.info )
 
 	tx2.file.write(
-		ask_write             = not opt.yes,
-		ask_overwrite         = not opt.yes,
+		ask_write             = not cfg.yes,
+		ask_overwrite         = not cfg.yes,
 		ask_write_default_yes = False )
 
 async_run(main())

+ 17 - 20
mmgen/main_txdo.py

@@ -21,8 +21,7 @@ mmgen-txdo: Create, sign and broadcast an online MMGen transaction
 """
 
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .util import die,fmt_list,async_run
 from .wallet import Wallet
 from .subseed import SubSeedIdxRange
@@ -41,7 +40,7 @@ opts_data = {
 -B, --no-blank         Don't blank screen before displaying unspent outputs
 -c, --comment-file=  f Source the transaction's comment from file 'f'
 -C, --fee-estimate-confs=c Desired number of confirmations for fee estimation
-                       (default: {g.fee_estimate_confs})
+                       (default: {cfg.fee_estimate_confs})
 -d, --outdir=        d Specify an alternate directory 'd' for output
 -D, --contract-data= D Path to hex-encoded contract data (ETH only)
 -e, --echo-passphrase  Print passphrase to screen when typing it
@@ -103,19 +102,19 @@ FMT CODES:
 {x}"""
 	},
 	'code': {
-		'options': lambda proto,help_notes,s: s.format(
-			g=g,gc=gc,pnm=gc.proj_name,pnl=gc.proj_name.lower(),
+		'options': lambda cfg,proto,help_notes,s: s.format(
+			gc=gc,cfg=cfg,pnm=gc.proj_name,pnl=gc.proj_name.lower(),
 			kgs=help_notes('keygen_backends'),
 			coin_id=help_notes('coin_id'),
 			fu=help_notes('rel_fee_desc'),
 			fl=help_notes('fee_spec_letters'),
 			ss=help_notes('dfl_subseeds'),
 			ss_max=SubSeedIdxRange.max_idx,
-			fe_all=fmt_list(g.autoset_opts['fee_estimate_mode'].choices,fmt='no_spc'),
-			fe_dfl=g.autoset_opts['fee_estimate_mode'].choices[0],
+			fe_all=fmt_list(cfg.autoset_opts['fee_estimate_mode'].choices,fmt='no_spc'),
+			fe_dfl=cfg.autoset_opts['fee_estimate_mode'].choices[0],
 			dsl=help_notes('dfl_seed_len'),
 			cu=proto.coin),
-		'notes': lambda help_notes,s: s.format(
+		'notes': lambda cfg,help_notes,s: s.format(
 			c = help_notes('txcreate'),
 			F = help_notes('fee'),
 			s = help_notes('txsign'),
@@ -124,34 +123,32 @@ FMT CODES:
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 from .tx import NewTX,OnlineSignedTX
 from .tx.sign import txsign,get_seed_files,get_keyaddrlist,get_keylist
 
-seed_files = get_seed_files(opt,cmd_args)
+seed_files = get_seed_files(cfg,cfg._args)
 
 async def main():
-	from .protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
 
-	tx1 = await NewTX(proto=proto)
+	tx1 = await NewTX(cfg=cfg,proto=cfg._proto)
 
 	from .rpc import rpc_init
-	tx1.rpc = await rpc_init(proto)
+	tx1.rpc = await rpc_init(cfg,cfg._proto)
 
 	tx2 = await tx1.create(
-		cmd_args = cmd_args,
-		locktime = int(opt.locktime or 0),
+		cmd_args = cfg._args,
+		locktime = int(cfg.locktime or 0),
 		caller   = 'txdo' )
 
-	kal = get_keyaddrlist(proto,opt)
-	kl = get_keylist(proto,opt)
+	kal = get_keyaddrlist(cfg,cfg._proto)
+	kl = get_keylist(cfg,cfg._proto)
 
-	tx3 = await txsign(tx2,seed_files,kl,kal)
+	tx3 = await txsign(cfg,tx2,seed_files,kl,kal)
 
 	if tx3:
-		tx4 = await OnlineSignedTX(data=tx3.__dict__)
+		tx4 = await OnlineSignedTX(cfg=cfg,data=tx3.__dict__)
 		tx4.file.write(ask_write=False)
 		await tx4.send(exit_on_fail=True)
 		tx4.file.write(ask_overwrite=False,ask_write=False)

+ 12 - 12
mmgen/main_txsend.py

@@ -24,8 +24,7 @@ import sys
 
 import mmgen.opts as opts
 from .globalvars import gc
-from .opts import opt
-from .util import vmsg,qmsg,async_run
+from .util import async_run
 
 opts_data = {
 	'sets': [('yes', True, 'quiet', True)],
@@ -43,39 +42,40 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-if len(cmd_args) == 1:
-	infile = cmd_args[0]
+if len(cfg._args) == 1:
+	infile = cfg._args[0]
 	from .fileutil import check_infile
 	check_infile(infile)
 else:
 	opts.usage()
 
-if not opt.status:
+if not cfg.status:
 	from .ui import do_license_msg
-	do_license_msg()
+	do_license_msg(cfg)
 
 async def main():
 
 	from .tx import OnlineSignedTX
 
 	tx = await OnlineSignedTX(
+		cfg        = cfg,
 		filename   = infile,
 		quiet_open = True )
 
 	from .rpc import rpc_init
-	tx.rpc = await rpc_init(tx.proto)
+	tx.rpc = await rpc_init(cfg,tx.proto)
 
-	vmsg(f'Signed transaction file {infile!r} is valid')
+	cfg._util.vmsg(f'Signed transaction file {infile!r} is valid')
 
-	if opt.status:
+	if cfg.status:
 		if tx.coin_txid:
-			qmsg(f'{tx.proto.coin} txid: {tx.coin_txid.hl()}')
+			cfg._util.qmsg(f'{tx.proto.coin} txid: {tx.coin_txid.hl()}')
 		await tx.status.display(usr_req=True)
 		sys.exit(0)
 
-	if not opt.yes:
+	if not cfg.yes:
 		tx.info.view_with_prompt('View transaction details?')
 		if tx.add_comment(): # edits an existing comment, returns true if changed
 			tx.file.write(ask_write_default_yes=True)

+ 21 - 19
mmgen/main_txsign.py

@@ -22,7 +22,6 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
 
 import mmgen.opts as opts
 from .globalvars import gc
-from .opts import opt
 from .util import msg,ymsg,die,async_run
 from .subseed import SubSeedIdxRange
 from .wallet import Wallet
@@ -83,7 +82,8 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda proto,help_notes,s: s.format(
+		'options': lambda cfg,proto,help_notes,s: s.format(
+			cfg=cfg,
 			gc=gc,
 			pnm=gc.proj_name,
 			pnl=gc.proj_name.lower(),
@@ -99,7 +99,9 @@ FMT CODES:
 	}
 }
 
-infiles = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+infiles = cfg._args
 
 if not infiles:
 	opts.usage()
@@ -108,14 +110,14 @@ from .fileutil import check_infile
 for i in infiles:
 	check_infile(i)
 
-if not opt.info and not opt.terse_info:
+if not cfg.info and not cfg.terse_info:
 	from .ui import do_license_msg
-	do_license_msg(immed=True)
+	do_license_msg(cfg,immed=True)
 
 from .tx.sign import *
 
-tx_files   = get_tx_files(opt,infiles)
-seed_files = get_seed_files(opt,infiles)
+tx_files   = get_tx_files(cfg,infiles)
+seed_files = get_seed_files(cfg,infiles)
 
 async def main():
 
@@ -129,33 +131,33 @@ async def main():
 			msg(orange(f'\nTransaction{tx_num_disp} of {len(tx_files)}:'))
 
 		from .tx import UnsignedTX
-		tx1 = UnsignedTX(filename=tx_file)
+		tx1 = UnsignedTX(cfg=cfg,filename=tx_file)
 
-		vmsg(f'Successfully opened transaction file {tx_file!r}')
+		cfg._util.vmsg(f'Successfully opened transaction file {tx_file!r}')
 
 		if tx1.proto.sign_mode == 'daemon':
 			from .rpc import rpc_init
-			tx1.rpc = await rpc_init(tx1.proto)
+			tx1.rpc = await rpc_init(cfg,tx1.proto)
 
-		if opt.tx_id:
+		if cfg.tx_id:
 			msg(tx1.txid)
 			continue
 
-		if opt.info or opt.terse_info:
-			tx1.view(pause=False,terse=opt.terse_info)
+		if cfg.info or cfg.terse_info:
+			tx1.view(pause=False,terse=cfg.terse_info)
 			continue
 
-		if not opt.yes:
+		if not cfg.yes:
 			tx1.info.view_with_prompt(f'View data for transaction{tx_num_disp}?')
 
-		kal = get_keyaddrlist(tx1.proto,opt)
-		kl = get_keylist(tx1.proto,opt)
+		kal = get_keyaddrlist(cfg,tx1.proto)
+		kl = get_keylist(cfg,tx1.proto)
 
-		tx2 = await txsign(tx1,seed_files,kl,kal,tx_num_disp)
+		tx2 = await txsign(cfg,tx1,seed_files,kl,kal,tx_num_disp)
 		if tx2:
-			if not opt.yes:
+			if not cfg.yes:
 				tx2.add_comment() # edits an existing comment
-			tx2.file.write(ask_write=not opt.yes,ask_write_default_yes=True,add_desc=tx_num_disp)
+			tx2.file.write(ask_write=not cfg.yes,ask_write_default_yes=True,add_desc=tx_num_disp)
 		else:
 			ymsg('Transaction could not be signed')
 			bad_tx_count += 1

+ 30 - 23
mmgen/main_wallet.py

@@ -22,10 +22,9 @@ main_wallet: Entry point for MMGen wallet-related scripts
 
 import sys,os
 import mmgen.opts as opts
-from .globalvars import g,gc
-from .opts import opt
+from .globalvars import gc
 from .color import green,yellow
-from .util import msg,qmsg,vmsg,gmsg_r,ymsg,bmsg,die,capfirst
+from .util import msg,gmsg_r,ymsg,bmsg,die,capfirst
 from .wallet import Wallet,get_wallet_cls
 
 usage = '[opts] [infile]'
@@ -113,7 +112,7 @@ opts_data = {
 -P, --passwd-file= f  Get wallet passphrase from file 'f'
 -q, --quiet           Produce quieter output; suppress some warnings
 -r, --usr-randchars=n Get 'n' characters of additional randomness from user
-                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+                      (min={cfg.min_urandchars}, max={cfg.max_urandchars}, default={cfg.usr_randchars})
 -S, --stdout          Write wallet data to stdout instead of file
 -v, --verbose         Produce more verbose output
 """,
@@ -127,16 +126,16 @@ FMT CODES:
 """
 	},
 	'code': {
-		'options': lambda help_notes,s: s.format(
+		'options': lambda cfg,help_notes,s: s.format(
 			iaction=capfirst(iaction),
 			oaction=capfirst(oaction),
 			ms_min=help_notes('MasterShareIdx').min_val,
 			ms_max=help_notes('MasterShareIdx').max_val,
 			dsl=help_notes('dfl_seed_len'),
-			g=g,
+			cfg=cfg,
 			gc=gc,
 		),
-		'notes': lambda help_notes,s: s.format(
+		'notes': lambda cfg,help_notes,s: s.format(
 			f=help_notes('fmt_codes'),
 			n_ss=('',help_notes('seedsplit')+'\n\n')[do_ss_note],
 			n_sw=('',help_notes('subwallet')+'\n\n')[do_sw_note],
@@ -146,7 +145,9 @@ FMT CODES:
 	}
 }
 
-cmd_args = opts.init(opts_data,opt_filter=opt_filter,need_proto=False)
+cfg = opts.init(opts_data,opt_filter=opt_filter,need_proto=False)
+
+cmd_args = cfg._args
 
 if invoked_as == 'subgen':
 	from .subseed import SubSeedIdx
@@ -154,7 +155,7 @@ if invoked_as == 'subgen':
 elif invoked_as == 'seedsplit':
 	from .obj import get_obj
 	from .seedsplit import SeedSplitSpecifier,MasterShareIdx
-	master_share = MasterShareIdx(opt.master_share) if opt.master_share else None
+	master_share = MasterShareIdx(cfg.master_share) if cfg.master_share else None
 	if cmd_args:
 		sss = get_obj(SeedSplitSpecifier,s=cmd_args.pop(),silent=True)
 		if master_share:
@@ -178,26 +179,27 @@ if cmd_args:
 		opts.usage()
 	check_infile(cmd_args[0])
 
-sf = get_seed_file(cmd_args,nargs,invoked_as=invoked_as)
+sf = get_seed_file(cfg,nargs,invoked_as=invoked_as)
 
 if invoked_as != 'chk':
 	from .ui import do_license_msg
-	do_license_msg()
+	do_license_msg(cfg)
 
 if invoked_as == 'gen':
 	ss_in = None
 else:
 	ss_in = Wallet(
-		sf,
+		cfg     = cfg,
+		fn      = sf,
 		passchg = invoked_as=='passchg' )
 	m1 = green('Processing input wallet ')
 	m2 = ss_in.seed.sid.hl()
-	m3 = yellow(' (default wallet)') if sf and os.path.dirname(sf) == g.data_dir else ''
+	m3 = yellow(' (default wallet)') if sf and os.path.dirname(sf) == cfg.data_dir else ''
 	msg(m1+m2+m3)
 
 if invoked_as == 'chk':
 	lbl = ss_in.ssdata.label.hl() if hasattr(ss_in.ssdata,'label') else 'NONE'
-	vmsg(f'Wallet label: {lbl}')
+	cfg._util.vmsg(f'Wallet label: {lbl}')
 	# TODO: display creation date
 	sys.exit(0)
 
@@ -206,20 +208,23 @@ if invoked_as != 'gen':
 
 if invoked_as == 'subgen':
 	ss_out = Wallet(
+		cfg      = cfg,
 		seed_bin = ss_in.seed.subseed(ss_idx,print_msg=True).data )
 elif invoked_as == 'seedsplit':
 	shares = ss_in.seed.split(sss.count,sss.id,master_share)
 	seed_out = shares.get_share_by_idx(sss.idx,base_seed=True)
 	msg(seed_out.get_desc(ui=True))
 	ss_out = Wallet(
+		cfg  = cfg,
 		seed = seed_out )
 else:
 	ss_out = Wallet(
+		cfg     = cfg,
 		ss      = ss_in,
 		passchg = invoked_as == 'passchg' )
 
 if invoked_as == 'gen':
-	qmsg(f"This wallet's Seed ID: {ss_out.seed.sid.hl()}")
+	cfg._util.qmsg(f"This wallet's Seed ID: {ss_out.seed.sid.hl()}")
 
 if invoked_as == 'passchg':
 	def data_changed(attrs):
@@ -227,7 +232,7 @@ if invoked_as == 'passchg':
 			if getattr(ss_out.ssdata,attr) != getattr(ss_in.ssdata,attr):
 				return True
 		return False
-	if not ( opt.force_update or data_changed(('passwd','hash_preset','label')) ):
+	if not ( cfg.force_update or data_changed(('passwd','hash_preset','label')) ):
 		die(1,'Password, hash preset and label are unchanged.  Taking no action')
 
 if invoked_as == 'passchg':
@@ -235,7 +240,7 @@ if invoked_as == 'passchg':
 	def secure_delete(fn):
 		bmsg('Securely deleting old wallet')
 		from .fileutil import shred_file
-		shred_file( fn, verbose = opt.verbose )
+		shred_file( fn, verbose = cfg.verbose )
 
 	def rename_old_wallet_maybe(silent):
 		# though very unlikely, old and new wallets could have same Key ID and thus same filename.
@@ -251,31 +256,33 @@ if invoked_as == 'passchg':
 		else:
 			return old_fn
 
-	if ss_in.infile.dirname == g.data_dir:
+	if ss_in.infile.dirname == cfg.data_dir:
 		from .ui import confirm_or_raise
 		confirm_or_raise(
+			cfg      = cfg,
 			message  = yellow('Confirmation of default wallet update'),
 			action   = 'update the default wallet',
 			exit_msg = 'Password not changed' )
 		old_wallet = rename_old_wallet_maybe(silent=True)
-		ss_out.write_to_file( desc='New wallet', outdir=g.data_dir )
+		ss_out.write_to_file( desc='New wallet', outdir=cfg.data_dir )
 		secure_delete( old_wallet )
 	else:
 		old_wallet = rename_old_wallet_maybe(silent=False)
 		ss_out.write_to_file()
 		from .ui import keypress_confirm
-		if keypress_confirm(f'Securely delete old wallet {old_wallet!r}?'):
+		if keypress_confirm( cfg, f'Securely delete old wallet {old_wallet!r}?' ):
 			secure_delete( old_wallet )
-elif invoked_as == 'gen' and not opt.outdir and not opt.stdout:
+elif invoked_as == 'gen' and not cfg.outdir and not cfg.stdout:
 	from .filename import find_file_in_dir
-	if find_file_in_dir( get_wallet_cls('mmgen'), g.data_dir ):
+	if find_file_in_dir( get_wallet_cls('mmgen'), cfg.data_dir ):
 		ss_out.write_to_file()
 	else:
 		from .ui import keypress_confirm
 		if keypress_confirm(
+				cfg,
 				'Make this wallet your default and move it to the data directory?',
 				default_yes = True ):
-			ss_out.write_to_file(outdir=g.data_dir)
+			ss_out.write_to_file(outdir=cfg.data_dir)
 		else:
 			ss_out.write_to_file()
 else:

+ 22 - 16
mmgen/main_xmrwallet.py

@@ -21,7 +21,11 @@ mmgen-xmrwallet: Perform various Monero wallet operations for addresses
                  in an MMGen XMR key-address file
 """
 
-from .common import *
+from collections import namedtuple
+
+import mmgen.opts as opts
+from .globalvars import gc
+from .util import ymsg,die,async_run
 from .xmrwallet import xmrwallet_uarg_info,MoneroWalletOps
 
 opts_data = {
@@ -58,9 +62,9 @@ opts_data = {
 -S, --no-stop-wallet-daemon      Don’t stop the wallet daemon at exit
 -w, --wallet-dir=D               Output or operate on wallets in directory 'D'
                                  instead of the working directory
--H, --wallet-rpc-host=host       Wallet RPC hostname (default: {g.monero_wallet_rpc_host!r})
--U, --wallet-rpc-user=user       Wallet RPC username (default: {g.monero_wallet_rpc_user!r})
--P, --wallet-rpc-password=pass   Wallet RPC password (default: {g.monero_wallet_rpc_password!r})
+-H, --wallet-rpc-host=host       Wallet RPC hostname (default: {cfg.monero_wallet_rpc_host!r})
+-U, --wallet-rpc-user=user       Wallet RPC username (default: {cfg.monero_wallet_rpc_user!r})
+-P, --wallet-rpc-password=pass   Wallet RPC password (default: {cfg.monero_wallet_rpc_password!r})
 """,
 	'notes': """
 
@@ -212,16 +216,18 @@ $ mmgen-xmrwallet --pager txview *XMR*.sigtx
 """
 	},
 	'code': {
-		'options': lambda s: s.format(
+		'options': lambda cfg,s: s.format(
 			D=xmrwallet_uarg_info['daemon'].annot,
 			R=xmrwallet_uarg_info['tx_relay_daemon'].annot,
-			g=g,
+			cfg=cfg,
 			gc=gc,
 		),
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+cmd_args = cfg._args
 
 if len(cmd_args) < 2:
 	opts.usage()
@@ -263,17 +269,17 @@ uo = namedtuple('uopts',[
 
 uargs = ua( op, infile, wallets, spec )
 uopts = uo(
-	opt.daemon or '',
-	opt.tx_relay_daemon or '',
-	opt.restore_height or 0,
-	opt.rescan_blockchain,
-	opt.no_start_wallet_daemon,
-	opt.no_stop_wallet_daemon,
-	opt.no_relay,
-	opt.wallet_dir,
+	cfg.daemon or '',
+	cfg.tx_relay_daemon or '',
+	cfg.restore_height or 0,
+	cfg.rescan_blockchain,
+	cfg.no_start_wallet_daemon,
+	cfg.no_stop_wallet_daemon,
+	cfg.no_relay,
+	cfg.wallet_dir,
 )
 
-m = getattr(MoneroWalletOps,op)(uargs,uopts)
+m = getattr(MoneroWalletOps,op)(cfg,uargs,uopts)
 
 try:
 	if async_run(m.main()):

+ 9 - 8
mmgen/mn_entry.py

@@ -23,7 +23,7 @@ mn_entry.py - Mnemonic user entry methods for the MMGen suite
 import time
 
 from .globalvars import *
-from .util import msg,msg_r,qmsg,fmt,fmt_list,capfirst,die,ascii_lowercase
+from .util import msg,msg_r,fmt,fmt_list,capfirst,die,ascii_lowercase
 from .term import get_char,get_char_raw
 from .color import cyan
 
@@ -232,7 +232,8 @@ class MnemonicEntry(object):
 	_sw = None
 	_usl = None
 
-	def __init__(self):
+	def __init__(self,cfg):
+		self.cfg = cfg
 		self.set_dfl_entry_mode()
 
 	@property
@@ -321,7 +322,7 @@ class MnemonicEntry(object):
 				return em_objs[int(uret)-1]
 			else:
 				msg_r(f'\b {uret!r}: invalid choice ')
-				time.sleep(g.err_disp_timeout)
+				time.sleep(self.cfg.err_disp_timeout)
 				msg_r(erase)
 
 	def get_mnemonic_from_user(self,mn_len,validate=True):
@@ -350,7 +351,7 @@ class MnemonicEntry(object):
 					sw       = self.shortest_word,
 			))
 
-		clear_line = '\n' if g.test_suite else '{r}{s}{r}'.format(r='\r',s=' '*40)
+		clear_line = '\n' if self.cfg.test_suite else '{r}{s}{r}'.format(r='\r',s=' '*40)
 		idx,idxs = 1,[] # initialize idx to a non-None value
 
 		while len(idxs) < mn_len:
@@ -366,7 +367,7 @@ class MnemonicEntry(object):
 
 		if validate:
 			self.bconv.tohex(words)
-			qmsg(
+			self.cfg._util.qmsg(
 				'Mnemonic is valid' if self.has_chksum else
 				'Mnemonic is well-formed (mnemonic format has no checksum to validate)' )
 
@@ -389,7 +390,7 @@ class MnemonicEntry(object):
 		In addition to setting the default entry mode for the current wordlist, checks validity
 		of all user-configured entry modes
 		"""
-		for k,v in g.mnemonic_entry_modes.items():
+		for k,v in self.cfg.mnemonic_entry_modes.items():
 			cls = self.get_cls_by_wordlist(k)
 			if v not in cls.entry_modes:
 				errmsg = """
@@ -422,10 +423,10 @@ class MnemonicEntryMonero(MnemonicEntry):
 	dfl_entry_mode = 'short'
 	has_chksum = True
 
-def mn_entry(wl_id,entry_mode=None):
+def mn_entry(cfg,wl_id,entry_mode=None):
 	if wl_id == 'words':
 		wl_id = 'mmgen'
-	me = MnemonicEntry.get_cls_by_wordlist(wl_id)()
+	me = MnemonicEntry.get_cls_by_wordlist(wl_id)(cfg)
 	import importlib
 	me.bconv = getattr(importlib.import_module(f'mmgen.{me.modname}'),me.modname)(wl_id)
 	me.wl = me.bconv.digits

+ 28 - 12
mmgen/msg.py

@@ -51,6 +51,9 @@ class coin_msg:
 
 	class base(MMGenObject):
 
+		def __init__(self,cfg):
+			self.cfg = cfg
+
 		ext = 'rawmsg.json'
 		signed = False
 		chksum_keys = ('addrlists','message','msghash_type','network')
@@ -85,10 +88,10 @@ class coin_msg:
 			return f'{self.filename_stem}.{coin_msg.signed.ext}'
 
 		def get_proto_from_file(self,filename):
-			data = json.loads(get_data_from_file(filename))
+			data = json.loads(get_data_from_file( self.cfg, filename ))
 			network_id = data['metadata']['network'] if 'metadata' in data else data['network'].lower()
 			coin,network = network_id.split('_')
-			return init_proto( coin=coin, network=network )
+			return init_proto( cfg=self.cfg, coin=coin, network=network )
 
 		def write_to_file(self,outdir=None,ask_overwrite=False):
 			data = {
@@ -98,6 +101,7 @@ class coin_msg:
 			}
 
 			write_data_to_file(
+				cfg           = self.cfg,
 				outfile       = os.path.join(outdir or '',self.filename),
 				data          = json.dumps(data,sort_keys=True,indent=4),
 				desc          = self.desc,
@@ -105,10 +109,15 @@ class coin_msg:
 
 	class new(base):
 
-		def __init__(self,message,addrlists,msghash_type,*args,**kwargs):
+		def __init__(self,cfg,message,addrlists,msghash_type,*args,**kwargs):
+
+			self.cfg = cfg
+
 			msghash_type = msghash_type or self.msg_cls.msghash_types[0]
+
 			if msghash_type not in self.msg_cls.msghash_types:
 				die(2,f'msghash_type {msghash_type!r} not supported for {self.proto.base_proto} protocol')
+
 			self.data = {
 				'network': '{}_{}'.format( self.proto.coin.lower(), self.proto.network ),
 				'addrlists': [MMGenIDRange(self.proto,i) for i in addrlists.split()],
@@ -119,13 +128,16 @@ class coin_msg:
 
 	class completed(base):
 
-		def __init__(self,data,infile,*args,**kwargs):
+		def __init__(self,cfg,data,infile,*args,**kwargs):
+
+			self.cfg = cfg
 
 			if data:
 				self.__dict__ = data
 				return
 
 			self.data = get_data_from_file(
+				cfg    = self.cfg,
 				infile = infile,
 				desc   = self.desc )
 
@@ -210,6 +222,7 @@ class coin_msg:
 
 			async def sign_list(al_in,seed):
 				al = KeyAddrList(
+					cfg         = self.cfg,
 					proto       = self.proto,
 					seed        = seed,
 					addr_idxs   = al_in.idxlist,
@@ -238,11 +251,11 @@ class coin_msg:
 
 			if self.proto.sign_mode == 'daemon':
 				from .rpc import rpc_init
-				self.rpc = await rpc_init(self.proto)
+				self.rpc = await rpc_init(self.cfg,self.proto)
 
 			from .wallet import Wallet
 			from .addrlist import KeyAddrList
-			wallet_seeds = [Wallet(fn=fn).seed for fn in wallet_files]
+			wallet_seeds = [Wallet(cfg=self.cfg,fn=fn).seed for fn in wallet_files]
 			need_sids = remove_dups([al.sid for al in self.addrlists], quiet=True)
 			saved_seeds = list()
 
@@ -302,7 +315,7 @@ class coin_msg:
 
 			if self.proto.sign_mode == 'daemon':
 				from .rpc import rpc_init
-				self.rpc = await rpc_init(self.proto)
+				self.rpc = await rpc_init(self.cfg,self.proto)
 
 			for k,v in sigs.items():
 				ret = await self.do_verify(
@@ -333,10 +346,13 @@ class coin_msg:
 
 	class exported_sigs(signed_online):
 
-		def __init__(self,infile,*args,**kwargs):
+		def __init__(self,cfg,infile,*args,**kwargs):
+
+			self.cfg = cfg
 
 			self.data = json.loads(
 				get_data_from_file(
+					cfg    = self.cfg,
 					infile = infile,
 					desc   = self.desc )
 				)
@@ -348,7 +364,7 @@ class coin_msg:
 				self.data['signatures']
 			)}
 
-def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**kwargs):
+def _get_obj(clsname,cfg,coin=None,network='mainnet',infile=None,data=None,*args,**kwargs):
 
 	assert not args, 'msg:_get_obj(): only keyword args allowed'
 
@@ -359,8 +375,8 @@ def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**k
 
 	proto = (
 		data['proto'] if data else
-		init_proto( coin=coin, network=network ) if coin else
-		coin_msg.base().get_proto_from_file(infile) )
+		init_proto( cfg=cfg, coin=coin, network=network ) if coin else
+		coin_msg.base(cfg=cfg).get_proto_from_file(infile) )
 
 	try:
 		msg_cls = getattr(
@@ -373,7 +389,7 @@ def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**k
 	me.msg_cls = msg_cls
 	me.proto = proto
 
-	me.__init__(infile=infile,data=data,*args,**kwargs)
+	me.__init__(cfg,infile=infile,data=data,*args,**kwargs)
 
 	return me
 

+ 0 - 1
mmgen/objmethods.py

@@ -21,7 +21,6 @@ objmethods: Mixin classes for MMGen data objects
 """
 
 import unicodedata
-from .globalvars import g
 import mmgen.color as color_mod
 
 if 'MMGenObjectDevTools' in __builtins__: # added to builtins by devinit.init_dev()

+ 125 - 121
mmgen/opts.py

@@ -21,17 +21,12 @@ opts: MMGen-specific options processing after generic processing by share.Opts
 """
 import sys,os
 
-from .globalvars import g,gc
+from .globalvars import gc,Config
 from .base_obj import Lockable
 
 import mmgen.share.Opts
 
-class UserOpts(Lockable):
-	_autolock = False
-	_default_to_none = True
-	_set_ok = ('usr_randchars',)
-	_reset_ok = ('quiet','verbose','yes')
-
+class UserOpts: pass
 opt = UserOpts()
 
 def usage():
@@ -54,22 +49,23 @@ def delete_data(opts_data):
 	del mmgen.share.Opts.process_uopts
 	del mmgen.share.Opts.parse_opts
 
-def post_init():
+def post_init(cfg):
 	global opts_data_save,opt_filter_save
-	if opt.help or opt.longhelp:
-		print_help(opt,opts_data_save,opt_filter_save)
+	if cfg.help or cfg.longhelp:
+		print_help(cfg,opts_data_save,opt_filter_save)
 	else:
 		delete_data(opts_data_save)
 		del opts_data_save,opt_filter_save
 
-def print_help(opt,opts_data,opt_filter):
+def print_help(cfg,opts_data,opt_filter):
+
 	if not 'code' in opts_data:
 		opts_data['code'] = {}
 
-	from .protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
+	from .protocol import init_proto_from_cfg
+	proto = init_proto_from_cfg(cfg,need_amt=True)
 
-	if getattr(opt,'longhelp',None):
+	if getattr(cfg,'longhelp',None):
 		opts_data['code']['long_options'] = common_opts_data['code']
 		def remove_unneeded_long_opts():
 			d = opts_data['text']['long_options']
@@ -79,16 +75,16 @@ def print_help(opt,opts_data,opt_filter):
 		remove_unneeded_long_opts()
 
 	from .ui import do_pager
-	do_pager(mmgen.share.Opts.make_help( proto, opt, opts_data, opt_filter ))
+	do_pager(mmgen.share.Opts.make_help( cfg, proto, opts_data, opt_filter ))
 
 	sys.exit(0)
 
 def fmt_opt(o):
 	return '--' + o.replace('_','-')
 
-def die_on_incompatible_opts(incompat_list):
-	for group in incompat_list:
-		bad = [k for k in opt.__dict__ if k in group and getattr(opt,k) != None]
+def die_on_incompatible_opts(cfg):
+	for group in cfg.incompatible_opts:
+		bad = [k for k in cfg.__dict__ if k in group and getattr(cfg,k) != None]
 		if len(bad) > 1:
 			from .util import die
 			die(1,'Conflicting options: {}'.format(', '.join(map(fmt_opt,bad))))
@@ -117,18 +113,14 @@ def opt_preproc_debug(po):
 	for e in d:
 		Msg('    {:<20}: {}'.format(*e))
 
-def opt_postproc_debug():
-	a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None]
-	b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None]
+def opt_postproc_debug(cfg):
+	a = [k for k in dir(cfg) if k[:2] != '__' and getattr(cfg,k) != None]
+	b = [k for k in dir(cfg) if k[:2] != '__' and getattr(cfg,k) == None]
 	from .util import Msg
-	Msg('    Opts after processing:')
-	for k in a:
-		v = getattr(opt,k)
-		Msg('        {:18}: {!r:<6} [{}]'.format(k,v,type(v).__name__))
 	Msg('    Global vars:')
-	for e in [d for d in dir(g) if d[:2] != '__']:
-		Msg('        {:<20}: {}'.format(e, getattr(g,e)))
-	Msg("    Opts set to 'None':")
+	for e in [d for d in dir(cfg) if d[:2] != '__']:
+		Msg('        {:<20}: {}'.format(e, getattr(cfg,e)))
+	Msg("    Global vars set to 'None':")
 	Msg('        {}\n'.format('\n        '.join(b)))
 	Msg('\n=== end opts.py debug ===\n')
 
@@ -157,15 +149,18 @@ def set_for_type(val,refval,desc,invert_bool=False,src=None):
 		type(refval).__name__) )
 
 def override_globals_from_cfg_file(
+		cfg,
 		ucfg,
-		autoset_opts,
+		cfgfile_autoset_opts,
+		cfgfile_auto_typeset_opts,
 		env_globals,
 		need_proto ):
 
 	if need_proto:
 		from .protocol import init_proto
+
 	for d in ucfg.get_lines():
-		if d.name in g.cfg_file_opts:
+		if d.name in cfg.cfg_file_opts:
 			ns = d.name.split('_')
 			if ns[0] in gc.core_coins:
 				if not need_proto:
@@ -174,10 +169,10 @@ def override_globals_from_cfg_file(
 					(ns[2:],ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet','testnet') else
 					(ns[1:],False)
 				)
-				cls = type(init_proto( ns[0], tn, need_amt=True )) # no instance yet, so override _class_ attr
+				cls = type(init_proto( cfg, ns[0], tn, need_amt=True )) # no instance yet, so override _class_ attr
 				attr = '_'.join(nse)
 			else:
-				cls = g                          # g is "singleton" instance, so override _instance_ attr
+				cls = cfg
 				attr = d.name
 			refval = getattr(cls,attr)
 			val = ucfg.parse_value(d.value,refval)
@@ -187,29 +182,31 @@ def override_globals_from_cfg_file(
 			val_conv = set_for_type(val,refval,attr,src=ucfg.fn)
 			if attr not in env_globals:
 				setattr(cls,attr,val_conv)
-		elif d.name in g.autoset_opts:
-			autoset_opts[d.name] = d.value
+		elif d.name in cfg.autoset_opts:
+			cfgfile_autoset_opts[d.name] = d.value
+		elif d.name in cfg.auto_typeset_opts:
+			cfgfile_auto_typeset_opts[d.name] = d.value
 		else:
 			from .util import die
 			die( 'CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}' )
 
-def override_globals_from_env():
+def override_globals_from_env(cfg):
 	for name,val in ((k,v) for k,v in os.environ.items() if k.startswith('MMGEN_')):
 		if name == 'MMGEN_DEBUG_ALL':
 			continue
-		elif name in g.env_opts:
+		elif name in cfg.env_opts:
 			if val: # ignore empty string values; string value of '0' or 'false' sets variable to False
 				disable = name.startswith('MMGEN_DISABLE_')
 				gname = name[(6,14)[disable]:].lower()
-				if hasattr(g,gname):
-					setattr(g,gname,set_for_type(val,getattr(g,gname),name,disable))
+				if hasattr(cfg,gname):
+					setattr(cfg,gname,set_for_type(val,getattr(cfg,gname),name,disable))
 					yield gname
 				else:
 					raise ValueError(f'Name {gname!r} not present in globals')
 		else:
 			raise ValueError(f'{name!r} is not a valid MMGen environment variable')
 
-def show_common_opts_diff():
+def show_common_opts_diff(cfg):
 
 	def common_opts_data_to_list():
 		for l in common_opts_data['text'].splitlines():
@@ -220,16 +217,16 @@ def show_common_opts_diff():
 		from .util import fmt_list
 		return fmt_list(['--'+s.replace('_','-') for s in set_data],fmt='col',indent='   ')
 
-	a = g.common_opts
+	a = cfg.common_opts
 	b = list(common_opts_data_to_list())
 	a_minus_b = [e for e in a if e not in b]
 	b_minus_a = [e for e in b if e not in a]
 	a_and_b   = [e for e in a if e in b]
 
 	from .util import msg
-	msg(f'g.common_opts - common_opts_data:\n   {do_fmt(a_minus_b) if a_minus_b else "None"}\n')
-	msg(f'common_opts_data - g.common_opts (these do not set global var):\n{do_fmt(b_minus_a)}\n')
-	msg(f'common_opts_data ^ g.common_opts (these set global var):\n{do_fmt(a_and_b)}\n')
+	msg(f'cfg.common_opts - common_opts_data:\n   {do_fmt(a_minus_b) if a_minus_b else "None"}\n')
+	msg(f'common_opts_data - cfg.common_opts (these do not set global var):\n{do_fmt(b_minus_a)}\n')
+	msg(f'common_opts_data ^ cfg.common_opts (these set global var):\n{do_fmt(a_and_b)}\n')
 
 	sys.exit(0)
 
@@ -291,8 +288,8 @@ def init(
 	parse_only  = False,
 	parsed_opts = None,
 	need_proto  = True,
-	do_post_init = False,
-	return_parsed = False ):
+	need_amt    = True,
+	do_post_init = False ):
 
 	if opts_data is None:
 		opts_data = opts_data_dfl
@@ -314,7 +311,9 @@ def init(
 	if parse_only and not any(k in po.user_opts for k in ('version','help','longhelp')):
 		return po
 
-	if g.debug_opts:
+	cfg = Config()
+
+	if cfg.debug_opts: # TODO: this does nothing
 		opt_preproc_debug(po)
 
 	# Copy parsed opts to opt, setting values to None if not set by user
@@ -323,8 +322,8 @@ def init(
 			+ po.skipped_opts
 			+ tuple(add_opts or [])
 			+ tuple(init_opts or [])
-			+ g.init_opts
-			+ g.common_opts ):
+			+ cfg.init_opts
+			+ cfg.common_opts ):
 		setattr(opt,o,po.user_opts[o] if o in po.user_opts else None)
 
 	if opt.version:
@@ -333,108 +332,110 @@ def init(
 	if opt.show_hash_presets:
 		_show_hash_presets() # exits
 
-	from .term import init_term
-	init_term()
-
 	from .util import wrap_ripemd160
 	wrap_ripemd160() # ripemd160 used by mmgen_cfg_file()
 
-	# === begin global var initialization === #
+	# === begin Config() initialization === #
+
+	env_globals = tuple(override_globals_from_env(cfg))
 
-	env_globals = tuple(override_globals_from_env())
+	# do this after setting ‘hold_protect_disable’ from env
+	from .term import init_term
+	init_term(cfg)
 
 	# --data-dir overrides computed value of data_dir_root
-	g.data_dir_root_override = opt.data_dir
+	cfg.data_dir_root_override = opt.data_dir
+	if opt.data_dir:
+		del opt.data_dir
 
 	from .fileutil import check_or_create_dir
-	check_or_create_dir(g.data_dir_root)
+	check_or_create_dir(cfg.data_dir_root)
 
 	cfgfile_autoset_opts = {}
+	cfgfile_auto_typeset_opts = {}
 
 	if not opt.skip_cfg_file:
 		from .cfgfile import mmgen_cfg_file
 		# check for changes in system template file - term must be initialized
-		mmgen_cfg_file('sample')
+		mmgen_cfg_file(cfg,'sample')
 		override_globals_from_cfg_file(
-			mmgen_cfg_file('usr'),
+			cfg,
+			mmgen_cfg_file(cfg,'usr'),
 			cfgfile_autoset_opts,
+			cfgfile_auto_typeset_opts,
 			env_globals,
 			need_proto )
 
-	# Set globals from opts, setting type from original global value
-	# Do here, before opts are set from globals below
-	for k in (g.common_opts + g.opt_sets_global):
-		if hasattr(opt,k):
-			val = getattr(opt,k)
-			if val != None and hasattr(g,k):
-				setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
-
-	if g.regtest or g.bob or g.alice or g.carol or gc.prog_name == 'mmgen-regtest':
-		g.network = 'regtest'
-		g.regtest_user = 'bob' if g.bob else 'alice' if g.alice else 'carol' if g.carol else None
+	# Set globals from opts, setting type from original global value if it exists:
+	auto_keys = tuple(cfg.autoset_opts.keys()) + tuple(cfg.auto_typeset_opts.keys())
+	for key,val in opt.__dict__.items():
+		if val is not None and key not in auto_keys:
+			setattr(cfg, key, set_for_type(val,getattr(cfg,key),'--'+key) if hasattr(cfg,key) else val)
+
+	if cfg.regtest or cfg.bob or cfg.alice or cfg.carol or gc.prog_name == 'mmgen-regtest':
+		cfg.network = 'regtest'
+		cfg.regtest_user = 'bob' if cfg.bob else 'alice' if cfg.alice else 'carol' if cfg.carol else None
 	else:
-		g.network = 'testnet' if g.testnet else 'mainnet'
+		cfg.network = 'testnet' if cfg.testnet else 'mainnet'
 
-	g.coin = g.coin.upper() or 'BTC'
-	g.token = g.token.upper() or None
+	cfg.coin = cfg.coin.upper()
+	cfg.token = cfg.token.upper() or None
 
 	# === end global var initialization === #
 
 	"""
-	g.color is finalized, so initialize color
+	cfg.color is finalized, so initialize color
 	"""
-	if g.color: # MMGEN_DISABLE_COLOR sets this to False
+	if cfg.color: # MMGEN_DISABLE_COLOR sets this to False
 		from .color import init_color
-		init_color(num_colors=('auto',256)[bool(g.force_256_color)])
-
-	# Set user opts from globals:
-	# - if opt is unset, set it to global value
-	# - if opt is set, convert its type to that of global value
-	for k in g.global_sets_opt:
-		if hasattr(opt,k) and getattr(opt,k) != None:
-			setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
-		else:
-			setattr(opt,k,getattr(g,k))
+		init_color(num_colors=('auto',256)[bool(cfg.force_256_color)])
 
-	if need_proto:
-		from .protocol import warn_trustlevel
-		warn_trustlevel(g.coin)
-
-	die_on_incompatible_opts(g.incompatible_opts)
+	die_on_incompatible_opts(cfg)
 
-	check_or_create_dir(g.data_dir)
-
-	# Check user-set opts without modifying them
-	check_usr_opts(po.user_opts)
+	check_or_create_dir(cfg.data_dir)
 
 	# Check autoset opts, setting if unset
-	for key in g.autoset_opts:
+	for key in cfg.autoset_opts:
 		if hasattr(opt,key):
+			assert not hasattr(cfg,key), f'{key!r} is in cfg!'
 			if getattr(opt,key) is not None:
-				setattr(opt, key, get_autoset_opt(key,getattr(opt,key),src='cmdline'))
+				setattr(cfg, key, get_autoset_opt(cfg,key,getattr(opt,key),src='cmdline'))
 			elif key in cfgfile_autoset_opts:
-				setattr(opt, key, get_autoset_opt(key,cfgfile_autoset_opts[key],src='cfgfile'))
+				setattr(cfg, key, get_autoset_opt(cfg,key,cfgfile_autoset_opts[key],src='cfgfile'))
 			else:
-				setattr(opt, key, g.autoset_opts[key].choices[0])
+				setattr(cfg, key, cfg.autoset_opts[key].choices[0])
 
-	set_auto_typeset_opts()
+	set_auto_typeset_opts(cfg,cfgfile_auto_typeset_opts)
 
-	if opt.verbose:
-		opt.quiet = None
+	if cfg.debug and gc.prog_name != 'test.py':
+		cfg.verbose = True
 
-	if g.debug and gc.prog_name != 'test.py':
-		opt.verbose,opt.quiet = (True,None)
+	if cfg.verbose:
+		cfg.quiet = False
 
-	if g.debug_opts:
-		opt_postproc_debug()
+	if cfg.debug_opts:
+		opt_postproc_debug(cfg)
 
-	g.lock()
-	opt.lock()
+	# Check user-set opts without modifying them
+	check_usr_opts(cfg,po.user_opts)
+
+	from .util import Util
+	cfg._util = Util(cfg)
+	cfg._uopts = po.user_opts
+	cfg._args = po.cmd_args
+
+	cfg.lock()
+
+	if need_proto:
+		from .protocol import warn_trustlevel,init_proto_from_cfg
+		warn_trustlevel(cfg)
+		# requires the default-to-none behavior, so do after the lock:
+		cfg._proto = init_proto_from_cfg(cfg,need_amt=need_amt)
 
-	# print help screen only after globals and opts initialized and locked:
-	if opt.help or opt.longhelp:
+	# print help screen only after globals initialized and locked:
+	if cfg.help or cfg.longhelp:
 		if not do_post_init:
-			print_help(opt,opts_data,opt_filter) # exits
+			print_help(cfg,opts_data,opt_filter) # exits
 
 	if do_post_init:
 		global opts_data_save,opt_filter_save
@@ -443,9 +444,9 @@ def init(
 	else:
 		delete_data(opts_data)
 
-	return po if return_parsed else po.cmd_args
+	return cfg
 
-def check_usr_opts(usr_opts): # Raises an exception if any check fails
+def check_usr_opts(cfg,usr_opts): # Raises an exception if any check fails
 
 	def opt_splits(val,sep,n,desc):
 		sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else repr(sep)
@@ -577,8 +578,8 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		if val == 0:
 			return
 		opt_is_int(val,desc)
-		opt_compares(val,'>=',g.min_urandchars,desc)
-		opt_compares(val,'<=',g.max_urandchars,desc)
+		opt_compares(val,'>=',cfg.min_urandchars,desc)
+		opt_compares(val,'<=',cfg.max_urandchars,desc)
 
 	def chk_tx_fee(key,val,desc):
 		pass
@@ -605,7 +606,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 #	def chk_bob(key,val,desc):
 #		from .proto.btc.regtest import MMGenRegtest
 #		try:
-#			os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
+#			os.stat(os.path.join(MMGenRegtest(cfg,cfg.coin).d.datadir,'regtest','debug.log'))
 #		except:
 #			die( 'UserOptError',
 #				'Regtest (Bob and Alice) mode not set up yet.  ' +
@@ -634,10 +635,10 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 	cfuncs = { k:v for k,v in locals().items() if k.startswith('chk_') }
 
 	for key in usr_opts:
-		val = getattr(opt,key)
+		val = getattr(cfg,key)
 		desc = f'parameter for {fmt_opt(key)!r} option'
 
-		if key in g.infile_opts:
+		if key in cfg.infile_opts:
 			from .fileutil import check_infile
 			check_infile(val) # file exists and is readable - dies on error
 		elif key == 'outdir':
@@ -645,19 +646,22 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 			check_outdir(val) # dies on error
 		elif 'chk_'+key in cfuncs:
 			cfuncs['chk_'+key](key,val,desc)
-		elif g.debug:
+		elif cfg.debug:
 			Msg(f'check_usr_opts(): No test for opt {key!r}')
 
-def set_auto_typeset_opts():
+def set_auto_typeset_opts(cfg,cfgfile_auto_typeset_opts):
 
 	def do_set(key,val,ref_type):
-		setattr(opt,key,None if val is None else ref_type(val))
+		assert not hasattr(cfg,key), f'{key!r} is in cfg!'
+		setattr(cfg,key,None if val is None else ref_type(val))
 
-	for key,ref_type in g.auto_typeset_opts.items():
+	for key,ref_type in cfg.auto_typeset_opts.items():
 		if hasattr(opt,key):
 			do_set(key, getattr(opt,key), ref_type)
+		elif key in cfgfile_auto_typeset_opts:
+			do_set(key, cfgfile_auto_typeset_opts[key], ref_type)
 
-def get_autoset_opt(key,val,src):
+def get_autoset_opt(cfg,key,val,src):
 
 	def die_on_err(desc):
 		from .util import fmt_list,die
@@ -687,6 +691,6 @@ def get_autoset_opt(key,val,src):
 			else:
 				die_on_err('unique substring of')
 
-	data = g.autoset_opts[key]
+	data = cfg.autoset_opts[key]
 
 	return getattr(opt_type,data.type)()

+ 6 - 4
mmgen/passwdlist.py

@@ -22,7 +22,6 @@ passwdlist: Password list class for the MMGen suite
 
 from collections import namedtuple
 
-from .globalvars import g
 from .util import ymsg,is_int,die
 from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString,TwComment
 from .key import PrivKey
@@ -66,6 +65,7 @@ class PasswordList(AddrList):
 
 	def __init__(
 			self,
+			cfg,
 			proto,
 			infile          = None,
 			seed            = None,
@@ -76,9 +76,10 @@ class PasswordList(AddrList):
 			chk_params_only = False,
 			skip_chksum_msg = False ):
 
+		self.cfg = cfg
 		self.proto = proto # proto is ignored
 
-		if not g.debug_addrlist:
+		if not cfg.debug_addrlist:
 			self.dmsg_sc = self.noop
 
 		if infile:
@@ -167,7 +168,7 @@ class PasswordList(AddrList):
 			from .xmrseed import xmrseed
 			from .protocol import init_proto
 			self.xmrseed = xmrseed()
-			self.xmrproto = init_proto('xmr')
+			self.xmrproto = init_proto( self.cfg, 'xmr' )
 			pw_bytes = xmrseed().seedlen_map_rev[self.pw_len]
 			try:
 				good_pw_len = xmrseed().seedlen_map[seed.byte_len]
@@ -193,6 +194,7 @@ class PasswordList(AddrList):
 		if pf in ('bip39','hex') and pw_bytes < seed.byte_len:
 			from .ui import keypress_confirm
 			if not keypress_confirm(
+					self.cfg,
 					f'WARNING: requested {self.pw_info[pf].desc} length has less entropy ' +
 					'than underlying seed!\nIs this what you want?',
 					default_yes = True ):
@@ -240,4 +242,4 @@ class PasswordList(AddrList):
 
 		self.dmsg_sc('str',scramble_key)
 		from .crypto import Crypto
-		return Crypto().scramble_seed(seed,scramble_key.encode())
+		return Crypto(self.cfg).scramble_seed(seed,scramble_key.encode())

+ 1 - 2
mmgen/proto/btc/addrdata.py

@@ -13,7 +13,6 @@ proto.btc.addrdata: Bitcoin base protocol addrdata classes
 """
 
 from ...addrdata import TwAddrData
-from ...util import vmsg
 
 class BitcoinTwAddrData(TwAddrData):
 
@@ -27,7 +26,7 @@ class BitcoinTwAddrData(TwAddrData):
 	}
 
 	async def get_tw_data(self,twctl=None):
-		vmsg('Getting address data from tracking wallet')
+		self.cfg._util.vmsg('Getting address data from tracking wallet')
 		c = self.rpc
 		if 'label_api' in c.caps:
 			accts = await c.call('listlabels')

+ 4 - 5
mmgen/proto/btc/daemon.py

@@ -14,8 +14,7 @@ proto.btc.daemon: Bitcoin base protocol daemon classes
 
 import os
 
-from ...globalvars import g,gc
-from ...opts import opt
+from ...globalvars import gc
 from ...util import list_gen
 from ...daemon import CoinDaemon,_nw,_dd
 
@@ -36,7 +35,7 @@ class bitcoin_core_daemon(CoinDaemon):
 
 	def init_datadir(self):
 		if self.network == 'regtest' and not self.test_suite:
-			return os.path.join( g.data_dir_root, 'regtest', g.coin.lower() )
+			return os.path.join( self.cfg.data_dir_root, 'regtest', self.cfg.coin.lower() )
 		else:
 			return super().init_datadir()
 
@@ -120,7 +119,7 @@ class bitcoin_core_daemon(CoinDaemon):
 			return ('importaddress',coinaddr,lbl,False)
 
 	def estimatefee_args(self,rpc):
-		return (opt.fee_estimate_confs,)
+		return (self.cfg.fee_estimate_confs,)
 
 	def sigfail_errmsg(self,e):
 		return e.args[0]
@@ -143,7 +142,7 @@ class bitcoin_cash_node_daemon(bitcoin_core_daemon):
 		return ('importaddress',coinaddr,lbl,False)
 
 	def estimatefee_args(self,rpc):
-		return () if rpc.daemon_version >= 190100 else (opt.fee_estimate_confs,)
+		return () if rpc.daemon_version >= 190100 else (self.cfg.fee_estimate_confs,)
 
 	def sigfail_errmsg(self,e):
 		return (

+ 3 - 4
mmgen/proto/btc/misc.py

@@ -12,10 +12,9 @@
 proto.btc.misc: miscellaneous functions for Bitcoin base protocol
 """
 
-from ...globalvars import g
 from ...util import msg,msg_r
 
-async def scantxoutset(rpc,descriptor_list):
+async def scantxoutset(cfg,rpc,descriptor_list):
 
 	import asyncio
 
@@ -28,8 +27,8 @@ async def scantxoutset(rpc,descriptor_list):
 
 	async def do_status():
 
-		CR = '\n' if g.test_suite else '\r'
-		sleep_secs = 0.1 if g.test_suite else 2
+		CR = '\n' if cfg.test_suite else '\r'
+		sleep_secs = 0.1 if cfg.test_suite else 2
 		m = f'{CR}Scanning UTXO set: '
 		msg_r(m + '0% completed ')
 

+ 17 - 17
mmgen/proto/btc/regtest.py

@@ -21,19 +21,18 @@ proto.btc.regtest: Coin daemon regression test mode setup and operations
 """
 
 import os,shutil,json
-from ...opts import opt
-from ...globalvars import g
 from ...util import msg,gmsg,die,capfirst,suf
 from ...protocol import init_proto
 from ...rpc import rpc_init,json_encoder
 from ...objmethods import MMGenObject
 
-def create_data_dir(data_dir):
+def create_data_dir(cfg,data_dir):
 	try: os.stat(os.path.join(data_dir,'regtest'))
 	except: pass
 	else:
 		from ...ui import keypress_confirm
 		if keypress_confirm(
+				cfg,
 				f'Delete your existing MMGen regtest setup at {data_dir!r} and create a new one?'):
 			shutil.rmtree(data_dir)
 		else:
@@ -69,18 +68,19 @@ class MMGenRegtest(MMGenObject):
 		'bch': 'n2fxhNx27GhHAWQhyuZ5REcBNrJqCJsJ12',
 	}
 
-	def __init__(self,coin):
+	def __init__(self,cfg,coin):
+		self.cfg = cfg
 		self.coin = coin.lower()
 		assert self.coin in self.coins, f'{coin!r}: invalid coin for regtest'
 
 		from ...daemon import CoinDaemon
-		self.proto = init_proto(self.coin,regtest=True,need_amt=True)
-		self.d = CoinDaemon(self.coin+'_rt',test_suite=g.test_suite)
+		self.proto = init_proto( cfg, self.coin, regtest=True, need_amt=True )
+		self.d = CoinDaemon(cfg,self.coin+'_rt',test_suite=cfg.test_suite)
 		self.miner_addr = self.miner_addrs[self.coin]
 
 	def create_hdseed_wif(self):
 		from ...tool.api import tool_api
-		t = tool_api()
+		t = tool_api(self.cfg)
 		t.init_coin(self.proto.coin,self.proto.network)
 		t.addrtype = 'compressed' if self.proto.coin == 'BCH' else 'bech32'
 		return t.hex2wif(self.hdseed)
@@ -114,13 +114,13 @@ class MMGenRegtest(MMGenObject):
 		if self.d.state != 'stopped':
 			await self.rpc_call('stop')
 
-		create_data_dir(self.d.datadir)
+		create_data_dir( self.cfg, self.d.datadir )
 
 		gmsg(f'Starting {self.coin.upper()} regtest setup')
 
 		self.d.start(silent=True)
 
-		rpc = await rpc_init(self.proto,backend=None,daemon=self.d)
+		rpc = await rpc_init(self.cfg,self.proto,backend=None,daemon=self.d)
 		for user in ('miner','bob','alice'):
 			gmsg(f'Creating {capfirst(user)}’s tracking wallet')
 			await rpc.icall(
@@ -149,7 +149,7 @@ class MMGenRegtest(MMGenObject):
 
 		gmsg('Setup complete')
 
-		if opt.setup_no_stop_daemon:
+		if self.cfg.setup_no_stop_daemon:
 			msg('Leaving regtest daemon running')
 		else:
 			msg('Stopping regtest daemon')
@@ -169,20 +169,20 @@ class MMGenRegtest(MMGenObject):
 	async def rpc_call(self,*args,wallet=None,start_daemon=True):
 		if start_daemon and self.d.state == 'stopped':
 			await self.start_daemon()
-		rpc = await rpc_init(self.proto,backend=None,daemon=self.d)
+		rpc = await rpc_init(self.cfg,self.proto,backend=None,daemon=self.d)
 		return await rpc.call(*args,wallet=wallet)
 
 	async def start(self):
 		if self.d.state == 'stopped':
 			await self.start_daemon(silent=False)
 		else:
-			msg(f'{g.coin} regtest daemon already started')
+			msg(f'{self.cfg.coin} regtest daemon already started')
 
 	async def stop(self):
 		if self.d.state == 'stopped':
-			msg(f'{g.coin} regtest daemon already stopped')
+			msg(f'{self.cfg.coin} regtest daemon already stopped')
 		else:
-			msg(f'Stopping {g.coin} regtest daemon')
+			msg(f'Stopping {self.cfg.coin} regtest daemon')
 			self.d.stop(silent=True)
 
 	def state(self):
@@ -222,13 +222,13 @@ class MMGenRegtest(MMGenObject):
 
 	async def fork(self,coin): # currently disabled
 
-		proto = init_proto(coin,False)
+		proto = init_proto( self.cfg, coin, False )
 		if not [f for f in proto.forks if f[2] == proto.coin.lower() and f[3] == True]:
 			die(1,f'Coin {proto.coin} is not a replayable fork of coin {coin}')
 
 		gmsg(f'Creating fork from coin {coin} to coin {proto.coin}')
 
-		source_rt = MMGenRegtest(coin)
+		source_rt = MMGenRegtest( self.cfg, coin )
 
 		try:
 			os.stat(source_rt.d.datadir)
@@ -246,7 +246,7 @@ class MMGenRegtest(MMGenObject):
 		try: os.makedirs(self.d.datadir)
 		except: pass
 
-		create_data_dir(self.d.datadir)
+		create_data_dir( self.cfg, self.d.datadir )
 		os.rmdir(self.d.datadir)
 		shutil.copytree(source_data_dir,self.d.datadir,symlinks=True)
 		await self.start_daemon(reindex=True)

+ 16 - 16
mmgen/proto/btc/rpc.py

@@ -14,9 +14,8 @@ proto.btc.rpc: Bitcoin base protocol RPC client class
 
 import os
 
-from ...globalvars import g
 from ...base_obj import AsyncInit
-from ...util import ymsg,vmsg,die,fmt
+from ...util import ymsg,die,fmt
 from ...fileutil import get_lines_from_file
 from ...rpc import RPCClient,auth_data
 
@@ -105,6 +104,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 
 	async def __init__(
 			self,
+			cfg,
 			proto,
 			daemon,
 			backend,
@@ -115,7 +115,8 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 		self.call_sigs = getattr(CallSigs,daemon.id,None)
 
 		super().__init__(
-			host = 'localhost' if g.test_suite else (g.rpc_host or 'localhost'),
+			cfg  = cfg,
+			host = 'localhost' if cfg.test_suite else (cfg.rpc_host or 'localhost'),
 			port = daemon.rpc_port )
 
 		self.set_auth()
@@ -182,15 +183,15 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 			await self.check_or_create_daemon_wallet()
 
 		# for regtest, wallet path must remain '/' until Carol’s user wallet has been created
-		if g.regtest_user:
-			self.wallet_path = f'/wallet/{g.regtest_user}'
+		if cfg.regtest_user:
+			self.wallet_path = f'/wallet/{cfg.regtest_user}'
 
 	def set_auth(self):
 		"""
 		MMGen's credentials override coin daemon's
 		"""
-		if g.rpc_user:
-			user,passwd = (g.rpc_user,g.rpc_password)
+		if self.cfg.rpc_user:
+			user,passwd = (self.cfg.rpc_user,self.cfg.rpc_password)
 		else:
 			user,passwd = self.get_daemon_cfg_options(('rpcuser','rpcpassword')).values()
 
@@ -221,7 +222,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 		is created, False otherwise
 		"""
 
-		if called or (self.chain == 'regtest' and g.regtest_user != 'carol'):
+		if called or (self.chain == 'regtest' and self.cfg.regtest_user != 'carol'):
 			return False
 
 		twname = self.daemon.tracking_wallet_name
@@ -230,7 +231,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 		m = f'Please fix your {self.daemon.desc} wallet installation or cmdline options'
 		ret = False
 
-		if g.carol:
+		if self.cfg.carol:
 			if 'carol' in loaded_wnames:
 				ret = True
 			elif wallet_create:
@@ -266,17 +267,16 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 	def get_daemon_cfg_fn(self):
 		# Use dirname() to remove 'bob' or 'alice' component
 		return os.path.join(
-			(os.path.dirname(g.data_dir) if self.proto.regtest else self.daemon.datadir),
+			(os.path.dirname(self.cfg.data_dir) if self.proto.regtest else self.daemon.datadir),
 			self.daemon.cfg_file )
 
 	def get_daemon_cfg_options(self,req_keys):
 
 		fn = self.get_daemon_cfg_fn()
-		from ...opts import opt
 		try:
-			lines = get_lines_from_file(fn,'daemon config file',silent=not opt.verbose)
+			lines = get_lines_from_file( self.cfg, fn, 'daemon config file', silent=not self.cfg.verbose )
 		except:
-			vmsg(f'Warning: {fn!r} does not exist or is unreadable')
+			self.cfg._util.vmsg(f'Warning: {fn!r} does not exist or is unreadable')
 			return dict((k,None) for k in req_keys)
 
 		def gen():
@@ -293,7 +293,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 
 	def get_daemon_auth_cookie(self):
 		fn = self.daemon.auth_cookie_fn
-		return get_lines_from_file(fn,'cookie',quiet=True)[0] if os.access(fn,os.R_OK) else ''
+		return get_lines_from_file( self.cfg, fn, 'cookie', quiet=True )[0] if os.access(fn,os.R_OK) else ''
 
 	def info(self,info_id):
 
@@ -302,7 +302,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 			if 'deployment_info' in self.caps:
 				return (
 					self.cached['deploymentinfo']['deployments']['segwit']['active']
-					or ( g.test_suite and not self.chain == 'regtest' )
+					or ( self.cfg.test_suite and not self.chain == 'regtest' )
 				)
 
 			d = self.cached['blockchaininfo']
@@ -319,7 +319,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 			except:
 				pass
 
-			if g.test_suite and not self.chain == 'regtest':
+			if self.cfg.test_suite and not self.chain == 'regtest':
 				return True
 
 			return False

+ 5 - 6
mmgen/proto/btc/tw/ctl.py

@@ -12,9 +12,8 @@
 proto.btc.tw.ctl: Bitcoin base protocol tracking wallet control class
 """
 
-from ....globalvars import g
 from ....tw.ctl import TwCtl,write_mode
-from ....util import msg,msg_r,rmsg,vmsg,die,suf,fmt_list
+from ....util import msg,msg_r,rmsg,die,suf,fmt_list
 
 class BitcoinTwCtl(TwCtl):
 
@@ -54,7 +53,7 @@ class BitcoinTwCtl(TwCtl):
 
 		start = start or 0
 		endless = stop == None
-		CR = '\n' if g.test_suite else '\r'
+		CR = '\n' if self.cfg.test_suite else '\r'
 
 		if not ( start >= 0 and (stop if stop is not None else start) >= start ):
 			die(1,f'{start} {stop}: invalid range')
@@ -101,7 +100,7 @@ class BitcoinTwCtl(TwCtl):
 	async def rescan_addresses(self,coin_addrs):
 
 		from ..misc import scantxoutset
-		res = await scantxoutset( self.rpc, [f'addr({addr})' for addr in coin_addrs] )
+		res = await scantxoutset( self.cfg, self.rpc, [f'addr({addr})' for addr in coin_addrs] )
 
 		if not res['success']:
 			msg('UTXO scanning failed or was interrupted')
@@ -113,8 +112,8 @@ class BitcoinTwCtl(TwCtl):
 				suf(res['unspents']),
 				len(blocks),
 				suf(blocks) ))
-			vmsg(f'Blocks to rescan: {fmt_list(blocks,fmt="bare")}')
-			CR = '\n' if g.test_suite else '\r'
+			self.cfg._util.vmsg(f'Blocks to rescan: {fmt_list(blocks,fmt="bare")}')
+			CR = '\n' if self.cfg.test_suite else '\r'
 			for n,block in enumerate(blocks):
 				msg_r(f'{CR}Rescanning block: {block} ({n+1}/{len(blocks)})')
 				# httplib seems to require fresh connection here, so specify timeout

+ 2 - 1
mmgen/proto/btc/tw/json.py

@@ -81,6 +81,7 @@ class BitcoinTwJSON(TwJSON):
 				if self.prune:
 					from .prune import TwAddressesPrune
 					self._addrlist = al = await TwAddressesPrune(
+						self.cfg,
 						self.proto,
 						get_data  = True,
 						warn_used = self.warn_used )
@@ -88,7 +89,7 @@ class BitcoinTwJSON(TwJSON):
 					self.pruned = al.do_prune()
 				else:
 					from .addresses import TwAddresses
-					self._addrlist = await TwAddresses(self.proto,get_data=True)
+					self._addrlist = await TwAddresses(self.cfg,self.proto,get_data=True)
 			return self._addrlist
 
 		async def get_entries(self): # TODO: include 'received' field

+ 3 - 4
mmgen/proto/btc/tw/txhistory.py

@@ -13,7 +13,6 @@ proto.btc.tw.txhistory: Bitcoin base protocol tracking wallet transaction histor
 """
 
 from collections import namedtuple
-from ....globalvars import g
 from ....tw.txhistory import TwTxHistory
 from ....tw.shared import get_tw_label,TwMMGenID
 from ....addr import CoinAddr
@@ -291,7 +290,7 @@ Filters/Actions: show [u]nconfirmed, [q]uit menu, r[e]draw:
 
 		data = list(gen_parsed_data())
 
-		if g.debug_tw:
+		if self.cfg.debug_tw:
 			import json
 			from ....rpc import json_encoder
 			def do_json_dump(*data):
@@ -328,7 +327,7 @@ Filters/Actions: show [u]nconfirmed, [q]uit menu, r[e]draw:
 			for tx in _wallet_txs:
 				tx['decoded'] = next(_decoded_txs)
 
-		if g.debug_tw:
+		if self.cfg.debug_tw:
 			do_json_dump((_wallet_txs, 'wallet-txs'),)
 
 		_wip = namedtuple('prevout',['txid','vout'])
@@ -353,7 +352,7 @@ Filters/Actions: show [u]nconfirmed, [q]uit menu, r[e]draw:
 		for d in txdata:
 			d['prevout_txs'] = [_prevout_txs_dict[txid] for txid in {i.txid for i in d['prevouts']} ]
 
-		if g.debug_tw:
+		if self.cfg.debug_tw:
 			do_json_dump(
 				(rpc_data,     'txhist-rpc'),
 				(data,         'txhist'),

+ 3 - 4
mmgen/proto/btc/tx/base.py

@@ -15,9 +15,8 @@ proto.btc.tx.base: Bitcoin base transaction class
 from collections import namedtuple
 
 import mmgen.tx.base as TxBase
-from ....opts import opt
 from ....obj import MMGenObject,MMGenList,HexStr
-from ....util import msg,dmsg,make_chksum_6,die,pp_fmt
+from ....util import msg,make_chksum_6,die,pp_fmt
 
 def addr2scriptPubKey(proto,addr):
 
@@ -247,12 +246,12 @@ class Base(TxBase.Base):
 
 		ret = (old_size * 3 + new_size) // 4
 
-		dmsg(
+		self.cfg._util.dmsg(
 			'\nData from estimate_size():\n' +
 			f'  inputs size: {isize}, outputs size: {osize}, witness size: {wsize}\n' +
 			f'  size: {new_size}, vsize: {ret}, old_size: {old_size}' )
 
-		return int(ret * (opt.vsize_adj or 1))
+		return int(ret * (self.cfg.vsize_adj or 1))
 
 	# convert absolute CoinAmt fee to sat/byte using estimated size
 	def fee_abs2rel(self,abs_fee,to_unit='satoshi'):

+ 9 - 10
mmgen/proto/btc/tx/new.py

@@ -14,9 +14,8 @@ proto.btc.tx.new: Bitcoin new transaction class
 
 import mmgen.tx.new as TxBase
 from .base import Base
-from ....opts import opt
 from ....obj import HexStr,MMGenTxID
-from ....util import msg,dmsg,vmsg,make_chksum_6,die
+from ....util import msg,make_chksum_6,die
 
 class New(Base,TxBase.New):
 	usr_fee_prompt = 'Enter transaction fee: '
@@ -27,15 +26,15 @@ class New(Base,TxBase.New):
 	def relay_fee(self):
 		kb_fee = self.proto.coin_amt(self.rpc.cached['networkinfo']['relayfee'])
 		ret = kb_fee * self.estimate_size() / 1024
-		vmsg('Relay fee: {} {c}/kB, for transaction: {} {c}'.format(kb_fee,ret,c=self.coin))
+		self.cfg._util.vmsg('Relay fee: {} {c}/kB, for transaction: {} {c}'.format(kb_fee,ret,c=self.coin))
 		return ret
 
 	async def get_rel_fee_from_network(self):
 		try:
 			ret = await self.rpc.call(
 				'estimatesmartfee',
-				opt.fee_estimate_confs,
-				opt.fee_estimate_mode.upper() )
+				self.cfg.fee_estimate_confs,
+				self.cfg.fee_estimate_mode.upper() )
 			fee_per_kb = ret['feerate'] if 'feerate' in ret else -2
 			fe_type = 'estimatesmartfee'
 		except:
@@ -57,13 +56,13 @@ class New(Base,TxBase.New):
 		from decimal import Decimal
 		tx_size = self.estimate_size()
 		ret = self.proto.coin_amt(
-			fee_per_kb * Decimal(opt.fee_adjust) * tx_size / 1024,
+			fee_per_kb * Decimal(self.cfg.fee_adjust) * tx_size / 1024,
 			from_decimal = True )
-		if opt.verbose:
+		if self.cfg.verbose:
 			msg(fmt(f"""
-				{fe_type.upper()} fee for {opt.fee_estimate_confs} confirmations: {fee_per_kb} {self.coin}/kB
+				{fe_type.upper()} fee for {self.cfg.fee_estimate_confs} confirmations: {fee_per_kb} {self.coin}/kB
 				TX size (estimated): {tx_size} bytes
-				Fee adjustment factor: {opt.fee_adjust:.2f}
+				Fee adjustment factor: {self.cfg.fee_adjust:.2f}
 				Absolute fee (fee_per_kb * adj_factor * tx_size / 1024): {ret} {self.coin}
 			""").strip())
 		return ret
@@ -114,7 +113,7 @@ class New(Base,TxBase.New):
 		if not bump:
 			self.inputs.sort_bip69()
 			# Set all sequence numbers to the same value, in conformity with the behavior of most modern wallets:
-			seqnum_val = self.proto.max_int - (2 if opt.rbf else 1 if locktime else 0)
+			seqnum_val = self.proto.max_int - (2 if self.cfg.rbf else 1 if locktime else 0)
 			for i in self.inputs:
 				i.sequence = seqnum_val
 

+ 3 - 4
mmgen/proto/btc/tx/online.py

@@ -14,7 +14,6 @@ proto.btc.tx.online: Bitcoin online signed transaction class
 
 import mmgen.tx.online as TxBase
 from .signed import Signed
-from ....globalvars import *
 from ....util import msg,ymsg,rmsg
 
 class OnlineSigned(Signed,TxBase.OnlineSigned):
@@ -23,7 +22,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 
 		self.check_correct_chain()
 
-		if not g.bogus_send:
+		if not self.cfg.bogus_send:
 			if self.has_segwit_outputs() and not self.rpc.info('segwit_is_active'):
 				die(2,'Transaction has Segwit outputs, but this blockchain does not support Segwit'
 						+ ' at the current height')
@@ -40,7 +39,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 		if prompt_user:
 			self.confirm_send()
 
-		if g.bogus_send:
+		if self.cfg.bogus_send:
 			ret = None
 		else:
 			try:
@@ -67,7 +66,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 				sys.exit(1)
 			return False
 		else:
-			if g.bogus_send:
+			if self.cfg.bogus_send:
 				m = 'BOGUS transaction NOT sent: {}'
 			else:
 				m = 'Transaction sent: {}'

+ 2 - 2
mmgen/proto/btc/tx/signed.py

@@ -14,7 +14,7 @@ proto.btc.tx.signed: Bitcoin signed transaction class
 
 import mmgen.tx.signed as TxBase
 from .completed import Completed
-from ....util import fmt,vmsg,die
+from ....util import fmt,die
 
 class Signed(Completed,TxBase.Signed):
 
@@ -22,7 +22,7 @@ class Signed(Completed,TxBase.Signed):
 		est_vsize = self.estimate_size()
 		d = tx_decoded
 		vsize = d['vsize'] if 'vsize' in d else d['size']
-		vmsg(f'\nVsize: {vsize} (true) {est_vsize} (estimated)')
+		self.cfg._util.vmsg(f'\nVsize: {vsize} (true) {est_vsize} (estimated)')
 		ratio = float(est_vsize) / vsize
 		if not (0.95 < ratio < 1.05): # allow for 5% error
 			die( 'BadTxSizeEstimate', fmt(f"""

+ 2 - 3
mmgen/proto/btc/tx/status.py

@@ -15,7 +15,6 @@ proto.btc.tx.status: Bitcoin transaction status class
 import time
 
 import mmgen.tx.status as TxBase
-from ....opts import opt
 from ....util import msg,suf,die,secs_to_dhms
 
 class Status(TxBase.Status):
@@ -82,7 +81,7 @@ class Status(TxBase.Status):
 					verbose           = False )
 				rep = ('' if d.get('bip125-replaceable') == 'yes' else 'NOT ') + 'replaceable'
 				t = d['timereceived']
-				if opt.quiet:
+				if tx.cfg.quiet:
 					msg('Transaction is in mempool')
 				else:
 					msg(f'TX status: in mempool, {rep}')
@@ -101,7 +100,7 @@ class Status(TxBase.Status):
 					f'has {r.replacing_confs} confirmation{suf(r.replacing_confs)}'
 				if r.replacing_confs else
 					'is in mempool' ) )
-			if not opt.quiet:
+			if not tx.cfg.quiet:
 				msg('Replacing transactions:')
 				d = []
 				for txid in r.replacing_txs:

+ 7 - 8
mmgen/proto/btc/tx/unsigned.py

@@ -14,9 +14,8 @@ proto.btc.tx.unsigned: Bitcoin unsigned transaction class
 
 import mmgen.tx.unsigned as TxBase
 from .completed import Completed
-from ....globalvars import *
 from ....obj import HexStr,CoinTxID,MMGenDict
-from ....util import msg,msg_r,ymsg,qmsg,suf
+from ....util import msg,msg_r,ymsg,suf
 
 class Unsigned(Completed,TxBase.Unsigned):
 	desc = 'unsigned transaction'
@@ -34,12 +33,12 @@ class Unsigned(Completed,TxBase.Unsigned):
 
 		self.check_pubkey_scripts()
 
-		qmsg(f'Passing {len(keys)} key{suf(keys)} to {self.rpc.daemon.exec_fn}')
+		self.cfg._util.qmsg(f'Passing {len(keys)} key{suf(keys)} to {self.rpc.daemon.exec_fn}')
 
 		if self.has_segwit_inputs():
 			from ....addrgen import KeyGenerator,AddrGenerator
-			kg = KeyGenerator(self.proto,'std')
-			ag = AddrGenerator(self.proto,'segwit')
+			kg = KeyGenerator( self.cfg, self.proto, 'std' )
+			ag = AddrGenerator( self.cfg, self.proto, 'segwit' )
 			keydict = MMGenDict([(d.addr,d.sec) for d in keys])
 
 		sig_data = []
@@ -68,7 +67,7 @@ class Unsigned(Completed,TxBase.Unsigned):
 
 		try:
 			self.update_serialized(ret['hex'])
-			new = await SignedTX(data=self.__dict__)
+			new = await SignedTX(cfg=self.cfg,data=self.__dict__)
 			tx_decoded = await self.rpc.call( 'decoderawtransaction', ret['hex'] )
 			new.compare_size_and_estimated_size(tx_decoded)
 			new.coin_txid = CoinTxID(self.deserialized.txid)
@@ -78,7 +77,7 @@ class Unsigned(Completed,TxBase.Unsigned):
 			return new
 		except Exception as e:
 			ymsg(f'\n{e.args[0]}')
-			if g.exec_wrapper:
-				import traceback
+			if self.cfg.exec_wrapper:
+				import sys,traceback
 				ymsg( '\n' + ''.join(traceback.format_exception(*sys.exc_info())) )
 			return False

+ 2 - 3
mmgen/proto/eth/addrdata.py

@@ -33,9 +33,8 @@ class EthereumTwAddrData(TwAddrData):
 
 	async def get_tw_data(self,twctl=None):
 		from ...tw.ctl import TwCtl
-		from ...util import vmsg
-		vmsg('Getting address data from tracking wallet')
-		twctl = (twctl or await TwCtl(self.proto)).mmid_ordered_dict
+		self.cfg._util.vmsg('Getting address data from tracking wallet')
+		twctl = (twctl or await TwCtl(self.cfg,self.proto)).mmid_ordered_dict
 		# emulate the output of RPC 'listaccounts' and 'getaddressesbyaccount'
 		return [(mmid+' '+d['comment'],[d['addr']]) for mmid,d in list(twctl.items())]
 

+ 9 - 8
mmgen/proto/eth/contract.py

@@ -25,7 +25,6 @@ from . import rlp
 
 from . import erigon_sleep
 from ...util import msg,pp_msg
-from ...globalvars import g
 from ...base_obj import AsyncInit
 from ...obj import MMGenObject,CoinTxID
 from ...addr import CoinAddr,TokenAddr
@@ -46,7 +45,7 @@ class TokenCommon(MMGenObject):
 
 	async def do_call(self,method_sig,method_args='',toUnit=False):
 		data = self.create_method_id(method_sig) + method_args
-		if g.debug:
+		if self.cfg.debug:
 			msg('ETH_CALL {}:  {}'.format(
 				method_sig,
 				'\n  '.join(parse_abi(data)) ))
@@ -121,7 +120,7 @@ class TokenCommon(MMGenObject):
 		if tx.sender.hex() != from_addr:
 			die(3,f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!')
 
-		if g.debug:
+		if self.cfg.debug:
 			msg('TOKEN DATA:')
 			pp_msg(tx.to_dict())
 			msg('PARSED ABI DATA:\n  {}'.format(
@@ -152,10 +151,11 @@ class TokenCommon(MMGenObject):
 
 class Token(TokenCommon):
 
-	def __init__(self,proto,addr,decimals,rpc=None):
+	def __init__(self,cfg,proto,addr,decimals,rpc=None):
 		if type(self).__name__ == 'Token':
 			from ...util2 import get_keccak
-			self.keccak_256 = get_keccak()
+			self.keccak_256 = get_keccak(cfg)
+		self.cfg = cfg
 		self.proto = proto
 		self.addr = TokenAddr(proto,addr)
 		assert isinstance(decimals,int),f'decimals param must be int instance, not {type(decimals)}'
@@ -165,13 +165,14 @@ class Token(TokenCommon):
 
 class TokenResolve(TokenCommon,metaclass=AsyncInit):
 
-	async def __init__(self,proto,rpc,addr):
+	async def __init__(self,cfg,proto,rpc,addr):
 		from ...util2 import get_keccak
-		self.keccak_256 = get_keccak()
+		self.keccak_256 = get_keccak(cfg)
+		self.cfg = cfg
 		self.proto = proto
 		self.rpc = rpc
 		self.addr = TokenAddr(proto,addr)
 		decimals = await self.get_decimals() # requires self.addr!
 		if not decimals:
 			die( 'TokenNotInBlockchain', f'Token {addr!r} not in blockchain' )
-		Token.__init__(self,proto,addr,decimals,rpc)
+		Token.__init__(self,cfg,proto,addr,decimals,rpc)

+ 9 - 9
mmgen/proto/eth/misc.py

@@ -15,7 +15,7 @@ proto.eth.misc: miscellaneous utilities for Ethereum base protocol
 from ...util import die
 from ...util2 import get_keccak
 
-def extract_key_from_geth_keystore_wallet(wallet_fn,passwd,check_addr=True):
+def extract_key_from_geth_keystore_wallet(cfg,wallet_fn,passwd,check_addr=True):
 	"""
 	Decrypt the encrypted private key in a Geth keystore wallet, returning the decrypted key
 	"""
@@ -42,7 +42,7 @@ def extract_key_from_geth_keystore_wallet(wallet_fn,passwd,check_addr=True):
 		dklen    = sp['dklen'] )
 
 	# Check password by comparing generated MAC to stored MAC
-	mac_chk = get_keccak()(hashed_pw[16:32] + bytes.fromhex( cdata['ciphertext'] )).digest().hex()
+	mac_chk = get_keccak(cfg)(hashed_pw[16:32] + bytes.fromhex( cdata['ciphertext'] )).digest().hex()
 	if mac_chk != cdata['mac']:
 		die(1,'Incorrect passphrase')
 
@@ -60,32 +60,32 @@ def extract_key_from_geth_keystore_wallet(wallet_fn,passwd,check_addr=True):
 	if check_addr:
 		from ...tool.coin import tool_cmd
 		from ...protocol import init_proto
-		t = tool_cmd( proto=init_proto('eth') )
+		t = tool_cmd( cfg=cfg, proto=init_proto(cfg,'eth') )
 		addr = t.wif2addr(key.hex())
 		addr_chk = wallet_data['address']
 		assert addr == addr_chk, f'incorrect address: ({addr} != {addr_chk})'
 
 	return key
 
-def hash_message(message,msghash_type):
-	return get_keccak()(
+def hash_message(cfg,message,msghash_type):
+	return get_keccak(cfg)(
 		{
 			'raw': message,
 			'eth_sign': '\x19Ethereum Signed Message:\n{}{}'.format( len(message), message ),
 		}[msghash_type].encode()
 	).digest()
 
-def ec_sign_message_with_privkey(message,key,msghash_type):
+def ec_sign_message_with_privkey(cfg,message,key,msghash_type):
 	"""
 	Sign an arbitrary string with an Ethereum private key, returning the signature
 
 	Conforms to the standard defined by the Geth `eth_sign` JSON-RPC call
 	"""
 	from py_ecc.secp256k1 import ecdsa_raw_sign
-	v,r,s = ecdsa_raw_sign( hash_message(message,msghash_type), key )
+	v,r,s = ecdsa_raw_sign( hash_message(cfg,message,msghash_type), key )
 	return '{:064x}{:064x}{:02x}'.format(r,s,v)
 
-def ec_recover_pubkey(message,sig,msghash_type):
+def ec_recover_pubkey(cfg,message,sig,msghash_type):
 	"""
 	Given a message and signature, recover the public key associated with the private key
 	used to make the signature
@@ -95,5 +95,5 @@ def ec_recover_pubkey(message,sig,msghash_type):
 	from py_ecc.secp256k1 import ecdsa_raw_recover
 	r,s,v = ( sig[:64], sig[64:128], sig[128:] )
 	return '{:064x}{:064x}'.format(
-		*ecdsa_raw_recover( hash_message(message,msghash_type), tuple(int(hexstr,16) for hexstr in (v,r,s)) )
+		*ecdsa_raw_recover( hash_message(cfg,message,msghash_type), tuple(int(hexstr,16) for hexstr in (v,r,s)) )
 	)

+ 3 - 2
mmgen/proto/eth/msg.py

@@ -24,7 +24,7 @@ class coin_msg(coin_msg):
 
 		async def do_sign(self,wif,message,msghash_type):
 			from .misc import ec_sign_message_with_privkey
-			return ec_sign_message_with_privkey( message, bytes.fromhex(wif), msghash_type )
+			return ec_sign_message_with_privkey( self.cfg, message, bytes.fromhex(wif), msghash_type )
 
 	class signed_online(coin_msg.signed_online):
 
@@ -32,7 +32,8 @@ class coin_msg(coin_msg):
 			from ...tool.coin import tool_cmd
 			from .misc import ec_recover_pubkey
 			return tool_cmd(
+				self.cfg,
 				proto = self.proto).pubhex2addr(
-					ec_recover_pubkey( message, sig, msghash_type )) == addr
+					ec_recover_pubkey( self.cfg, message, sig, msghash_type )) == addr
 
 	class exported_sigs(coin_msg.exported_sigs,signed_online): pass

+ 1 - 2
mmgen/proto/eth/params.py

@@ -12,7 +12,6 @@
 proto.eth.params: Ethereum protocol
 """
 
-from ...globalvars import g
 from ...protocol import CoinProtocol,_nw,decoded_addr
 from ...util import is_hex_str_lc,Msg
 
@@ -57,7 +56,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
 	def decode_addr(self,addr):
 		if is_hex_str_lc(addr) and len(addr) == self.addr_len * 2:
 			return decoded_addr( bytes.fromhex(addr), None, 'ethereum' )
-		if g.debug:
+		if self.cfg.debug:
 			Msg(f'Invalid address: {addr}')
 		return False
 

+ 3 - 2
mmgen/proto/eth/rpc.py

@@ -14,7 +14,6 @@ proto.eth.rpc: Ethereum base protocol RPC client class
 
 import re
 
-from ...globalvars import g
 from ...base_obj import AsyncInit
 from ...obj import Int
 from ...util import die,oneshot_warning_group
@@ -37,6 +36,7 @@ class EthereumRPCClient(RPCClient,metaclass=AsyncInit):
 
 	async def __init__(
 			self,
+			cfg,
 			proto,
 			daemon,
 			backend,
@@ -47,7 +47,8 @@ class EthereumRPCClient(RPCClient,metaclass=AsyncInit):
 		self.call_sigs = getattr(CallSigs,daemon.id,None)
 
 		super().__init__(
-			host = 'localhost' if g.test_suite else (g.rpc_host or 'localhost'),
+			cfg  = cfg,
+			host = 'localhost' if cfg.test_suite else (cfg.rpc_host or 'localhost'),
 			port = daemon.rpc_port )
 
 		await self.set_backend_async(backend)

+ 3 - 3
mmgen/proto/eth/tw/bal.py

@@ -30,9 +30,9 @@ class EthereumTwGetBalance(TwGetBalance):
 		'ge_minconf': 'Balance',
 	}
 
-	async def __init__(self,proto,*args,**kwargs):
-		self.twctl = await TwCtl(proto,mode='w')
-		await super().__init__(proto,*args,**kwargs)
+	async def __init__(self,cfg,proto,*args,**kwargs):
+		self.twctl = await TwCtl(cfg,proto,mode='w')
+		await super().__init__(cfg,proto,*args,**kwargs)
 
 	async def create_data(self):
 		in_data = self.twctl.mmid_ordered_dict

+ 4 - 4
mmgen/proto/eth/tw/ctl.py

@@ -172,9 +172,9 @@ class EthereumTokenTwCtl(EthereumTwCtl):
 	symbol = None
 	cur_eth_balances = {}
 
-	async def __init__(self,proto,mode='r',token_addr=None):
+	async def __init__(self,cfg,proto,mode='r',token_addr=None):
 
-		await super().__init__(proto,mode=mode)
+		await super().__init__(cfg,proto,mode=mode)
 
 		for v in self.data['tokens'].values():
 			self.conv_types(v)
@@ -211,7 +211,7 @@ class EthereumTokenTwCtl(EthereumTwCtl):
 		return 'token ' + self.get_param('symbol')
 
 	async def rpc_get_balance(self,addr):
-		return await Token(self.proto,self.token,self.decimals,self.rpc).get_balance(addr)
+		return await Token(self.cfg,self.proto,self.token,self.decimals,self.rpc).get_balance(addr)
 
 	async def get_eth_balance(self,addr,force_rpc=False):
 		cache = self.cur_eth_balances
@@ -232,7 +232,7 @@ class EthereumTokenTwCtl(EthereumTwCtl):
 		once, upon token import.  Thereafter, token address, symbol and decimals are resolved
 		either from the tracking wallet (online operations) or transaction file (when signing).
 		"""
-		t = await TokenResolve(self.proto,self.rpc,tokenaddr)
+		t = await TokenResolve(self.cfg,self.proto,self.rpc,tokenaddr)
 		self.data['tokens'][tokenaddr] = {
 			'params': {
 				'symbol': await t.get_symbol(),

+ 1 - 2
mmgen/proto/eth/tw/view.py

@@ -12,7 +12,6 @@
 proto.eth.tw.view: Ethereum base protocol base class for tracking wallet view classes
 """
 
-from ....globalvars import g
 from ....tw.view import TwView
 
 class EthereumTwView(TwView):
@@ -26,6 +25,6 @@ class EthereumTwView(TwView):
 	def gen_subheader(self,cw,color):
 		if self.disp_prec == 8:
 			yield 'Balances truncated to 8 decimal points'
-		if g.cached_balances:
+		if self.cfg.cached_balances:
 			from ....color import nocolor,yellow
 			yield (nocolor,yellow)[color]('WARNING: Using cached balances. These may be out of date!')

+ 1 - 2
mmgen/proto/eth/tx/base.py

@@ -16,7 +16,6 @@ from collections import namedtuple
 
 import mmgen.tx.base as TxBase
 from ....obj import HexStr,Int
-from ....util import dmsg
 
 class Base(TxBase.Base):
 
@@ -33,7 +32,7 @@ class Base(TxBase.Base):
 	# given absolute fee in ETH, return gas price in Gwei using self.gas
 	def fee_abs2rel(self,abs_fee,to_unit='Gwei'):
 		ret = self.proto.coin_amt(int(abs_fee.toWei() // self.gas.toWei()),'wei')
-		dmsg(f'fee_abs2rel() ==> {ret} ETH')
+		self.cfg._util.dmsg(f'fee_abs2rel() ==> {ret} ETH')
 		return ret if to_unit == 'eth' else ret.to_unit(to_unit,show_decimal=True)
 
 	# given rel fee (gasPrice) in wei, return absolute fee using self.gas (Ethereum-only method)

+ 12 - 13
mmgen/proto/eth/tx/new.py

@@ -16,7 +16,6 @@ import json
 
 import mmgen.tx.new as TxBase
 from .base import Base,TokenBase
-from ....opts import opt
 from ....obj import Int,ETHNonce,MMGenTxID,Str,HexStr
 from ....util import msg,is_int,is_hex_str,make_chksum_6
 from ....tw.ctl import TwCtl
@@ -34,16 +33,16 @@ class New(Base,TxBase.New):
 
 		super().__init__(*args,**kwargs)
 
-		if opt.gas:
-			self.gas = self.start_gas = self.proto.coin_amt(int(opt.gas),'wei')
+		if self.cfg.gas:
+			self.gas = self.start_gas = self.proto.coin_amt(int(self.cfg.gas),'wei')
 		else:
 			self.gas = self.proto.coin_amt(self.dfl_gas,'wei')
 			self.start_gas = self.proto.coin_amt(self.dfl_start_gas,'wei')
 
-		if opt.contract_data:
+		if self.cfg.contract_data:
 			m = "'--contract-data' option may not be used with token transaction"
 			assert not 'Token' in type(self).__name__, m
-			with open(opt.contract_data) as fp:
+			with open(self.cfg.contract_data) as fp:
 				self.usr_contract_data = HexStr(fp.read().strip())
 			self.disable_fee_check = True
 
@@ -93,7 +92,7 @@ class New(Base,TxBase.New):
 	def select_unspent(self,unspent):
 		from ....ui import line_input
 		while True:
-			reply = line_input('Enter an account to spend from: ').strip()
+			reply = line_input( self.cfg, 'Enter an account to spend from: ' ).strip()
 			if reply:
 				if not is_int(reply):
 					msg('Account number must be an integer')
@@ -119,10 +118,10 @@ class New(Base,TxBase.New):
 			from_unit='wei'
 		)
 
-	# given fee estimate (gas price) in wei, return absolute fee, adjusting by opt.fee_adjust
+	# given fee estimate (gas price) in wei, return absolute fee, adjusting by self.cfg.fee_adjust
 	def fee_est2abs(self,rel_fee,fe_type=None):
-		ret = self.fee_gasPrice2abs(rel_fee) * opt.fee_adjust
-		if opt.verbose:
+		ret = self.fee_gasPrice2abs(rel_fee) * self.cfg.fee_adjust
+		if self.cfg.verbose:
 			msg(f'Estimated fee: {ret} ETH')
 		return ret
 
@@ -146,10 +145,10 @@ class New(Base,TxBase.New):
 
 	async def get_input_addrs_from_cmdline(self):
 		ret = []
-		if opt.inputs:
-			data_root = (await TwCtl(self.proto)).data_root # must create new instance here
+		if self.cfg.inputs:
+			data_root = (await TwCtl(self.cfg,self.proto)).data_root # must create new instance here
 			errmsg = 'Address {!r} not in tracking wallet'
-			for addr in opt.inputs.split(','):
+			for addr in self.cfg.inputs.split(','):
 				if is_mmgen_id(self.proto,addr):
 					for waddr in data_root:
 						if data_root[waddr]['mmid'] == addr:
@@ -178,7 +177,7 @@ class TokenNew(TokenBase,New):
 
 	async def make_txobj(self): # called by create_serialized()
 		await super().make_txobj()
-		t = Token(self.proto,self.twctl.token,self.twctl.decimals)
+		t = Token(self.cfg,self.proto,self.twctl.token,self.twctl.decimals)
 		o = self.txobj
 		o['token_addr'] = t.addr
 		o['decimals'] = t.decimals

+ 3 - 5
mmgen/proto/eth/tx/online.py

@@ -12,8 +12,6 @@
 proto.eth.tx.online: Ethereum online signed transaction class
 """
 
-from ....globalvars import *
-
 import mmgen.tx.online as TxBase
 from .signed import Signed,TokenSigned
 from .. import erigon_sleep
@@ -37,7 +35,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 		if prompt_user:
 			self.confirm_send()
 
-		if g.bogus_send:
+		if self.cfg.bogus_send:
 			ret = None
 		else:
 			try:
@@ -52,7 +50,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 				sys.exit(1)
 			return False
 		else:
-			if g.bogus_send:
+			if self.cfg.bogus_send:
 				m = 'BOGUS transaction NOT sent: {}'
 			else:
 				m = 'Transaction sent: {}'
@@ -78,6 +76,6 @@ class TokenOnlineSigned(TokenSigned,OnlineSigned):
 		assert self.twctl.token == o['to']
 		o['token_addr'] = TokenAddr(self.proto,o['to'])
 		o['decimals']   = self.twctl.decimals
-		t = Token(self.proto,o['token_addr'],o['decimals'])
+		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
 		o['amt'] = t.transferdata2amt(o['data'])
 		o['token_to'] = t.transferdata2sendaddr(o['data'])

+ 3 - 3
mmgen/proto/eth/tx/unsigned.py

@@ -79,7 +79,7 @@ class Unsigned(Completed,TxBase.Unsigned):
 			await self.do_sign(keys[0].sec.wif,tx_num_str)
 			msg('OK')
 			from ....tx import SignedTX
-			return await SignedTX(data=self.__dict__)
+			return await SignedTX(cfg=self.cfg,data=self.__dict__)
 		except Exception as e:
 			msg(f'{e}: transaction signing failed!')
 			return False
@@ -92,12 +92,12 @@ class TokenUnsigned(TokenCompleted,Unsigned):
 		o = self.txobj
 		o['token_addr'] = TokenAddr(self.proto,d['token_addr'])
 		o['decimals'] = Int(d['decimals'])
-		t = Token(self.proto,o['token_addr'],o['decimals'])
+		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
 		o['data'] = t.create_data(o['to'],o['amt'])
 		o['token_to'] = t.transferdata2sendaddr(o['data'])
 
 	async def do_sign(self,wif,tx_num_str):
 		o = self.txobj
-		t = Token(self.proto,o['token_addr'],o['decimals'])
+		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
 		tx_in = t.make_tx_in(o['from'],o['to'],o['amt'],self.start_gas,o['gasPrice'],nonce=o['nonce'])
 		(self.serialized,self.coin_txid) = await t.txsign(tx_in,wif,o['from'],chain_id=o['chainId'])

+ 3 - 4
mmgen/proto/secp256k1/keygen.py

@@ -19,7 +19,7 @@ class backend:
 
 	class libsecp256k1(keygen_base):
 
-		def __init__(self):
+		def __init__(self,cfg):
 			from .secp256k1 import priv2pub
 			self.priv2pub = priv2pub
 
@@ -41,13 +41,12 @@ class backend:
 				if not silent:
 					from ...util import ymsg
 					ymsg(str(e))
-				from ...util import qmsg
-				qmsg('Using (slow) native Python ECDSA library for public key generation')
+				self.cfg._util.qmsg('Using (slow) native Python ECDSA library for public key generation')
 				return 'python_ecdsa'
 
 	class python_ecdsa(keygen_base):
 
-		def __init__(self):
+		def __init__(self,cfg):
 			import ecdsa
 			self.ecdsa = ecdsa
 

+ 9 - 6
mmgen/proto/xmr/daemon.py

@@ -14,8 +14,7 @@ proto.xmr.daemon: Monero base protocol daemon classes
 
 import os
 
-from ...globalvars import g,gc
-from ...opts import opt
+from ...globalvars import gc
 from ...util import list_gen,die,contains_any
 from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd
 
@@ -46,6 +45,7 @@ class monero_daemon(CoinDaemon):
 
 		from .rpc import MoneroRPCClient
 		self.rpc = MoneroRPCClient(
+			cfg    = self.cfg,
 			proto  = self.proto,
 			host   = self.host,
 			port   = self.rpc_port,
@@ -91,6 +91,7 @@ class MoneroWalletDaemon(RPCDaemon):
 
 	def __init__(
 			self,
+			cfg,
 			proto,
 			wallet_dir,
 			test_suite  = False,
@@ -105,7 +106,7 @@ class MoneroWalletDaemon(RPCDaemon):
 		self.proto = proto
 		self.test_suite = test_suite
 
-		super().__init__()
+		super().__init__(cfg)
 
 		self.network = proto.network
 		self.wallet_dir = wallet_dir
@@ -123,13 +124,14 @@ class MoneroWalletDaemon(RPCDaemon):
 		self.daemon_port = (
 			None if daemon_addr else
 			CoinDaemon(
+				cfg        = self.cfg,
 				proto      = proto,
 				test_suite = test_suite).rpc_port
 		)
 
-		self.host = host or opt.wallet_rpc_host or g.monero_wallet_rpc_host
-		self.user = user or opt.wallet_rpc_user or g.monero_wallet_rpc_user
-		self.passwd = passwd or opt.wallet_rpc_password or g.monero_wallet_rpc_password
+		self.host = host or self.cfg.wallet_rpc_host or self.cfg.monero_wallet_rpc_host
+		self.user = user or self.cfg.wallet_rpc_user or self.cfg.monero_wallet_rpc_user
+		self.passwd = passwd or self.cfg.wallet_rpc_password or self.cfg.monero_wallet_rpc_password
 
 		assert self.host
 		assert self.user
@@ -157,5 +159,6 @@ class MoneroWalletDaemon(RPCDaemon):
 
 		from .rpc import MoneroWalletRPCClient
 		self.rpc = MoneroWalletRPCClient(
+			cfg             = self.cfg,
 			daemon          = self,
 			test_connection = False )

+ 8 - 8
mmgen/proto/xmr/keygen.py

@@ -19,13 +19,13 @@ class backend:
 
 	class base(keygen_base):
 
-		def __init__(self):
+		def __init__(self,cfg):
 
 			from ...proto.xmr.params import mainnet
 			self.proto_cls = mainnet
 
 			from ...util2 import get_keccak
-			self.keccak_256 = get_keccak()
+			self.keccak_256 = get_keccak(cfg)
 
 		def to_viewkey(self,privkey):
 			return self.proto_cls.preprocess_key(
@@ -35,8 +35,8 @@ class backend:
 
 	class nacl(base):
 
-		def __init__(self):
-			super().__init__()
+		def __init__(self,cfg):
+			super().__init__(cfg)
 			from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp
 			self.scalarmultbase = crypto_scalarmult_ed25519_base_noclamp
 
@@ -49,8 +49,8 @@ class backend:
 
 	class ed25519(base):
 
-		def __init__(self):
-			super().__init__()
+		def __init__(self,cfg):
+			super().__init__(cfg)
 			from ...contrib.ed25519 import edwards,encodepoint,B,scalarmult
 			self.edwards     = edwards
 			self.encodepoint = encodepoint
@@ -85,7 +85,7 @@ class backend:
 
 	class ed25519ll_djbec(ed25519):
 
-		def __init__(self):
-			super().__init__()
+		def __init__(self,cfg):
+			super().__init__(cfg)
 			from ...contrib.ed25519ll_djbec import scalarmult
 			self.scalarmult = scalarmult

+ 5 - 4
mmgen/proto/xmr/rpc.py

@@ -13,7 +13,6 @@ proto.xmr.rpc: Monero base protocol RPC client class
 """
 
 import re
-from ...globalvars import g
 from ...rpc import RPCClient,IPPort,auth_data
 
 class MoneroRPCClient(RPCClient):
@@ -24,6 +23,7 @@ class MoneroRPCClient(RPCClient):
 
 	def __init__(
 			self,
+			cfg,
 			proto,
 			host,
 			port,
@@ -42,7 +42,7 @@ class MoneroRPCClient(RPCClient):
 			if host.endswith('.onion'):
 				self.network_proto = 'http'
 
-		super().__init__(host,port,test_connection)
+		super().__init__(cfg,host,port,test_connection)
 
 		if self.auth_type:
 			self.auth = auth_data(user,passwd)
@@ -69,7 +69,7 @@ class MoneroRPCClient(RPCClient):
 				if self.daemon and self.daemon_version > self.daemon.coind_version:
 					self.handle_unsupported_daemon_version(
 						proto.name,
-						ignore_daemon_version or proto.ignore_daemon_version or g.ignore_daemon_version )
+						ignore_daemon_version or proto.ignore_daemon_version or self.cfg.ignore_daemon_version )
 			else: # restricted (public) node:
 				self.daemon_version_str = None
 				self.daemon_version = None
@@ -100,10 +100,11 @@ class MoneroWalletRPCClient(MoneroRPCClient):
 
 	auth_type = 'digest'
 
-	def __init__(self,daemon,test_connection=True):
+	def __init__(self,cfg,daemon,test_connection=True):
 
 		RPCClient.__init__(
 			self,
+			cfg,
 			daemon.host,
 			daemon.rpc_port,
 			test_connection = test_connection )

+ 1 - 1
mmgen/proto/zec/keygen.py

@@ -19,7 +19,7 @@ class backend:
 
 	class nacl(keygen_base):
 
-		def __init__(self):
+		def __init__(self,cfg):
 			from nacl.bindings import crypto_scalarmult_base
 			self.crypto_scalarmult_base = crypto_scalarmult_base
 			from ...sha2 import Sha256

+ 1 - 2
mmgen/proto/zec/params.py

@@ -32,8 +32,7 @@ class mainnet(mainnet):
 
 	def __init__(self,*args,**kwargs):
 		super().__init__(*args,**kwargs)
-		from ...opts import opt
-		self.coin_id = 'ZEC-Z' if opt.type in ('zcash_z','Z') else 'ZEC-T'
+		self.coin_id = 'ZEC-Z' if self.cfg.type in ('zcash_z','Z') else 'ZEC-T'
 
 	def get_wif_ver_bytes_len(self,key_data):
 		"""

+ 21 - 15
mmgen/protocol.py

@@ -22,7 +22,7 @@ protocol: Coin protocol base classes and initializer
 
 from collections import namedtuple
 
-from .globalvars import g,gc
+from .globalvars import gc
 from .objmethods import MMGenObject
 
 decoded_wif = namedtuple('decoded_wif',['sec','pubkey_type','compressed'])
@@ -54,7 +54,8 @@ class CoinProtocol(MMGenObject):
 		is_fork_of = None
 		networks   = ('mainnet','testnet','regtest')
 
-		def __init__(self,coin,name,network,tokensym=None,need_amt=False):
+		def __init__(self,cfg,coin,name,network,tokensym=None,need_amt=False):
+			self.cfg        = cfg
 			self.coin       = coin.upper()
 			self.coin_id    = self.coin
 			self.name       = name
@@ -96,7 +97,7 @@ class CoinProtocol(MMGenObject):
 
 			if self.base_coin in ('ETH','XMR'):
 				from .util2 import get_keccak
-				self.keccak_256 = get_keccak()
+				self.keccak_256 = get_keccak(cfg)
 
 			if need_amt:
 				import mmgen.amt
@@ -111,7 +112,7 @@ class CoinProtocol(MMGenObject):
 			return self.coin
 
 		@classmethod
-		def chain_name_to_network(cls,coin,chain_name):
+		def chain_name_to_network(cls,cfg,coin,chain_name):
 			"""
 			The generic networks 'mainnet', 'testnet' and 'regtest' are required for all coins
 			that support transaction operations.
@@ -121,7 +122,7 @@ class CoinProtocol(MMGenObject):
 			For Bitcoin and Bitcoin forks, 'network' and 'chain_name' are equivalent.
 			"""
 			for network in ('mainnet','testnet','regtest'):
-				proto = init_proto(coin,network=network)
+				proto = init_proto( cfg, coin, network=network )
 				for proto_chain_name in proto.chain_names:
 					if chain_name == proto_chain_name:
 						return network
@@ -211,7 +212,7 @@ class CoinProtocol(MMGenObject):
 				elif pk == self.secp256k1_ge: # ditto
 					die(4,'Private key == secp256k1_ge!')
 				else:
-					if not g.test_suite:
+					if not self.cfg.test_suite:
 						ymsg(f'Warning: private key is greater than secp256k1 group order!:\n  {hexpriv}')
 					return (pk % self.secp256k1_ge).to_bytes(self.privkey_len,'big')
 
@@ -231,6 +232,7 @@ class CoinProtocol(MMGenObject):
 				compressed  = False )
 
 def init_proto(
+		cfg,
 		coin       = None,
 		testnet    = False,
 		regtest    = False,
@@ -271,20 +273,24 @@ def init_proto(
 		)
 
 	return getattr(CoinProtocol,proto_name)(
+		cfg       = cfg,
 		coin      = coin,
 		name      = name,
 		network   = network,
 		tokensym  = tokensym,
 		need_amt  = need_amt )
 
-def init_proto_from_opts(need_amt=False):
+def init_proto_from_cfg(cfg,need_amt):
 	return init_proto(
-		coin      = g.coin,
-		network   = g.network,
-		tokensym  = g.token,
+		cfg       = cfg,
+		coin      = cfg.coin,
+		network   = cfg.network,
+		tokensym  = cfg.token,
 		need_amt  = need_amt )
 
-def warn_trustlevel(coinsym):
+def warn_trustlevel(cfg):
+
+	coinsym = cfg.coin
 
 	if coinsym.lower() in CoinProtocol.coins:
 		trust_level = CoinProtocol.coins[coinsym.lower()].trust_level
@@ -306,7 +312,7 @@ def warn_trustlevel(coinsym):
 		Are you sure you want to continue?
 	"""
 
-	from .util import qmsg,fmt
+	from .util import fmt
 	from .color import red,yellow,green
 
 	warning = fmt(m).strip().format(
@@ -319,11 +325,11 @@ def warn_trustlevel(coinsym):
 		}[trust_level],
 		p = gc.proj_name )
 
-	if g.test_suite:
-		qmsg(warning)
+	if cfg.test_suite:
+		cfg._util.qmsg(warning)
 		return
 
 	from .ui import keypress_confirm
-	if not keypress_confirm(warning,default_yes=True):
+	if not keypress_confirm( cfg, warning, default_yes=True ):
 		import sys
 		sys.exit(0)

+ 16 - 10
mmgen/rpc.py

@@ -20,11 +20,12 @@
 rpc: Cryptocoin RPC library for the MMGen suite
 """
 
-import base64,json,asyncio,importlib
+import re,base64,json,asyncio,importlib
 from decimal import Decimal
 from collections import namedtuple
 
-from .common import *
+from .globalvars import gc
+from .util import msg,die,fmt,fmt_list,pp_fmt
 from .base_obj import AsyncInit
 from .obj import NonNegativeInt
 from .objmethods import Hilite,InitErrors,MMGenObject
@@ -85,6 +86,7 @@ class RPCBackends:
 	class base:
 
 		def __init__(self,caller):
+			self.cfg            = caller.cfg
 			self.host           = caller.host
 			self.port           = caller.port
 			self.proxy          = caller.proxy
@@ -109,7 +111,7 @@ class RPCBackends:
 		async def __init__(self,caller):
 			super().__init__(caller)
 			import aiohttp
-			self.connector = aiohttp.TCPConnector(limit_per_host=g.aiohttp_rpc_queue_len)
+			self.connector = aiohttp.TCPConnector(limit_per_host=self.cfg.aiohttp_rpc_queue_len)
 			self.session = aiohttp.ClientSession(
 				headers = { 'Content-Type': 'application/json' },
 				connector = self.connector,
@@ -261,14 +263,16 @@ class RPCClient(MMGenObject):
 	network_proto = 'http'
 	proxy = None
 
-	def __init__(self,host,port,test_connection=True):
+	def __init__(self,cfg,host,port,test_connection=True):
+
+		self.cfg = cfg
 
 		# aiohttp workaround, and may speed up RPC performance overall on some systems:
 		if gc.platform == 'win' and host == 'localhost':
 			host = '127.0.0.1'
 
 		global dmsg_rpc,dmsg_rpc_backend
-		if not g.debug_rpc:
+		if not self.cfg.debug_rpc:
 			dmsg_rpc = dmsg_rpc_backend = noop
 
 		dmsg_rpc(f'=== {type(self).__name__}.__init__() debug ===')
@@ -285,11 +289,11 @@ class RPCClient(MMGenObject):
 		self.host_url = f'{self.network_proto}://{host}:{port}'
 		self.host = host
 		self.port = port
-		self.timeout = g.http_timeout
+		self.timeout = self.cfg.http_timeout
 		self.auth = None
 
 	def _get_backend(self,backend):
-		backend_id = backend or opt.rpc_backend
+		backend_id = backend or self.cfg.rpc_backend
 		if backend_id == 'auto':
 			return {'linux':RPCBackends.httplib,'win':RPCBackends.requests}[gc.platform](self)
 		else:
@@ -443,6 +447,7 @@ class RPCClient(MMGenObject):
 				""",indent='    '))
 
 async def rpc_init(
+		cfg,
 		proto,
 		backend               = None,
 		daemon                = None,
@@ -458,15 +463,16 @@ async def rpc_init(
 
 	from .daemon import CoinDaemon
 	rpc = await cls(
+		cfg           = cfg,
 		proto         = proto,
-		daemon        = daemon or CoinDaemon(proto=proto,test_suite=g.test_suite),
-		backend       = backend or opt.rpc_backend,
+		daemon        = daemon or CoinDaemon(cfg,proto=proto,test_suite=cfg.test_suite),
+		backend       = backend or cfg.rpc_backend,
 		ignore_wallet = ignore_wallet )
 
 	if rpc.daemon_version > rpc.daemon.coind_version:
 		rpc.handle_unsupported_daemon_version(
 			proto.name,
-			ignore_daemon_version or proto.ignore_daemon_version or g.ignore_daemon_version )
+			ignore_daemon_version or proto.ignore_daemon_version or cfg.ignore_daemon_version )
 
 	if rpc.chain not in proto.chain_names:
 		die( 'RPCChainMismatch', '\n' + fmt(f"""

+ 6 - 6
mmgen/seed.py

@@ -54,19 +54,20 @@ class SeedBase(MMGenObject):
 	data = ImmutableAttr(bytes,typeconv=False)
 	sid  = ImmutableAttr(SeedID,typeconv=False)
 
-	def __init__(self,seed_bin=None,nSubseeds=None):
+	def __init__(self,cfg,seed_bin=None,nSubseeds=None):
+
 		if not seed_bin:
-			from .opts import opt
 			from .crypto import Crypto
 			from hashlib import sha256
 			# Truncate random data for smaller seed lengths
-			seed_bin = sha256(Crypto().get_random(1033)).digest()[:(opt.seed_len or self.dfl_len)//8]
+			seed_bin = sha256(Crypto(cfg).get_random(1033)).digest()[:(cfg.seed_len or self.dfl_len)//8]
 		elif len(seed_bin)*8 not in self.lens:
 			die(3,f'{len(seed_bin)*8}: invalid seed bit length')
 
+		self.cfg = cfg
 		self.data = seed_bin
 		self.sid  = SeedID(seed=self)
-		self.nSubseeds = nSubseeds # will override opt.subseeds
+		self.nSubseeds = nSubseeds # overrides cfg.subseeds
 
 	@property
 	def bitlen(self):
@@ -90,10 +91,9 @@ class Seed(SeedBase):
 	def subseeds(self):
 		if not hasattr(self,'_subseeds'):
 			from .subseed import SubSeedList
-			from .opts import opt
 			self._subseeds = SubSeedList(
 				self,
-				length = self.nSubseeds or opt.subseeds )
+				length = self.nSubseeds or self.cfg.subseeds )
 		return self._subseeds
 
 	def subseed(self,*args,**kwargs):

+ 18 - 13
mmgen/seedsplit.py

@@ -20,7 +20,6 @@
 seedsplit: Seed split classes and methods for the MMGen suite
 """
 
-from .globalvars import g
 from .color import yellow
 from .obj import MMGenPWIDString,MMGenIdx
 from .subseed import *
@@ -77,7 +76,7 @@ class SeedShareList(SubSeedList):
 			for nonce in range(SeedShare.max_nonce+1):
 				ms = SeedShareMaster(self,master_idx,nonce)
 				if ms.sid == parent_seed.sid:
-					if g.debug_subseed:
+					if parent_seed.cfg.debug_subseed:
 						msg(f'master_share seed ID collision with parent seed, incrementing nonce to {nonce+1}')
 				else:
 					return ms
@@ -103,7 +102,7 @@ class SeedShareList(SubSeedList):
 			self.last_share = ls = SeedShareLast(self)
 			if last_share_debug(ls) or ls.sid in self.data['long'] or ls.sid == parent_seed.sid:
 				# collision: throw out entire split list and redo with new start nonce
-				if g.debug_subseed:
+				if parent_seed.cfg.debug_subseed:
 					self._collision_debug_msg(ls.sid,count,nonce,'nonce_start',debug_last_share)
 			else:
 				self.data['long'][ls.sid] = (count,nonce)
@@ -111,7 +110,7 @@ class SeedShareList(SubSeedList):
 		else:
 			die( 'SubSeedNonceRangeExceeded', 'nonce range exceeded' )
 
-		if g.debug_subseed:
+		if parent_seed.cfg.debug_subseed:
 			A = parent_seed.data
 			B = self.join().data
 			assert A == B, f'Data mismatch!\noriginal seed: {A!r}\nrejoined seed: {B!r}'
@@ -137,6 +136,7 @@ class SeedShareList(SubSeedList):
 
 	def join(self):
 		return Seed.join_shares(
+			self.parent_seed.cfg,
 			[self.get_share_by_idx(i+1) for i in range(len(self))] )
 
 	def format(self):
@@ -210,7 +210,7 @@ class SeedShare(SeedShareBase,SubSeed):
 				b':master:' +
 				parent_list.master_share.idx.to_bytes(2,'big')
 			)
-		return Crypto().scramble_seed(seed.data,scramble_key)[:seed.byte_len]
+		return Crypto(parent_list.parent_seed.cfg).scramble_seed(seed.data,scramble_key)[:seed.byte_len]
 
 class SeedShareLast(SeedShareBase,SeedBase):
 
@@ -222,6 +222,7 @@ class SeedShareLast(SeedShareBase,SeedBase):
 		self.parent_list = parent_list
 		SeedBase.__init__(
 			self,
+			parent_list.parent_seed.cfg,
 			seed_bin=self.make_subseed_bin(parent_list) )
 
 	@staticmethod
@@ -244,10 +245,12 @@ class SeedShareMaster(SeedBase,SeedShareBase):
 		self.idx = idx
 		self.nonce = nonce
 		self.parent_list = parent_list
+		self.cfg = parent_list.parent_seed.cfg
 
-		SeedBase.__init__( self, self.make_base_seed_bin() )
+		SeedBase.__init__( self, self.cfg, self.make_base_seed_bin() )
 
 		self.derived_seed = SeedBase(
+			self.cfg,
 			self.make_derived_seed_bin( parent_list.id_str, parent_list.count )
 		)
 
@@ -262,14 +265,14 @@ class SeedShareMaster(SeedBase,SeedShareBase):
 		seed = self.parent_list.parent_seed
 		# field maximums: idx: 65535 (1024)
 		scramble_key = b'master_share:' + self.idx.to_bytes(2,'big') + self.nonce.to_bytes(2,'big')
-		return Crypto().scramble_seed(seed.data,scramble_key)[:seed.byte_len]
+		return Crypto(self.cfg).scramble_seed(seed.data,scramble_key)[:seed.byte_len]
 
 	# Don't bother with avoiding seed ID collision here, as sid of derived seed is not used
 	# by user as an identifier
 	def make_derived_seed_bin(self,id_str,count):
 		# field maximums: id_str: none (256 chars), count: 65535 (1024)
 		scramble_key = id_str.encode() + b':' + count.to_bytes(2,'big')
-		return Crypto().scramble_seed(self.data,scramble_key)[:self.byte_len]
+		return Crypto(self.cfg).scramble_seed(self.data,scramble_key)[:self.byte_len]
 
 	def get_desc(self,ui=False):
 		psid = self.parent_list.parent_seed.sid
@@ -281,15 +284,17 @@ class SeedShareMasterJoining(SeedShareMaster):
 	id_str = ImmutableAttr(SeedSplitIDString)
 	count  = ImmutableAttr(SeedShareCount)
 
-	def __init__(self,idx,base_seed,id_str,count):
+	def __init__(self,cfg,idx,base_seed,id_str,count):
 
-		SeedBase.__init__(self,seed_bin=base_seed.data)
+		SeedBase.__init__( self, cfg, seed_bin=base_seed.data )
 
+		self.cfg = cfg
 		self.id_str = id_str or 'default'
 		self.count = count
-		self.derived_seed = SeedBase( self.make_derived_seed_bin(self.id_str,self.count) )
+		self.derived_seed = SeedBase( cfg, self.make_derived_seed_bin(self.id_str,self.count) )
 
 def join_shares(
+		cfg,
 		seed_list,
 		master_idx = None,
 		id_str     = None ):
@@ -315,8 +320,8 @@ def join_shares(
 		add_share(ss)
 
 	if master_idx:
-		add_share(SeedShareMasterJoining(master_idx,master_share,id_str,d.count+1).derived_seed)
+		add_share(SeedShareMasterJoining( cfg, master_idx, master_share, id_str, d.count+1 ).derived_seed)
 
 	SeedShareCount(d.count) # check that d.count is in valid range
 
-	return Seed(seed_bin=d.ret.to_bytes(d.byte_len,'big'))
+	return Seed( cfg, seed_bin=d.ret.to_bytes(d.byte_len,'big') )

+ 4 - 4
mmgen/share/Opts.py

@@ -49,7 +49,7 @@ def print_help(*args):
 	print(make_help(*args))
 	sys.exit(0)
 
-def make_help(proto,opt,opts_data,opt_filter):
+def make_help(cfg,proto,opts_data,opt_filter):
 
 	def parse_lines(text):
 		filtered = False
@@ -62,7 +62,7 @@ def make_help(proto,opt,opts_data,opt_filter):
 			elif not filtered:
 				yield line
 
-	opts_type,fs = ('options','{:<3} --{} {}') if opt.help else ('long_options','{}  --{} {}')
+	opts_type,fs = ('options','{:<3} --{} {}') if cfg.help else ('long_options','{}  --{} {}')
 	t = opts_data['text']
 	c = opts_data['code']
 	nl = '\n  '
@@ -71,10 +71,10 @@ def make_help(proto,opt,opts_data,opt_filter):
 
 	from mmgen.help import help_notes_func
 	def help_notes(k):
-		return help_notes_func(proto,opt,k)
+		return help_notes_func(proto,cfg,k)
 
 	def gen_arg_tuple(func,text):
-		d = {'proto': proto,'help_notes':help_notes}
+		d = {'proto': proto,'help_notes':help_notes,'cfg':cfg}
 		for arg in func.__code__.co_varnames:
 			yield d[arg] if arg in d else text
 

+ 5 - 5
mmgen/subseed.py

@@ -21,7 +21,7 @@ subseed: Subseed classes and methods for the MMGen suite
 """
 
 from .color import green
-from .util import msg_r,msg,qmsg,die
+from .util import msg_r,msg,die
 from .obj import MMGenRange,IndexedDict
 from .seed import *
 
@@ -66,6 +66,7 @@ class SubSeed(SeedBase):
 		self.parent_list = parent_list
 		SeedBase.__init__(
 			self,
+			parent_list.parent_seed.cfg,
 			seed_bin=self.make_subseed_bin( parent_list, idx, nonce, length ))
 
 	@staticmethod
@@ -75,7 +76,7 @@ class SubSeed(SeedBase):
 		# field maximums: idx: 4294967295 (1000000), nonce: 65535 (1000), short: 255 (1)
 		scramble_key  = idx.to_bytes(4,'big') + nonce.to_bytes(2,'big') + short.to_bytes(1,'big')
 		from .crypto import Crypto
-		return Crypto().scramble_seed(seed.data,scramble_key)[:16 if short else seed.byte_len]
+		return Crypto(parent_list.parent_seed.cfg).scramble_seed(seed.data,scramble_key)[:16 if short else seed.byte_len]
 
 class SubSeedList(MMGenObject):
 	have_short = True
@@ -129,7 +130,7 @@ class SubSeedList(MMGenObject):
 
 		def do_msg(subseed):
 			if print_msg:
-				qmsg('{} {} ({}:{})'.format(
+				self.parent_seed.cfg._util.qmsg('{} {} ({}:{})'.format(
 					green('Found subseed'),
 					subseed.sid.hl(),
 					self.parent_seed.sid.hl(),
@@ -186,8 +187,7 @@ class SubSeedList(MMGenObject):
 			for nonce in range(self.nonce_start,self.member_type.max_nonce+1): # handle SeedID collisions
 				sid = make_chksum_8(self.member_type.make_subseed_bin(self,idx,nonce,length))
 				if sid in self.data['long'] or sid in self.data['short'] or sid == self.parent_seed.sid:
-					from .globalvars import g
-					if g.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds
+					if self.parent_seed.cfg.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds
 						self._collision_debug_msg(sid,idx,nonce)
 				else:
 					self.data[length][sid] = (idx,nonce)

+ 8 - 4
mmgen/term.py

@@ -20,10 +20,12 @@
 term: Terminal classes for the MMGen suite
 """
 
+# TODO: reimplement as instance instead of class
+
 import sys,os,time
 from collections import namedtuple
 
-from .globalvars import g,gc
+from .globalvars import gc
 from .util import msg,msg_r,die
 
 try:
@@ -108,7 +110,7 @@ class MMGenTermLinux(MMGenTerm):
 
 	@classmethod
 	def kb_hold_protect(cls):
-		if g.hold_protect_disable:
+		if cls.cfg.hold_protect_disable:
 			return
 		tty.setcbreak(cls.stdin_fd)
 		timeout = 0.3
@@ -130,7 +132,7 @@ class MMGenTermLinux(MMGenTerm):
 		timeout = 0.3
 		tty.setcbreak(cls.stdin_fd)
 		msg_r(prompt)
-		if g.hold_protect_disable:
+		if cls.cfg.hold_protect_disable:
 			prehold_protect = False
 		while True:
 			# Protect against held-down key before read()
@@ -271,7 +273,7 @@ def get_term():
 		'mswin': (MMGenTermMSWin if sys.stdin.isatty() else MMGenTermMSWinStub),
 	}[_platform]
 
-def init_term(noecho=False):
+def init_term(cfg,noecho=False):
 
 	term = get_term()
 
@@ -281,5 +283,7 @@ def init_term(noecho=False):
 	for var in ('get_char','get_char_raw','kb_hold_protect','get_terminal_size'):
 		setattr( self, var, getattr(term,var) )
 
+	term.cfg = cfg # setting the _class_ attribute
+
 def reset_term():
 	get_term().reset()

+ 13 - 13
mmgen/tool/api.py

@@ -35,7 +35,10 @@ class tool_api(
 
 	Example:
 		from mmgen.tool.api import tool_api
-		tool = tool_api()
+		from mmgen.opts import init
+
+		# Initialize a tool API instance:
+		tool = tool_api(init())
 
 		# Set the coin and network:
 		tool.init_coin('btc','mainnet')
@@ -63,16 +66,15 @@ class tool_api(
 	need_proto = True
 	need_addrtype = True
 
-	def __init__(self):
+	def __init__(self,cfg):
 		"""
 		Initializer - takes no arguments
 		"""
-		import mmgen.opts as opts
-		from ..opts import opt
-		opts.UserOpts._reset_ok += ('usr_randchars',)
-		if not opt._lock:
+		type(cfg)._reset_ok += ('usr_randchars',)
+		if not cfg._lock:
+			import mmgen.opts as opts
 			opts.init()
-		super().__init__()
+		super().__init__(cfg)
 
 	def init_coin(self,coinsym,network):
 		"""
@@ -81,8 +83,8 @@ class tool_api(
 		Valid choices for network: 'mainnet','testnet','regtest'
 		"""
 		from ..protocol import init_proto,warn_trustlevel
-		warn_trustlevel(coinsym)
-		self.proto = init_proto(coinsym,network=network,need_amt=True)
+		warn_trustlevel(self.cfg)
+		self.proto = init_proto( self.cfg, coinsym, network=network, need_amt=True )
 		return self.proto
 
 	@property
@@ -139,10 +141,8 @@ class tool_api(
 		The number of keystrokes of entropy to be gathered from the user.
 		Setting to zero disables user entropy gathering.
 		"""
-		from ..opts import opt
-		return opt.usr_randchars
+		return self.cfg.usr_randchars
 
 	@usr_randchars.setter
 	def usr_randchars(self,val):
-		from ..opts import opt
-		opt.usr_randchars = val
+		self.cfg.usr_randchars = val

+ 6 - 6
mmgen/tool/coin.py

@@ -45,8 +45,8 @@ class tool_cmd(tool_cmd_base):
 
 	def _init_generators(self,arg=None):
 		return generator_data(
-			kg = KeyGenerator( self.proto, self.mmtype.pubkey_type ),
-			ag = AddrGenerator( self.proto, self.mmtype ),
+			kg = KeyGenerator( self.cfg, self.proto, self.mmtype.pubkey_type ),
+			ag = AddrGenerator( self.cfg, self.proto, self.mmtype ),
 		)
 
 	def randwif(self):
@@ -54,7 +54,7 @@ class tool_cmd(tool_cmd_base):
 		from ..crypto import Crypto
 		return PrivKey(
 			self.proto,
-			Crypto().get_random(32),
+			Crypto(self.cfg).get_random(32),
 			pubkey_type = self.mmtype.pubkey_type,
 			compressed  = self.mmtype.compressed ).wif
 
@@ -64,7 +64,7 @@ class tool_cmd(tool_cmd_base):
 		from ..crypto import Crypto
 		privkey = PrivKey(
 			self.proto,
-			Crypto().get_random(32),
+			Crypto(self.cfg).get_random(32),
 			pubkey_type = self.mmtype.pubkey_type,
 			compressed  = self.mmtype.compressed )
 		return (
@@ -136,7 +136,7 @@ class tool_cmd(tool_cmd_base):
 		if self.proto.base_proto == 'Ethereum' and len(pubkeyhex) == 128: # support raw ETH pubkeys
 			pubkeyhex = '04' + pubkeyhex
 		from ..keygen import keygen_public_data
-		ag = AddrGenerator( self.proto, self.mmtype )
+		ag = AddrGenerator( self.cfg, self.proto, self.mmtype )
 		return ag.to_addr(keygen_public_data(
 			pubkey        = bytes.fromhex(pubkeyhex),
 			viewkey_bytes = None,
@@ -192,4 +192,4 @@ class tool_cmd(tool_cmd_base):
 	def eth_checksummed_addr(self,addr:'sstr'):
 		"create a checksummed Ethereum address"
 		from ..protocol import init_proto
-		return init_proto('eth').checksummed_addr(addr)
+		return init_proto( self.cfg, 'eth' ).checksummed_addr(addr)

+ 8 - 8
mmgen/tool/common.py

@@ -31,21 +31,21 @@ class tool_cmd_base(MMGenObject):
 	need_addrtype = False
 	need_amt = False
 
-	def __init__(self,cmdname=None,proto=None,mmtype=None):
+	def __init__(self,cfg,cmdname=None,proto=None,mmtype=None):
+
+		self.cfg = cfg
 
 		if self.need_proto:
-			from ..protocol import init_proto_from_opts
-			self.proto = proto or init_proto_from_opts(need_amt=self.need_amt)
-			from ..globalvars import g
-			if g.token:
-				self.proto.tokensym = g.token.upper()
+			from ..protocol import init_proto_from_cfg
+			self.proto = proto or cfg._proto or init_proto_from_cfg(cfg,need_amt=True)
+			if cfg.token:
+				self.proto.tokensym = cfg.token.upper()
 
 		if self.need_addrtype:
-			from ..opts import opt
 			from ..addr import MMGenAddrType
 			self.mmtype = MMGenAddrType(
 				self.proto,
-				mmtype or opt.type or self.proto.dfl_mmtype )
+				mmtype or cfg.type or self.proto.dfl_mmtype )
 
 	@property
 	def user_commands(self):

+ 5 - 5
mmgen/tool/file.py

@@ -27,18 +27,17 @@ class tool_cmd(tool_cmd_base):
 
 	need_proto = True
 
-	def __init__(self,cmdname=None,proto=None,mmtype=None):
+	def __init__(self,cfg,cmdname=None,proto=None,mmtype=None):
 		if cmdname == 'txview':
 			self.need_amt = True
-		super().__init__(cmdname=cmdname,proto=proto,mmtype=mmtype)
+		super().__init__(cfg=cfg,cmdname=cmdname,proto=proto,mmtype=mmtype)
 
 	def _file_chksum(self,mmgen_addrfile,obj):
 		kwargs = {'skip_chksum_msg':True}
 		if not obj.__name__ == 'PasswordList':
 			kwargs.update({'key_address_validity_check':False})
-		ret = obj( self.proto, mmgen_addrfile, **kwargs )
-		from ..opts import opt
-		if opt.verbose:
+		ret = obj( self.cfg, self.proto, mmgen_addrfile, **kwargs )
+		if self.cfg.verbose:
 			from ..util import msg,capfirst
 			if ret.al_id.mmtype.name == 'password':
 				msg('Passwd fmt:  {}\nPasswd len:  {}\nID string:   {}'.format(
@@ -99,6 +98,7 @@ class tool_cmd(tool_cmd_base):
 
 		async def process_file(f):
 			return (await CompletedTX(
+				cfg        = self.cfg,
 				filename   = f.name,
 				quiet_open = True)).info.format( terse=terse, sort=tx_sort )
 

+ 6 - 6
mmgen/tool/filecrypt.py

@@ -37,18 +37,18 @@ class tool_cmd(tool_cmd_base):
 	"""
 	def encrypt(self,infile:str,outfile='',hash_preset=''):
 		"encrypt a file"
-		data = get_data_from_file( infile, 'data for encryption', binary=True )
-		enc_d = Crypto().mmgen_encrypt( data, 'data', hash_preset )
+		data = get_data_from_file( self.cfg, infile, 'data for encryption', binary=True )
+		enc_d = Crypto(self.cfg).mmgen_encrypt( data, 'data', hash_preset )
 		if not outfile:
 			outfile = f'{os.path.basename(infile)}.{Crypto.mmenc_ext}'
-		write_data_to_file( outfile, enc_d, 'encrypted data', binary=True )
+		write_data_to_file( self.cfg, outfile, enc_d, 'encrypted data', binary=True )
 		return True
 
 	def decrypt(self,infile:str,outfile='',hash_preset=''):
 		"decrypt a file"
-		enc_d = get_data_from_file( infile, 'encrypted data', binary=True )
+		enc_d = get_data_from_file( self.cfg, infile, 'encrypted data', binary=True )
 		while True:
-			dec_d = Crypto().mmgen_decrypt( enc_d, 'data', hash_preset )
+			dec_d = Crypto(self.cfg).mmgen_decrypt( enc_d, 'data', hash_preset )
 			if dec_d:
 				break
 			msg('Trying again...')
@@ -58,5 +58,5 @@ class tool_cmd(tool_cmd_base):
 			outfile = remove_extension(o,Crypto.mmenc_ext)
 			if outfile == o:
 				outfile += '.dec'
-		write_data_to_file( outfile, dec_d, 'decrypted data', binary=True )
+		write_data_to_file( self.cfg, outfile, dec_d, 'decrypted data', binary=True )
 		return True

+ 7 - 9
mmgen/tool/fileutil.py

@@ -24,7 +24,7 @@ import os
 
 from .common import tool_cmd_base
 from ..globalvars import gc
-from ..util import msg,msg_r,qmsg,die,suf,make_full_path
+from ..util import msg,msg_r,die,suf,make_full_path
 from ..crypto import Crypto
 
 class tool_cmd(tool_cmd_base):
@@ -94,7 +94,6 @@ class tool_cmd(tool_cmd_base):
 		from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes
 		from cryptography.hazmat.backends import default_backend
 
-		from ..opts import opt
 		from ..util2 import parse_bytespec
 
 		def encrypt_worker(wid):
@@ -111,11 +110,11 @@ class tool_cmd(tool_cmd_base):
 				q2.task_done()
 
 		nbytes = parse_bytespec(nbytes)
-		if opt.outdir:
-			outfile = make_full_path( opt.outdir, outfile )
+		if self.cfg.outdir:
+			outfile = make_full_path( self.cfg.outdir, outfile )
 		f = open(outfile,'wb')
 
-		key = Crypto().get_random(32)
+		key = Crypto(self.cfg).get_random(32)
 		q1,q2 = ( Queue(), Queue() )
 
 		for i in range(max(1,threads-2)):
@@ -146,14 +145,13 @@ class tool_cmd(tool_cmd_base):
 
 		if not silent:
 			msg(f'\rRead: {nbytes} bytes')
-			qmsg(f'\r{nbytes} byte{suf(nbytes)} of random data written to file {outfile!r}')
+			self.cfg._util.qmsg(f'\r{nbytes} byte{suf(nbytes)} of random data written to file {outfile!r}')
 
 		return True
 
 	def extract_key_from_geth_wallet( self, wallet_file:str, check_addr=True ):
 		"decrypt the encrypted private key in a Geth keystore wallet, returning the decrypted key"
 		from ..ui import line_input
-		from ..opts import opt
 		from ..proto.eth.misc import extract_key_from_geth_keystore_wallet
-		passwd = line_input( 'Enter passphrase: ', echo=opt.echo_passphrase ).strip().encode()
-		return extract_key_from_geth_keystore_wallet( wallet_file, passwd, check_addr ).hex()
+		passwd = line_input( self.cfg, 'Enter passphrase: ', echo=self.cfg.echo_passphrase ).strip().encode()
+		return extract_key_from_geth_keystore_wallet( self.cfg, wallet_file, passwd, check_addr ).hex()

+ 5 - 5
mmgen/tool/help.py

@@ -131,7 +131,7 @@ def gen_tool_usage():
 	for line in m2.rstrip().split('\n'):
 		yield line.lstrip('\t')
 
-def gen_tool_cmd_usage(mod,cmdname):
+def gen_tool_cmd_usage(cfg,mod,cmdname):
 
 	from ..globalvars import gc
 	from ..util import capfirst
@@ -182,14 +182,14 @@ def gen_tool_cmd_usage(mod,cmdname):
 		for line in docstr.split('\n')[1:]:
 			yield line.lstrip('\t')
 
-def usage(cmdname=None,exit_val=1):
+def usage(cfg,cmdname=None,exit_val=1):
 
 	from ..util import Msg,die
 
 	if cmdname:
 		for mod,cmdlist in main_tool.mods.items():
 			if cmdname in cmdlist:
-				Msg('\n'.join(gen_tool_cmd_usage(mod,cmdname)))
+				Msg('\n'.join(gen_tool_cmd_usage(cfg,mod,cmdname)))
 				break
 		else:
 			die(1,f'{cmdname!r}: no such tool command')
@@ -205,8 +205,8 @@ class tool_cmd(tool_cmd_base):
 
 	def help(self,command_name=''):
 		"display usage information for a single command or all commands"
-		usage(command_name,exit_val=0)
+		usage(self.cfg,command_name,exit_val=0)
 
 	def usage(self,command_name=''):
 		"display usage information for a single command or all commands"
-		usage(command_name,exit_val=0)
+		usage(self.cfg,command_name,exit_val=0)

+ 5 - 6
mmgen/tool/mnemonic.py

@@ -62,7 +62,7 @@ class tool_cmd(tool_cmd_base):
 
 	def _xmr_reduce(self,bytestr):
 		from ..protocol import init_proto
-		proto = init_proto('xmr')
+		proto = init_proto( self.cfg, 'xmr' )
 		if len(bytestr) != proto.privkey_len:
 			die(1,'{!r}: invalid bit length for Monero private key (must be {})'.format(
 				len(bytestr*8),
@@ -72,11 +72,10 @@ class tool_cmd(tool_cmd_base):
 	def _do_random_mn(self,nbytes:int,fmt:str):
 		assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
 		from ..crypto import Crypto
-		randbytes = Crypto().get_random(nbytes)
+		randbytes = Crypto(self.cfg).get_random(nbytes)
 		if fmt == 'xmrseed':
 			randbytes = self._xmr_reduce(randbytes)
-		from ..opts import opt
-		if opt.verbose:
+		if self.cfg.verbose:
 			from ..util import msg
 			msg(f'Seed: {randbytes.hex()}')
 		return self.hex2mn(randbytes.hex(),fmt=fmt)
@@ -111,7 +110,7 @@ class tool_cmd(tool_cmd_base):
 			print_mn: 'print the seed phrase after entry' = False ):
 		"convert an interactively supplied mnemonic seed phrase to a hexadecimal string"
 		from ..mn_entry import mn_entry
-		mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
+		mn = mn_entry( self.cfg, fmt ).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
 		if print_mn:
 			from ..util import msg
 			msg(mn)
@@ -119,7 +118,7 @@ class tool_cmd(tool_cmd_base):
 
 	def mn_stats(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 		"show stats for a mnemonic wordlist"
-		return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist()
+		return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist(self.cfg)
 
 	def mn_printlist(self,
 			fmt: mn_opts_disp = dfl_mnemonic_fmt,

+ 14 - 14
mmgen/tool/rpc.py

@@ -33,11 +33,11 @@ class tool_cmd(tool_cmd_base):
 	async def daemon_version(self):
 		"print coin daemon version"
 		from ..daemon import CoinDaemon
-		from ..globalvars import g
-		d = CoinDaemon( proto=self.proto, test_suite=g.test_suite )
+		d = CoinDaemon( cfg=self.cfg, proto=self.proto, test_suite=self.cfg.test_suite )
 		if self.proto.base_proto == 'Monero':
 			from ..proto.xmr.rpc import MoneroRPCClient
 			r = MoneroRPCClient(
+				cfg    = self.cfg,
 				proto  = self.proto,
 				daemon = d,
 				host   = d.host,
@@ -47,7 +47,7 @@ class tool_cmd(tool_cmd_base):
 				ignore_daemon_version = True )
 		else:
 			from ..rpc import rpc_init
-			r = await rpc_init( self.proto, ignore_daemon_version=True )
+			r = await rpc_init( self.cfg, self.proto, ignore_daemon_version=True )
 		return f'{d.coind_name} version {r.daemon_version} ({r.daemon_version_str})'
 
 	async def getbalance(self,
@@ -56,8 +56,7 @@ class tool_cmd(tool_cmd_base):
 			pager:   'send output to pager' = False ):
 		"list confirmed/unconfirmed, spendable/unspendable balances in tracking wallet"
 		from ..tw.bal import TwGetBalance
-		from ..globalvars import g
-		return (await TwGetBalance(self.proto,minconf,quiet)).format(color=g.color)
+		return (await TwGetBalance(self.cfg,self.proto,minconf,quiet)).format(color=self.cfg.color)
 
 	async def twops(self,
 			obj,pager,reverse,detail,sort,age_fmt,interactive,
@@ -94,7 +93,7 @@ class tool_cmd(tool_cmd_base):
 		"view tracking wallet unspent outputs"
 
 		from ..tw.unspent import TwUnspentOutputs
-		obj = await TwUnspentOutputs(self.proto,minconf=minconf)
+		obj = await TwUnspentOutputs(self.cfg,self.proto,minconf=minconf)
 		return await self.twops(
 			obj,pager,reverse,wide,sort,age_fmt,interactive,
 			show_mmid = show_mmid )
@@ -109,7 +108,7 @@ class tool_cmd(tool_cmd_base):
 			interactive: 'enable interactive operation' = False ):
 		"view transaction history of tracking wallet"
 
-		obj = await TwTxHistory(self.proto,sinceblock=sinceblock)
+		obj = await TwTxHistory(self.cfg,self.proto,sinceblock=sinceblock)
 		return await self.twops(
 			obj,pager,reverse,detail,sort,age_fmt,interactive )
 
@@ -146,7 +145,7 @@ class tool_cmd(tool_cmd_base):
 		assert showused in (0,1,2), f"‘showused’ must have a value of 0, 1 or 2"
 
 		from ..tw.addresses import TwAddresses
-		obj = await TwAddresses(self.proto,minconf=minconf,mmgen_addrs=mmgen_addrs)
+		obj = await TwAddresses(self.cfg,self.proto,minconf=minconf,mmgen_addrs=mmgen_addrs)
 		return await self.twops(
 			obj,pager,reverse,wide,sort,age_fmt,interactive,
 			showcoinaddrs = showcoinaddrs,
@@ -157,7 +156,7 @@ class tool_cmd(tool_cmd_base):
 	async def add_label(self,mmgen_or_coin_addr:str,label:str):
 		"add descriptive label for address in tracking wallet"
 		from ..tw.ctl import TwCtl
-		return await (await TwCtl(self.proto,mode='w')).set_comment(mmgen_or_coin_addr,label)
+		return await (await TwCtl(self.cfg,self.proto,mode='w')).set_comment(mmgen_or_coin_addr,label)
 
 	async def remove_label(self,mmgen_or_coin_addr:str):
 		"remove descriptive label for address in tracking wallet"
@@ -168,7 +167,7 @@ class tool_cmd(tool_cmd_base):
 		"remove an address from tracking wallet"
 		from ..tw.ctl import TwCtl
 		# returns None on failure:
-		ret = await (await TwCtl(self.proto,mode='w')).remove_address(mmgen_or_coin_addr)
+		ret = await (await TwCtl(self.cfg,self.proto,mode='w')).remove_address(mmgen_or_coin_addr)
 		if ret:
 			from ..util import msg
 			msg(f'Address {ret!r} deleted from tracking wallet')
@@ -177,7 +176,7 @@ class tool_cmd(tool_cmd_base):
 	async def resolve_address(self,mmgen_or_coin_addr:str):
 		"resolve an MMGen address in the tracking wallet to a coin address or vice-versa"
 		from ..tw.ctl import TwCtl
-		ret = await (await TwCtl(self.proto,mode='w')).resolve_address( mmgen_or_coin_addr )
+		ret = await (await TwCtl(self.cfg,self.proto,mode='w')).resolve_address( mmgen_or_coin_addr )
 		if ret:
 			from ..util import Msg
 			from ..addr import is_coin_addr
@@ -188,7 +187,7 @@ class tool_cmd(tool_cmd_base):
 	async def rescan_address(self,mmgen_or_coin_addr:str):
 		"rescan an address in the tracking wallet to update its balance"
 		from ..tw.ctl import TwCtl
-		return await (await TwCtl(self.proto,mode='w')).rescan_address( mmgen_or_coin_addr )
+		return await (await TwCtl(self.cfg,self.proto,mode='w')).rescan_address( mmgen_or_coin_addr )
 
 	async def rescan_blockchain(self,
 			start_block: int = None,
@@ -203,7 +202,7 @@ class tool_cmd(tool_cmd_base):
 		  parameter.
 		"""
 		from ..tw.ctl import TwCtl
-		ret = await (await TwCtl(self.proto,mode='w')).rescan_blockchain(start_block,stop_block)
+		ret = await (await TwCtl(self.cfg,self.proto,mode='w')).rescan_blockchain(start_block,stop_block)
 		return True
 
 	async def twexport(self,include_amts=True,pretty=False,prune=False,warn_used=False):
@@ -228,6 +227,7 @@ class tool_cmd(tool_cmd_base):
 		"""
 		from ..tw.json import TwJSON
 		await TwJSON.Export(
+			self.cfg,
 			self.proto,
 			include_amts = include_amts,
 			pretty       = pretty,
@@ -249,5 +249,5 @@ class tool_cmd(tool_cmd_base):
 		  rescan_blockchain’.
 		"""
 		from ..tw.json import TwJSON
-		await TwJSON.Import( self.proto, filename, ignore_checksum=ignore_checksum, batch=batch )
+		await TwJSON.Import( self.cfg, self.proto, filename, ignore_checksum=ignore_checksum, batch=batch )
 		return True

+ 9 - 9
mmgen/tool/util.py

@@ -94,7 +94,7 @@ class tool_cmd(tool_cmd_base):
 			nbytes: 'number of bytes to output' = 32 ):
 		"print 'n' bytes (default 32) of random data in hex format"
 		from ..crypto import Crypto
-		return Crypto().get_random( nbytes ).hex()
+		return Crypto(self.cfg).get_random( nbytes ).hex()
 
 	def hexreverse(self,hexstr:'sstr'):
 		"reverse bytes of a hexadecimal string"
@@ -103,7 +103,7 @@ class tool_cmd(tool_cmd_base):
 	def hexlify(self,infile:str):
 		"convert bytes in file to hexadecimal (use '-' for stdin)"
 		from ..fileutil import get_data_from_file
-		data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
+		data = get_data_from_file( self.cfg, infile, dash=True, quiet=True, binary=True )
 		return data.hex()
 
 	def unhexlify(self,hexstr:'sstr'):
@@ -117,7 +117,7 @@ class tool_cmd(tool_cmd_base):
 		"create hexdump of data from file (use '-' for stdin)"
 		from ..fileutil import get_data_from_file
 		from ..util2 import pretty_hexdump
-		data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
+		data = get_data_from_file( self.cfg, infile, dash=True, quiet=True, binary=True )
 		return pretty_hexdump( data, cols=cols, line_nums=line_nums ).rstrip()
 
 	def unhexdump(self,infile:str):
@@ -127,7 +127,7 @@ class tool_cmd(tool_cmd_base):
 			msvcrt.setmode( sys.stdout.fileno(), os.O_BINARY )
 		from ..fileutil import get_data_from_file
 		from ..util2 import decode_pretty_hexdump
-		hexdata = get_data_from_file( infile, dash=True, quiet=True )
+		hexdata = get_data_from_file( self.cfg, infile, dash=True, quiet=True )
 		return decode_pretty_hexdump(hexdata)
 
 	def hash160(self,hexstr:'sstr'):
@@ -144,7 +144,7 @@ class tool_cmd(tool_cmd_base):
 		from hashlib import sha256
 		if file_input:
 			from ..fileutil import get_data_from_file
-			b = get_data_from_file( data, binary=True )
+			b = get_data_from_file( self.cfg, data, binary=True )
 		elif hex_input:
 			from ..util2 import decode_pretty_hexdump
 			b = decode_pretty_hexdump(data)
@@ -157,7 +157,7 @@ class tool_cmd(tool_cmd_base):
 		from ..util import make_chksum_6
 		from ..fileutil import get_data_from_file
 		return make_chksum_6(
-			get_data_from_file( infile, dash=True, quiet=True, binary=True ))
+			get_data_from_file( self.cfg, infile, dash=True, quiet=True, binary=True ))
 
 	def str2id6(self,string:'sstr'): # retain ignoring of space for backwards compat
 		"generate 6-character MMGen ID for a string, ignoring spaces in string"
@@ -169,7 +169,7 @@ class tool_cmd(tool_cmd_base):
 		from ..util import make_chksum_8
 		from ..fileutil import get_data_from_file
 		return make_chksum_8(
-			get_data_from_file( infile, dash=True, quiet=True, binary=True ))
+			get_data_from_file( self.cfg, infile, dash=True, quiet=True, binary=True ))
 
 	def randb58(self,
 			nbytes: 'number of bytes to output' = 32,
@@ -177,13 +177,13 @@ class tool_cmd(tool_cmd_base):
 		"generate random data (default: 32 bytes) and convert it to base 58"
 		from ..baseconv import baseconv
 		from ..crypto import Crypto
-		return baseconv('b58').frombytes( Crypto().get_random(nbytes), pad=pad, tostr=True )
+		return baseconv('b58').frombytes( Crypto(self.cfg).get_random(nbytes), pad=pad, tostr=True )
 
 	def bytestob58(self,infile:str,pad: 'pad output to this width' = 0):
 		"convert bytes to base 58 (supply data via STDIN)"
 		from ..fileutil import get_data_from_file
 		from ..baseconv import baseconv
-		data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
+		data = get_data_from_file( self.cfg, infile, dash=True, quiet=True, binary=True )
 		return baseconv('b58').frombytes( data, pad=pad, tostr=True )
 
 	def b58tobytes(self,b58_str:'sstr',pad: 'pad output to this width' = 0):

+ 14 - 13
mmgen/tool/wallet.py

@@ -22,7 +22,6 @@ tool.wallet: Wallet routines for the 'mmgen-tool' utility
 
 from .common import tool_cmd_base
 
-from ..opts import opt
 from ..subseed import SubSeedList
 from ..seedsplit import MasterShareIdx
 from ..wallet import Wallet
@@ -30,32 +29,33 @@ from ..wallet import Wallet
 class tool_cmd(tool_cmd_base):
 	"key, address or subseed generation from an MMGen wallet"
 
-	def __init__(self,cmdname=None,proto=None,mmtype=None):
+	def __init__(self,cfg,cmdname=None,proto=None,mmtype=None):
 		self.need_proto = cmdname in ('gen_key','gen_addr')
-		super().__init__(cmdname=cmdname,proto=proto,mmtype=mmtype)
+		super().__init__(cfg,cmdname=cmdname,proto=proto,mmtype=mmtype)
 
 	def _get_seed_file(self,wallet):
 		from ..fileutil import get_seed_file
 		return get_seed_file(
+			cfg     = self.cfg,
 			wallets = [wallet] if wallet else [],
 			nargs   = 1 )
 
 	def get_subseed(self,subseed_idx:str,wallet=''):
 		"get the Seed ID of a single subseed by Subseed Index for default or specified wallet"
-		opt.quiet = True
-		return Wallet(self._get_seed_file(wallet)).seed.subseed(subseed_idx).sid
+		self.cfg.quiet = True
+		return Wallet(self.cfg,self._get_seed_file(wallet)).seed.subseed(subseed_idx).sid
 
 	def get_subseed_by_seed_id(self,seed_id:str,wallet='',last_idx=SubSeedList.dfl_len):
 		"get the Subseed Index of a single subseed by Seed ID for default or specified wallet"
-		opt.quiet = True
-		ret = Wallet(self._get_seed_file(wallet)).seed.subseed_by_seed_id( seed_id, last_idx )
+		self.cfg.quiet = True
+		ret = Wallet(self.cfg,self._get_seed_file(wallet)).seed.subseed_by_seed_id( seed_id, last_idx )
 		return ret.ss_idx if ret else None
 
 	def list_subseeds(self,subseed_idx_range:str,wallet=''):
 		"list a range of subseed Seed IDs for default or specified wallet"
-		opt.quiet = True
+		self.cfg.quiet = True
 		from ..subseed import SubSeedIdxRange
-		return Wallet(self._get_seed_file(wallet)).seed.subseeds.format( *SubSeedIdxRange(subseed_idx_range) )
+		return Wallet(self.cfg,self._get_seed_file(wallet)).seed.subseeds.format( *SubSeedIdxRange(subseed_idx_range) )
 
 	def list_shares(self,
 			share_count: int,
@@ -63,8 +63,8 @@ class tool_cmd(tool_cmd_base):
 			master_share: f'(min:1, max:{MasterShareIdx.max_val}, 0=no master share)' = 0,
 			wallet = '' ):
 		"list the Seed IDs of the shares resulting from a split of default or specified wallet"
-		opt.quiet = True
-		return Wallet(self._get_seed_file(wallet)).seed.split( share_count, id_str, master_share ).format()
+		self.cfg.quiet = True
+		return Wallet(self.cfg,self._get_seed_file(wallet)).seed.split( share_count, id_str, master_share ).format()
 
 	def gen_key(self,mmgen_addr:str,wallet=''):
 		"generate a single WIF key for specified MMGen address from default or specified wallet"
@@ -79,14 +79,15 @@ class tool_cmd(tool_cmd_base):
 		from ..addrlist import AddrList,AddrIdxList
 
 		addr = MMGenID( self.proto, mmgen_addr )
-		opt.quiet = True
-		ss = Wallet(self._get_seed_file(wallet))
+		self.cfg.quiet = True
+		ss = Wallet(self.cfg,self._get_seed_file(wallet))
 
 		if ss.seed.sid != addr.sid:
 			from ..util import die
 			die(1,f'Seed ID of requested address ({addr.sid}) does not match wallet ({ss.seed.sid})')
 
 		d = AddrList(
+			cfg       = self.cfg,
 			proto     = self.proto,
 			seed      = ss.seed,
 			addr_idxs = AddrIdxList(str(addr.idx)),

+ 4 - 5
mmgen/tw/addresses.py

@@ -74,9 +74,9 @@ class TwAddresses(TwView):
 	def coinaddr_list(self):
 		return [d.addr for d in self.data]
 
-	async def __init__(self,proto,minconf=1,mmgen_addrs='',get_data=False):
+	async def __init__(self,cfg,proto,minconf=1,mmgen_addrs='',get_data=False):
 
-		await super().__init__(proto)
+		await super().__init__(cfg,proto)
 
 		self.minconf = NonNegativeInt(minconf)
 
@@ -320,10 +320,9 @@ class TwAddresses(TwView):
 			top = len(data) - 1 if top is None else top )
 
 		if start is not None:
-			from ..opts import opt
 			for d in data[start:]:
 				if d.al_id == al_id:
-					if not d.recvd and (opt.autochg_ignore_labels or not d.comment):
+					if not d.recvd and (self.cfg.autochg_ignore_labels or not d.comment):
 						if d.comment:
 							msg('{} {} {} {}{}'.format(
 								yellow('WARNING: address'),
@@ -362,7 +361,7 @@ class TwAddresses(TwView):
 
 			from ..ui import line_input
 			while True:
-				res = line_input(prompt)
+				res = line_input( self.cfg, prompt )
 				if is_int(res) and 0 < int(res) <= len(addrs):
 					return addrs[int(res)-1]
 				msg(f'{res}: invalid entry')

+ 3 - 3
mmgen/tw/bal.py

@@ -29,10 +29,10 @@ from ..rpc import rpc_init
 
 class TwGetBalance(MMGenObject,metaclass=AsyncInit):
 
-	def __new__(cls,proto,*args,**kwargs):
+	def __new__(cls,cfg,proto,*args,**kwargs):
 		return MMGenObject.__new__(proto.base_proto_subclass(cls,'tw.bal'))
 
-	async def __init__(self,proto,minconf,quiet):
+	async def __init__(self,cfg,proto,minconf,quiet):
 
 		class BalanceInfo(dict):
 			def __init__(self):
@@ -50,7 +50,7 @@ class TwGetBalance(MMGenObject,metaclass=AsyncInit):
 		self.quiet = quiet
 		self.proto = proto
 		self.data = {k:self.balance_info() for k in self.start_labels}
-		self.rpc = await rpc_init(proto)
+		self.rpc = await rpc_init(cfg,proto)
 
 		if minconf < 2 and 'lt_minconf' in self.conf_cols:
 			del self.conf_cols['lt_minconf']

+ 16 - 15
mmgen/tw/ctl.py

@@ -23,8 +23,7 @@ tw.ctl: Tracking wallet control class for the MMGen suite
 import json
 from collections import namedtuple
 
-from ..globalvars import g
-from ..util import msg,msg_r,qmsg,dmsg,suf,die
+from ..util import msg,msg_r,suf,die
 from ..base_obj import AsyncInit
 from ..objmethods import MMGenObject
 from ..obj import TwComment,get_obj
@@ -53,10 +52,10 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 	aggressive_sync = False
 	importing = False
 
-	def __new__(cls,proto,*args,**kwargs):
+	def __new__(cls,cfg,proto,*args,**kwargs):
 		return MMGenObject.__new__(proto.base_proto_subclass(cls,'tw.ctl'))
 
-	async def __init__(self,proto,mode='r',token_addr=None,rpc_ignore_wallet=False):
+	async def __init__(self,cfg,proto,mode='r',token_addr=None,rpc_ignore_wallet=False):
 
 		assert mode in ('r','w','i'), f"{mode!r}: wallet mode must be 'r','w' or 'i'"
 		if mode == 'i':
@@ -64,7 +63,8 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 			mode = 'w'
 
 		# TODO: create on demand - only certain ops require RPC
-		self.rpc = await rpc_init( proto, ignore_wallet=rpc_ignore_wallet )
+		self.cfg = cfg
+		self.rpc = await rpc_init( cfg, proto, ignore_wallet=rpc_ignore_wallet )
 		self.proto = proto
 		self.mode = mode
 		self.desc = self.base_desc = f'{self.proto.name} tracking wallet'
@@ -86,9 +86,9 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 	def init_from_wallet_file(self):
 		import os
 		tw_dir = (
-			os.path.join(g.data_dir) if self.proto.coin == 'BTC' else
+			os.path.join(self.cfg.data_dir) if self.proto.coin == 'BTC' else
 			os.path.join(
-				g.data_dir_root,
+				self.cfg.data_dir_root,
 				'altcoins',
 				self.proto.coin.lower(),
 				('' if self.proto.network == 'mainnet' else 'testnet')
@@ -99,7 +99,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 		check_or_create_dir(tw_dir)
 
 		try:
-			self.orig_data = get_data_from_file(self.tw_fn,quiet=True)
+			self.orig_data = get_data_from_file( self.cfg, self.tw_fn, quiet=True )
 			self.data = json.loads(self.orig_data)
 		except:
 			try: os.stat(self.tw_fn)
@@ -116,7 +116,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 		if self.mode == 'w':
 			import atexit
 			def del_twctl(twctl):
-				dmsg(f'Running exit handler del_twctl() for {twctl!r}')
+				self.cfg._util.dmsg(f'Running exit handler del_twctl() for {twctl!r}')
 				del twctl
 			atexit.register(del_twctl,self)
 
@@ -135,7 +135,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 		"""
 		if getattr(self,'mode',None) == 'w': # mode attr might not exist in this state
 			self.write()
-		elif g.debug:
+		elif self.cfg.debug:
 			msg('read-only wallet, doing nothing')
 
 	def conv_types(self,ad):
@@ -163,7 +163,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 	def get_cached_balance(self,addr,session_cache,data_root):
 		if addr in session_cache:
 			return self.proto.coin_amt(session_cache[addr])
-		if not g.cached_balances:
+		if not self.cfg.cached_balances:
 			return None
 		if addr in data_root and 'balance' in data_root[addr]:
 			return self.proto.coin_amt(data_root[addr]['balance'])
@@ -185,6 +185,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 	def write_changed(self,data,quiet):
 		from ..fileutil import write_data_to_file
 		write_data_to_file(
+			self.cfg,
 			self.tw_fn,
 			data,
 			desc              = f'{self.base_desc} data',
@@ -198,14 +199,14 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 
 	def write(self,quiet=True):
 		if not self.use_tw_file:
-			dmsg("'use_tw_file' is False, doing nothing")
+			self.cfg._util.dmsg("'use_tw_file' is False, doing nothing")
 			return
-		dmsg(f'write(): checking if {self.desc} data has changed')
+		self.cfg._util.dmsg(f'write(): checking if {self.desc} data has changed')
 
 		wdata = json.dumps(self.data)
 		if self.orig_data != wdata:
 			self.write_changed(wdata,quiet=quiet)
-		elif g.debug:
+		elif self.cfg.debug:
 			msg('Data is unchanged\n')
 
 	async def resolve_address(self,addrspec):
@@ -296,7 +297,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
 		async def do_import(address,comment,message):
 			try:
 				res = await self.import_address( address, comment )
-				qmsg(message)
+				self.cfg._util.qmsg(message)
 				return res
 			except Exception as e:
 				die(2,f'\nImport of address {address!r} failed: {e.args[0]!r}')

+ 15 - 14
mmgen/tw/json.py

@@ -15,8 +15,7 @@ tw.json: export and import tracking wallet to JSON format
 import os,json
 from collections import namedtuple
 
-from ..opts import opt
-from ..util import msg,ymsg,fmt,suf,die,make_timestamp,make_chksum_8,compare_or_die
+from ..util import msg,ymsg,fmt,suf,die,make_timestamp,make_chksum_8
 from ..base_obj import AsyncInit
 from ..objmethods import MMGenObject
 from ..rpc import json_encoder
@@ -30,10 +29,11 @@ class TwJSON:
 		pruned = None
 		fn_pfx = 'mmgen-tracking-wallet-dump'
 
-		def __new__(cls,proto,*args,**kwargs):
+		def __new__(cls,cfg,proto,*args,**kwargs):
 			return MMGenObject.__new__(proto.base_proto_subclass(TwJSON,'tw.json',cls.__name__))
 
-		def __init__(self,proto):
+		def __init__(self,cfg,proto):
+			self.cfg = cfg
 			self.proto = proto
 			self.coin = proto.coin_id.lower()
 			self.network = proto.network
@@ -54,7 +54,7 @@ class TwJSON:
 				from ..addrlist import AddrIdxList
 				prune_id = AddrIdxList(idx_list=self.pruned).id_str
 				fn = get_fn(prune_id)
-				if len(fn) > os.statvfs(opt.outdir or os.curdir).f_namemax:
+				if len(fn) > os.statvfs(self.cfg.outdir or os.curdir).f_namemax:
 					fn = get_fn(f'idhash={make_chksum_8(prune_id.encode()).lower()}')
 			else:
 				fn = get_fn(None)
@@ -84,11 +84,11 @@ class TwJSON:
 
 		blockchain_rescan_warning = None
 
-		async def __init__(self,proto,filename,ignore_checksum=False,batch=False):
+		async def __init__(self,cfg,proto,filename,ignore_checksum=False,batch=False):
 
-			super().__init__(proto)
+			super().__init__(cfg,proto)
 
-			self.twctl = await TwCtl( proto, mode='i', rpc_ignore_wallet=True )
+			self.twctl = await TwCtl( cfg, proto, mode='i', rpc_ignore_wallet=True )
 
 			def check_network(data):
 				coin,network = data['network'].split('_')
@@ -108,7 +108,7 @@ class TwJSON:
 			def verify_data(d):
 				check_network(d['data'])
 				check_chksum(d)
-				compare_or_die(
+				self.cfg._util.compare_or_die(
 					val1  = self.mappings_chksum,
 					val2  = d['data']['mappings_checksum'],
 					desc1 = 'computed mappings checksum',
@@ -118,7 +118,7 @@ class TwJSON:
 				return True
 
 			from ..fileutil import get_data_from_file
-			self.data = json.loads(get_data_from_file(filename,quiet=True))
+			self.data = json.loads(get_data_from_file( self.cfg, filename, quiet=True ))
 			self.keys = self.data['data']['entries_keys']
 			self.entries = await self.get_entries()
 
@@ -141,7 +141,7 @@ class TwJSON:
 			msg('\n'+fmt(self.info_msg.strip(),indent='  '))
 
 			from ..ui import keypress_confirm
-			if not keypress_confirm('Continue?'):
+			if not keypress_confirm( self.cfg, 'Continue?' ):
 				msg('Exiting at user request')
 				return False
 
@@ -152,7 +152,7 @@ class TwJSON:
 
 	class Export(Base,metaclass=AsyncInit):
 
-		async def __init__(self,proto,include_amts=True,pretty=False,prune=False,warn_used=False):
+		async def __init__(self,cfg,proto,include_amts=True,pretty=False,prune=False,warn_used=False):
 
 			if prune and not self.can_prune:
 				die(1,f'Pruning not supported for {proto.name} protocol')
@@ -160,12 +160,12 @@ class TwJSON:
 			self.prune = prune
 			self.warn_used = warn_used
 
-			super().__init__(proto)
+			super().__init__(cfg,proto)
 
 			if not include_amts:
 				self.keys.remove('amount')
 
-			self.twctl = await TwCtl( proto )
+			self.twctl = await TwCtl( cfg, proto )
 
 			self.entries = await self.get_entries()
 
@@ -190,6 +190,7 @@ class TwJSON:
 
 			from ..fileutil import write_data_to_file
 			write_data_to_file(
+				cfg     = self.cfg,
 				outfile = self.dump_fn,
 				data    = self.json_dump(
 					{

+ 1 - 1
mmgen/tw/prune.py

@@ -70,7 +70,7 @@ class TwAddressesPrune(TwAddresses):
 			from ..ui import line_input
 			msg('')
 			while True:
-				reply = line_input(prompt).strip()
+				reply = line_input( parent.cfg, prompt ).strip()
 				if reply:
 					from ..addrlist import AddrIdxList
 					from ..obj import get_obj

+ 2 - 2
mmgen/tw/txhistory.py

@@ -42,8 +42,8 @@ class TwTxHistory(TwView):
 	filters = ('show_unconfirmed',)
 	mod_subpath = 'tw.txhistory'
 
-	async def __init__(self,proto,sinceblock=0):
-		await super().__init__(proto)
+	async def __init__(self,cfg,proto,sinceblock=0):
+		await super().__init__(cfg,proto)
 		self.sinceblock = NonNegativeInt( sinceblock if sinceblock >= 0 else self.rpc.blockcount + sinceblock )
 
 	@property

+ 2 - 3
mmgen/tw/unspent.py

@@ -20,7 +20,6 @@
 tw.unspent: Tracking wallet unspent outputs class for the MMGen suite
 """
 
-from ..globalvars import g
 from ..util import msg,suf,fmt
 from ..objmethods import MMGenObject
 from ..obj import (
@@ -77,8 +76,8 @@ class TwUnspentOutputs(TwView):
 			def amt2(self,value):
 				return self.proto.coin_amt(value)
 
-	async def __init__(self,proto,minconf=1,addrs=[]):
-		await super().__init__(proto)
+	async def __init__(self,cfg,proto,minconf=1,addrs=[]):
+		await super().__init__(cfg,proto)
 		self.minconf  = minconf
 		self.addrs    = addrs
 		from ..globalvars import gc

+ 14 - 12
mmgen/tw/view.py

@@ -23,8 +23,6 @@ tw.view: base class for tracking wallet view classes
 import sys,time,asyncio
 from collections import namedtuple
 
-from ..globalvars import g
-from ..opts import opt
 from ..objmethods import Hilite,InitErrors,MMGenObject
 from ..obj import get_obj,MMGenIdx,MMGenList
 from ..color import nocolor,yellow,green,red,blue
@@ -182,15 +180,16 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 		}
 	}
 
-	def __new__(cls,proto,*args,**kwargs):
+	def __new__(cls,cfg,proto,*args,**kwargs):
 		return MMGenObject.__new__(proto.base_proto_subclass(cls,cls.mod_subpath))
 
-	async def __init__(self,proto):
+	async def __init__(self,cfg,proto):
+		self.cfg = cfg
 		self.proto = proto
-		self.rpc = await rpc_init(proto)
+		self.rpc = await rpc_init(cfg,proto)
 		if self.has_wallet:
 			from .ctl import TwCtl
-			self.twctl = await TwCtl(proto,mode='w')
+			self.twctl = await TwCtl(cfg,proto,mode='w')
 		self.amt_keys = {'amt':'iwidth','amt2':'iwidth2'} if self.has_amt2 else {'amt':'iwidth'}
 
 	@property
@@ -283,15 +282,15 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 		user_resized = False
 		while True:
 			ts = get_terminal_size()
-			cols = g.columns or ts.width
+			cols = self.cfg.columns or ts.width
 			lines = ts.height
 			if cols >= min_cols and (min_lines is None or lines >= min_lines):
 				if user_resized:
 					msg_r(CUR_HOME + ERASE_ALL)
 				return _term_dimensions(cols,ts.height)
 			if sys.stdout.isatty():
-				if g.columns and cols < min_cols:
-					die(1,'\n'+fmt(self.twidth_diemsg.format(g.columns,self.desc,min_cols),indent='  '))
+				if self.cfg.columns and cols < min_cols:
+					die(1,'\n'+fmt(self.twidth_diemsg.format(self.cfg.columns,self.desc,min_cols),indent='  '))
 				else:
 					m,dim = (self.twidth_errmsg,min_cols) if cols < min_cols else (self.theight_errmsg,min_lines)
 					get_char_raw( CUR_HOME + ERASE_ALL + fmt( m.format(self.desc,dim), append='' ))
@@ -520,7 +519,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 				self.key_mappings.update(self.scroll_keys[gc.platform])
 			return self.key_mappings
 
-		scroll = self.scroll = g.scroll
+		scroll = self.scroll = self.cfg.scroll
 
 		key_mappings = make_key_mappings(scroll)
 		action_classes = { k: getattr(self,action_map[v[:2]])() for k,v in key_mappings.items() }
@@ -533,7 +532,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 		self.oneshot_msg = ''
 		prompt += '\b'
 
-		clear_screen = '\n\n' if opt.no_blank else CUR_HOME + ('' if scroll else ERASE_ALL)
+		clear_screen = '\n\n' if self.cfg.no_blank else CUR_HOME + ('' if scroll else ERASE_ALL)
 
 		from ..term import get_term,get_char,get_char_raw
 
@@ -581,7 +580,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 
 	def keypress_confirm(self,*args,**kwargs):
 		from ..ui import keypress_confirm
-		if keypress_confirm(*args,no_nl=self.scroll,**kwargs):
+		if keypress_confirm( self.cfg, *args, no_nl=self.scroll, **kwargs ):
 			return True
 		else:
 			if self.scroll:
@@ -620,6 +619,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 			from ..exception import UserNonConfirmation
 			try:
 				write_data_to_file(
+					cfg     = parent.cfg,
 					outfile = outfile,
 					data    = print_hdr + await parent.format(
 						display_type    = output_type,
@@ -654,6 +654,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 			while True:
 				msg_r(parent.blank_prompt if parent.scroll else '\n')
 				ret = line_input(
+					parent.cfg,
 					f'Enter {parent.item_desc} number (or ENTER to return to main menu): ' )
 				if ret == '':
 					if parent.scroll:
@@ -734,6 +735,7 @@ class TwView(MMGenObject,metaclass=AsyncInit):
 
 			from ..ui import line_input
 			res = line_input(
+				parent.cfg,
 				'Enter label text for {} {}: '.format(parent.item_desc,red(f'#{idx}')),
 				insert_txt = cur_comment )
 

+ 6 - 5
mmgen/tx/__init__.py

@@ -33,7 +33,7 @@ def _get_cls_info(clsname,modname,args,kwargs):
 		proto = kwargs['data']['proto']
 	elif 'filename' in kwargs:
 		from .file import MMGenTxFile
-		proto = MMGenTxFile.get_proto( kwargs['filename'], quiet_open=True )
+		proto = MMGenTxFile.get_proto( kwargs['cfg'], kwargs['filename'], quiet_open=True )
 	elif clsname == 'Base':
 		proto = None
 	else:
@@ -52,19 +52,20 @@ def _get_cls_info(clsname,modname,args,kwargs):
 
 	kwargs['proto'] = proto
 
-	return ( proto, clsname, modname, kwargs )
+	return ( kwargs['cfg'], proto, clsname, modname, kwargs )
+
 
 def _get_obj( _clsname, _modname, *args, **kwargs ):
 	"""
 	determine cls/mod/proto and pass them to _base_proto_subclass() to get a transaction instance
 	"""
-	proto,clsname,modname,kwargs = _get_cls_info(_clsname,_modname,args,kwargs)
+	cfg,proto,clsname,modname,kwargs = _get_cls_info(_clsname,_modname,args,kwargs)
 
 	return _base_proto_subclass( clsname, modname, proto )(*args,**kwargs)
 
 async def _get_obj_async( _clsname, _modname, *args, **kwargs ):
 
-	proto,clsname,modname,kwargs = _get_cls_info(_clsname,_modname,args,kwargs)
+	cfg,proto,clsname,modname,kwargs = _get_cls_info(_clsname,_modname,args,kwargs)
 
 	# NB: tracking wallet needed to retrieve the 'symbol' and 'decimals' parameters of token addr
 	# (see twctl:import_token()).
@@ -72,7 +73,7 @@ async def _get_obj_async( _clsname, _modname, *args, **kwargs ):
 	# signing.
 	if proto and proto.tokensym and clsname in ('New','OnlineSigned'):
 		from ..tw.ctl import TwCtl
-		kwargs['twctl'] = await TwCtl(proto)
+		kwargs['twctl'] = await TwCtl(cfg,proto)
 
 	return _base_proto_subclass( clsname, modname, proto )(*args,**kwargs)
 

+ 7 - 6
mmgen/tx/base.py

@@ -26,7 +26,6 @@ from ..obj import (
 )
 from ..addr import MMGenID,CoinAddr
 from ..util import msg,ymsg,fmt,remove_dups,make_timestamp,die
-from ..opts import opt
 
 class MMGenTxIO(MMGenListItem):
 	vout     = ListItemAttr(NonNegativeInt)
@@ -110,6 +109,7 @@ class Base(MMGenObject):
 		desc = 'transaction outputs'
 
 	def __init__(self,*args,**kwargs):
+		self.cfg      = kwargs['cfg']
 		self.inputs   = self.InputList(self)
 		self.outputs  = self.OutputList(self)
 		self.name     = type(self).__name__
@@ -171,13 +171,14 @@ class Base(MMGenObject):
 	def add_comment(self,infile=None):
 		if infile:
 			from ..fileutil import get_data_from_file
-			self.comment = MMGenTxComment(get_data_from_file(infile,'transaction comment'))
+			self.comment = MMGenTxComment(get_data_from_file( self.cfg, infile, 'transaction comment' ))
 		else:
 			from ..ui import keypress_confirm,line_input
 			if keypress_confirm(
+					self.cfg,
 					prompt = 'Edit transaction comment?' if self.comment else 'Add a comment to transaction?',
 					default_yes = False ):
-				res = MMGenTxComment(line_input('Comment: ',insert_txt=self.comment))
+				res = MMGenTxComment(line_input( self.cfg, 'Comment: ', insert_txt=self.comment ))
 				if not res:
 					ymsg('Warning: comment is empty')
 				changed = res != self.comment
@@ -199,11 +200,11 @@ class Base(MMGenObject):
 			fs = fmt(self.non_mmgen_inputs_msg,strip_char='\t',indent=indent).strip()
 			m = fs.format('\n    '.join(non_mmaddrs))
 			if caller in ('txdo','txsign'):
-				if not opt.keys_from_file:
+				if not self.cfg.keys_from_file:
 					die( 'UserOptError', f'\n{indent}ERROR: {m}\n' )
 			else:
 				msg(f'\n{indent}WARNING: {m}\n')
-				if not opt.yes:
+				if not self.cfg.yes:
 					from ..ui import keypress_confirm
-					if not keypress_confirm('Continue?',default_yes=True):
+					if not keypress_confirm( self.cfg, 'Continue?', default_yes=True ):
 						die(1,'Exiting at user request')

+ 4 - 5
mmgen/tx/bump.py

@@ -14,7 +14,6 @@ tx.bump: transaction bump class
 
 from .new import New
 from .completed import Completed
-from ..opts import opt
 from ..util import is_int
 
 class Bump(Completed,New):
@@ -56,14 +55,14 @@ class Bump(Completed,New):
 			else:
 				die(1,'Insufficient funds to bump transaction')
 
-		init_reply = opt.output_to_reduce
+		init_reply = self.cfg.output_to_reduce
 		chg_idx = self.chg_idx
 
 		while True:
 			if init_reply == None:
 				from ..ui import line_input
 				m = 'Choose an output to deduct the fee from (Hit ENTER for the change output): '
-				reply = line_input(m) or 'c'
+				reply = line_input( self.cfg, m ) or 'c'
 			else:
 				reply,init_reply = init_reply,None
 			if chg_idx == None and not is_int(reply):
@@ -79,11 +78,11 @@ class Bump(Completed,New):
 					cm = ' (change output)' if chg_idx == idx else ''
 					prompt = f'Fee will be deducted from output {idx+1}{cm} ({o_amt} {self.coin})'
 					if check_sufficient_funds(o_amt):
-						if opt.yes:
+						if self.cfg.yes:
 							msg(prompt)
 						else:
 							from ..ui import keypress_confirm
-							if not keypress_confirm(prompt+'.  OK?',default_yes=True):
+							if not keypress_confirm( self.cfg, prompt+'.  OK?', default_yes=True ):
 								continue
 						self.bump_output_idx = idx
 						return idx

+ 2 - 2
mmgen/tx/completed.py

@@ -20,11 +20,11 @@ class Completed(Base):
 	"""
 	filename_api = True
 
-	def __init__(self,filename=None,data=None,quiet_open=False,*args,**kwargs):
+	def __init__(self,cfg,filename=None,data=None,quiet_open=False,*args,**kwargs):
 
 		assert (filename or data) and not (filename and data), 'CompletedTX_chk1'
 
-		super().__init__(*args,**kwargs)
+		super().__init__(cfg=cfg,*args,**kwargs)
 
 		if data:
 			data['twctl'] = self.twctl

+ 11 - 10
mmgen/tx/file.py

@@ -20,7 +20,7 @@
 tx.file: Transaction file operations for the MMGen suite
 """
 
-from ..common import *
+from ..util import ymsg,make_chksum_6,die
 from ..obj import MMGenObject,HexStr,MMGenTxID,CoinTxID,MMGenTxComment
 
 class MMGenTxFile(MMGenObject):
@@ -58,12 +58,12 @@ class MMGenTxFile(MMGenObject):
 			return io_list( parent=tx, data=[io(tx.proto,**e) for e in d] )
 
 		from ..fileutil import get_data_from_file
-		tx_data = get_data_from_file(infile,tx.desc+' data',quiet=quiet_open)
+		tx_data = get_data_from_file( tx.cfg, infile, tx.desc+' data', quiet=quiet_open )
 
 		try:
 			desc = 'data'
-			if len(tx_data) > g.max_tx_file_size:
-				die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)' )
+			if len(tx_data) > tx.cfg.max_tx_file_size:
+				die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({tx.cfg.max_tx_file_size} bytes)' )
 			tx_data = tx_data.splitlines()
 			assert len(tx_data) >= 5,'number of lines less than 5'
 			assert len(tx_data[0]) == 6,'invalid length of first line'
@@ -104,10 +104,10 @@ class MMGenTxFile(MMGenObject):
 			tx.chain = metadata.pop(0).lower() if len(metadata) == 5 else 'mainnet'
 
 			from ..protocol import CoinProtocol,init_proto
-			network = CoinProtocol.Base.chain_name_to_network(coin,tx.chain)
+			network = CoinProtocol.Base.chain_name_to_network(tx.cfg,coin,tx.chain)
 
 			desc = 'initialization of protocol'
-			tx.proto = init_proto(coin,network=network,need_amt=True)
+			tx.proto = init_proto( tx.cfg, coin, network=network, need_amt=True )
 			if tokensym:
 				tx.proto.tokensym = tokensym
 
@@ -186,8 +186,8 @@ class MMGenTxFile(MMGenObject):
 
 		self.chksum = make_chksum_6(' '.join(lines))
 		fmt_data = '\n'.join([self.chksum] + lines) + '\n'
-		if len(fmt_data) > g.max_tx_file_size:
-			die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)' )
+		if len(fmt_data) > tx.cfg.max_tx_file_size:
+			die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({tx.cfg.max_tx_file_size} bytes)' )
 		return fmt_data
 
 	def write(self,
@@ -208,6 +208,7 @@ class MMGenTxFile(MMGenObject):
 
 		from ..fileutil import write_data_to_file
 		write_data_to_file(
+			cfg                   = self.tx.cfg,
 			outfile               = self.filename,
 			data                  = self.fmt_data,
 			desc                  = self.tx.desc + add_desc,
@@ -217,8 +218,8 @@ class MMGenTxFile(MMGenObject):
 			ask_write_default_yes = ask_write_default_yes )
 
 	@classmethod
-	def get_proto(cls,filename,quiet_open=False):
+	def get_proto(cls,cfg,filename,quiet_open=False):
 		from . import BaseTX
-		tmp_tx = BaseTX()
+		tmp_tx = BaseTX(cfg=cfg)
 		cls(tmp_tx).parse(filename,metadata_only=True,quiet_open=quiet_open)
 		return tmp_tx.proto

+ 1 - 2
mmgen/tx/info.py

@@ -14,7 +14,6 @@ tx.info: transaction info class
 
 from ..globalvars import gc
 from ..color import red,green,orange
-from ..opts import opt
 from ..util import msg,msg_r
 
 import importlib
@@ -84,7 +83,7 @@ class TxInfo:
 				d = tx.dcoin,
 				c = tx.coin )
 
-			if opt.verbose:
+			if tx.cfg.verbose:
 				yield self.format_verbose_footer()
 
 		return ''.join(gen_view()) # TX label might contain non-ascii chars

+ 35 - 35
mmgen/tx/new.py

@@ -12,13 +12,11 @@
 tx.new: new transaction class
 """
 
-from ..globalvars import *
-from ..opts import opt
 from .base import Base
 from ..globalvars import gc
 from ..color import pink,yellow
 from ..obj import get_obj,MMGenList
-from ..util import msg,qmsg,fmt,die,suf,remove_dups,get_extension
+from ..util import msg,fmt,die,suf,remove_dups,get_extension
 from ..addr import (
 	is_mmgen_id,
 	MMGenAddrType,
@@ -28,7 +26,7 @@ from ..addr import (
 	is_addrlist_id
 )
 
-def mmaddr2coinaddr(mmaddr,ad_w,ad_f,proto):
+def mmaddr2coinaddr(cfg,mmaddr,ad_w,ad_f,proto):
 
 	def wmsg(k):
 		messages = {
@@ -59,7 +57,7 @@ def mmaddr2coinaddr(mmaddr,ad_w,ad_f,proto):
 			if coin_addr:
 				msg(wmsg('addr_in_addrfile_only'))
 				from ..ui import keypress_confirm
-				if not (opt.yes or keypress_confirm('Continue anyway?')):
+				if not (cfg.yes or keypress_confirm( cfg, 'Continue anyway?' )):
 					sys.exit(1)
 			else:
 				die(2,wmsg('addr_not_found'))
@@ -125,8 +123,8 @@ class New(Base):
 			if abs_fee:
 				prompt = '{} TX fee{}: {}{} {} ({} {})\n'.format(
 						desc,
-						(f' (after {opt.fee_adjust:.2f}X adjustment)'
-							if opt.fee_adjust != 1 and desc.startswith('Network-estimated')
+						(f' (after {self.cfg.fee_adjust:.2f}X adjustment)'
+							if self.cfg.fee_adjust != 1 and desc.startswith('Network-estimated')
 								else ''),
 						('','≈')[self.fee_is_approximate],
 						abs_fee.hl(),
@@ -134,11 +132,11 @@ class New(Base):
 						pink(str(self.fee_abs2rel(abs_fee))),
 						self.rel_fee_disp)
 				from ..ui import keypress_confirm
-				if opt.yes or keypress_confirm(prompt+'OK?',default_yes=True):
-					if opt.yes:
+				if self.cfg.yes or keypress_confirm( self.cfg, prompt+'OK?', default_yes=True ):
+					if self.cfg.yes:
 						msg(prompt)
 					return abs_fee
-			fee = line_input(self.usr_fee_prompt)
+			fee = line_input( self.cfg, self.usr_fee_prompt )
 			desc = 'User-selected'
 
 	# we don't know fee yet, so perform preliminary check with fee == 0
@@ -153,19 +151,19 @@ class New(Base):
 
 	async def get_fee_from_user(self,have_estimate_fail=[]):
 
-		if opt.fee:
+		if self.cfg.fee:
 			desc = 'User-selected'
-			start_fee = opt.fee
+			start_fee = self.cfg.fee
 		else:
 			desc = 'Network-estimated ({}, {} conf{})'.format(
-				opt.fee_estimate_mode.upper(),
-				pink(str(opt.fee_estimate_confs)),
-				suf(opt.fee_estimate_confs) )
+				self.cfg.fee_estimate_mode.upper(),
+				pink(str(self.cfg.fee_estimate_confs)),
+				suf(self.cfg.fee_estimate_confs) )
 			fee_per_kb,fe_type = await self.get_rel_fee_from_network()
 
 			if fee_per_kb < 0:
 				if not have_estimate_fail:
-					msg(self.fee_fail_fs.format(c=opt.fee_estimate_confs,t=fe_type))
+					msg(self.fee_fail_fs.format(c=self.cfg.fee_estimate_confs,t=fe_type))
 					have_estimate_fail.append(True)
 				start_fee = None
 			else:
@@ -181,7 +179,7 @@ class New(Base):
 		arg,amt = arg_in.split(',',1) if ',' in arg_in else (arg_in,None)
 
 		if is_mmgen_id(self.proto,arg):
-			coin_addr = mmaddr2coinaddr(arg,ad_w,ad_f,self.proto)
+			coin_addr = mmaddr2coinaddr(self.cfg,arg,ad_w,ad_f,self.proto)
 		elif is_coin_addr(self.proto,arg):
 			coin_addr = CoinAddr(self.proto,arg)
 		elif is_mmgen_addrtype(self.proto,arg) or is_addrlist_id(self.proto,arg):
@@ -189,7 +187,7 @@ class New(Base):
 				die(2,f'Change addresses not supported for {self.proto.name} protocol')
 
 			from ..tw.addresses import TwAddresses
-			al = await TwAddresses(self.proto,get_data=True)
+			al = await TwAddresses(self.cfg,self.proto,get_data=True)
 
 			if is_mmgen_addrtype(self.proto,arg):
 				arg = MMGenAddrType(self.proto,arg)
@@ -253,9 +251,9 @@ class New(Base):
 		from ..fileutil import check_infile
 		for addrfile in addrfiles:
 			check_infile(addrfile)
-			ad_f.add(AddrList( self.proto, addrfile ))
+			ad_f.add(AddrList( self.cfg, self.proto, addrfile ))
 
-		ad_w = await TwAddrData(self.proto,twctl=self.twctl)
+		ad_w = await TwAddrData(self.cfg,self.proto,twctl=self.twctl)
 
 		await self.process_cmd_args(cmd_args,ad_f,ad_w)
 
@@ -271,6 +269,7 @@ class New(Base):
 	def confirm_autoselected_addr(self,chg):
 		from ..ui import keypress_confirm
 		if not keypress_confirm(
+				self.cfg,
 				'Using {a} as {b} address. OK?'.format(
 					a = chg.mmid.hl(),
 					b = 'single output' if len(self.outputs) == 1 else 'change' ),
@@ -279,9 +278,10 @@ class New(Base):
 
 	async def warn_chg_addr_used(self,chg):
 		from ..tw.addresses import TwAddresses
-		if (await TwAddresses(self.proto,get_data=True)).is_used(chg.addr):
+		if (await TwAddresses(self.cfg,self.proto,get_data=True)).is_used(chg.addr):
 			from ..ui import keypress_confirm
 			if not keypress_confirm(
+					self.cfg,
 					'{a} {b} {c}\n{d}'.format(
 						a = yellow(f'Requested change address'),
 						b = (chg.mmid or chg.addr).hl(),
@@ -297,7 +297,7 @@ class New(Base):
 		prompt = 'Enter a range or space-separated list of outputs to spend: '
 		from ..ui import line_input
 		while True:
-			reply = line_input(prompt).strip()
+			reply = line_input( self.cfg, prompt ).strip()
 			if reply:
 				from ..addrlist import AddrIdxList
 				selected = get_obj(AddrIdxList, fmt_str=','.join(reply.split()) )
@@ -315,7 +315,7 @@ class New(Base):
 			return idx + 1
 
 		def get_uo_nums():
-			for addr in opt.inputs.split(','):
+			for addr in self.cfg.inputs.split(','):
 				if is_mmgen_id(self.proto,addr):
 					attr = 'twmmid'
 				elif is_coin_addr(self.proto,addr):
@@ -354,7 +354,7 @@ class New(Base):
 	async def get_inputs_from_user(self,outputs_sum):
 
 		while True:
-			us_f = self.select_unspent_cmdline if opt.inputs else self.select_unspent
+			us_f = self.select_unspent_cmdline if self.cfg.inputs else self.select_unspent
 			sel_nums = us_f(self.twuo.data)
 
 			msg(f'Selected output{suf(sel_nums)}: {{}}'.format(' '.join(str(n) for n in sel_nums)))
@@ -373,8 +373,8 @@ class New(Base):
 			if funds_left >= 0:
 				p = self.final_inputs_ok_msg(funds_left)
 				from ..ui import keypress_confirm
-				if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
-					if opt.yes:
+				if self.cfg.yes or keypress_confirm( self.cfg, p+'. OK?', default_yes=True ):
+					if self.cfg.yes:
 						msg(p)
 					return funds_left
 			else:
@@ -386,21 +386,21 @@ class New(Base):
 
 		from ..tw.unspent import TwUnspentOutputs
 
-		if opt.comment_file:
-			self.add_comment(opt.comment_file)
+		if self.cfg.comment_file:
+			self.add_comment(self.cfg.comment_file)
 
 		twuo_addrs = await self.get_input_addrs_from_cmdline()
 
-		self.twuo = await TwUnspentOutputs(self.proto,minconf=opt.minconf,addrs=twuo_addrs)
+		self.twuo = await TwUnspentOutputs(self.cfg,self.proto,minconf=self.cfg.minconf,addrs=twuo_addrs)
 		await self.twuo.get_data()
 
 		if not do_info:
 			await self.get_outputs_from_cmdline(cmd_args)
 
 		from ..ui import do_license_msg
-		do_license_msg()
+		do_license_msg(self.cfg)
 
-		if not opt.inputs:
+		if not self.cfg.inputs:
 			await self.twuo.view_filter_and_sort()
 
 		self.twuo.display_total()
@@ -422,7 +422,7 @@ class New(Base):
 
 		self.update_change_output(funds_left)
 
-		if not opt.yes:
+		if not self.cfg.yes:
 			self.add_comment()  # edits an existing comment
 
 		await self.create_serialized(locktime=locktime) # creates self.txid too
@@ -432,12 +432,12 @@ class New(Base):
 		self.chain = self.proto.chain_name
 		self.check_fee()
 
-		qmsg('Transaction successfully created')
+		self.cfg._util.qmsg('Transaction successfully created')
 
 		from . import UnsignedTX
-		new = UnsignedTX(data=self.__dict__)
+		new = UnsignedTX(cfg=self.cfg,data=self.__dict__)
 
-		if not opt.yes:
+		if not self.cfg.yes:
 			new.info.view_with_prompt('View transaction details?')
 
 		del new.twuo.twctl

+ 3 - 3
mmgen/tx/online.py

@@ -22,11 +22,11 @@ class OnlineSigned(Signed):
 		return _base_proto_subclass('Status','status',self.proto)(self)
 
 	def confirm_send(self):
-		from ..opts import opt
 		from ..util import msg
 		from ..ui import confirm_or_raise
 		confirm_or_raise(
-			message = '' if opt.quiet else 'Once this transaction is sent, there’s no taking it back!',
+			cfg     = self.cfg,
+			message = '' if self.cfg.quiet else 'Once this transaction is sent, there’s no taking it back!',
 			action  = f'broadcast this transaction to the {self.proto.coin} {self.proto.network.upper()} network',
-			expect  = 'YES' if opt.quiet or opt.yes else 'YES, I REALLY WANT TO DO THIS' )
+			expect  = 'YES' if self.cfg.quiet or self.cfg.yes else 'YES, I REALLY WANT TO DO THIS' )
 		msg('Sending transaction')

+ 28 - 24
mmgen/tx/sign.py

@@ -20,9 +20,8 @@
 tx.sign: Sign a transaction generated by 'mmgen-txcreate'
 """
 
-from ..globalvars import g,gc
-from ..opts import opt
-from ..util import msg,vmsg,qmsg,suf,fmt,die,remove_dups,get_extension
+from ..globalvars import gc
+from ..util import msg,suf,fmt,die,remove_dups,get_extension
 from ..obj import MMGenList
 from ..addr import MMGenAddrType
 from ..addrlist import AddrIdxList,KeyAddrList
@@ -38,14 +37,14 @@ def get_seed_for_seed_id(sid,infiles,saved_seeds):
 	subseeds_checked = False
 	while True:
 		if infiles:
-			seed = Wallet(infiles.pop(0),ignore_in_fmt=True).seed
+			seed = Wallet(cfg,infiles.pop(0),ignore_in_fmt=True).seed
 		elif subseeds_checked == False:
 			seed = saved_seeds[list(saved_seeds)[0]].subseed_by_seed_id(sid,print_msg=True)
 			subseeds_checked = True
 			if not seed: continue
-		elif opt.in_fmt:
-			qmsg(f'Need seed data for Seed ID {sid}')
-			seed = Wallet().seed
+		elif cfg.in_fmt:
+			cfg._util.qmsg(f'Need seed data for Seed ID {sid}')
+			seed = Wallet(cfg).seed
 			msg(f'User input produced Seed ID {seed.sid}')
 			if not seed.sid == sid: # TODO: add test
 				seed = seed.subseed_by_seed_id(sid,print_msg=True)
@@ -60,7 +59,7 @@ def get_seed_for_seed_id(sid,infiles,saved_seeds):
 def generate_kals_for_mmgen_addrs(need_keys,infiles,saved_seeds,proto):
 	mmids = [e.mmid for e in need_keys]
 	sids = remove_dups((i.sid for i in mmids),quiet=True)
-	vmsg(f"Need seed{suf(sids)}: {' '.join(sids)}")
+	cfg._util.vmsg(f"Need seed{suf(sids)}: {' '.join(sids)}")
 	def gen_kals():
 		for sid in sids:
 			# Returns only if seed is found
@@ -69,6 +68,7 @@ def generate_kals_for_mmgen_addrs(need_keys,infiles,saved_seeds,proto):
 				idx_list = [i.idx for i in mmids if i.sid == sid and i.mmtype == id_str]
 				if idx_list:
 					yield KeyAddrList(
+						cfg       = cfg,
 						proto     = proto,
 						seed      = seed,
 						addr_idxs = AddrIdxList(idx_list=idx_list),
@@ -83,7 +83,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
 	desc,src_desc = (
 		('key-address file','From key-address file:') if keyaddr_list else
 		('seed(s)','Generated from seed:') )
-	qmsg(f'Checking {gc.proj_name} -> {tx.proto.coin} address mappings for {src} (from {desc})')
+	cfg._util.qmsg(f'Checking {gc.proj_name} -> {tx.proto.coin} address mappings for {src} (from {desc})')
 	d = (
 		MMGenList([keyaddr_list]) if keyaddr_list else
 		generate_kals_for_mmgen_addrs(need_keys,infiles,saved_seeds,tx.proto) )
@@ -104,52 +104,56 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
 							{{'tx file:':<23}} {{e.mmid}} -> {{e.addr}}
 							""").strip())
 	if new_keys:
-		vmsg(f'Added {len(new_keys)} wif key{suf(new_keys)} from {desc}')
+		cfg._util.vmsg(f'Added {len(new_keys)} wif key{suf(new_keys)} from {desc}')
 	return new_keys
 
 def _pop_matching_fns(args,cmplist): # strips found args
 	return list(reversed([args.pop(args.index(a)) for a in reversed(args) if get_extension(a) in cmplist]))
 
-def get_tx_files(opt,args):
+def get_tx_files(cfg,args):
 	from .unsigned import Unsigned
 	ret = _pop_matching_fns(args,[Unsigned.ext])
 	if not ret:
 		die(1,'You must specify a raw transaction file!')
 	return ret
 
-def get_seed_files(opt,args):
+def get_seed_files(cfg,args):
 	# favor unencrypted seed sources first, as they don't require passwords
 	ret = _pop_matching_fns( args, get_wallet_extensions('unenc') )
 	from ..filename import find_file_in_dir
-	wf = find_file_in_dir(get_wallet_cls('mmgen'),g.data_dir) # Make this the first encrypted ss in the list
+	wf = find_file_in_dir(get_wallet_cls('mmgen'),cfg.data_dir) # Make this the first encrypted ss in the list
 	if wf:
 		ret.append(wf)
 	ret += _pop_matching_fns( args, get_wallet_extensions('enc') )
-	if not (ret or opt.mmgen_keys_from_file or opt.keys_from_file): # or opt.use_wallet_dat
+	if not (ret or cfg.mmgen_keys_from_file or cfg.keys_from_file): # or cfg.use_wallet_dat
 		die(1,'You must specify a seed or key source!')
 	return ret
 
-def get_keyaddrlist(proto,opt):
-	if opt.mmgen_keys_from_file:
-		return KeyAddrList(proto,opt.mmgen_keys_from_file)
+def get_keyaddrlist(cfg,proto):
+	if cfg.mmgen_keys_from_file:
+		return KeyAddrList( cfg, proto, cfg.mmgen_keys_from_file )
 	return None
 
-def get_keylist(proto,opt):
-	if opt.keys_from_file:
+def get_keylist(cfg,proto):
+	if cfg.keys_from_file:
 		from ..fileutil import get_lines_from_file
-		return get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
+		return get_lines_from_file( cfg, cfg.keys_from_file, 'key-address data', trim_comments=True )
 	return None
 
-async def txsign(tx,seed_files,kl,kal,tx_num_str=''):
+async def txsign(cfg_parm,tx,seed_files,kl,kal,tx_num_str=''):
 
 	keys = MMGenList() # list of AddrListEntry objects
 	non_mmaddrs = tx.get_non_mmaddrs('inputs')
 
+	global cfg
+	cfg = cfg_parm
+
 	if non_mmaddrs:
 		tx.check_non_mmgen_inputs(caller='txsign',non_mmaddrs=non_mmaddrs)
 		tmp = KeyAddrList(
-			proto = tx.proto,
-			addrlist = non_mmaddrs,
+			cfg         = cfg,
+			proto       = tx.proto,
+			addrlist    = non_mmaddrs,
 			skip_chksum = True )
 		if kl:
 			tmp.add_wifs(kl)
@@ -162,7 +166,7 @@ async def txsign(tx,seed_files,kl,kal,tx_num_str=''):
 				sep + sep.join(missing) ))
 		keys += tmp.data
 
-	if opt.mmgen_keys_from_file:
+	if cfg.mmgen_keys_from_file:
 		keys += add_keys(tx,'inputs',keyaddr_list=kal)
 		add_keys(tx,'outputs',keyaddr_list=kal)
 

+ 16 - 15
mmgen/ui.py

@@ -14,32 +14,32 @@ ui: Interactive user interface functions for the MMGen suite
 
 import sys,os
 
-from .globalvars import g,gc
-from .opts import opt
-from .util import msg,msg_r,Msg,dmsg,die
+from .globalvars import gc
+from .util import msg,msg_r,Msg,die
 
-def confirm_or_raise(message,action,expect='YES',exit_msg='Exiting at user request'):
+def confirm_or_raise(cfg,message,action,expect='YES',exit_msg='Exiting at user request'):
 	if message:
 		msg(message)
 	if line_input(
+			cfg,
 			(f'{action}  ' if action[0].isupper() else f'Are you sure you want to {action}?\n') +
 			f'Type uppercase {expect!r} to confirm: '
 		).strip() != expect:
 		die( 'UserNonConfirmation', exit_msg )
 
-def get_words_from_user(prompt):
-	words = line_input(prompt, echo=opt.echo_passphrase).split()
-	if g.debug:
+def get_words_from_user(cfg,prompt):
+	words = line_input( cfg, prompt, echo=cfg.echo_passphrase ).split()
+	if cfg.debug:
 		msg('Sanitized input: [{}]'.format(' '.join(words)))
 	return words
 
-def get_data_from_user(desc='data'): # user input MUST be UTF-8
-	data = line_input(f'Enter {desc}: ',echo=opt.echo_passphrase)
-	if g.debug:
+def get_data_from_user(cfg,desc='data'): # user input MUST be UTF-8
+	data = line_input( cfg, f'Enter {desc}: ', echo=cfg.echo_passphrase )
+	if cfg.debug:
 		msg(f'User input: [{data}]')
 	return data
 
-def line_input(prompt,echo=True,insert_txt='',hold_protect=True):
+def line_input(cfg,prompt,echo=True,insert_txt='',hold_protect=True):
 	"""
 	multi-line prompts OK
 	one-line prompts must begin at beginning of line
@@ -62,7 +62,7 @@ def line_input(prompt,echo=True,insert_txt='',hold_protect=True):
 		from .term import kb_hold_protect
 		kb_hold_protect()
 
-	if g.test_suite_popen_spawn:
+	if cfg.test_suite_popen_spawn:
 		msg(prompt)
 		sys.stderr.flush()
 		reply = os.read(0,4096).decode().rstrip('\n') # strip NL to mimic behavior of input()
@@ -83,6 +83,7 @@ def line_input(prompt,echo=True,insert_txt='',hold_protect=True):
 	return reply.strip()
 
 def keypress_confirm(
+	cfg,
 	prompt,
 	default_yes     = False,
 	verbose         = False,
@@ -94,7 +95,7 @@ def keypress_confirm(
 
 	nl = f'\r{" "*len(prompt)}\r' if no_nl else '\n'
 
-	if g.accept_defaults:
+	if cfg.accept_defaults:
 		msg(prompt)
 		return default_yes
 
@@ -136,9 +137,9 @@ def do_pager(text):
 		Msg(text+end_msg)
 	set_vt100()
 
-def do_license_msg(immed=False):
+def do_license_msg(cfg,immed=False):
 
-	if opt.quiet or g.no_license or opt.yes or not g.stdin_tty:
+	if cfg.quiet or cfg.no_license or cfg.yes or not cfg.stdin_tty:
 		return
 
 	import mmgen.contrib.license as gpl

+ 56 - 57
mmgen/util.py

@@ -23,8 +23,7 @@ util: Frequently-used variables, classes and utility functions for the MMGen sui
 import sys,os,time,re
 
 from .color import *
-from .globalvars import g,gc,gv
-from .opts import opt
+from .globalvars import gv,gc
 
 ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
 
@@ -32,6 +31,61 @@ hexdigits = '0123456789abcdefABCDEF'
 hexdigits_uc = '0123456789ABCDEF'
 hexdigits_lc = '0123456789abcdef'
 
+def noop(*args,**kwargs):
+	pass
+
+class Util:
+
+	def __init__(self,cfg):
+
+		self.cfg = cfg
+
+		if cfg.quiet:
+			self.qmsg = self.qmsg_r = noop
+		else:
+			self.qmsg = msg
+			self.qmsg_r = msg_r
+
+		if cfg.verbose:
+			self.vmsg = msg
+			self.vmsg_r = msg_r
+			self.Vmsg = Msg
+			self.Vmsg_r = Msg_r
+		else:
+			self.vmsg = self.vmsg_r = self.Vmsg = self.Vmsg_r = noop
+
+		self.dmsg = msg if cfg.debug else noop
+
+		if cfg.pager:
+			from .ui import do_pager
+			self.stdout_or_pager = do_pager
+		else:
+			self.stdout_or_pager = Msg_r
+
+	def compare_chksums(self,chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False):
+
+		if not chk1 == chk2:
+			fs = "{} ERROR: {} checksum ({}) doesn't match {} checksum ({})"
+			m = fs.format((hdr+':\n   ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1)
+			if die_on_fail:
+				die(3,m)
+			else:
+				if verbose or self.cfg.verbose:
+					msg(m)
+				return False
+
+		if self.cfg.verbose:
+			msg(f'{capfirst(desc1)} checksum OK ({chk1})')
+
+		return True
+
+	def compare_or_die(self, val1, desc1, val2, desc2, e='Error'):
+		if val1 != val2:
+			die(3,f"{e}: {desc2} ({val2}) doesn't match {desc1} ({val1})")
+		if self.cfg.debug:
+			msg(f'{capfirst(desc2)} OK ({val2})')
+		return True
+
 if gc.platform == 'win':
 	def msg_r(s):
 		try:
@@ -85,34 +139,6 @@ def bmsg(s):
 def pumsg(s):
 	msg(purple(s))
 
-def qmsg(s):
-	if not opt.quiet:
-		msg(s)
-
-def qmsg_r(s):
-	if not opt.quiet:
-		msg_r(s)
-
-def vmsg(s,force=False):
-	if opt.verbose or force:
-		msg(s)
-
-def vmsg_r(s,force=False):
-	if opt.verbose or force:
-		msg_r(s)
-
-def Vmsg(s,force=False):
-	if opt.verbose or force:
-		Msg(s)
-
-def Vmsg_r(s,force=False):
-	if opt.verbose or force:
-		Msg_r(s)
-
-def dmsg(s):
-	if opt.debug:
-		msg(s)
-
 def mmsg(*args):
 	for d in args:
 		Msg(repr(d))
@@ -319,26 +345,6 @@ def strip_comments(lines):
 	pat = re.compile('#.*')
 	return [m for m in [pat.sub('',l).rstrip() for l in lines] if m != '']
 
-def compare_chksums(chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False):
-
-	if not chk1 == chk2:
-		fs = "{} ERROR: {} checksum ({}) doesn't match {} checksum ({})"
-		m = fs.format((hdr+':\n   ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1)
-		if die_on_fail:
-			die(3,m)
-		else:
-			vmsg(m,force=verbose)
-			return False
-
-	vmsg(f'{capfirst(desc1)} checksum OK ({chk1})')
-	return True
-
-def compare_or_die(val1, desc1, val2, desc2, e='Error'):
-	if val1 != val2:
-		die(3,f"{e}: {desc2} ({val2}) doesn't match {desc1} ({val1})")
-	dmsg(f'{capfirst(desc2)} OK ({val2})')
-	return True
-
 def make_full_path(outdir,outfile):
 	return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
 
@@ -377,13 +383,6 @@ class oneshot_warning_group(oneshot_warning):
 	def __init__(self,wcls,div=None,fmt_args=[],reverse=False):
 		self.do(getattr(self,wcls),div,fmt_args,reverse)
 
-def stdout_or_pager(s):
-	if opt.pager:
-		from .ui import do_pager
-		do_pager(s)
-	else:
-		Msg_r(s)
-
 def get_subclasses(cls,names=False):
 	def gen(cls):
 		for i in cls.__subclasses__():

+ 5 - 5
mmgen/util2.py

@@ -13,7 +13,7 @@ util2: Less frequently-used variables, classes and utility functions for the MMG
 """
 
 import re,time
-from .util import msg,qmsg,suf,hexdigits
+from .util import msg,suf,hexdigits
 
 def die_wait(delay,ev=0,s=''):
 	assert isinstance(delay,int)
@@ -36,12 +36,12 @@ def removeprefix(s,pfx): # workaround for pre-Python 3.9
 def removesuffix(s,sfx): # workaround for pre-Python 3.9
 	return s[:-len(sfx)] if s.endswith(sfx) else s
 
-def get_keccak(cached_ret=[]):
+# called with no arguments by pyethereum.utils:
+def get_keccak(cfg=None,cached_ret=[]):
 
 	if not cached_ret:
-		from .opts import opt
-		if getattr(opt,'use_internal_keccak_module',False):
-			qmsg('Using internal keccak module by user request')
+		if cfg and cfg.use_internal_keccak_module:
+			cfg._util.qmsg('Using internal keccak module by user request')
 			from .contrib.keccak import keccak_256
 		else:
 			try:

+ 10 - 9
mmgen/wallet/__init__.py

@@ -15,8 +15,6 @@ wallet.__init__: wallet class initializer
 import importlib
 from collections import namedtuple
 
-from ..globalvars import g
-from ..opts import opt
 from ..util import die,get_extension
 from ..objmethods import MMGenObject
 from ..seed import Seed
@@ -101,6 +99,7 @@ def _get_me(modname):
 	return MMGenObject.__new__( getattr( importlib.import_module(f'mmgen.wallet.{modname}'), 'wallet' ) )
 
 def Wallet(
+	cfg,
 	fn            = None,
 	ss            = None,
 	seed_bin      = None,
@@ -111,31 +110,31 @@ def Wallet(
 	in_fmt        = None,
 	passwd_file   = None ):
 
-	in_fmt = in_fmt or opt.in_fmt
+	in_fmt = in_fmt or cfg.in_fmt
 
 	ss_out = (
 		get_wallet_data(
-			fmt_code    = opt.out_fmt,
+			fmt_code    = cfg.out_fmt,
 			die_on_fail = True ).type
-		if opt.out_fmt else None )
+		if cfg.out_fmt else None )
 
 	if seed or seed_bin:
 		me = _get_me( ss_out or 'mmgen' ) # default to native wallet format
-		me.seed = seed or Seed(seed_bin=seed_bin)
+		me.seed = seed or Seed( cfg, seed_bin=seed_bin )
 		me.op = 'new'
 	elif ss:
 		me = _get_me( ss.type if passchg else (ss_out or 'mmgen') )
 		me.seed = ss.seed
 		me.ss_in = ss
 		me.op = 'pwchg_new' if passchg else 'conv'
-	elif fn or opt.hidden_incog_input_params:
+	elif fn or cfg.hidden_incog_input_params:
 		if fn:
 			wd = get_wallet_data(ext=get_extension(fn),die_on_fail=True)
 			if in_fmt and (not ignore_in_fmt) and in_fmt not in wd.fmt_codes:
 				die(1,f'{in_fmt}: --in-fmt parameter does not match extension of input file')
 			me = _get_me( wd.type )
 		else:
-			fn = ','.join(opt.hidden_incog_input_params.split(',')[:-1]) # permit comma in filename
+			fn = ','.join(cfg.hidden_incog_input_params.split(',')[:-1]) # permit comma in filename
 			me = _get_me( 'incog_hidden' )
 		from ..filename import MMGenFile
 		me.infile = MMGenFile( fn, subclass=type(me) )
@@ -145,9 +144,11 @@ def Wallet(
 		me.op = 'pwchg_old' if passchg else 'old'
 	else: # called with no arguments: initialize with random seed
 		me = _get_me( ss_out or 'mmgen' ) # default to native wallet format
-		me.seed = Seed()
+		me.seed = Seed(cfg)
 		me.op = 'new'
 
+	me.cfg = cfg
+
 	me.__init__(
 		in_data     = in_data,
 		passwd_file = passwd_file )

+ 10 - 10
mmgen/wallet/base.py

@@ -14,9 +14,7 @@ wallet.base: wallet base class
 
 import os
 
-from ..globalvars import g
-from ..opts import opt
-from ..util import msg,qmsg,die
+from ..util import msg,die
 from ..objmethods import MMGenObject
 from . import Wallet,wallet_data,get_wallet_cls
 
@@ -45,7 +43,7 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 		in_data       = None,
 		passwd_file   = None ):
 
-		self.passwd_file = passwd_file or opt.passwd_file
+		self.passwd_file = passwd_file or self.cfg.passwd_file
 		self.ssdata = self.WalletData()
 		self.msg = {}
 		self.in_data = in_data
@@ -57,7 +55,7 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 		if hasattr(self,'seed'):
 			self._encrypt()
 			return
-		elif hasattr(self,'infile') or self.in_data or not g.stdin_tty:
+		elif hasattr(self,'infile') or self.in_data or not self.cfg.stdin_tty:
 			self._deformat_once()
 			self._decrypt_retry()
 		else:
@@ -66,7 +64,7 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 			self._deformat_retry()
 			self._decrypt_retry()
 
-		qmsg('Valid {} for Seed ID {}{}'.format(
+		self.cfg._util.qmsg('Valid {} for Seed ID {}{}'.format(
 			self.desc,
 			self.seed.sid.hl(),
 			(f', seed length {self.seed.bitlen}' if self.seed.bitlen != 256 else '')
@@ -76,6 +74,7 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 		if hasattr(self,'infile'):
 			from ..fileutil import get_data_from_file
 			self.fmt_data = get_data_from_file(
+				self.cfg,
 				self.infile.name,
 				self.desc,
 				binary = self.file_mode=='binary' )
@@ -86,7 +85,7 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 
 	def _get_data_from_user(self,desc):
 		from ..ui import get_data_from_user
-		return get_data_from_user(desc)
+		return get_data_from_user( self.cfg, desc )
 
 	def _deformat_once(self):
 		self._get_data()
@@ -118,16 +117,17 @@ class wallet(MMGenObject,metaclass=WalletMeta):
 		}
 
 		if outdir:
-			# write_data_to_file(): outfile with absolute path overrides opt.outdir
+			# write_data_to_file(): outfile with absolute path overrides self.cfg.outdir
 			of = os.path.abspath(os.path.join(outdir,self._filename()))
 
 		from ..fileutil import write_data_to_file
 		write_data_to_file(
+			self.cfg,
 			of if outdir else self._filename(),
 			self.fmt_data,
 			**kwargs )
 
 	def check_usr_seed_len(self,bitlen=None):
 		chk = bitlen or self.seed.bitlen
-		if opt.seed_len and opt.seed_len != chk:
-			die(1,f'ERROR: requested seed length ({opt.seed_len}) doesn’t match seed length of source ({chk})')
+		if self.cfg.seed_len and self.cfg.seed_len != chk:
+			die(1,f'ERROR: requested seed length ({self.cfg.seed_len}) doesn’t match seed length of source ({chk})')

+ 10 - 11
mmgen/wallet/brain.py

@@ -12,8 +12,7 @@
 wallet.brain: brainwallet wallet class
 """
 
-from ..opts import opt
-from ..util import msg,qmsg,qmsg_r
+from ..util import msg
 from ..color import yellow
 from .enc import wallet
 from .seed import Seed
@@ -26,7 +25,7 @@ class wallet(wallet):
 
 	def get_bw_params(self):
 		# already checked
-		a = opt.brain_params.split(',')
+		a = self.cfg.brain_params.split(',')
 		return int(a[0]),a[1]
 
 	def _deformat(self):
@@ -35,25 +34,25 @@ class wallet(wallet):
 
 	def _decrypt(self):
 		d = self.ssdata
-		if opt.brain_params:
+		if self.cfg.brain_params:
 			bw_seed_len,d.hash_preset = self.get_bw_params()
 		else:
-			if not opt.seed_len:
-				qmsg(f'Using default seed length of {yellow(str(Seed.dfl_len))} bits\n'
+			if not self.cfg.seed_len:
+				self.cfg._util.qmsg(f'Using default seed length of {yellow(str(Seed.dfl_len))} bits\n'
 					+ 'If this is not what you want, use the --seed-len option' )
 			self._get_hash_preset()
-			bw_seed_len = opt.seed_len or Seed.dfl_len
-		qmsg_r('Hashing brainwallet data.  Please wait...')
+			bw_seed_len = self.cfg.seed_len or Seed.dfl_len
+		self.cfg._util.qmsg_r('Hashing brainwallet data.  Please wait...')
 		# Use buflen arg of scrypt.hash() to get seed of desired length
 		seed = self.crypto.scrypt_hash_passphrase(
 			self.brainpasswd.encode(),
 			b'',
 			d.hash_preset,
 			buflen = bw_seed_len // 8 )
-		qmsg('Done')
-		self.seed = Seed(seed)
+		self.cfg._util.qmsg('Done')
+		self.seed = Seed( self.cfg, seed )
 		msg(f'Seed ID: {self.seed.sid}')
-		qmsg('Check this value against your records')
+		self.cfg._util.qmsg('Check this value against your records')
 		return True
 
 	def _format(self):

+ 10 - 12
mmgen/wallet/dieroll.py

@@ -13,8 +13,6 @@ wallet.dieroll: dieroll wallet class
 """
 
 import time
-from ..globalvars import g
-from ..opts import opt
 from ..util import msg,msg_r,die,fmt,remove_whitespace
 from ..util2 import block_format
 from ..seed import Seed
@@ -53,16 +51,16 @@ class wallet(wallet):
 		seed_len = rmap[len(d)]
 		seed_bytes = bc.tobytes( d, pad='seed' )[-seed_len:]
 
-		if self.interactive_input and opt.usr_randchars:
+		if self.interactive_input and self.cfg.usr_randchars:
 			from ..ui import keypress_confirm
-			if keypress_confirm(self.user_entropy_prompt):
+			if keypress_confirm( self.cfg, self.user_entropy_prompt ):
 				from ..crypto import Crypto
-				seed_bytes = Crypto().add_user_random(
+				seed_bytes = Crypto(self.cfg).add_user_random(
 					rand_bytes = seed_bytes,
 					desc       = 'gathered from your die rolls' )
 				self.desc += ' plus user-supplied entropy'
 
-		self.seed = Seed(seed_bytes)
+		self.seed = Seed( self.cfg, seed_bytes )
 		self.ssdata.hexseed = seed_bytes.hex()
 
 		self.check_usr_seed_len()
@@ -70,9 +68,9 @@ class wallet(wallet):
 
 	def _get_data_from_user(self,desc):
 
-		if not g.stdin_tty:
+		if not self.cfg.stdin_tty:
 			from ..ui import get_data_from_user
-			return get_data_from_user(desc)
+			return get_data_from_user( self.cfg, desc )
 
 		bc = baseconv('b6d')
 
@@ -88,23 +86,23 @@ class wallet(wallet):
 
 		CUR_HIDE = '\033[?25l'
 		CUR_SHOW = '\033[?25h'
-		cr = '\n' if g.test_suite else '\r'
+		cr = '\n' if self.cfg.test_suite else '\r'
 		prompt_fs = f'\b\b\b   {cr}Enter die roll #{{}}: {CUR_SHOW}'
-		clear_line = '' if g.test_suite else '\r' + ' ' * 25
+		clear_line = '' if self.cfg.test_suite else '\r' + ' ' * 25
 		invalid_msg = CUR_HIDE + cr + 'Invalid entry' + ' ' * 11
 
 		from ..term import get_char
 		def get_digit(n):
 			p = prompt_fs
 			while True:
-				time.sleep(g.short_disp_timeout)
+				time.sleep(self.cfg.short_disp_timeout)
 				ch = get_char(p.format(n),num_bytes=1)
 				if ch in bc.digits:
 					msg_r(CUR_HIDE + ' OK')
 					return ch
 				else:
 					msg_r(invalid_msg)
-					sleep = g.err_disp_timeout
+					sleep = self.cfg.err_disp_timeout
 					p = clear_line + prompt_fs
 
 		dierolls,n = [],1

+ 16 - 17
mmgen/wallet/enc.py

@@ -13,15 +13,14 @@ wallet.enc: encrypted wallet base class
 """
 
 from ..globalvars import gc
-from ..opts import opt
-from ..util import msg,qmsg,make_chksum_8
+from ..util import msg,make_chksum_8
 from .base import wallet
 
 class wallet(wallet):
 
 	def __init__(self,*args,**kwargs):
 		from mmgen.crypto import Crypto
-		self.crypto = Crypto()
+		self.crypto = Crypto(self.cfg)
 		return super().__init__(*args,**kwargs)
 
 	def _decrypt_retry(self):
@@ -45,19 +44,19 @@ class wallet(wallet):
 	def _get_hash_preset(self,add_desc=''):
 		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'):
 			old_hp = self.ss_in.ssdata.hash_preset
-			if opt.keep_hash_preset:
+			if self.cfg.keep_hash_preset:
 				hp = old_hp
-				qmsg(f'Reusing hash preset {hp!r} at user request')
-			elif opt.hash_preset:
-				hp = opt.hash_preset
-				qmsg(f'Using hash preset {hp!r} requested on command line')
+				self.cfg._util.qmsg(f'Reusing hash preset {hp!r} at user request')
+			elif self.cfg.hash_preset:
+				hp = self.cfg.hash_preset
+				self.cfg._util.qmsg(f'Using hash preset {hp!r} requested on command line')
 			else: # Prompt, using old value as default
-				hp = self._get_hash_preset_from_user(old_hp,add_desc)
-			if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
-				qmsg('Hash preset {}'.format( 'unchanged' if hp == old_hp else f'changed to {hp!r}' ))
-		elif opt.hash_preset:
-			hp = opt.hash_preset
-			qmsg(f'Using hash preset {hp!r} requested on command line')
+				hp = self._get_hash_preset_from_user( old_preset=old_hp, add_desc=add_desc )
+			if (not self.cfg.keep_hash_preset) and self.op == 'pwchg_new':
+				self.cfg._util.qmsg('Hash preset {}'.format( 'unchanged' if hp == old_hp else f'changed to {hp!r}' ))
+		elif self.cfg.hash_preset:
+			hp = self.cfg.hash_preset
+			self.cfg._util.qmsg(f'Using hash preset {hp!r} requested on command line')
 		else:
 			hp = self._get_hash_preset_from_user(
 				old_preset = gc.dfl_hash_preset,
@@ -83,13 +82,13 @@ class wallet(wallet):
 
 		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd'):
 			old_pw = self.ss_in.ssdata.passwd
-			if opt.keep_passphrase:
+			if self.cfg.keep_passphrase:
 				d.passwd = old_pw
-				qmsg('Reusing passphrase at user request')
+				self.cfg._util.qmsg('Reusing passphrase at user request')
 			else:
 				d.passwd = self._get_new_passphrase()
 				if self.op == 'pwchg_new':
-					qmsg('Passphrase {}'.format( 'unchanged' if d.passwd == old_pw else 'changed' ))
+					self.cfg._util.qmsg('Passphrase {}'.format( 'unchanged' if d.passwd == old_pw else 'changed' ))
 		else:
 			d.passwd = self._get_new_passphrase()
 

+ 15 - 17
mmgen/wallet/incog_base.py

@@ -12,10 +12,8 @@
 wallet.incog_base: incognito wallet base class
 """
 
-from ..globalvars import g
-from ..opts import opt
 from ..seed import Seed
-from ..util import msg,vmsg,qmsg,make_chksum_8
+from ..util import msg,make_chksum_8
 from .enc import wallet
 
 class wallet(wallet):
@@ -40,18 +38,18 @@ class wallet(wallet):
 		return (
 			self.crypto.aesctr_iv_len
 			+ self.crypto.salt_len
-			+ (0 if opt.old_incog_fmt else self.crypto.hincog_chk_len)
+			+ (0 if self.cfg.old_incog_fmt else self.crypto.hincog_chk_len)
 			+ seed_len//8 )
 
 	def _incog_data_size_chk(self):
 		# valid sizes: 56, 64, 72
 		dlen = len(self.fmt_data)
-		seed_len = opt.seed_len or Seed.dfl_len
+		seed_len = self.cfg.seed_len or Seed.dfl_len
 		valid_dlen = self._get_incog_data_len(seed_len)
 		if dlen == valid_dlen:
 			return True
 		else:
-			if opt.old_incog_fmt:
+			if self.cfg.old_incog_fmt:
 				msg('WARNING: old-style incognito format requested.  Are you sure this is correct?')
 			msg(f'Invalid incognito data size ({dlen} bytes) for this seed length ({seed_len} bits)')
 			msg(f'Valid data size for this seed length: {valid_dlen} bytes')
@@ -63,7 +61,7 @@ class wallet(wallet):
 
 	def _encrypt (self):
 		self._get_first_pw_and_hp_and_encrypt_seed()
-		if opt.old_incog_fmt:
+		if self.cfg.old_incog_fmt:
 			die(1,'Writing old-format incognito wallets is unsupported')
 		d = self.ssdata
 		crypto = self.crypto
@@ -71,8 +69,8 @@ class wallet(wallet):
 		d.iv = crypto.get_random( crypto.aesctr_iv_len )
 		d.iv_id = self._make_iv_chksum(d.iv)
 		msg(f'New Incog Wallet ID: {d.iv_id}')
-		qmsg('Make a record of this value')
-		vmsg('\n  ' + self.msg['record_incog_id'].strip()+'\n')
+		self.cfg._util.qmsg('Make a record of this value')
+		self.cfg._util.vmsg('\n  ' + self.msg['record_incog_id'].strip()+'\n')
 
 		d.salt = crypto.get_random( crypto.salt_len )
 		seed_key = crypto.make_key(
@@ -95,7 +93,7 @@ class wallet(wallet):
 			desc        = 'incog wrapper key' )
 
 		d.key_id = make_chksum_8(d.wrapper_key)
-		vmsg(f'Key ID: {d.key_id}')
+		self.cfg._util.vmsg(f'Key ID: {d.key_id}')
 
 		d.target_data_len = self._get_incog_data_len(self.seed.bitlen)
 
@@ -128,8 +126,8 @@ class wallet(wallet):
 		d.incog_id       = self._make_iv_chksum(d.iv)
 		d.enc_incog_data = self.fmt_data[self.crypto.aesctr_iv_len:]
 		msg(f'Incog Wallet ID: {d.incog_id}')
-		qmsg('Check this value against your records')
-		vmsg('\n  ' + self.msg['check_incog_id'].strip()+'\n')
+		self.cfg._util.qmsg('Check this value against your records')
+		self.cfg._util.vmsg('\n  ' + self.msg['check_incog_id'].strip()+'\n')
 
 		return True
 
@@ -137,7 +135,7 @@ class wallet(wallet):
 		chk,seed = data[:8],data[8:]
 		from hashlib import sha256
 		if sha256(seed).digest()[:8] == chk:
-			qmsg('Passphrase{} are correct'.format( self.msg['decrypt_params'].format('and') ))
+			self.cfg._util.qmsg('Passphrase{} are correct'.format( self.msg['decrypt_params'].format('and') ))
 			return seed
 		else:
 			msg('Incorrect passphrase{}'.format( self.msg['decrypt_params'].format('or') ))
@@ -146,7 +144,7 @@ class wallet(wallet):
 	def _verify_seed_oldfmt(self,seed):
 		m = f'Seed ID: {make_chksum_8(seed)}.  Is the Seed ID correct?'
 		from ..ui import keypress_confirm
-		if keypress_confirm(m, True):
+		if keypress_confirm( self.cfg, m, True ):
 			return seed
 		else:
 			return False
@@ -179,9 +177,9 @@ class wallet(wallet):
 			hash_preset = d.hash_preset,
 			desc        = 'main key' )
 
-		qmsg(f'Key ID: {make_chksum_8(seed_key)}')
+		self.cfg._util.qmsg(f'Key ID: {make_chksum_8(seed_key)}')
 
-		verify_seed_func = getattr( self, '_verify_seed_'+ ('oldfmt' if opt.old_incog_fmt else 'newfmt') )
+		verify_seed_func = getattr( self, '_verify_seed_'+ ('oldfmt' if self.cfg.old_incog_fmt else 'newfmt') )
 
 		seed = verify_seed_func(
 			crypto.decrypt_seed(
@@ -191,7 +189,7 @@ class wallet(wallet):
 				key_id   = '' ))
 
 		if seed:
-			self.seed = Seed(seed)
+			self.seed = Seed( self.cfg, seed )
 			msg(f'Seed ID: {self.seed.sid}')
 			return True
 		else:

+ 14 - 13
mmgen/wallet/incog_hidden.py

@@ -15,9 +15,8 @@ wallet.incog_hidden: hidden incognito wallet class
 import os
 
 from ..globalvars import gc
-from ..opts import opt
 from ..seed import Seed
-from ..util import msg,dmsg,qmsg,die,compare_or_die,capfirst
+from ..util import msg,die,capfirst
 from ..util2 import parse_bytespec
 from .incog_base import wallet
 
@@ -49,7 +48,7 @@ class wallet(wallet):
 	}
 
 	def _get_hincog_params(self,wtype):
-		a = getattr(opt,'hidden_incog_'+ wtype +'_params').split(',')
+		a = getattr(self.cfg,'hidden_incog_'+ wtype +'_params').split(',')
 		return ','.join(a[:-1]),int(a[-1]) # permit comma in filename
 
 	def _check_valid_offset(self,fn,action):
@@ -68,10 +67,10 @@ class wallet(wallet):
 		d = self.ssdata
 		d.hincog_offset = self._get_hincog_params('input')[1]
 
-		qmsg(f'Getting hidden incog data from file {self.infile.name!r}')
+		self.cfg._util.qmsg(f'Getting hidden incog data from file {self.infile.name!r}')
 
 		# Already sanity-checked:
-		d.target_data_len = self._get_incog_data_len(opt.seed_len or Seed.dfl_len)
+		d.target_data_len = self._get_incog_data_len(self.cfg.seed_len or Seed.dfl_len)
 		self._check_valid_offset(self.infile,'read')
 
 		flgs = os.O_RDONLY|os.O_BINARY if gc.platform == 'win' else os.O_RDONLY
@@ -79,13 +78,13 @@ class wallet(wallet):
 		os.lseek(fh,int(d.hincog_offset),os.SEEK_SET)
 		self.fmt_data = os.read(fh,d.target_data_len)
 		os.close(fh)
-		qmsg(f'Data read from file {self.infile.name!r} at offset {d.hincog_offset}')
+		self.cfg._util.qmsg(f'Data read from file {self.infile.name!r} at offset {d.hincog_offset}')
 
 	# overrides method in Wallet
 	def write_to_file(self):
 		d = self.ssdata
 		self._format()
-		compare_or_die(
+		self.cfg._util.compare_or_die(
 			val1  = d.target_data_len,
 			desc1 = 'target data length',
 			val2  = len(self.fmt_data),
@@ -94,8 +93,8 @@ class wallet(wallet):
 		k = ('output','input')[self.op=='pwchg_new']
 		fn,d.hincog_offset = self._get_hincog_params(k)
 
-		if opt.outdir and not os.path.dirname(fn):
-			fn = os.path.join(opt.outdir,fn)
+		if self.cfg.outdir and not os.path.dirname(fn):
+			fn = os.path.join(self.cfg.outdir,fn)
 
 		check_offset = True
 		try:
@@ -103,18 +102,19 @@ class wallet(wallet):
 		except:
 			from ..ui import keypress_confirm,line_input
 			if keypress_confirm(
+					self.cfg,
 					f'Requested file {fn!r} does not exist.  Create?',
 					default_yes = True ):
 				min_fsize = d.target_data_len + d.hincog_offset
 				msg('\n  ' + self.msg['choose_file_size'].strip().format(min_fsize)+'\n')
 				while True:
-					fsize = parse_bytespec(line_input('Enter file size: '))
+					fsize = parse_bytespec(line_input( self.cfg, 'Enter file size: ' ))
 					if fsize >= min_fsize:
 						break
 					msg(f'File size must be an integer no less than {min_fsize}')
 
 				from ..tool.fileutil import tool_cmd
-				tool_cmd().rand2file(fn,str(fsize))
+				tool_cmd(self.cfg).rand2file(fn,str(fsize))
 				check_offset = False
 			else:
 				die(1,'Exiting at user request')
@@ -122,16 +122,17 @@ class wallet(wallet):
 		from ..filename import MMGenFile
 		f = MMGenFile(fn,subclass=type(self),write=True)
 
-		dmsg('{} data len {}, offset {}'.format(
+		self.cfg._util.dmsg('{} data len {}, offset {}'.format(
 			capfirst(self.desc),
 			d.target_data_len,
 			d.hincog_offset ))
 
 		if check_offset:
 			self._check_valid_offset(f,'write')
-			if not opt.quiet:
+			if not self.cfg.quiet:
 				from ..ui import confirm_or_raise
 				confirm_or_raise(
+					self.cfg,
 					message = '',
 					action  = f'alter file {f.name!r}' )
 

+ 17 - 19
mmgen/wallet/mmgen.py

@@ -14,10 +14,8 @@ wallet.mmgen: MMGen native wallet class
 
 import os
 
-from ..globalvars import g
-from ..opts import opt
 from ..seed import Seed
-from ..util import msg,qmsg,make_timestamp,make_chksum_6,split_into_cols,is_chksum_6,compare_chksums
+from ..util import msg,make_timestamp,make_chksum_6,split_into_cols,is_chksum_6
 from ..obj import MMGenWalletLabel,get_obj
 from ..baseconv import baseconv
 
@@ -28,9 +26,9 @@ class wallet(wallet):
 	desc = 'MMGen wallet'
 
 	def __init__(self,*args,**kwargs):
-		if opt.label:
+		if self.cfg.label:
 			self.label = MMGenWalletLabel(
-				opt.label,
+				self.cfg.label,
 				msg = "Error in option '--label'" )
 		else:
 			self.label = None
@@ -43,7 +41,7 @@ class wallet(wallet):
 			'for no label' )
 		from ..ui import line_input
 		while True:
-			ret = line_input(prompt)
+			ret = line_input( self.cfg, prompt )
 			if ret:
 				lbl = get_obj(MMGenWalletLabel,s=ret)
 				if lbl:
@@ -57,19 +55,19 @@ class wallet(wallet):
 	def _get_label(self):
 		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'):
 			old_lbl = self.ss_in.ssdata.label
-			if opt.keep_label:
+			if self.cfg.keep_label:
 				lbl = old_lbl
-				qmsg('Reusing label {} at user request'.format( lbl.hl2(encl='‘’') ))
+				self.cfg._util.qmsg('Reusing label {} at user request'.format( lbl.hl2(encl='‘’') ))
 			elif self.label:
 				lbl = self.label
-				qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
+				self.cfg._util.qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
 			else: # Prompt, using old value as default
 				lbl = self._get_label_from_user(old_lbl)
-			if (not opt.keep_label) and self.op == 'pwchg_new':
-				qmsg('Label {}'.format( 'unchanged' if lbl == old_lbl else f'changed to {lbl!r}' ))
+			if (not self.cfg.keep_label) and self.op == 'pwchg_new':
+				self.cfg._util.qmsg('Label {}'.format( 'unchanged' if lbl == old_lbl else f'changed to {lbl!r}' ))
 		elif self.label:
 			lbl = self.label
-			qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
+			self.cfg._util.qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
 		else:
 			lbl = self._get_label_from_user()
 		self.ssdata.label = lbl
@@ -110,7 +108,7 @@ class wallet(wallet):
 				return False
 
 			chk = make_chksum_6(' '.join(lines[1:]))
-			if not compare_chksums(lines[0],'master',chk,'computed',
+			if not self.cfg._util.compare_chksums(lines[0],'master',chk,'computed',
 						hdr='For wallet master checksum',verbose=True):
 				return False
 
@@ -132,9 +130,9 @@ class wallet(wallet):
 		hpdata = lines[3].split()
 
 		d.hash_preset = hp = hpdata[0][:-1]  # a string!
-		qmsg(f'Hash preset of wallet: {hp!r}')
-		if opt.hash_preset and opt.hash_preset != hp:
-			qmsg(f'Warning: ignoring user-requested hash preset {opt.hash_preset!r}')
+		self.cfg._util.qmsg(f'Hash preset of wallet: {hp!r}')
+		if self.cfg.hash_preset and self.cfg.hash_preset != hp:
+			self.cfg._util.qmsg(f'Warning: ignoring user-requested hash preset {self.cfg.hash_preset!r}')
 
 		hash_params = tuple(map(int,hpdata[1:]))
 
@@ -152,7 +150,7 @@ class wallet(wallet):
 				msg(f'Invalid format for {key} in {self.desc}: {l}')
 				return False
 
-			if not compare_chksums(chk,key,
+			if not self.cfg._util.compare_chksums(chk,key,
 					make_chksum_6(b58_val),'computed checksum',verbose=True):
 				return False
 
@@ -169,11 +167,11 @@ class wallet(wallet):
 		d = self.ssdata
 		# Needed for multiple transactions with {}-txsign
 		d.passwd = self._get_passphrase(
-			add_desc = os.path.basename(self.infile.name) if opt.quiet else '' )
+			add_desc = os.path.basename(self.infile.name) if self.cfg.quiet else '' )
 		key = self.crypto.make_key( d.passwd, d.salt, d.hash_preset )
 		ret = self.crypto.decrypt_seed( d.enc_seed, key, d.seed_id, d.key_id )
 		if ret:
-			self.seed = Seed(ret)
+			self.seed = Seed( self.cfg, ret )
 			return True
 		else:
 			return False

+ 4 - 4
mmgen/wallet/mmhex.py

@@ -14,7 +14,7 @@ wallet.mmhex: MMGen hexadecimal file wallet class
 
 from ..util import make_chksum_6,split_into_cols
 from ..seed import Seed
-from ..util import msg,vmsg_r,is_chksum_6,is_hex_str,compare_chksums
+from ..util import msg,is_chksum_6,is_hex_str
 from .unenc import wallet
 
 class wallet(wallet):
@@ -52,12 +52,12 @@ class wallet(wallet):
 			msg(f'{hstr!r}: not a hexadecimal string, in {desc}')
 			return False
 
-		vmsg_r(f'Validating {desc} checksum...')
+		self.cfg._util.vmsg_r(f'Validating {desc} checksum...')
 
-		if not compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True):
+		if not self.cfg._util.compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True):
 			return False
 
-		self.seed = Seed(bytes.fromhex(hstr))
+		self.seed = Seed( self.cfg, bytes.fromhex(hstr) )
 		self.ssdata.chksum = chk
 		self.ssdata.hexseed = hstr
 

+ 7 - 8
mmgen/wallet/mnemonic.py

@@ -12,9 +12,8 @@
 wallet.mnemonic: MMGen mnemonic wallet base class
 """
 
-from ..globalvars import g
 from ..baseconv import baseconv
-from ..util import msg,compare_or_die
+from ..util import msg
 from ..seed import Seed
 from .unenc import wallet
 
@@ -31,14 +30,14 @@ class wallet(wallet):
 
 	def _get_data_from_user(self,desc):
 
-		if not g.stdin_tty:
+		if not self.cfg.stdin_tty:
 			from ..ui import get_data_from_user
-			return get_data_from_user(desc)
+			return get_data_from_user( self.cfg, desc )
 
 		mn_len = self._choose_seedlen( self.mn_lens )
 
 		from ..mn_entry import mn_entry
-		return mn_entry(self.wl_id).get_mnemonic_from_user(mn_len)
+		return mn_entry( self.cfg, self.wl_id ).get_mnemonic_from_user(mn_len)
 
 	def _format(self):
 
@@ -49,7 +48,7 @@ class wallet(wallet):
 		rev = bc.tohex( mn, 'seed' )
 
 		# Internal error, so just die on fail
-		compare_or_die( rev, 'recomputed seed', hexseed, 'original', e='Internal error' )
+		self.cfg._util.compare_or_die( rev, 'recomputed seed', hexseed, 'original', e='Internal error' )
 
 		self.ssdata.mnemonic = mn
 		self.fmt_data = ' '.join(mn) + '\n'
@@ -78,14 +77,14 @@ class wallet(wallet):
 			return False
 
 		# Internal error, so just die:
-		compare_or_die(
+		self.cfg._util.compare_or_die(
 			val1  = ' '.join(rev),
 			val2  = ' '.join(mn),
 			desc1 = 'recomputed mnemonic',
 			desc2 = 'original mnemonic',
 			e     = 'Internal error' )
 
-		self.seed = Seed(bytes.fromhex(hexseed))
+		self.seed = Seed( self.cfg, bytes.fromhex(hexseed) )
 		self.ssdata.mnemonic = mn
 
 		self.check_usr_seed_len()

+ 1 - 1
mmgen/wallet/plainhex.py

@@ -36,7 +36,7 @@ class wallet(wallet):
 			msg(f'Invalid data length ({len(d)}) in {desc}')
 			return False
 
-		self.seed = Seed(bytes.fromhex(d))
+		self.seed = Seed( self.cfg, bytes.fromhex(d) )
 		self.ssdata.hexseed = d
 
 		self.check_usr_seed_len()

+ 4 - 4
mmgen/wallet/seed.py

@@ -12,7 +12,7 @@
 wallet.seed: seed file wallet class
 """
 
-from ..util import msg,vmsg_r,make_chksum_6,split_into_cols,is_chksum_6,compare_chksums
+from ..util import msg,make_chksum_6,split_into_cols,is_chksum_6
 from ..baseconv import baseconv,is_b58_str
 from ..seed import Seed
 from .unenc import wallet
@@ -48,9 +48,9 @@ class wallet(wallet):
 			msg(f'{b!r}: not a base 58 string, in {desc}')
 			return False
 
-		vmsg_r(f'Validating {desc} checksum...')
+		self.cfg._util.vmsg_r(f'Validating {desc} checksum...')
 
-		if not compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True):
+		if not self.cfg._util.compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True):
 			return False
 
 		ret = baseconv('b58').tobytes(b,pad='seed')
@@ -59,7 +59,7 @@ class wallet(wallet):
 			msg(f'Invalid base-58 encoded seed: {val}')
 			return False
 
-		self.seed = Seed(ret)
+		self.seed = Seed( self.cfg, ret )
 		self.ssdata.chksum = a
 		self.ssdata.b58seed = b
 

+ 3 - 3
mmgen/wallet/unenc.py

@@ -12,7 +12,6 @@
 wallet.unenc: unencrypted wallet base class
 """
 
-from ..globalvars import g
 from ..color import blue,yellow
 from ..util import msg,msg_r,capfirst,is_int
 from .base import wallet
@@ -41,7 +40,7 @@ class wallet(wallet):
 				r = get_char('\r'+prompt)
 				if is_int(r) and 1 <= int(r) <= len(ok_lens):
 					break
-			msg_r(('\r','\n')[g.test_suite] + ' '*len(prompt) + '\r')
+			msg_r(('\r','\n')[self.cfg.test_suite] + ' '*len(prompt) + '\r')
 			return ok_lens[int(r)-1]
 
 		msg('{} {}'.format(
@@ -54,7 +53,8 @@ class wallet(wallet):
 			prompt = self.choose_seedlen_confirm.format(usr_len)
 			from ..ui import keypress_confirm
 			if keypress_confirm(
+					self.cfg,
 					prompt,
 					default_yes = True,
-					no_nl       = not g.test_suite ):
+					no_nl       = not self.cfg.test_suite ):
 				return usr_len

+ 44 - 33
mmgen/xmrwallet.py

@@ -22,9 +22,6 @@ xmrwallet.py - MoneroWalletOps class
 
 import os,re,time,json
 from collections import namedtuple
-
-from .globalvars import g
-from .opts import opt
 from .objmethods import MMGenObject,Hilite,InitErrors
 from .obj import CoinTxID
 from .color import red,yellow,green,blue,cyan,pink,orange
@@ -42,7 +39,6 @@ from .util import (
 	make_timestr,
 	make_chksum_6,
 	capfirst,
-	stdout_or_pager,
 )
 from .seed import SeedID
 from .protocol import init_proto
@@ -203,6 +199,7 @@ class MoneroMMGenTX:
 			)
 			from .fileutil import write_data_to_file
 			write_data_to_file(
+				cfg                   = self.cfg,
 				outfile               = fn,
 				data                  = out,
 				desc                  = 'MoneroMMGenTX data',
@@ -216,7 +213,10 @@ class MoneroMMGenTX:
 			assert not args, 'Non-keyword args not permitted'
 
 			d = namedtuple('kwargs_tuple',kwargs)(**kwargs)
-			proto = init_proto( 'xmr', network=d.network, need_amt=True )
+			self.cfg = d.cfg
+
+			proto = init_proto( self.cfg, 'xmr', network=d.network, need_amt=True )
+
 			now = int(time.time())
 
 			self.data = self.xmrwallet_tx_data(
@@ -237,12 +237,13 @@ class MoneroMMGenTX:
 
 	class Signed(Base):
 
-		def __init__(self,fn):
+		def __init__(self,cfg,fn):
 			from .fileutil import get_data_from_file
+			self.cfg = cfg
 			self.fn = fn
-			d_wrap = json.loads(get_data_from_file(fn))['MoneroMMGenTX']
+			d_wrap = json.loads(get_data_from_file( cfg, fn ))['MoneroMMGenTX']
 			d = self.xmrwallet_tx_data(**d_wrap['data'])
-			proto = init_proto( 'xmr', network=d.network, need_amt=True )
+			proto = init_proto( cfg, 'xmr', network=d.network, need_amt=True )
 			self.data = self.xmrwallet_tx_data(
 				op             = d.op,
 				create_time    = d.create_time,
@@ -283,7 +284,7 @@ class MoneroWalletOps:
 
 		opts = ('wallet_dir',)
 
-		def __init__(self,uarg_tuple,uopt_tuple):
+		def __init__(self,cfg,uarg_tuple,uopt_tuple):
 
 			def gen_classes():
 				for cls in type(self).__mro__:
@@ -291,6 +292,7 @@ class MoneroWalletOps:
 					if cls.__name__ == 'base':
 						break
 
+			self.cfg = cfg
 			classes = tuple(gen_classes())
 			self.opts = tuple(set(opt for cls in classes for opt in cls.opts))
 
@@ -314,7 +316,7 @@ class MoneroWalletOps:
 					cls.check_uopts(self)
 					id_cur = id(cls.check_uopts)
 
-			self.proto = init_proto( 'xmr', network=g.network, need_amt=True )
+			self.proto = init_proto( cfg, 'xmr', network=self.cfg.network, need_amt=True )
 
 		def check_uopts(self):
 
@@ -363,7 +365,7 @@ class MoneroWalletOps:
 		)
 		wallet_exists = True
 
-		def __init__(self,uarg_tuple,uopt_tuple):
+		def __init__(self,cfg,uarg_tuple,uopt_tuple):
 
 			def wallet_exists(fn):
 				try: os.stat(fn)
@@ -379,9 +381,10 @@ class MoneroWalletOps:
 					elif not exists and self.wallet_exists:
 						die(1,f'Wallet {fn!r} not found!')
 
-			super().__init__(uarg_tuple,uopt_tuple)
+			super().__init__(cfg,uarg_tuple,uopt_tuple)
 
 			self.kal = KeyAddrList(
+				cfg,
 				self.proto,
 				uarg.infile,
 				key_address_validity_check = True )
@@ -391,13 +394,15 @@ class MoneroWalletOps:
 			check_wallets()
 
 			self.wd = MoneroWalletDaemon(
+				cfg         = self.cfg,
 				proto       = self.proto,
 				wallet_dir  = uopt.wallet_dir or '.',
-				test_suite  = g.test_suite,
+				test_suite  = self.cfg.test_suite,
 				daemon_addr = uopt.daemon or None,
 			)
 
 			self.c = MoneroWalletRPCClient(
+				cfg             = self.cfg,
 				daemon          = self.wd,
 				test_connection = False,
 			)
@@ -423,7 +428,7 @@ class MoneroWalletOps:
 				uopt.wallet_dir or '.','{}-{}-MoneroWallet{}'.format(
 					self.kal.al_id.sid,
 					d.idx,
-					f'.{g.network}' if g.network != 'mainnet' else ''))
+					f'.{self.cfg.network}' if self.cfg.network != 'mainnet' else ''))
 
 		async def main(self):
 			gmsg('\n{}ing {} wallet{}'.format(
@@ -582,6 +587,7 @@ class MoneroWalletOps:
 					get_tx_metadata = True
 				)
 				return MoneroMMGenTX.NewSigned(
+					cfg            = self.parent.cfg,
 					op             = uarg.op,
 					network        = self.parent.proto.network,
 					seed_id        = self.parent.kal.al_id.sid,
@@ -609,6 +615,7 @@ class MoneroWalletOps:
 					die(3,'More than one TX required.  Cannot perform this sweep')
 
 				return MoneroMMGenTX.NewSigned(
+					cfg            = self.parent.cfg,
 					op             = uarg.op,
 					network        = self.parent.proto.network,
 					seed_id        = self.parent.kal.al_id.sid,
@@ -654,23 +661,24 @@ class MoneroWalletOps:
 				restore_height = uopt.restore_height,
 				language       = 'English' )
 
-			pp_msg(ret) if opt.debug else msg('  Address: {}'.format( ret['address'] ))
+			pp_msg(ret) if self.cfg.debug else msg('  Address: {}'.format( ret['address'] ))
 			return True
 
 	class sync(wallet):
 		name    = 'sync'
 		opts    = ('rescan_blockchain',)
 
-		def __init__(self,uarg_tuple,uopt_tuple):
+		def __init__(self,cfg,uarg_tuple,uopt_tuple):
 
-			super().__init__(uarg_tuple,uopt_tuple)
+			super().__init__(cfg,uarg_tuple,uopt_tuple)
 
 			host,port = uopt.daemon.split(':') if uopt.daemon else ('localhost',self.wd.daemon_port)
 
 			from .daemon import CoinDaemon
 			self.dc = MoneroRPCClient(
+				cfg    = self.cfg,
 				proto  = self.proto,
-				daemon = CoinDaemon('xmr'),
+				daemon = CoinDaemon( self.cfg, 'xmr' ),
 				host   = host,
 				port   = int(port),
 				user   = None,
@@ -843,18 +851,20 @@ class MoneroWalletOps:
 			m = re.fullmatch(uarg_info['tx_relay_daemon'].pat,uopt.tx_relay_daemon,re.ASCII)
 
 			wd2 = MoneroWalletDaemon(
+				cfg         = self.cfg,
 				proto       = self.proto,
 				wallet_dir  = uopt.wallet_dir or '.',
-				test_suite  = g.test_suite,
+				test_suite  = self.cfg.test_suite,
 				daemon_addr = m[1],
 				proxy       = m[2] )
 
-			if g.test_suite:
+			if self.cfg.test_suite:
 				wd2.usr_daemon_args = ['--daemon-ssl-allow-any-cert']
 
 			wd2.start()
 
 			self.c = MoneroWalletRPCClient(
+				cfg    = self.cfg,
 				daemon = wd2 )
 
 		async def main(self):
@@ -879,9 +889,9 @@ class MoneroWalletOps:
 				dest_addr = self.dest_addr
 			elif self.dest == None:
 				dest_acct = self.account
-				if keypress_confirm(f'\nCreate new address for account #{self.account}?'):
+				if keypress_confirm( self.cfg, f'\nCreate new address for account #{self.account}?' ):
 					dest_addr_chk = h.create_new_addr(self.account)
-				elif keypress_confirm(f'Sweep to last existing address of account #{self.account}?'):
+				elif keypress_confirm( self.cfg, f'Sweep to last existing address of account #{self.account}?' ):
 					dest_addr_chk = None
 				else:
 					die(1,'Exiting at user request')
@@ -895,11 +905,11 @@ class MoneroWalletOps:
 				h2.open_wallet('destination')
 				accts_data = h2.get_accts()[0]
 
-				if keypress_confirm(f'\nCreate new account for wallet {bn!r}?'):
+				if keypress_confirm( self.cfg, f'\nCreate new account for wallet {bn!r}?' ):
 					dest_acct,dest_addr = h2.create_acct()
 					dest_addr_idx = 0
 					h2.get_accts()
-				elif keypress_confirm(f'Sweep to last existing account of wallet {bn!r}?'):
+				elif keypress_confirm( self.cfg, f'Sweep to last existing account of wallet {bn!r}?' ):
 					dest_acct,dest_addr_chk = h2.get_last_acct(accts_data)
 					dest_addr,dest_addr_idx = h2.get_last_addr(dest_acct,display=False)
 					assert dest_addr_chk == dest_addr, 'dest_addr_chk2'
@@ -927,7 +937,7 @@ class MoneroWalletOps:
 			if uopt.no_relay:
 				return True
 
-			if keypress_confirm(f'Relay {self.name} transaction?'):
+			if keypress_confirm( self.cfg, f'Relay {self.name} transaction?' ):
 				w_desc = 'source'
 				if uopt.tx_relay_daemon:
 					await h.stop_wallet('source')
@@ -1019,7 +1029,7 @@ class MoneroWalletOps:
 
 			if addr['label'] == self.label:
 				ymsg('\nLabel is unchanged, operation cancelled')
-			elif keypress_confirm('  {} label?'.format('Set' if self.label else 'Remove')):
+			elif keypress_confirm( self.cfg, '  {} label?'.format('Set' if self.label else 'Remove') ):
 				h.set_label( self.account, self.address_idx, self.label )
 				accts_data = h.get_accts(print=False)[0]
 				ret = h.print_addrs(accts_data,self.account)
@@ -1036,9 +1046,9 @@ class MoneroWalletOps:
 		name = 'relay'
 		opts = ('tx_relay_daemon',)
 
-		def __init__(self,uarg_tuple,uopt_tuple):
+		def __init__(self,cfg,uarg_tuple,uopt_tuple):
 
-			super().__init__(uarg_tuple,uopt_tuple)
+			super().__init__(cfg,uarg_tuple,uopt_tuple)
 
 			if uopt.tx_relay_daemon:
 				m = re.fullmatch(uarg_info['tx_relay_daemon'].pat,uopt.tx_relay_daemon,re.ASCII)
@@ -1047,11 +1057,12 @@ class MoneroWalletOps:
 				md = None
 			else:
 				from .daemon import CoinDaemon
-				md = CoinDaemon('xmr',test_suite=g.test_suite)
+				md = CoinDaemon( self.cfg, 'xmr', test_suite=self.cfg.test_suite )
 				host,port = md.host,md.rpc_port
 				proxy = None
 
 			self.dc = MoneroRPCClient(
+				cfg    = self.cfg,
 				proto  = self.proto,
 				daemon = md,
 				host   = host,
@@ -1061,7 +1072,7 @@ class MoneroWalletOps:
 				test_connection = False, # relay is presumably a public node, so avoid extra connections
 				proxy  = proxy )
 
-			self.tx = MoneroMMGenTX.Signed(uarg.infile)
+			self.tx = MoneroMMGenTX.Signed( self.cfg, uarg.infile )
 
 		async def main(self):
 			msg('\n' + self.tx.get_info())
@@ -1069,7 +1080,7 @@ class MoneroWalletOps:
 			if uopt.tx_relay_daemon:
 				self.display_tx_relay_info()
 
-			if keypress_confirm('Relay transaction?'):
+			if keypress_confirm( self.cfg, 'Relay transaction?' ):
 				res = self.dc.call_raw(
 					'send_raw_transaction',
 					tx_as_hex = self.tx.data.blob
@@ -1088,10 +1099,10 @@ class MoneroWalletOps:
 		name = 'txview'
 
 		async def main(self):
-			stdout_or_pager(
+			self.cfg._util.stdout_or_pager(
 				'\n'.join(
 					tx.get_info() for tx in
 					sorted(
-						(MoneroMMGenTX.Signed(fn) for fn in uarg.infile),
+						(MoneroMMGenTX.Signed( self.cfg, fn ) for fn in uarg.infile),
 						key = lambda x: x.data.sign_time )
 			))

+ 3 - 3
scripts/compute-file-chksum.py

@@ -17,11 +17,11 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 from mmgen.fileutil import get_lines_from_file
-lines = get_lines_from_file(cmd_args[0])
-start = (1,0)[bool(opt.include_first_line)]
+lines = get_lines_from_file( cfg, cfg._args[0] )
+start = (1,0)[bool(cfg.include_first_line)]
 a = make_chksum_6(' '.join(lines[start:]).encode())
 if start == 1:
 	b = lines[0]

+ 18 - 20
scripts/create-token.py

@@ -175,19 +175,20 @@ contract Token is ERC20Interface, Owned, SafeMath {
 }
 """ % req_solc_ver_pat
 
-def create_src(proto,template,token_data,owner_addr):
+def create_src(cfg,template,token_data):
 
 	def gen():
 		for k in token_data.fields:
 			field = getattr(token_data,k)
 			if k == 'owner_addr':
+				owner_addr = cfg._args[0]
 				from mmgen.addr import is_coin_addr
-				if not is_coin_addr( proto, owner_addr.lower() ):
-					die(1,f'{owner_addr}: not a valid {proto.coin} coin address')
+				if not is_coin_addr( cfg._proto, owner_addr.lower() ):
+					die(1,f'{owner_addr}: not a valid {cfg._proto.coin} coin address')
 				val = '0x' + owner_addr
 			else:
 				val = (
-					getattr(opt,k)
+					getattr(cfg,k)
 					or getattr(field,'default',None)
 					or die(1,f'The --{k} option must be specified')
 				)
@@ -231,10 +232,10 @@ def check_solc_version():
 		Msg(f'solc version ({version_str}) does not match requirement ({req_solc_ver_pat})')
 		return False
 
-def compile_code(code):
+def compile_code(cfg,code):
 	cmd = ['solc','--optimize','--bin','--overwrite']
-	if not opt.stdout:
-		cmd += ['--output-dir', opt.outdir or '.']
+	if not cfg.stdout:
+		cmd += ['--output-dir', cfg.outdir or '.']
 	cmd += ['-']
 	msg(f"Executing: {' '.join(cmd)}")
 	cp = run(cmd,input=code.encode(),stdout=PIPE,stderr=PIPE)
@@ -247,37 +248,34 @@ def compile_code(code):
 	if err:
 		ymsg('Solidity compiler produced the following warning:')
 		msg(err)
-	if opt.stdout:
+	if cfg.stdout:
 		o = out.split('\n')
 		return {k:o[i+2] for k in ('SafeMath','Owned','Token') for i in range(len(o)) if k in o[i]}
 	else:
-		vmsg(out)
+		cfg._util.vmsg(out)
 
 if __name__ == '__main__':
 
-	cmd_args = opts.init(opts_data)
+	cfg = opts.init(opts_data)
 
-	if opt.check_solc_version:
+	if cfg.check_solc_version:
 		sys.exit(0 if check_solc_version() else 1)
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts()
-
-	if not proto.coin in ('ETH','ETC'):
+	if not cfg._proto.coin in ('ETH','ETC'):
 		die(1,'--coin option must be ETH or ETC')
 
-	if not len(cmd_args) == 1:
+	if not len(cfg._args) == 1:
 		opts.usage()
 
-	code = create_src( proto, solidity_code_template, token_data, cmd_args[0] )
+	code = create_src( cfg, solidity_code_template, token_data )
 
-	if opt.preprocess:
+	if cfg.preprocess:
 		Msg(code)
 		sys.exit(0)
 
-	out = compile_code(code)
+	out = compile_code( cfg, code )
 
-	if opt.stdout:
+	if cfg.stdout:
 		print(json.dumps(out))
 
 	msg('Contract successfully compiled')

+ 4 - 4
scripts/tx-v2-to-v3.py

@@ -22,13 +22,13 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 import asyncio
 from mmgen.tx import CompletedTX
 
-if len(cmd_args) != 1:
+if len(cfg._args) != 1:
 	opts.usage()
 
-tx = asyncio.run(CompletedTX(cmd_args[0],quiet_open=True))
-tx.file.write(ask_tty=False,ask_overwrite=not opt.quiet,ask_write=not opt.quiet)
+tx = asyncio.run(CompletedTX(cfg._args[0],quiet_open=True))
+tx.file.write(ask_tty=False,ask_overwrite=not cfg.quiet,ask_write=not cfg.quiet)

+ 5 - 5
scripts/uninstall-mmgen.py

@@ -51,12 +51,12 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 if gc.platform == 'linux' and os.getenv('USER') != 'root':
 	die(1,'This program must be run as root')
 
-if len(cmd_args):
+if len(cfg._args):
 	opts.usage()
 
 mod_dir = os.path.split(normalize_path(modpath_save))[0]
@@ -77,13 +77,13 @@ for d in (ulb,mod_pardir):
 	# add files only, not directories
 	del_list += [os.path.join(d,e) for e in os.listdir(d) if is_reg(os.path.join(d,e)) and e[:6] == 'mmgen-']
 
-if opt.list_paths:
+if cfg.list_paths:
 	die(1,'\n'.join(del_list))
 
-if not opt.no_prompt:
+if not cfg.no_prompt:
 	m = 'Deleting the following paths and files:\n  {}\nProceed?'
 	from mmgen.ui import keypress_confirm
-	if not keypress_confirm(m.format('\n  '.join(del_list))):
+	if not keypress_confirm( cfg, m.format('\n  '.join(del_list)) ):
 		die(1,'Exiting at user request')
 
 import shutil

+ 55 - 50
test/gentest.py

@@ -28,10 +28,9 @@ sys.path.insert(0,overlay_setup(repo_root))
 
 # Import these _after_ local path's been added to sys.path
 import mmgen.opts as opts
-from mmgen.globalvars import g,gc
-from mmgen.opts import opt
+from mmgen.globalvars import gc
 from mmgen.color import green,red,purple
-from mmgen.util import msg,qmsg,qmsg_r,vmsg,capfirst,is_int,die
+from mmgen.util import msg,capfirst,is_int,die
 
 results_file = 'gentest.out.json'
 
@@ -149,7 +148,7 @@ class GenTool(object):
 		self.data = {}
 
 	def __del__(self):
-		if opt.save_results:
+		if cfg.save_results:
 			key = f'{self.proto.coin}-{self.proto.network}-{self.addr_type.name}-{self.desc}'.lower()
 			saved_results[key] = {k.hex():v._asdict() for k,v in self.data.items()}
 
@@ -251,14 +250,14 @@ def find_or_check_tool(proto,addr_type,toolname):
 	if toolname not in ext_progs + ['ext']:
 		die(1,f'{toolname!r}: unsupported tool for network {proto.network}')
 
-	if opt.all_coins and toolname == 'ext':
+	if cfg.all_coins and toolname == 'ext':
 		die(1,"'--all-coins' must be combined with a specific external testing tool")
 	else:
 		tool = cinfo.get_test_support(
 			proto.coin,
 			addr_type.name,
 			proto.network,
-			verbose = not opt.quiet,
+			verbose = not cfg.quiet,
 			toolname = toolname if toolname != 'ext' else None )
 		if tool and toolname in ext_progs and toolname != tool:
 			sys.exit(3)
@@ -283,11 +282,11 @@ def test_equal(desc,a_val,b_val,in_bytes,sec,wif,a_desc,b_desc):
 				w=max(len(e) for e in (a_desc,b_desc)) + 1
 		).rstrip())
 
-def do_ab_test(proto,cfg,addr_type,gen1,kg2,ag,tool,cache_data):
+def do_ab_test(proto,scfg,addr_type,gen1,kg2,ag,tool,cache_data):
 
 	def do_ab_inner(n,trounds,in_bytes):
 		global last_t
-		if opt.verbose or time.time() - last_t >= 0.1:
+		if cfg.verbose or time.time() - last_t >= 0.1:
 			qmsg_r(f'\rRound {i+1}/{trounds} ')
 			last_t = time.time()
 		sec = PrivKey(proto,in_bytes,compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
@@ -296,7 +295,7 @@ def do_ab_test(proto,cfg,addr_type,gen1,kg2,ag,tool,cache_data):
 		tinfo = ( in_bytes, sec, sec.wif, type(kg1).__name__, type(kg2).__name__ if kg2 else tool.desc )
 
 		def do_msg():
-			if opt.verbose:
+			if cfg.verbose:
 				msg( fs.format( b=in_bytes.hex(), r=sec.hex(), k=sec.wif, v=vk2, a=addr1 ))
 
 		if tool:
@@ -321,10 +320,10 @@ def do_ab_test(proto,cfg,addr_type,gen1,kg2,ag,tool,cache_data):
 			for privbytes in tuple(tool.data)[len(edgecase_sks):]:
 				yield privbytes
 		else:
-			for i in range(cfg.rounds):
+			for i in range(scfg.rounds):
 				yield getrand(32)
 
-	kg1 = KeyGenerator( proto, addr_type.pubkey_type, gen1 )
+	kg1 = KeyGenerator( cfg, proto, addr_type.pubkey_type, gen1 )
 	if type(kg1) == type(kg2):
 		die(4,'Key generators are the same!')
 
@@ -364,39 +363,39 @@ def do_ab_test(proto,cfg,addr_type,gen1,kg2,ag,tool,cache_data):
 	qmsg(purple('edge cases:'))
 	for i,privbytes in enumerate(edgecase_sks):
 		do_ab_inner(i,len(edgecase_sks),privbytes)
-	qmsg(green('\rOK            ' if opt.verbose else 'OK'))
+	qmsg(green('\rOK            ' if cfg.verbose else 'OK'))
 
 	qmsg(purple('random input:'))
 	for i,privbytes in enumerate(get_randbytes()):
-		do_ab_inner(i,cfg.rounds,privbytes)
-	qmsg(green('\rOK            ' if opt.verbose else 'OK'))
+		do_ab_inner(i,scfg.rounds,privbytes)
+	qmsg(green('\rOK            ' if cfg.verbose else 'OK'))
 
 def init_tool(proto,addr_type,toolname):
 	return globals()['GenTool'+capfirst(toolname.replace('-','_'))](proto,addr_type)
 
-def ab_test(proto,cfg):
+def ab_test(proto,scfg):
 
-	addr_type = MMGenAddrType( proto=proto, id_str=opt.type or proto.dfl_mmtype )
+	addr_type = MMGenAddrType( proto=proto, id_str=cfg.type or proto.dfl_mmtype )
 
-	if cfg.gen2:
-		assert cfg.gen1 != 'all', "'all' must be used only with external tool"
-		kg2 = KeyGenerator( proto, addr_type.pubkey_type, cfg.gen2 )
+	if scfg.gen2:
+		assert scfg.gen1 != 'all', "'all' must be used only with external tool"
+		kg2 = KeyGenerator( cfg, proto, addr_type.pubkey_type, scfg.gen2 )
 		tool = None
 	else:
-		toolname = find_or_check_tool( proto, addr_type, cfg.tool )
+		toolname = find_or_check_tool( proto, addr_type, scfg.tool )
 		if toolname == None:
-			ymsg(f'Warning: skipping tool {cfg.tool!r} for {proto.coin} {addr_type.name}')
+			ymsg(f'Warning: skipping tool {scfg.tool!r} for {proto.coin} {addr_type.name}')
 			return
 		tool = init_tool( proto, addr_type, toolname )
 		kg2 = None
 
-	ag = AddrGenerator( proto, addr_type )
+	ag = AddrGenerator( cfg, proto, addr_type )
 
-	if cfg.all_backends: # check all backends against external tool
+	if scfg.all_backends: # check all backends against external tool
 		for n in range(len(get_backends(addr_type.pubkey_type))):
-			do_ab_test( proto, cfg, addr_type, gen1=n+1, kg2=kg2, ag=ag, tool=tool, cache_data=cfg.rounds < 1000 and not n )
+			do_ab_test( proto, scfg, addr_type, gen1=n+1, kg2=kg2, ag=ag, tool=tool, cache_data=scfg.rounds < 1000 and not n )
 	else:                # check specific backend against external tool or another backend
-		do_ab_test( proto, cfg, addr_type, gen1=cfg.gen1, kg2=kg2, ag=ag, tool=tool, cache_data=False )
+		do_ab_test( proto, scfg, addr_type, gen1=scfg.gen1, kg2=kg2, ag=ag, tool=tool, cache_data=False )
 
 def speed_test(proto,kg,ag,rounds):
 	qmsg(green('Testing speed of address generator {!r} for coin {}'.format(
@@ -419,7 +418,7 @@ def speed_test(proto,kg,ag,rounds):
 	qmsg(
 		f'\rRound {i+1}/{rounds} ' +
 		f'\n{rounds} addresses generated' +
-		('' if g.test_suite_deterministic else f' in {time.time()-start:.2f} seconds')
+		('' if cfg.test_suite_deterministic else f' in {time.time()-start:.2f} seconds')
 	)
 
 def dump_test(proto,kg,ag,filename):
@@ -446,7 +445,7 @@ def dump_test(proto,kg,ag,filename):
 		tinfo = (b_sec,b_sec.hex(),b_wif,type(kg).__name__,filename)
 		test_equal('addresses',a_addr,b_addr,*tinfo)
 
-	qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
+	qmsg(green(('\n','')[bool(cfg.verbose)] + 'OK'))
 
 def get_protos(proto,addr_type,toolname):
 
@@ -455,17 +454,17 @@ def get_protos(proto,addr_type,toolname):
 	for coin in cinfo.external_tests[proto.network][toolname]:
 		if coin.lower() not in CoinProtocol.coins:
 			continue
-		ret = init_proto(coin,testnet=proto.testnet)
+		ret = init_proto( cfg, coin, testnet=proto.testnet )
 		if addr_type not in ret.mmtypes:
 			continue
 		yield ret
 
 def parse_args():
 
-	if len(cmd_args) != 2:
+	if len(cfg._args) != 2:
 		opts.usage()
 
-	arg1,arg2 = cmd_args
+	arg1,arg2 = cfg._args
 	gen1,gen2,rounds = (0,0,0)
 	tool,all_backends,dumpfile = (None,None,None)
 
@@ -498,13 +497,12 @@ def parse_args():
 				die(1,"First part of first argument must be a generator backend number or 'all'")
 
 		if is_int(b):
-			if opt.all_coins:
+			if cfg.all_coins:
 				die(1,'--all-coins must be used with external tool only')
 			gen2 = b
 		else:
 			tool = b
-			proto = init_proto_from_opts()
-			ext_progs = list(cinfo.external_tests[proto.network]) + ['ext']
+			ext_progs = list(cinfo.external_tests[cfg._proto.network]) + ['ext']
 			if b not in ext_progs:
 				die(1,f'Second part of first argument must be a generator backend number or one of {ext_progs}')
 
@@ -519,21 +517,21 @@ def parse_args():
 
 def main():
 
-	cfg = parse_args()
-	proto = init_proto_from_opts()
-	addr_type = MMGenAddrType( proto=proto, id_str=opt.type or proto.dfl_mmtype )
+	scfg = parse_args()
 
-	if cfg.test == 'ab':
-		protos = get_protos(proto,addr_type,cfg.tool) if opt.all_coins else [proto]
-		for proto in protos:
-			ab_test( proto, cfg )
+	addr_type = MMGenAddrType( proto=proto, id_str=cfg.type or proto.dfl_mmtype )
+
+	if scfg.test == 'ab':
+		protos = get_protos(proto,addr_type,scfg.tool) if cfg.all_coins else [proto]
+		for p in protos:
+			ab_test( p, scfg )
 	else:
-		kg = KeyGenerator( proto, addr_type.pubkey_type, cfg.gen1 )
-		ag = AddrGenerator( proto, addr_type )
-		if cfg.test == 'speed':
-			speed_test( proto, kg, ag, cfg.rounds )
-		elif cfg.test == 'dump':
-			dump_test( proto, kg, ag, cfg.dumpfile )
+		kg = KeyGenerator( cfg, proto, addr_type.pubkey_type, scfg.gen1 )
+		ag = AddrGenerator( cfg, proto, addr_type )
+		if scfg.test == 'speed':
+			speed_test( proto, kg, ag, scfg.rounds )
+		elif scfg.test == 'dump':
+			dump_test( proto, kg, ag, scfg.dumpfile )
 
 	if saved_results:
 		import json
@@ -542,19 +540,26 @@ def main():
 
 from subprocess import run,PIPE,DEVNULL
 from collections import namedtuple
-from mmgen.protocol import init_proto,init_proto_from_opts,CoinProtocol
+from mmgen.protocol import init_proto,CoinProtocol
 from mmgen.altcoin import init_genonly_altcoins,CoinInfo as cinfo
 from mmgen.key import PrivKey
 from mmgen.addr import MMGenAddrType
 from mmgen.addrgen import KeyGenerator,AddrGenerator
 from mmgen.keygen import get_backends
-
-from test.include.common import getrand,get_ethkey
+from test.include.common import getrand,get_ethkey,set_globals
 
 gtr = namedtuple('gen_tool_result',['wif','addr','viewkey'])
 sd = namedtuple('saved_data_item',['reduced','wif','addr','viewkey'])
 
 sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
-cmd_args = opts.init(opts_data)
+
+cfg = opts.init(opts_data)
+set_globals(cfg)
+
+qmsg = cfg._util.qmsg
+qmsg_r = cfg._util.qmsg_r
+vmsg = cfg._util.vmsg
+
+proto = cfg._proto
 
 main()

+ 5 - 1
test/hashfunc.py

@@ -23,7 +23,6 @@ test/hashfunc.py: Test internal implementations of SHA256, SHA512 and Keccak256
 import sys,os
 import include.tests_header
 from mmgen.util import die
-from test.include.common import getrand
 
 assert len(sys.argv) in (2,3),"Test takes 1 or 2 arguments: test name, plus optional rounds count"
 test = sys.argv[1].capitalize()
@@ -137,6 +136,11 @@ class TestSha512(TestSha2):
 		0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
 		0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 )
 
+from test.include.common import getrand,set_globals
+from mmgen.globalvars import Config
+
+set_globals(Config())
+
 t = globals()['Test'+test]()
 msg(f'Testing internal implementation of {t.desc}\n')
 t.test_constants()

+ 24 - 23
test/include/coin_daemon_control.py

@@ -49,7 +49,7 @@ Valid network IDs: {nid}, all, or no_xmr
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 from mmgen.daemon import *
 
@@ -60,58 +60,59 @@ class warn_missing_exec(oneshot_warning):
 def run(network_id=None,proto=None,daemon_id=None,missing_exec_ok=True):
 
 	d = CoinDaemon(
+		cfg,
 		network_id = network_id,
 		proto      = proto,
-		test_suite = not opt.usermode,
-		opts       = ['no_daemonize'] if opt.no_daemonize else None,
-		port_shift = int(opt.port_shift or 0),
-		datadir    = opt.datadir,
+		test_suite = not cfg.usermode,
+		opts       = ['no_daemonize'] if cfg.no_daemonize else None,
+		port_shift = int(cfg.port_shift or 0),
+		datadir    = cfg.datadir,
 		daemon_id  = daemon_id )
 
-	if opt.mainnet_only and d.network != 'mainnet':
+	if cfg.mainnet_only and d.network != 'mainnet':
 		return
 
-	d.debug = d.debug or opt.debug
-	d.wait = not opt.no_wait
+	d.debug = d.debug or cfg.debug
+	d.wait = not cfg.no_wait
 
 	if missing_exec_ok:
 		try:
 			d.get_exec_version_str()
 		except:
-			if not opt.quiet:
+			if not cfg.quiet:
 				warn_missing_exec( div=d.exec_fn, fmt_args=(d.exec_fn,) )
 			return
-	if opt.print_version:
+	if cfg.print_version:
 		msg('{:16} {}'.format( d.exec_fn+':', d.get_exec_version_str() ))
-	elif opt.get_state:
+	elif cfg.get_state:
 		print(d.state_msg())
-	elif opt.testing:
+	elif cfg.testing:
 		for cmd in d.start_cmds if action == 'start' else [d.stop_cmd]:
 			print(' '.join(cmd))
 	else:
 		if action == 'stop' and hasattr(d,'rpc'):
-			async_run(d.rpc.stop_daemon(quiet=opt.quiet))
+			async_run(d.rpc.stop_daemon(quiet=cfg.quiet))
 		else:
-			d.cmd(action,quiet=opt.quiet)
+			d.cmd(action,quiet=cfg.quiet)
 
-if opt.daemon_ids:
+if cfg.daemon_ids:
 	print('\n'.join(CoinDaemon.all_daemon_ids()))
-elif 'all' in cmd_args or 'no_xmr' in cmd_args:
-	if len(cmd_args) != 1:
+elif 'all' in cfg._args or 'no_xmr' in cfg._args:
+	if len(cfg._args) != 1:
 		die(1,"'all' or 'no_xmr' must be the sole argument")
 	from mmgen.protocol import init_proto
 	for coin in CoinDaemon.coins:
-		if coin == 'XMR' and cmd_args[0] == 'no_xmr':
+		if coin == 'XMR' and cfg._args[0] == 'no_xmr':
 			continue
-		for daemon_id in CoinDaemon.get_daemon_ids(coin):
-			for network in CoinDaemon.get_daemon(coin,daemon_id).networks:
+		for daemon_id in CoinDaemon.get_daemon_ids(cfg,coin):
+			for network in CoinDaemon.get_daemon(cfg,coin,daemon_id).networks:
 				run(
-					proto           = init_proto(coin=coin,network=network),
+					proto           = init_proto( cfg, coin=coin, network=network ),
 					daemon_id       = daemon_id,
 					missing_exec_ok = True )
 else:
-	ids = cmd_args
-	network_ids = CoinDaemon.get_network_ids()
+	ids = cfg._args
+	network_ids = CoinDaemon.get_network_ids(cfg)
 	if not ids:
 		opts.usage()
 	for i in ids:

+ 46 - 18
test/include/common.py

@@ -25,6 +25,33 @@ from subprocess import run,PIPE
 from mmgen.common import *
 from mmgen.fileutil import write_data_to_file,get_data_from_file
 
+def noop(*args,**kwargs):
+	pass
+
+def set_globals(cfg):
+	"""
+	make `cfg`, `qmsg`, `vmsg`, etc. available as globals to scripts by setting
+	the module attr
+	"""
+	import test.include.common as this
+	this.cfg = cfg
+
+	if cfg.quiet:
+		this.qmsg = this.qmsg_r = noop
+	else:
+		this.qmsg = msg
+		this.qmsg_r = msg_r
+
+	if cfg.verbose:
+		this.vmsg = msg
+		this.vmsg_r = msg_r
+		this.Vmsg = Msg
+		this.Vmsg_r = Msg_r
+	else:
+		this.vmsg = this.vmsg_r = this.Vmsg = this.Vmsg_r = noop
+
+	this.dmsg = msg if cfg.debug else noop
+
 def strip_ansi_escapes(s):
 	import re
 	return re.sub('\x1b' + r'\[[;0-9]+?m','',s)
@@ -65,7 +92,7 @@ ref_kafile_pass = 'kafile password'
 ref_kafile_hash_preset = '1'
 
 def getrand(n):
-	if g.test_suite_deterministic:
+	if cfg.test_suite_deterministic:
 		from mmgen.test import fake_urandom
 		return fake_urandom(n)
 	else:
@@ -120,6 +147,7 @@ def get_tmpfile(cfg,fn):
 
 def write_to_file(fn,data,binary=False):
 	write_data_to_file(
+		cfg,
 		fn,
 		data,
 		quiet = True,
@@ -130,7 +158,7 @@ def write_to_tmpfile(cfg,fn,data,binary=False):
 	write_to_file(  os.path.join(cfg['tmpdir'],fn), data=data, binary=binary )
 
 def read_from_file(fn,binary=False):
-	return get_data_from_file(fn,quiet=True,binary=binary)
+	return get_data_from_file( cfg, fn, quiet=True, binary=binary )
 
 def read_from_tmpfile(cfg,fn,binary=False):
 	return read_from_file(os.path.join(cfg['tmpdir'],fn),binary=binary)
@@ -139,9 +167,9 @@ def joinpath(*args,**kwargs):
 	return os.path.join(*args,**kwargs)
 
 def ok():
-	if opt.profile:
+	if cfg.profile:
 		return
-	if opt.verbose or opt.exact_output:
+	if cfg.verbose or cfg.exact_output:
 		gmsg('OK')
 	else:
 		msg(' OK')
@@ -161,11 +189,11 @@ def init_coverage():
 	return coverdir,acc_file
 
 def silence():
-	if not (opt.verbose or opt.exact_output):
+	if not (cfg.verbose or cfg.exact_output):
 		gv.stdout = gv.stderr = open(os.devnull,'w')
 
 def end_silence():
-	if not (opt.verbose or opt.exact_output):
+	if not (cfg.verbose or cfg.exact_output):
 		gv.stdout.close()
 		gv.stdout = sys.stdout
 		gv.stderr = sys.stderr
@@ -177,38 +205,38 @@ def omsg_r(s):
 	sys.stderr.flush()
 
 def imsg(s):
-	if opt.verbose or opt.exact_output:
+	if cfg.verbose or cfg.exact_output:
 		omsg(s)
 def imsg_r(s):
-	if opt.verbose or opt.exact_output:
+	if cfg.verbose or cfg.exact_output:
 		omsg_r(s)
 
 def iqmsg(s):
-	if not opt.quiet:
+	if not cfg.quiet:
 		omsg(s)
 def iqmsg_r(s):
-	if not opt.quiet:
+	if not cfg.quiet:
 		omsg_r(s)
 
 def oqmsg(s):
-	if not (opt.verbose or opt.exact_output):
+	if not (cfg.verbose or cfg.exact_output):
 		omsg(s)
 def oqmsg_r(s):
-	if not (opt.verbose or opt.exact_output):
+	if not (cfg.verbose or cfg.exact_output):
 		omsg_r(s)
 
 def end_msg(t):
 	omsg(green(
 		'All requested tests finished OK' +
-		('' if g.test_suite_deterministic else f', elapsed time: {t//60:02d}:{t%60:02d}')
+		('' if cfg.test_suite_deterministic else f', elapsed time: {t//60:02d}:{t%60:02d}')
 	))
 
 def start_test_daemons(*network_ids,remove_datadir=False):
-	if not opt.no_daemon_autostart:
+	if not cfg.no_daemon_autostart:
 		return test_daemons_ops(*network_ids,op='start',remove_datadir=remove_datadir)
 
 def stop_test_daemons(*network_ids,force=False,remove_datadir=False):
-	if force or not opt.no_daemon_stop:
+	if force or not cfg.no_daemon_stop:
 		return test_daemons_ops(*network_ids,op='stop',remove_datadir=remove_datadir)
 
 def restart_test_daemons(*network_ids,remove_datadir=False):
@@ -217,12 +245,12 @@ def restart_test_daemons(*network_ids,remove_datadir=False):
 	return start_test_daemons(*network_ids,remove_datadir=remove_datadir)
 
 def test_daemons_ops(*network_ids,op,remove_datadir=False):
-	if not opt.no_daemon_autostart:
+	if not cfg.no_daemon_autostart:
 		from mmgen.daemon import CoinDaemon
-		silent = not (opt.verbose or opt.exact_output)
+		silent = not (cfg.verbose or cfg.exact_output)
 		ret = False
 		for network_id in network_ids:
-			d = CoinDaemon(network_id,test_suite=True)
+			d = CoinDaemon(cfg,network_id,test_suite=True)
 			if remove_datadir:
 				d.stop(silent=True)
 				d.remove_datadir()

+ 22 - 24
test/include/pexpect.py

@@ -21,10 +21,8 @@ test.include.pexpect: pexpect implementation for MMGen test suites
 """
 
 import sys,os,time
-from mmgen.globalvars import g
-from mmgen.opts import opt
-from mmgen.util import msg,msg_r,vmsg,vmsg_r,rmsg,red,yellow,green,cyan,die
-from .common import *
+from mmgen.util import msg,msg_r,rmsg,red,yellow,green,cyan,die
+from .common import cfg,vmsg,vmsg_r,getrandstr,strip_ansi_escapes
 
 try:
 	import pexpect
@@ -48,18 +46,18 @@ class MMGenPexpect:
 		self.skip_ok = False
 		self.sent_value = None
 
-		if opt.direct_exec:
+		if cfg.direct_exec:
 			msg('')
 			from subprocess import run,DEVNULL
 			run([args[0]] + args[1:],check=True,stdout=DEVNULL if no_output else None)
 		else:
-			timeout = int(timeout or opt.pexpect_timeout or 0) or (60,5)[bool(opt.debug_pexpect)]
+			timeout = int(timeout or cfg.pexpect_timeout or 0) or (60,5)[bool(cfg.debug_pexpect)]
 			if pexpect_spawn:
 				self.p = pexpect.spawn(args[0],args[1:],encoding='utf8',timeout=timeout,env=env)
 			else:
 				self.p = PopenSpawn(args,encoding='utf8',timeout=timeout,env=env)
 
-			if opt.exact_output:
+			if cfg.exact_output:
 				self.p.logfile = sys.stdout
 
 	def do_decrypt_ka_data(self,hp,pw,desc='key-address data',check=True,have_yes_opt=False):
@@ -84,13 +82,13 @@ class MMGenPexpect:
 			self.p.sendeof()
 		self.p.read()
 		ret = self.p.wait()
-		if ret != self.req_exit_val and not opt.coverage:
+		if ret != self.req_exit_val and not cfg.coverage:
 			die(1,red(f'test.py: spawned program exited with value {ret}'))
-		if opt.profile:
+		if cfg.profile:
 			return
 		if not self.skip_ok:
 			m = 'OK\n' if ret == 0 else f'OK[{ret}]\n'
-			sys.stderr.write( green(m) if opt.exact_output or opt.verbose else ' '+m )
+			sys.stderr.write( green(m) if cfg.exact_output or cfg.verbose else ' '+m )
 		return self
 
 	def license(self):
@@ -101,7 +99,7 @@ class MMGenPexpect:
 		self.expect('Enter a wallet label, or hit ENTER for no label: ',label+'\n')
 
 	def usr_rand(self,num_chars):
-		if opt.usr_random:
+		if cfg.usr_random:
 			self.interactive()
 			self.send('\n')
 		else:
@@ -109,7 +107,7 @@ class MMGenPexpect:
 			vmsg_r('SEND ')
 			while rand_chars:
 				ch = rand_chars.pop(0)
-				msg_r(yellow(ch)+' ' if opt.verbose else '+')
+				msg_r(yellow(ch)+' ' if cfg.verbose else '+')
 				ret = self.expect('left: ',ch,delay=0.005)
 			self.expect('ENTER to continue: ','\n')
 
@@ -134,7 +132,7 @@ class MMGenPexpect:
 			return self.expect_getend("Overwriting file '").rstrip("'")
 		self.expect(NL,nonl=True)
 		outfile = self.p.before.strip().strip("'")
-		if opt.debug_pexpect:
+		if cfg.debug_pexpect:
 			rmsg(f'Outfile [{outfile}]')
 		vmsg('{} file: {}'.format( desc, cyan(outfile.replace('"',"")) ))
 		return outfile
@@ -154,14 +152,14 @@ class MMGenPexpect:
 
 	def expect_getend(self,s,regex=False):
 		ret = self.expect(s,regex=regex,nonl=True)
-		if opt.debug_pexpect:
+		if cfg.debug_pexpect:
 			debug_pexpect_msg(self.p)
 		# readline() of partial lines doesn't work with PopenSpawn, so do this instead:
 		self.expect(NL,nonl=True,silent=True)
-		if opt.debug_pexpect:
+		if cfg.debug_pexpect:
 			debug_pexpect_msg(self.p)
 		end = self.p.before.rstrip()
-		if not g.debug:
+		if not cfg.debug:
 			vmsg(f' ==> {cyan(end)}')
 		return end
 
@@ -186,25 +184,25 @@ class MMGenPexpect:
 	def expect(self,s,t='',delay=None,regex=False,nonl=False,silent=False):
 
 		if not silent:
-			if opt.verbose:
+			if cfg.verbose:
 				msg_r('EXPECT ' + yellow(str(s)))
-			elif not opt.exact_output:
+			elif not cfg.exact_output:
 				msg_r('+')
 
 		try:
 			ret = (self.p.expect_exact,self.p.expect)[bool(regex)](s) if s else 0
 		except pexpect.TIMEOUT:
-			if opt.debug_pexpect:
+			if cfg.debug_pexpect:
 				raise
 			m1 = f'\nERROR.  Expect {s!r} timed out.  Exiting\n'
 			m2 = f'before: [{self.p.before}]\n'
 			m3 = f'sent value: [{self.sent_value}]' if self.sent_value != None else ''
 			raise pexpect.TIMEOUT(m1+m2+m3)
 
-		if opt.debug_pexpect:
+		if cfg.debug_pexpect:
 			debug_pexpect_msg(self.p)
 
-		if opt.verbose and type(s) != str:
+		if cfg.verbose and type(s) != str:
 			msg_r(f' ==> {ret} ')
 
 		if ret == -1:
@@ -223,10 +221,10 @@ class MMGenPexpect:
 			time.sleep(delay)
 		ret = self.p.send(t) # returns num bytes written
 		self.sent_value = t if ret else None
-		if opt.demo and delay:
+		if cfg.demo and delay:
 			time.sleep(delay)
-		if opt.verbose:
-			ls = '' if opt.debug or not s else ' '
+		if cfg.verbose:
+			ls = '' if cfg.debug or not s else ' '
 			es = '' if s else '  '
 			yt = yellow('{!r}'.format( t.replace('\n',r'\n') ))
 			msg(f'{ls}SEND {es}{yt}')

+ 13 - 13
test/misc/cfg.py

@@ -2,13 +2,15 @@
 
 from mmgen.common import *
 
-cmd_args = opts.init()
+cfg = opts.init()
+
+cmd_args = cfg._args
 
 from mmgen.cfgfile import mmgen_cfg_file
 
-cf_usr = mmgen_cfg_file('usr')
-cf_sys = mmgen_cfg_file('sys')
-cf_sample = mmgen_cfg_file('sample')
+cf_usr = mmgen_cfg_file(cfg,'usr')
+cf_sys = mmgen_cfg_file(cfg,'sys')
+cf_sample = mmgen_cfg_file(cfg,'sample')
 
 msg(f'Usr cfg file:    {os.path.relpath(cf_usr.fn)}')
 msg(f'Sys cfg file:    {os.path.relpath(cf_sys.fn)}')
@@ -21,21 +23,19 @@ if cmd_args:
 		pu = cf_usr.get_lines()
 		msg('usr cfg: {}'.format( ' '.join(f'{i.name}={i.value}' for i in pu) ))
 	elif cmd_args[0] == 'coin_specific_vars':
-		from mmgen.protocol import init_proto_from_opts
-		proto = init_proto_from_opts(need_amt=True)
 		for varname in cmd_args[1:]:
 			msg('{}.{}: {}'.format(
-				type(proto).__name__,
+				type(cfg._proto).__name__,
 				varname,
-				getattr(proto,varname)
+				getattr(cfg._proto,varname)
 			))
 	elif cmd_args[0] == 'autoset_opts':
-		assert opt.rpc_backend == 'aiohttp', "opt.rpc_backend != 'aiohttp'"
+		assert cfg.rpc_backend == 'aiohttp', "cfg.rpc_backend != 'aiohttp'"
 	elif cmd_args[0] == 'autoset_opts_cmdline':
-		assert opt.rpc_backend == 'curl', "opt.rpc_backend != 'curl'"
+		assert cfg.rpc_backend == 'curl', "cfg.rpc_backend != 'curl'"
 	elif cmd_args[0] == 'mnemonic_entry_modes':
 		from mmgen.mn_entry import mn_entry
 		msg('mnemonic_entry_modes: {}\nmmgen: {}\nbip39: {}'.format(
-			g.mnemonic_entry_modes,
-			mn_entry('mmgen').usr_dfl_entry_mode,
-			mn_entry('bip39').usr_dfl_entry_mode ))
+			cfg.mnemonic_entry_modes,
+			mn_entry(cfg,'mmgen').usr_dfl_entry_mode,
+			mn_entry(cfg,'bip39').usr_dfl_entry_mode ))

+ 9 - 10
test/misc/get_passphrase.py

@@ -6,9 +6,8 @@ os.chdir(os.path.dirname(os.path.dirname(pn)))
 sys.path[0] = os.curdir
 
 from mmgen.common import *
-g.color = True
 
-cmd_args = opts.init({
+cfg = opts.init({
 	'text': {
 		'desc':    '',
 		'usage':   '',
@@ -19,15 +18,13 @@ cmd_args = opts.init({
 -L, --label=l         d
 -m, --keep-label      e
 		"""
-	}})
-
-from mmgen.wallet import Wallet
+	}},init_opts={'color':True})
 
 def crypto():
 	desc = 'test data'
 
 	from mmgen.crypto import Crypto
-	crypto = Crypto()
+	crypto = Crypto(cfg)
 
 	pw = crypto.get_new_passphrase(data_desc=desc,hash_preset=gc.dfl_hash_preset,passwd_file=None)
 	msg(f'==> got new passphrase: [{pw}]\n')
@@ -44,16 +41,18 @@ def crypto():
 def seed():
 	for n in range(1,3):
 		msg(f'------- NEW WALLET {n} -------\n')
-		w1 = Wallet()
+		w1 = Wallet(cfg)
 		msg(f'\n==> got pw,preset,lbl: [{w1.ssdata.passwd}][{w1.ssdata.hash_preset}][{w1.ssdata.label}]\n')
 
 	for n in range(1,3):
 		msg(f'------- PASSCHG {n} -------\n')
-		w2 = Wallet(ss=w1,passchg=True)
+		w2 = Wallet(cfg,ss=w1,passchg=True)
 		msg(f'\n==> got pw,preset,lbl: [{w2.ssdata.passwd}][{w2.ssdata.hash_preset}][{w2.ssdata.label}]\n')
 
 	msg(f'------- WALLET FROM FILE -------\n')
-	w3 = Wallet(fn='test/ref/FE3C6545-D782B529[128,1].mmdat') # passphrase: 'reference password'
+	w3 = Wallet(cfg,fn='test/ref/FE3C6545-D782B529[128,1].mmdat') # passphrase: 'reference password'
 	msg(f'\n==> got pw,preset,lbl: [{w3.ssdata.passwd}][{w3.ssdata.hash_preset}][{w3.ssdata.label}]\n')
 
-globals()[cmd_args[0]]()
+from mmgen.wallet import Wallet
+
+globals()[cfg._args[0]]()

+ 14 - 7
test/misc/input_func.py

@@ -7,23 +7,30 @@ sys.path[0] = os.curdir
 
 from mmgen.common import *
 
-cmd_args = opts.init({'text': { 'desc': '', 'usage':'', 'options':'-e, --echo-passphrase foo' }})
+cfg = opts.init({'text': { 'desc': '', 'usage':'', 'options':'-e, --echo-passphrase foo' }})
 
-if cmd_args[0] == 'passphrase':
+cmd_args = cfg._args
+
+cmd = cmd_args[0]
+
+if cmd == 'passphrase':
 	from mmgen.ui import get_words_from_user
 	pw = get_words_from_user(
-		('Enter passphrase: ','Enter passphrase (echoed): ')[bool(opt.echo_passphrase)] )
+		cfg,
+		('Enter passphrase: ','Enter passphrase (echoed): ')[bool(cfg.echo_passphrase)] )
 	msg('Entered: {}'.format(' '.join(pw)))
-elif cmd_args[0] in ('get_char','line_input'):
+elif cmd in ('get_char','line_input'):
 	from mmgen.term import get_char
 	from mmgen.ui import line_input
 	from ast import literal_eval
 	func_args = literal_eval(cmd_args[1])
 	Msg(f'\n  term: {get_char.__self__.__name__}')
-	Msg(f'  g.hold_protect_disable: {g.hold_protect_disable}')
+	Msg(f'  cfg.hold_protect_disable: {cfg.hold_protect_disable}')
+	if cmd == 'line_input':
+		func_args.update({'cfg':cfg})
 	Msg('  {name}( {args} )'.format(
-		name = cmd_args[0],
+		name = cmd,
 		args = ', '.join(f'{k}={v!r}' for k,v in func_args.items())
 		))
-	ret = locals()[cmd_args[0]](**func_args)
+	ret = locals()[cmd](**func_args)
 	Msg('  ==> {!r}'.format(ret))

+ 1 - 1
test/misc/oneshot_warning.py

@@ -2,7 +2,7 @@
 
 from mmgen.common import *
 
-cmd_args = opts.init()
+cfg = opts.init()
 
 class foo(oneshot_warning):
 

+ 12 - 8
test/misc/opts.py

@@ -37,38 +37,42 @@ sample note: {nn}
 """
 	},
 	'code': {
-		'options': lambda help_notes,s: s.format(
+		'options': lambda cfg,help_notes,s: s.format(
 			kgs=help_notes('keygen_backends'),
 			coin_id=help_notes('coin_id'),
-			g=g,
 		),
 		'notes': lambda s: s.format(nn='a note'),
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-if cmd_args == ['show_common_opts_diff']:
+if cfg._args == ['show_common_opts_diff']:
 	from mmgen.opts import show_common_opts_diff
-	show_common_opts_diff()
+	show_common_opts_diff(cfg)
 	sys.exit(0)
 
 for k in (
 	'foo',               # added opt
 	'print_checksum',    # sets 'quiet'
 	'quiet','verbose',   # init_opts, incompatible_opts
-	'fee_estimate_mode', # autoset_opts
 	'passwd_file',       # infile_opts - check_infile()
 	'outdir',            # check_outdir()
 	'cached_balances',   # opt_sets_global
 	'minconf',           # global_sets_opt
 	'hidden_incog_input_params',
 	):
-	msg('{:30} {}'.format( f'opt.{k}:', getattr(opt,k) ))
+	msg('{:30} {}'.format( f'cfg.{k}:', getattr(cfg,k) ))
 
 msg('')
 for k in (
 	'cached_balances',   # opt_sets_global
 	'minconf',           # global_sets_opt
 	):
-	msg('{:30} {}'.format( f'g.{k}:', getattr(opt,k) ))
+	msg('{:30} {}'.format( f'cfg.{k}:', getattr(cfg,k) ))
+
+msg('')
+for k in (
+	'fee_estimate_mode', # autoset_opts
+	):
+	msg('{:30} {}'.format( f'cfg.{k}:', getattr(cfg,k) ))

+ 10 - 10
test/misc/term.py

@@ -44,7 +44,7 @@ available commands for platform {gc.platform!r}:
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 from mmgen.term import get_char,get_char_raw,get_terminal_size,get_term
 from mmgen.ui import line_input,keypress_confirm,do_license_msg
@@ -54,8 +54,8 @@ def cmsg(m):
 	msg('\n'+cyan(m))
 
 def confirm(m):
-	if not keypress_confirm(m):
-		if keypress_confirm('Are you sure you want to exit test?'):
+	if not keypress_confirm( cfg, m ):
+		if keypress_confirm( cfg, 'Are you sure you want to exit test?' ):
 			die(1,'Exiting test at user request')
 		else:
 			msg('Continuing...')
@@ -80,7 +80,7 @@ def tt_color():
 def tt_license():
 	cmsg('Testing do_license_msg() with pager')
 	ymsg('Press "w" to test the pager, then "c" to continue')
-	do_license_msg()
+	do_license_msg(cfg)
 
 def tt_line_input():
 	set_vt100()
@@ -92,7 +92,7 @@ def tt_line_input():
 		on screen or entered text.
 	"""))
 	get_char_raw('Ready? ',num_bytes=1)
-	reply = line_input('\nEnter text: ')
+	reply = line_input( cfg, '\nEnter text: ' )
 	confirm(f'Did you enter the text {reply!r}?')
 
 def _tt_get_char(raw=False,one_char=False,immed_chars=''):
@@ -145,7 +145,7 @@ def _tt_get_char(raw=False,one_char=False,immed_chars=''):
 def tt_urand():
 	cmsg('Testing _get_random_data_from_user():')
 	from mmgen.crypto import Crypto
-	ret = Crypto()._get_random_data_from_user(uchars=10,desc='data').decode()
+	ret = Crypto(cfg)._get_random_data_from_user(uchars=10,desc='data').decode()
 	msg(f'USER ENTROPY (user input + keystroke timings):\n\n{fmt(ret,"  ")}')
 	times = ret.splitlines()[1:]
 	avg_prec = sum(len(t.split('.')[1]) for t in times) // len(times)
@@ -153,7 +153,7 @@ def tt_urand():
 		ymsg(f'WARNING: Avg. time precision of only {avg_prec} decimal points.  User entropy quality is degraded!')
 	else:
 		msg(f'Average time precision: {avg_prec} decimal points - OK')
-	line_input('Press ENTER to continue: ')
+	line_input( cfg, 'Press ENTER to continue: ' )
 
 def tt_txview():
 	cmsg('Testing tx.info.view_with_prompt() (try each viewing option)')
@@ -163,7 +163,7 @@ def tt_txview():
 	while True:
 		tx.info.view_with_prompt('View data for transaction?',pause=False)
 		set_vt100()
-		if not keypress_confirm('Continue testing transaction view?',default_yes=True):
+		if not keypress_confirm( cfg, 'Continue testing transaction view?', default_yes=True ):
 			break
 
 def tt_get_char_one():
@@ -186,8 +186,8 @@ def tt_get_char_one_char_immed_chars():
 
 get_term().register_cleanup()
 
-if cmd_args:
-	locals()['tt_'+cmd_args[0]]()
+if cfg._args:
+	locals()['tt_'+cfg._args[0]]()
 else:
 	for command in commands:
 		locals()['tt_'+command]()

+ 6 - 6
test/misc/term_ni.py

@@ -6,27 +6,27 @@ sys.path[0] = os.curdir
 
 from mmgen.common import *
 
-cmd_args = opts.init()
+cfg = opts.init()
 
 from mmgen.term import get_term,get_char_raw
 term = get_term()
 
-if cmd_args[0] == 'echo':
+if cfg._args[0] == 'echo':
 
 	from mmgen.ui import line_input
 
 	term.init(noecho=True)
-	line_input('noecho> ')
+	line_input( cfg, 'noecho> ' )
 	get_char_raw()
 
 	term.set('echo')
-	line_input('echo> ')
+	line_input( cfg, 'echo> ' )
 
 	term.set('noecho')
-	line_input('noecho> ')
+	line_input( cfg, 'noecho> ' )
 	get_char_raw()
 
-elif cmd_args[0] == 'cleanup':
+elif cfg._args[0] == 'cleanup':
 
 	term.register_cleanup()
 

+ 5 - 2
test/misc/tool_api_test.py

@@ -8,7 +8,6 @@ test.misc.tool_api_test: test the MMGen suite tool API
 """
 
 import sys,os
-from mmgen.common import *
 from mmgen.key import PrivKey
 from mmgen.addr import CoinAddr
 
@@ -64,7 +63,7 @@ def test_triplet(tool,coin,network,addrtype,key_idx,wif_chk,addr_chk):
 def run_test():
 
 	from mmgen.tool.api import tool_api
-	tool = tool_api()
+	tool = tool_api(cfg)
 
 	tool.coins
 	tool.print_addrtypes()
@@ -110,4 +109,8 @@ def run_test():
 			'SKxuS56e99jpCeD9mMQ5o63zoGPakNdM9HCvt4Vt2cypvRjCdvGJ',
 			'zchFELwBxqsAubsLQ8yZgPCDDGukjXJssgCbiTPwFNmFwn9haLnDatzfhLdZzJT4PcU4o2yr92B52UFirUzEdF6ZYM2gBkM' )
 
+from mmgen.opts import init
+
+cfg = init()
+
 run_test()

+ 3 - 3
test/misc/utf8_output.py

@@ -2,7 +2,7 @@
 
 from mmgen.common import *
 
-cmd_args = opts.init()
+cfg = opts.init()
 
 from mmgen.util import msg
 
@@ -13,7 +13,7 @@ text = {
 	'jp': 'Japanese text: {}'.format('必要なのは、信用ではなく暗号化された証明に基づく電子取引システムであり、')
 }
 
-if not cmd_args or not cmd_args[0] in text:
+if not cfg._args or not cfg._args[0] in text:
 	die(2,'argument must be one of {}'.format(list(text.keys())))
 
-msg(text[cmd_args[0]])
+msg(text[cfg._args[0]])

+ 15 - 11
test/objattrtest.py

@@ -30,7 +30,6 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir))
 os.environ['MMGEN_TEST_SUITE'] = '1'
 
 # Import these _after_ local path's been added to sys.path
-from test.objattrtest_py_d.oat_common import *
 from mmgen.common import *
 from mmgen.addrlist import *
 from mmgen.passwdlist import *
@@ -55,7 +54,12 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+from test.include.common import set_globals
+set_globals(cfg)
+
+from test.objattrtest_py_d.oat_common import *
 
 pd = namedtuple('permission_bits', ['read_ok','delete_ok','reassign_ok'])
 
@@ -120,10 +124,10 @@ def test_attr(data,obj,attrname,dobj,bits,attrval_type):
 				if d[k] != bits[k]:
 					fs = 'init value {iv}={a} for attr {n!r} does not match test data ({iv}={b})'
 					die(4,fs.format(iv=k,n=attrname,a=d[k],b=bits[k]))
-				if opt.verbose and d[k] == True:
+				if cfg.verbose and d[k] == True:
 					msg_r(f' {k}={d[k]!r}')
 
-		if opt.show_nonstandard_init:
+		if cfg.show_nonstandard_init:
 			for k,v in (('typeconv',False),('set_none_ok',True)):
 				if d[k] == v:
 					msg_r(f' {k}={v}')
@@ -136,19 +140,19 @@ def test_object(test_data,objname):
 	else:
 		cls = globals()[objname]
 
-	fs = 'Testing attribute ' + ('{!r:<15}{dt:13}' if opt.show_descriptor_type else '{!r}')
+	fs = 'Testing attribute ' + ('{!r:<15}{dt:13}' if cfg.show_descriptor_type else '{!r}')
 	data = test_data[objname]
 	obj = cls(*data.args,**data.kwargs)
 
 	for attrname,adata in data.attrs.items():
 		dobj = get_descriptor_obj(type(obj),attrname)
-		if opt.verbose:
+		if cfg.verbose:
 			msg_r(fs.format(attrname,dt=type(dobj).__name__.replace('MMGen','')))
 		bits = parse_permbits(adata[0])
 		test_attr(data,obj,attrname,dobj,bits,adata[1])
 		for perm_name,perm_value in bits._asdict().items():
 			test_attr_perm(obj,attrname,perm_name,perm_value,dobj,adata[1])
-		vmsg('')
+		cfg._util.vmsg('')
 
 def do_loop():
 	import importlib
@@ -156,12 +160,12 @@ def do_loop():
 	test_data = importlib.import_module(modname).tests
 	gmsg(f'Running immutable attribute tests for {proto.coin} {proto.network}')
 
-	utests = cmd_args
+	utests = cfg._args
 	for obj in test_data:
 		if utests and obj not in utests: continue
-		msg((blue if opt.verbose else nocolor)(f'Testing {obj}'))
+		msg((blue if cfg.verbose else nocolor)(f'Testing {obj}'))
 		test_object(test_data,obj)
 
-from mmgen.protocol import init_proto_from_opts
-proto = init_proto_from_opts(need_amt=True)
+proto = cfg._proto
+
 do_loop()

+ 4 - 3
test/objattrtest_py_d/oat_btc_mainnet.py

@@ -8,10 +8,11 @@ test.objattrtest_py_d.oat_btc_mainnet: BTC mainnet test vectors for MMGen data o
 """
 
 from .oat_common import *
+from ..include.common import cfg
 from mmgen.protocol import init_proto
 from mmgen.amt import BTCAmt
 
-proto = init_proto('btc',need_amt=True)
+proto = init_proto( cfg, 'btc', need_amt=True )
 
 sample_objs.update({
 	'PrivKey':   PrivKey(proto,seed_bin,compressed=True,pubkey_type='std'),
@@ -70,7 +71,7 @@ tests = {
 		'data': (0b001, bytes),
 		'sid':  (0b001, SeedID),
 		},
-		[seed_bin],
+		[cfg,seed_bin],
 		{},
 	),
 	'SubSeed': atd({
@@ -105,7 +106,7 @@ tests = {
 		'id_str': (0b001, SeedSplitIDString),
 		'count':  (0b001, SeedShareCount),
 		},
-		[sample_objs['MasterShareIdx'], sample_objs['Seed'], 'foo', 2],
+		[cfg,sample_objs['MasterShareIdx'], sample_objs['Seed'], 'foo', 2],
 		{},
 	),
 	# twuo.py

+ 4 - 4
test/objattrtest_py_d/oat_common.py

@@ -17,7 +17,7 @@ from mmgen.addr import *
 from mmgen.tx import *
 from mmgen.tw.unspent import *
 from mmgen.key import *
-from ..include.common import getrand
+from ..include.common import cfg,getrand
 
 from collections import namedtuple
 atd = namedtuple('attrtest_entry',['attrs','args','kwargs'])
@@ -43,13 +43,13 @@ sample_objs = {
 	'CoinTxID':  CoinTxID('aa'*32),
 
 	'SeedID':    SeedID(sid='F00F00BB'),
-	'Seed':      Seed(seed_bin=seed_bin),
+	'Seed':      Seed(cfg,seed_bin=seed_bin),
 
-	'SubSeedList': SubSeedList(Seed(seed_bin=seed_bin)),
+	'SubSeedList': SubSeedList(Seed(cfg,seed_bin=seed_bin)),
 	'SubSeedIdx':  SubSeedIdx('1S'),
 
 	'SeedSplitIDString': SeedSplitIDString('alice'),
-	'SeedShareList':     SeedShareList(Seed(seed_bin=seed_bin),SeedShareCount(2)),
+	'SeedShareList':     SeedShareList(Seed(cfg,seed_bin=seed_bin),SeedShareCount(2)),
 	'SeedShareIdx':      SeedShareIdx(1),
 	'SeedShareCount':    SeedShareCount(2),
 	'MasterShareIdx':    MasterShareIdx(7),

+ 25 - 22
test/objtest.py

@@ -60,7 +60,10 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+from test.include.common import set_globals
+set_globals(cfg)
 
 def run_test(test,arg,input_data,arg1,exc_name):
 	arg_copy = arg
@@ -96,22 +99,22 @@ def run_test(test,arg,input_data,arg1,exc_name):
 	else:
 		args = [arg]
 
-	if opt.getobj:
+	if cfg.getobj:
 		if args:
 			assert len(args) == 1, 'objtest_chk1: only one positional arg is allowed'
 			kwargs.update( { arg1: args[0] } )
-		if opt.silent:
+		if cfg.silent:
 			kwargs.update( { 'silent': True } )
 
 	try:
-		if not opt.super_silent:
+		if not cfg.super_silent:
 			arg_disp = repr(arg_copy[0] if type(arg_copy) == tuple else arg_copy)
-			if g.test_suite_deterministic and isinstance(arg_copy,dict):
+			if cfg.test_suite_deterministic and isinstance(arg_copy,dict):
 				arg_disp = re.sub(r'object at 0x[0-9a-f]+','object at [SCRUBBED]',arg_disp)
 			msg_r((green if input_data=='good' else orange)(f'{arg_disp+":":<22}'))
 		cls = globals()[test]
 
-		if opt.getobj:
+		if cfg.getobj:
 			ret = get_obj(globals()[test],**kwargs)
 		else:
 			ret = cls(*args,**kwargs)
@@ -121,14 +124,14 @@ def run_test(test,arg,input_data,arg1,exc_name):
 		if isinstance(ret_chk,str): ret_chk = ret_chk.encode()
 		if isinstance(ret,str): ret = ret.encode()
 
-		if opt.getobj:
+		if cfg.getobj:
 			if input_data == 'bad':
 				assert ret == False, 'non-False return on bad input data'
 		else:
-			if (opt.silent and input_data=='bad' and ret!=bad_ret) or (not opt.silent and input_data=='bad'):
+			if (cfg.silent and input_data=='bad' and ret!=bad_ret) or (not cfg.silent and input_data=='bad'):
 				raise UserWarning(f"Non-'None' return value {ret!r} with bad input data")
 
-		if opt.silent and input_data=='good' and ret==bad_ret:
+		if cfg.silent and input_data=='good' and ret==bad_ret:
 			raise UserWarning("'None' returned with good input data")
 
 		if input_data=='good':
@@ -137,17 +140,17 @@ def run_test(test,arg,input_data,arg1,exc_name):
 			if ret != ret_chk and repr(ret) != repr(ret_chk):
 				raise UserWarning(f"Return value ({ret!r}) doesn't match expected value ({ret_chk!r})")
 
-		if opt.super_silent:
+		if cfg.super_silent:
 			return
 
-		if opt.getobj and (not opt.silent and input_data == 'bad'):
+		if cfg.getobj and (not cfg.silent and input_data == 'bad'):
 			pass
 		else:
 			try: ret_disp = ret.decode()
 			except: ret_disp = ret
 			msg(f'==> {ret_disp!r}')
 
-		if opt.verbose and issubclass(cls,MMGenObject):
+		if cfg.verbose and issubclass(cls,MMGenObject):
 			ret.pmsg() if hasattr(ret,'pmsg') else pmsg(ret)
 
 	except Exception as e:
@@ -156,16 +159,16 @@ def run_test(test,arg,input_data,arg1,exc_name):
 		if not type(e).__name__ == exc_name:
 			msg(f'Incorrect exception: expected {exc_name} but got {type(e).__name__}')
 			raise
-		if opt.super_silent:
+		if cfg.super_silent:
 			pass
-		elif opt.silent:
+		elif cfg.silent:
 			msg(f'==> {exc_name}')
 		else:
 			msg( yellow(f' {exc_name}:') + str(e) )
 	except SystemExit as e:
 		if input_data == 'good':
 			raise ValueError('Error on good input data')
-		if opt.verbose:
+		if cfg.verbose:
 			msg(f'exitval: {e.code}')
 	except UserWarning as e:
 		msg(f'==> {ret!r}')
@@ -178,21 +181,21 @@ def do_loop():
 	gmsg(f'Running data object tests for {proto.coin} {proto.network}')
 
 	clr = None
-	utests = cmd_args
+	utests = cfg._args
 	for test in test_data:
 		arg1 = test_data[test].get('arg1')
 		if utests and test not in utests: continue
-		nl = ('\n','')[bool(opt.super_silent) or clr == None]
-		clr = (blue,nocolor)[bool(opt.super_silent)]
+		nl = ('\n','')[bool(cfg.super_silent) or clr == None]
+		clr = (blue,nocolor)[bool(cfg.super_silent)]
 
-		if opt.getobj and arg1 is None:
+		if cfg.getobj and arg1 is None:
 			msg(gray(f'{nl}Skipping {test}'))
 			continue
 		else:
 			msg(clr(f'{nl}Testing {test}'))
 
 		for k in ('bad','good'):
-			if not opt.super_silent:
+			if not cfg.super_silent:
 				msg(purple(capfirst(k)+' input:'))
 			for arg in test_data[test][k]:
 				run_test(
@@ -203,6 +206,6 @@ def do_loop():
 					exc_name   = test_data[test].get('exc_name') or ('ObjectInitError','None')[k=='good'],
 				)
 
-from mmgen.protocol import init_proto_from_opts
-proto = init_proto_from_opts(need_amt=True)
+proto = cfg._proto
+
 do_loop()

+ 4 - 3
test/objtest_py_d/ot_btc_mainnet.py

@@ -14,9 +14,10 @@ from mmgen.addrlist import AddrIdxList
 from mmgen.seedsplit import *
 from mmgen.key import *
 from .ot_common import *
+from ..include.common import cfg
 
 from mmgen.protocol import init_proto
-proto = init_proto('btc',need_amt=True)
+proto = init_proto( cfg, 'btc', need_amt=True )
 tw_pfx = proto.base_coin.lower() + ':'
 zero_addr = '1111111111111111111114oLvT2'
 
@@ -123,8 +124,8 @@ tests = {
 			),
 		'good': (
 			{'sid':'F00BAA12'},
-			{'seed': Seed(r16),    'ret': SeedID(seed=Seed(r16))},
-			{'sid': Seed(r16).sid, 'ret': SeedID(seed=Seed(r16))}
+			{'seed': Seed(cfg,r16),     'ret': SeedID(seed=Seed(cfg,r16))},
+			{'sid':  Seed(cfg,r16).sid, 'ret': SeedID(seed=Seed(cfg,r16))}
 			)
 	},
 	'SubSeedIdx': {

+ 2 - 1
test/objtest_py_d/ot_btc_testnet.py

@@ -9,9 +9,10 @@ test.objtest_py_d.ot_btc_testnet: BTC testnet test vectors for MMGen data object
 
 from mmgen.obj import *
 from .ot_common import *
+from ..include.common import cfg
 
 from mmgen.protocol import init_proto
-proto = init_proto('btc',network='testnet',need_amt=True)
+proto = init_proto( cfg, 'btc', network='testnet', need_amt=True )
 
 tests = {
 	'CoinAddr': {

+ 0 - 1
test/objtest_py_d/ot_common.py

@@ -8,7 +8,6 @@ test.objtest_py_d.ot_common: shared data for MMGen data objects tests
 """
 
 import os
-from mmgen.globalvars import g
 from ..include.common import *
 
 r32,r24,r16,r17,r18 = getrand(32),getrand(24),getrand(16),getrand(17),getrand(18)

+ 2 - 1
test/objtest_py_d/ot_ltc_mainnet.py

@@ -11,9 +11,10 @@ from decimal import Decimal
 
 from mmgen.obj import *
 from .ot_common import *
+from ..include.common import cfg
 
 from mmgen.protocol import init_proto
-proto = init_proto('ltc',need_amt=True)
+proto = init_proto( cfg, 'ltc', need_amt=True )
 
 tests = {
 	'LTCAmt': {

+ 2 - 1
test/objtest_py_d/ot_ltc_testnet.py

@@ -9,9 +9,10 @@ test.objtest_py_d.ot_ltc_testnet: LTC testnet test vectors for MMGen data object
 
 from mmgen.obj import *
 from .ot_common import *
+from ..include.common import cfg
 
 from mmgen.protocol import init_proto
-proto = init_proto('ltc',network='testnet',need_amt=True)
+proto = init_proto( cfg, 'ltc', network='testnet', need_amt=True )
 
 tests = {
 	'CoinAddr': {

+ 1 - 0
test/overlay/fakemods/mmgen/proto/btc/tw/unspent.py

@@ -10,6 +10,7 @@ if overlay_fake_os.getenv('MMGEN_BOGUS_UNSPENT_DATA'):
 			import json
 			from ....fileutil import get_data_from_file
 			return json.loads(get_data_from_file(
+				self.cfg,
 				overlay_fake_os.getenv('MMGEN_BOGUS_UNSPENT_DATA')
 			), parse_float=Decimal)
 

+ 16 - 11
test/scrambletest.py

@@ -21,12 +21,13 @@ test/scrambletest.py: seed scrambling and addrlist data generation tests for all
                       supported coins + passwords
 """
 
-import sys,os
+import sys,os,time
 from subprocess import run,PIPE
 
 from include.tests_header import repo_root
-from mmgen.common import *
-from test.include.common import *
+import mmgen.opts as opts
+from mmgen.globalvars import gc
+from mmgen.util import msg,msg_r,bmsg
 
 opts_data = {
 	'text': {
@@ -49,10 +50,14 @@ If no command is given, the whole suite of tests is run.
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
+
+from test.include.common import set_globals,end_msg,green
+
+set_globals(cfg)
 
 os.environ['MMGEN_DEBUG_ADDRLIST'] = '1'
-if not opt.system:
+if not cfg.system:
 	os.environ['PYTHONPATH'] = repo_root
 
 from collections import namedtuple
@@ -95,7 +100,7 @@ passwd_data = {
 'xmrseed_dfl_αω':td('62f5b72a5ca89cab', 'xmrseed:25:αω','-αω-xmrseed-25','αω xmrseed:25','tequila eden skulls giving jester hospital dreams bakery adjust nanny cactus inwardly films amply nanny soggy vials muppet yellow woken ashtray organs exhale foes eden'),
 }
 
-cvr_opts = ' -m trace --count --coverdir={} --file={}'.format( *init_coverage() ) if opt.coverage else ''
+cvr_opts = ' -m trace --count --coverdir={} --file={}'.format( *init_coverage() ) if cfg.coverage else ''
 cmd_base = f'python3{cvr_opts} cmds/mmgen-{{}}gen -qS'
 
 def get_cmd_output(cmd):
@@ -105,7 +110,7 @@ def get_cmd_output(cmd):
 	return cp.stdout.decode().splitlines()
 
 def do_test(cmd,tdata,msg_str,addr_desc):
-	vmsg(green(f'Executing: {cmd}'))
+	cfg._util.vmsg(green(f'Executing: {cmd}'))
 	msg_r('Testing: ' + msg_str)
 
 	lines = get_cmd_output(cmd)
@@ -113,11 +118,11 @@ def do_test(cmd,tdata,msg_str,addr_desc):
 	cmd_out['addr'] = lines[-2].split(None,1)[-1]
 
 	ref_data = tdata._asdict()
-	vmsg('')
+	cfg._util.vmsg('')
 	for k in ref_data:
 		if cmd_out[k] == ref_data[k]:
 			s = k.replace('seed','seed[:8]').replace('addr',addr_desc)
-			vmsg(f'  {s:9}: {cmd_out[k]}')
+			cfg._util.vmsg(f'  {s:9}: {cmd_out[k]}')
 		else:
 			die(4,f'\nError: sc_{k} value {cmd_out[k]} does not match reference value {ref_data[k]}')
 	msg('OK')
@@ -126,7 +131,7 @@ def do_coin_tests():
 	bmsg('Testing address scramble strings and list IDs')
 	for tname,tdata in (
 			tuple(bitcoin_data.items()) +
-			tuple(altcoin_data.items() if not opt.no_altcoin else []) ):
+			tuple(altcoin_data.items() if not cfg.no_altcoin else []) ):
 		if tname == 'zec_zcash_z' and gc.platform == 'win':
 			msg("Skipping 'zec_zcash_z' test for Windows platform")
 			continue
@@ -148,7 +153,7 @@ def do_passwd_tests():
 
 start_time = int(time.time())
 
-cmds = cmd_args or ('coin','pw')
+cmds = cfg._args or ('coin','pw')
 for cmd in cmds:
 	{'coin': do_coin_tests, 'pw': do_passwd_tests }[cmd]()
 

+ 94 - 88
test/test.py

@@ -22,7 +22,7 @@ test/test.py: Test suite for the MMGen wallet system
 
 def check_segwit_opts():
 	for k,m in (('segwit','S'),('segwit_random','S'),('bech32','B')):
-		if getattr(opt,k) and m not in proto.mmtypes:
+		if getattr(cfg,k) and m not in proto.mmtypes:
 			die(1,f'--{k.replace("_","-")} option incompatible with {proto.cls_name}')
 
 def create_shm_dir(data_dir,trash_dir):
@@ -92,7 +92,6 @@ try:
 except:
 	pass
 
-g.quiet = False # if 'quiet' was set in config file, disable here
 os.environ['MMGEN_QUIET'] = '0' # for this script and spawned scripts
 
 opts_data = {
@@ -170,20 +169,23 @@ environment var
 # we need some opt values before running opts.init, so parse without initializing:
 po = opts.init(opts_data,parse_only=True)
 
-from test.include.common import *
-from test.test_py_d.common import *
+from test.include.common import set_globals,get_test_data_dir
 
 data_dir = get_test_data_dir() # include/common.py
 
 # step 1: delete data_dir symlink in ./test;
-opt.resuming = any(k in po.user_opts for k in ('resume','resume_after'))
-opt.skipping_deps = opt.resuming or 'skip_deps' in po.user_opts
-
-if not opt.skipping_deps:
+if not po.user_opts.get('skipping_deps'):
 	try: os.unlink(data_dir)
 	except: pass
 
-opts.UserOpts._reset_ok += (
+# step 2: opts.init will create new data_dir in ./test (if not cfg.skipping_deps)
+cfg = opts.init(opts_data)
+
+set_globals(cfg)
+
+from test.test_py_d.common import * # this must be loaded after set_globals()
+
+type(cfg)._reset_ok += (
 	'no_daemon_autostart',
 	'names',
 	'no_timings',
@@ -191,45 +193,49 @@ opts.UserOpts._reset_ok += (
 	'resuming',
 	'skipping_deps' )
 
-# step 2: opts.init will create new data_dir in ./test (if not opt.skipping_deps)
-parsed_opts = opts.init(opts_data,return_parsed=True)
-usr_args = parsed_opts.cmd_args
+cfg.resuming = any(k in po.user_opts for k in ('resume','resume_after'))
+cfg.skipping_deps = cfg.resuming or 'skip_deps' in po.user_opts
+
+cmd_args = cfg._args
 
-if opt.pexpect_spawn and gc.platform == 'win':
+if cfg.pexpect_spawn and gc.platform == 'win':
 	die(1,'--pexpect-spawn option not supported on Windows platform, exiting')
 
-if opt.daemon_id and opt.daemon_id in g.blacklisted_daemons.split():
-	die(1,f'test.py: daemon {opt.daemon_id!r} blacklisted, exiting')
+if cfg.daemon_id and cfg.daemon_id in cfg.blacklisted_daemons.split():
+	die(1,f'test.py: daemon {cfg.daemon_id!r} blacklisted, exiting')
 
-network_id = g.coin.lower() + ('_tn' if opt.testnet else '')
+network_id = cfg.coin.lower() + ('_tn' if cfg.testnet else '')
 
-from mmgen.protocol import init_proto_from_opts
-proto = init_proto_from_opts()
+proto = cfg._proto
 
 # step 3: move data_dir to /dev/shm and symlink it back to ./test:
 trash_dir = os.path.join('test','trash')
 
-if not opt.skipping_deps:
+if not cfg.skipping_deps:
 	shm_dir = create_shm_dir(data_dir,trash_dir)
 
 check_segwit_opts()
 
-testing_segwit = opt.segwit or opt.segwit_random or opt.bech32
+testing_segwit = cfg.segwit or cfg.segwit_random or cfg.bech32
 
-if g.test_suite_deterministic:
-	opt.no_timings = True
+if cfg.test_suite_deterministic:
+	cfg.no_timings = True
 	init_color(num_colors=0)
 	os.environ['MMGEN_DISABLE_COLOR'] = '1'
 
-if opt.profile:
-	opt.names = True
+if cfg.profile:
+	cfg.names = True
 
-if opt.exact_output:
-	def msg(s): pass
-	qmsg = qmsg_r = vmsg = vmsg_r = msg_r = msg
+if cfg.exact_output:
+	def noop(s):
+		pass
+	qmsg = qmsg_r = msg = noop
+else:
+	qmsg = cfg._util.qmsg
+	qmsg_r = cfg._util.qmsg_r
 
-if opt.skipping_deps:
-	opt.no_daemon_autostart = True
+if cfg.skipping_deps:
+	cfg.no_daemon_autostart = True
 
 from test.test_py_d.cfg import cfgs,fixup_cfgs
 
@@ -280,16 +286,16 @@ def list_cmds():
 	sys.exit(0)
 
 def do_between():
-	if opt.pause:
+	if cfg.pause:
 		confirm_continue()
-	elif (opt.verbose or opt.exact_output) and not opt.skipping_deps:
+	elif (cfg.verbose or cfg.exact_output) and not cfg.skipping_deps:
 		sys.stderr.write('\n')
 
 def list_tmpdirs():
 	return {k:cfgs[k]['tmpdir'] for k in cfgs}
 
 def clean(usr_dirs=None,clean_overlay=True):
-	if opt.skipping_deps:
+	if cfg.skipping_deps:
 		return
 	all_dirs = list_tmpdirs()
 	dirnums = map(int,(usr_dirs if usr_dirs is not None else all_dirs))
@@ -335,11 +341,11 @@ def set_environ_for_spawned_scripts():
 	os.environ['MMGEN_COLUMNS'] = str(get_terminal_size().width)
 
 	if os.getenv('MMGEN_DEBUG_ALL'):
-		for name in g.env_opts:
+		for name in cfg.env_opts:
 			if name[:11] == 'MMGEN_DEBUG':
 				os.environ[name] = '1'
 
-	if not opt.system:
+	if not cfg.system:
 		os.environ['PYTHONPATH'] = repo_root
 
 	os.environ['MMGEN_NO_LICENSE'] = '1'
@@ -392,12 +398,12 @@ class CmdGroupMgr(object):
 					if sg_name in (None,sg_key):
 						for e in add_entries(
 								sg_key,
-								add_deps = sg_name and not opt.skipping_deps,
-								added_subgroups = [sg_name] if opt.deps_only else [] ):
+								add_deps = sg_name and not cfg.skipping_deps,
+								added_subgroups = [sg_name] if cfg.deps_only else [] ):
 							yield e
-					if opt.deps_only and sg_key == sg_name:
+					if cfg.deps_only and sg_key == sg_name:
 						return
-				elif not opt.skipping_deps:
+				elif not cfg.skipping_deps:
 					yield (name,data)
 
 		return tuple(gen())
@@ -470,12 +476,12 @@ class CmdGroupMgr(object):
 		for gname in self.cmd_groups:
 			ginfo.append(( gname, self.get_cls_by_gname(gname) ))
 
-		if opt.list_current_cmd_groups:
-			exclude = (opt.exclude_groups or '').split(',')
+		if cfg.list_current_cmd_groups:
+			exclude = (cfg.exclude_groups or '').split(',')
 			ginfo = [g for g in ginfo
 						if network_id in g[1].networks
 							and not g[0] in exclude
-							and g[0] in tuple(self.cmd_groups_dfl) + tuple(usr_args) ]
+							and g[0] in tuple(self.cmd_groups_dfl) + tuple(cmd_args) ]
 			desc = 'CONFIGURED'
 		else:
 			desc = 'AVAILABLE'
@@ -529,7 +535,7 @@ class TestSuiteRunner(object):
 	'test suite runner'
 
 	def __del__(self):
-		if opt.log:
+		if cfg.log:
 			self.log_fd.close()
 
 	def __init__(self,data_dir,trash_dir):
@@ -544,21 +550,21 @@ class TestSuiteRunner(object):
 		self.resume_cmd = None
 		self.deps_only = None
 
-		if opt.log:
+		if cfg.log:
 			self.log_fd = open(log_file,'a')
 			self.log_fd.write(f'\nLog started: {make_timestr()} UTC\n')
 			omsg(f'INFO → Logging to file {log_file!r}')
 		else:
 			self.log_fd = None
 
-		if opt.coverage:
+		if cfg.coverage:
 			coverdir,accfile = init_coverage()
 			omsg(f'INFO → Writing coverage files to {coverdir!r}')
 			self.pre_args = ['python3','-m','trace','--count','--coverdir='+coverdir,'--file='+accfile]
 		else:
 			self.pre_args = ['python3'] if gc.platform == 'win' else []
 
-		if opt.pexpect_spawn:
+		if cfg.pexpect_spawn:
 			omsg(f'INFO → Using pexpect.spawn() for real terminal emulation')
 
 	def spawn_wrapper(self,cmd,
@@ -572,12 +578,12 @@ class TestSuiteRunner(object):
 			timeout       = None,
 			pexpect_spawn = None ):
 
-		desc = self.ts.test_name if opt.names else self.gm.dpy_data[self.ts.test_name][1]
+		desc = self.ts.test_name if cfg.names else self.gm.dpy_data[self.ts.test_name][1]
 		if extra_desc:
 			desc += ' ' + extra_desc
 
 		cmd_path = (
-			cmd if opt.system # opt.system is broken for main test group with overlay tree
+			cmd if cfg.system # cfg.system is broken for main test group with overlay tree
 			else os.path.relpath(os.path.join(repo_root,cmd_dir,cmd)) )
 
 		args = (
@@ -591,7 +597,7 @@ class TestSuiteRunner(object):
 		qargs = ['{q}{}{q}'.format( a, q = "'" if ' ' in a else '' ) for a in args]
 		cmd_disp = ' '.join(qargs).replace('\\','/') # for mingw
 
-		if opt.log:
+		if cfg.log:
 			self.log_fd.write('[{}][{}:{}] {}\n'.format(
 				proto.coin.lower(),
 				self.ts.group_name,
@@ -605,11 +611,11 @@ class TestSuiteRunner(object):
 					args ))
 
 		if not no_msg:
-			t_pfx = '' if opt.no_timings else f'[{time.time() - self.start_time:08.2f}] '
-			if opt.verbose or opt.print_cmdline or opt.exact_output:
+			t_pfx = '' if cfg.no_timings else f'[{time.time() - self.start_time:08.2f}] '
+			if cfg.verbose or cfg.print_cmdline or cfg.exact_output:
 				omsg(green(f'{t_pfx}Testing: {desc}'))
 				if not msg_only:
-					clr1,clr2 = (nocolor,nocolor) if opt.print_cmdline else (green,cyan)
+					clr1,clr2 = (nocolor,nocolor) if cfg.print_cmdline else (green,cyan)
 					omsg(
 						clr1('Executing: ') +
 						clr2(repr(cmd_disp) if gc.platform == 'win' else cmd_disp)
@@ -623,8 +629,8 @@ class TestSuiteRunner(object):
 		# NB: the `pexpect_spawn` arg enables hold_protect and send_delay while the corresponding cmdline
 		# option does not.  For performance reasons, this is the desired behavior.  For full emulation of
 		# the user experience with hold protect enabled, specify --buf-keypress or --demo.
-		send_delay = 0.4 if pexpect_spawn is True or opt.buf_keypress else None
-		pexpect_spawn = pexpect_spawn if pexpect_spawn is not None else bool(opt.pexpect_spawn)
+		send_delay = 0.4 if pexpect_spawn is True or cfg.buf_keypress else None
+		pexpect_spawn = pexpect_spawn if pexpect_spawn is not None else bool(cfg.pexpect_spawn)
 
 		os.environ['MMGEN_HOLD_PROTECT_DISABLE'] = '' if send_delay else '1'
 		os.environ['MMGEN_TEST_SUITE_POPEN_SPAWN'] = '' if pexpect_spawn else '1'
@@ -649,7 +655,7 @@ class TestSuiteRunner(object):
 		t = int(time.time() - self.start_time)
 		sys.stderr.write(green(
 			f'{self.cmd_total} test{suf(self.cmd_total)} performed' +
-			('\n' if opt.no_timings else f'.  Elapsed time: {t//60:02d}:{t%60:02d}\n')
+			('\n' if cfg.no_timings else f'.  Elapsed time: {t//60:02d}:{t%60:02d}\n')
 		))
 
 	def init_group(self,gname,sg_name=None,cmd=None,quiet=False,do_clean=True):
@@ -657,7 +663,7 @@ class TestSuiteRunner(object):
 		ts_cls = CmdGroupMgr().load_mod(gname)
 
 		for k in ('segwit','segwit_random','bech32'):
-			if getattr(opt,k):
+			if getattr(cfg,k):
 				segwit_opt = k
 				break
 		else:
@@ -706,28 +712,28 @@ class TestSuiteRunner(object):
 		# pass through opts from cmdline (po.user_opts)
 		self.passthru_opts = ['--{}{}'.format(
 				k.replace('_','-'),
-				'=' + getattr(opt,k) if getattr(opt,k) != True else ''
-			) for k in self.ts.base_passthru_opts + self.ts.passthru_opts if k in parsed_opts.user_opts]
+				'' if cfg._uopts[k] is True else '=' + cfg._uopts[k]
+			) for k in cfg._uopts if k in self.ts.base_passthru_opts + self.ts.passthru_opts]
 
-		if opt.resuming:
-			rc = opt.resume or opt.resume_after
-			offset = 1 if opt.resume_after else 0
+		if cfg.resuming:
+			rc = cfg.resume or cfg.resume_after
+			offset = 1 if cfg.resume_after else 0
 			self.resume_cmd = self.gm.cmd_list[self.gm.cmd_list.index(rc)+offset]
 			omsg(f'INFO → Resuming at command {self.resume_cmd!r}')
-			if opt.step:
-				opt.exit_after = self.resume_cmd
+			if cfg.step:
+				cfg.exit_after = self.resume_cmd
 
-		if opt.exit_after and opt.exit_after not in self.gm.cmd_list:
-			die(1,f'{opt.exit_after!r}: command not recognized')
+		if cfg.exit_after and cfg.exit_after not in self.gm.cmd_list:
+			die(1,f'{cfg.exit_after!r}: command not recognized')
 
 		return True
 
-	def run_tests(self,usr_args):
+	def run_tests(self,cmd_args):
 		self.start_time = time.time()
 		self.daemon_started = False
 		gname_save = None
-		if usr_args:
-			for arg in usr_args:
+		if cmd_args:
+			for arg in cmd_args:
 				if arg in self.gm.cmd_groups:
 					if not self.init_group(arg):
 						continue
@@ -752,7 +758,7 @@ class TestSuiteRunner(object):
 						if not self.init_group(gname,sg_name,cmdname,quiet=same_grp,do_clean=not same_grp):
 							continue
 						if cmdname:
-							if opt.deps_only:
+							if cfg.deps_only:
 								self.deps_only = cmdname
 							try:
 								self.check_needs_rerun(cmdname,build=True)
@@ -772,13 +778,13 @@ class TestSuiteRunner(object):
 					else:
 						die(1,f'{arg!r}: command not recognized')
 		else:
-			if opt.exclude_groups:
-				exclude = opt.exclude_groups.split(',')
+			if cfg.exclude_groups:
+				exclude = cfg.exclude_groups.split(',')
 				for e in exclude:
 					if e not in self.gm.cmd_groups_dfl:
 						die(1,f'{e!r}: group not recognized')
 			for gname in self.gm.cmd_groups_dfl:
-				if opt.exclude_groups and gname in exclude:
+				if cfg.exclude_groups and gname in exclude:
 					continue
 				if not self.init_group(gname):
 					continue
@@ -832,7 +838,7 @@ class TestSuiteRunner(object):
 				for fn in fns:
 					if not root:
 						os.unlink(fn)
-				if not (dpy and opt.skipping_deps):
+				if not (dpy and cfg.skipping_deps):
 					self.run_test(cmd)
 				if not root:
 					do_between()
@@ -862,10 +868,10 @@ class TestSuiteRunner(object):
 				return
 			bmsg(f'Resuming at {self.resume_cmd!r}')
 			self.resume_cmd = None
-			opt.skipping_deps = False
-			opt.resuming = False
+			cfg.skipping_deps = False
+			cfg.resuming = False
 
-		if opt.profile:
+		if cfg.profile:
 			start = time.time()
 
 		self.ts.test_name = cmd # NB: Do not remove, this needs to be set twice
@@ -873,24 +879,24 @@ class TestSuiteRunner(object):
 #		self.ts.test_dpydata = cdata
 		self.ts.tmpdir_num = cdata[0]
 #		self.ts.cfg = cfgs[str(cdata[0])] # will remove this eventually
-		cfg = cfgs[str(cdata[0])]
+		test_cfg = cfgs[str(cdata[0])]
 		for k in (  'seed_len', 'seed_id',
 					'wpasswd', 'kapasswd',
 					'segwit', 'hash_preset',
 					'bw_filename', 'bw_params', 'ref_bw_seed_id',
 					'addr_idx_list', 'pass_idx_list' ):
-			if k in cfg:
-				setattr(self.ts,k,cfg[k])
+			if k in test_cfg:
+				setattr(self.ts,k,test_cfg[k])
 
 		ret = getattr(self.ts,cmd)(*arg_list) # run the test
 		if type(ret).__name__ == 'coroutine':
 			ret = async_run(ret)
 		self.process_retval(cmd,ret)
 
-		if opt.profile:
+		if cfg.profile:
 			omsg('\r\033[50C{:.4f}'.format( time.time() - start ))
 
-		if cmd == opt.exit_after:
+		if cmd == cfg.exit_after:
 			sys.exit(0)
 
 	def warn_skipped(self):
@@ -925,7 +931,7 @@ class TestSuiteRunner(object):
 		if cmd not in self.gm.cmd_list:
 			die(1,f'{cmd!r}: unrecognized command')
 
-		if not opt.quiet:
+		if not cfg.quiet:
 			omsg(f'Checking dependencies for {cmd!r}')
 
 		self.check_needs_rerun(self.ts,cmd,build=False)
@@ -966,18 +972,18 @@ class TestSuiteRunner(object):
 
 # main()
 
-if not opt.skipping_deps: # do this before list cmds exit, so we stay in sync with shm_dir
+if not cfg.skipping_deps: # do this before list cmds exit, so we stay in sync with shm_dir
 	create_tmp_dirs(shm_dir)
 
-if opt.list_cmd_groups:
+if cfg.list_cmd_groups:
 	CmdGroupMgr().list_cmd_groups()
-elif opt.list_cmds:
+elif cfg.list_cmds:
 	list_cmds()
-elif usr_args and usr_args[0] in utils:
-	globals()[usr_args[0]](*usr_args[1:])
+elif cmd_args and cmd_args[0] in utils:
+	globals()[cmd_args[0]](*cmd_args[1:])
 	sys.exit(0)
 
-if opt.pause:
+if cfg.pause:
 	set_restore_term_at_exit()
 
 set_environ_for_spawned_scripts()
@@ -986,7 +992,7 @@ from mmgen.exception import TestSuiteException,TestSuiteFatalException
 
 try:
 	tr = TestSuiteRunner(data_dir,trash_dir)
-	tr.run_tests(usr_args)
+	tr.run_tests(cmd_args)
 	tr.warn_skipped()
 	if tr.daemon_started:
 		stop_test_daemons(network_id)

+ 3 - 2
test/test_py_d/cfg.py

@@ -13,6 +13,7 @@ test.test_py_d.cfg: configuration data for test.py
 """
 
 from .common import *
+from ..include.common import cfg
 
 cmd_groups_dfl = {
 	'misc':             ('TestSuiteMisc',{}),
@@ -229,8 +230,8 @@ def fixup_cfgs():
 		cfgs[target]['tmpdir'] = os.path.join('test','tmp',target)
 
 	for k in cfgs:
-		cfgs[k]['segwit'] = randbool() if opt.segwit_random else bool(opt.segwit or opt.bech32)
+		cfgs[k]['segwit'] = randbool() if cfg.segwit_random else bool(cfg.segwit or cfg.bech32)
 
-	if g.debug_utf8:
+	if cfg.debug_utf8:
 		for k in cfgs:
 			cfgs[k]['tmpdir'] += '-α'

+ 7 - 6
test/test_py_d/common.py

@@ -21,7 +21,7 @@ test.test_py_d.common: Shared routines and data for the test.py test suite
 """
 
 import sys,os
-from mmgen.globalvars import g,gc
+from mmgen.globalvars import gc
 from mmgen.util import msg
 from ..include.common import *
 
@@ -71,9 +71,9 @@ chksum_pat = r'\b[A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4}\b'
 Ctrl_U = '\x15'
 
 def ok_msg():
-	if opt.profile:
+	if cfg.profile:
 		return
-	sys.stderr.write(green('\nOK\n') if opt.exact_output or opt.verbose else ' OK\n')
+	sys.stderr.write(green('\nOK\n') if cfg.exact_output or cfg.verbose else ' OK\n')
 
 def skip(name,reason=None):
 	msg('Skipping {}{}'.format( name, f' ({reason})' if reason else '' ))
@@ -82,10 +82,11 @@ def skip(name,reason=None):
 def confirm_continue():
 	from mmgen.ui import keypress_confirm
 	if keypress_confirm(
+			cfg,
 			blue('Continue? (Y/n): '),
 			default_yes     = True,
 			complete_prompt = True ):
-		if opt.verbose or opt.exact_output:
+		if cfg.verbose or cfg.exact_output:
 			sys.stderr.write('\n')
 	else:
 		raise KeyboardInterrupt('Exiting at user request')
@@ -96,7 +97,7 @@ def randbool():
 def disable_debug():
 	global save_debug
 	save_debug = {}
-	for k in g.env_opts:
+	for k in cfg.env_opts:
 		if k[:11] == 'MMGEN_DEBUG':
 			save_debug[k] = os.getenv(k)
 			os.environ[k] = ''
@@ -118,7 +119,7 @@ def get_file_with_ext(tdir,ext,delete=True,no_dot=False,return_list=False,delete
 
 	if len(flist) > 1 or delete_all:
 		if delete or delete_all:
-			if (opt.exact_output or opt.verbose) and not opt.quiet:
+			if (cfg.exact_output or cfg.verbose) and not cfg.quiet:
 				if delete_all:
 					msg(f'Deleting all *.{ext} files in {tdir!r}')
 				else:

+ 5 - 6
test/test_py_d/ts_autosign.py

@@ -23,8 +23,7 @@ test.test_py_d.ts_autosign: Autosign tests for the test.py test suite
 import os,shutil
 from subprocess import run
 
-from mmgen.globalvars import g,gc
-from mmgen.opts import opt
+from mmgen.globalvars import gc
 
 from ..include.common import *
 from .common import *
@@ -88,7 +87,7 @@ class TestSuiteAutosignBase(TestSuiteBase):
 			die(1,f'Test {type(self).__name__} not supported for Windows platform')
 		self.network_ids = [c+'_tn' for c in self.daemon_coins] + self.daemon_coins
 
-		if self.simulate and not opt.exact_output:
+		if self.simulate and not cfg.exact_output:
 			die(1,red('This command must be run with --exact-output enabled!'))
 
 		if self.simulate or not self.live:
@@ -168,7 +167,7 @@ class TestSuiteAutosignBase(TestSuiteBase):
 		mn = read_from_file(mn_file).strip().split()
 		from mmgen.mn_entry import mn_entry
 		entry_mode = 'full'
-		mne = mn_entry(mn_type,entry_mode)
+		mne = mn_entry( cfg, mn_type, entry_mode )
 		t.expect('Type a number.*: ',str(mne.entry_modes.index(entry_mode)+1),regex=True)
 		stealth_mnemonic_entry(t,mne,mn,entry_mode)
 		wf = t.written_to_file('Autosign wallet')
@@ -203,7 +202,7 @@ class TestSuiteAutosignBase(TestSuiteBase):
 
 		for f,fn in zip(tfs,tfns):
 			if fn: # use empty fn to skip file
-				if g.debug_utf8:
+				if cfg.debug_utf8:
 					ext = '.testnet.rawtx' if fn.endswith('.testnet.rawtx') else '.rawtx'
 					fn = fn[:-len(ext)] + '-α' + ext
 				target = joinpath(self.mountpoint,'tx',fn)
@@ -432,7 +431,7 @@ class TestSuiteAutosignLive(TestSuiteAutosignBTC):
 		t = self.spawn(
 			'mmgen-autosign',
 			self.opts + led_opts + ['--quiet','--no-summary','wait'])
-		if not opt.exact_output:
+		if not cfg.exact_output:
 			omsg('')
 		prompt_insert_sign(t)
 

+ 5 - 7
test/test_py_d/ts_base.py

@@ -21,8 +21,7 @@ test.test_py_d.ts_base: Base class for the test.py test suite
 """
 
 import os
-from mmgen.globalvars import g,gc
-from mmgen.opts import opt
+from mmgen.globalvars import gc
 from ..include.common import *
 from .common import *
 
@@ -37,13 +36,12 @@ class TestSuiteBase(object):
 	need_daemon = False
 
 	def __init__(self,trunner,cfgs,spawn):
-		from mmgen.protocol import init_proto_from_opts
-		self.proto = init_proto_from_opts(need_amt=True)
+		self.proto = cfg._proto
 		self.tr = trunner
 		self.cfgs = cfgs
 		self.spawn = spawn
 		self.have_dfl_wallet = False
-		self.usr_rand_chars = (5,30)[bool(opt.usr_random)]
+		self.usr_rand_chars = (5,30)[bool(cfg.usr_random)]
 		self.usr_rand_arg = f'-r{self.usr_rand_chars}'
 		self.altcoin_pfx = '' if self.proto.base_coin == 'BTC' else '-'+self.proto.base_coin
 		self.tn_ext = ('','.testnet')[self.proto.testnet]
@@ -54,11 +52,11 @@ class TestSuiteBase(object):
 
 	@property
 	def tmpdir(self):
-		return os.path.join('test','tmp','{}{}'.format(self.tmpdir_num,'-α' if g.debug_utf8 else ''))
+		return os.path.join('test','tmp','{}{}'.format(self.tmpdir_num,'-α' if cfg.debug_utf8 else ''))
 
 	@property
 	def segwit_mmtype(self):
-		return ('segwit','bech32')[bool(opt.bech32)] if self.segwit else None
+		return ('segwit','bech32')[bool(cfg.bech32)] if self.segwit else None
 
 	@property
 	def segwit_arg(self):

+ 3 - 3
test/test_py_d/ts_cfgfile.py

@@ -130,7 +130,7 @@ class TestSuiteCfgFile(TestSuiteBase):
 			t.expect(s)
 
 		if t.pexpect_spawn: # view and exit pager
-			time.sleep(1 if opt.exact_output else t.send_delay)
+			time.sleep(1 if cfg.exact_output else t.send_delay)
 			t.send('q')
 
 		t.expect(cp,'n')
@@ -202,7 +202,7 @@ class TestSuiteCfgFile(TestSuiteBase):
 			('ETH','True', '5.4321',True),
 			('ETC','False','5.4321',False)
 		):
-			if opt.no_altcoin and coin != 'BTC':
+			if cfg.no_altcoin and coin != 'BTC':
 				continue
 			t = self.spawn_test(
 				args = [
@@ -244,7 +244,7 @@ class TestSuiteCfgFile(TestSuiteBase):
 
 	def chain_names(self):
 
-		if opt.no_altcoin:
+		if cfg.no_altcoin:
 			return 'skip'
 
 		def run(chk,testnet):

+ 0 - 2
test/test_py_d/ts_chainsplit.py

@@ -21,8 +21,6 @@ test.test_py_d.ts_chainsplit: Forking scenario tests for the test.py test suite
 This module is unmaintained and currently non-functional
 """
 import os
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.util import die
 from ..include.common import *
 from .common import *

+ 33 - 28
test/test_py_d/ts_ethdev.py

@@ -25,8 +25,7 @@ from decimal import Decimal
 from collections import namedtuple
 from subprocess import run,PIPE,DEVNULL
 
-from mmgen.globalvars import g,gc
-from mmgen.opts import opt
+from mmgen.globalvars import gc
 from mmgen.util import die
 from mmgen.protocol import CoinProtocol
 from ..include.common import *
@@ -119,7 +118,7 @@ token_bals_getbalance = {
 from .ts_base import *
 from .ts_shared import *
 
-coin = g.coin
+coin = cfg.coin
 
 class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	'Ethereum transacting, token deployment and tracking wallet operations'
@@ -365,10 +364,10 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 			return
 
 		from mmgen.protocol import init_proto
-		self.proto = init_proto(g.coin,network='regtest',need_amt=True)
+		self.proto = init_proto( cfg, cfg.coin, network='regtest', need_amt=True )
 
 		from mmgen.daemon import CoinDaemon
-		self.daemon = CoinDaemon(self.proto.coin+'_rt',test_suite=True)
+		self.daemon = CoinDaemon( cfg, self.proto.coin+'_rt', test_suite=True )
 
 		self.using_solc = check_solc_ver()
 		if not self.using_solc:
@@ -405,7 +404,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	@property
 	async def rpc(self):
 		from mmgen.rpc import rpc_init
-		return await rpc_init(self.proto)
+		return await rpc_init(cfg,self.proto)
 
 	async def setup(self):
 		self.spawn('',msg_only=True)
@@ -431,11 +430,11 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		if d.id in ('geth','erigon'):
 			imsg('  {:19} {}'.format('Cmdline:',' '.join(e for e in d.start_cmd if not 'verbosity' in e)))
 
-		if not opt.no_daemon_autostart:
+		if not cfg.no_daemon_autostart:
 			if not d.id in ('geth','erigon'):
 				d.stop(silent=True)
 				d.remove_datadir()
-			d.start( silent = not (opt.verbose or opt.exact_output) )
+			d.start( silent = not (cfg.verbose or cfg.exact_output) )
 			rpc = await self.rpc
 			imsg(f'Daemon: {rpc.daemon.coind_name} v{rpc.daemon_version_str}')
 
@@ -449,6 +448,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 			from mmgen.proto.eth.misc import extract_key_from_geth_keystore_wallet
 			key = extract_key_from_geth_keystore_wallet(
+				cfg       = cfg,
 				wallet_fn = wallet_fn,
 				passwd = b'' )
 
@@ -552,7 +552,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		return t
 
 	def addrimport(self,ext='21-23]{}.regtest.addrs',expect='9/9',add_args=[],bad_input=False):
-		ext = ext.format('-α' if g.debug_utf8 else '')
+		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		fn = self.get_file_with_ext(ext,no_dot=True,delete=False)
 		t = self.spawn('mmgen-addrimport', self.eth_args[1:-1] + add_args + [fn])
 		if bad_input:
@@ -605,7 +605,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		return t
 
 	def txsign(self,ni=False,ext='{}.regtest.rawtx',add_args=[],dev_send=False):
-		ext = ext.format('-α' if g.debug_utf8 else '')
+		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		keyfile = joinpath(self.tmpdir,parity_devkey_fn)
 		txfile = self.get_file_with_ext(ext,no_dot=True)
 		t = self.spawn( 'mmgen-txsign',
@@ -620,17 +620,17 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		return self.txsign_ui_common(t,ni=ni,has_label=True)
 
 	def txsend(self,ni=False,ext='{}.regtest.sigtx',add_args=[]):
-		ext = ext.format('-α' if g.debug_utf8 else '')
+		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,no_dot=True)
 		t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
 		txid = self.txsend_ui_common(t,
-			quiet      = not g.debug,
+			quiet      = not cfg.debug,
 			bogus_send = False,
 			has_label  = True )
 		return t
 
 	def txview(self,ext_fs):
-		ext = ext_fs.format('-α' if g.debug_utf8 else '')
+		ext = ext_fs.format('-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,no_dot=True)
 		return self.spawn( 'mmgen-tool',['--verbose','txview',txfile] )
 
@@ -650,7 +650,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 			caller  = 'txdo',
 			acct    = '1',
 			no_read = True )
-		self._do_confirm_send(t,quiet=not g.debug,sure=False)
+		self._do_confirm_send(t,quiet=not cfg.debug,sure=False)
 		t.read()
 		self.get_file_with_ext('sigtx',delete_all=True)
 		return t
@@ -686,7 +686,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	def bal3(self):    return self.bal(n='3')
 
 	def tx_status(self,ext,expect_str,expect_str2='',add_args=[],exit_val=0):
-		ext = ext.format('-α' if g.debug_utf8 else '')
+		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,no_dot=True)
 		t = self.spawn('mmgen-txsend', self.eth_args + add_args + ['--status',txfile])
 		t.expect(expect_str)
@@ -707,7 +707,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 			key = self.keystore_data['key']
 			imsg(f'Key:       {key}')
 			from mmgen.proto.eth.misc import ec_sign_message_with_privkey
-			return ec_sign_message_with_privkey(self.message,bytes.fromhex(key),'eth_sign')
+			return ec_sign_message_with_privkey(cfg,self.message,bytes.fromhex(key),'eth_sign')
 
 		async def create_signature_rpc():
 			addr = self.read_from_tmpfile('signer_addr').strip()
@@ -791,7 +791,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 			eth_fee_res      = True )
 
 	def txbump(self,ext=',40000]{}.regtest.rawtx',fee='50G',add_args=[]):
-		ext = ext.format('-α' if g.debug_utf8 else '')
+		ext = ext.format('-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,no_dot=True)
 		t = self.spawn('mmgen-txbump', self.eth_args + add_args + ['--yes',txfile])
 		t.expect('or gas price: ',fee+'\n')
@@ -905,7 +905,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 	async def get_tx_receipt(self,txid):
 		from mmgen.tx import NewTX
-		tx = await NewTX(proto=self.proto)
+		tx = await NewTX(cfg=cfg,proto=self.proto)
 		tx.rpc = await self.rpc
 		res = await tx.get_receipt(txid)
 		imsg(f'Gas sent:  {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
@@ -931,7 +931,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		t = self.spawn( 'mmgen-'+mmgen_cmd, self.eth_args + args)
 		if mmgen_cmd == 'txcreate':
 			t.written_to_file('transaction')
-			ext = '[0,8000]{}.regtest.rawtx'.format('-α' if g.debug_utf8 else '')
+			ext = '[0,8000]{}.regtest.rawtx'.format('-α' if cfg.debug_utf8 else '')
 			txfile = self.get_file_with_ext(ext,no_dot=True)
 			t = self.spawn('mmgen-txsign', self.eth_args + ['--yes','-k',keyfile,txfile],no_msg=True)
 			self.txsign_ui_common(t,ni=True)
@@ -940,7 +940,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 		txid = self.txsend_ui_common(t,
 			caller = mmgen_cmd,
-			quiet  = mmgen_cmd == 'txdo' or not g.debug,
+			quiet  = mmgen_cmd == 'txdo' or not cfg.debug,
 			bogus_send = False )
 		addr = strip_ansi_escapes(t.expect_getend('Contract address: '))
 		if (await self.get_tx_receipt(txid)).status == 0:
@@ -971,12 +971,12 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		sid = dfl_sid
 		from mmgen.tool.wallet import tool_cmd
 		usr_mmaddrs = [f'{sid}:E:{i}' for i in (11,21)]
-		usr_addrs = [tool_cmd(cmdname='gen_addr',proto=self.proto).gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs]
 
 		from mmgen.proto.eth.contract import TokenResolve
 		async def do_transfer(rpc):
 			for i in range(2):
 				tk = await TokenResolve(
+					cfg,
 					self.proto,
 					rpc,
 					self.read_from_tmpfile(f'token_addr{i+1}').strip() )
@@ -998,6 +998,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		async def show_bals(rpc):
 			for i in range(2):
 				tk = await TokenResolve(
+					cfg,
 					self.proto,
 					rpc,
 					self.read_from_tmpfile(f'token_addr{i+1}').strip() )
@@ -1008,7 +1009,11 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 					usr_mmaddrs[i],
 					usr_addrs[i] ))
 
+		def gen_addr(addr):
+			return tool_cmd(cfg,cmdname='gen_addr',proto=self.proto).gen_addr(addr,dfl_words_file)
+
 		silence()
+		usr_addrs = list(map(gen_addr,usr_mmaddrs))
 		if op == 'show_bals':
 			await show_bals(await self.rpc)
 		elif op == 'do_transfer':
@@ -1191,7 +1196,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 			add_args = ['98831F3A:E:3,0.4321']):
 		args = ['--fee=20G','--cached-balances'] + add_args + [dfl_words_file]
 		t = self.txcreate(args=args,acct=acct,caller='txdo',fee_res_data=fee_res_data,no_read=True)
-		self._do_confirm_send(t,quiet=not g.debug,sure=False)
+		self._do_confirm_send(t,quiet=not cfg.debug,sure=False)
 		return t
 
 	def txcreate_refresh_balances(self,
@@ -1322,7 +1327,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	async def twmove(self):
 		self.spawn('',msg_only=True)
 		from mmgen.tw.ctl import TwCtl
-		twctl = await TwCtl(self.proto)
+		twctl = await TwCtl(cfg,self.proto)
 		imsg(f'Moving tracking wallet')
 		bakfile = twctl.tw_fn + '.bak.json'
 		if os.path.exists(bakfile):
@@ -1332,7 +1337,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 	def twimport(self,add_args=[],expect_str=None):
 		from mmgen.tw.json import TwJSON
-		fn = joinpath( self.tmpdir, TwJSON.Base(self.proto).dump_fn )
+		fn = joinpath( self.tmpdir, TwJSON.Base(cfg,self.proto).dump_fn )
 		t = self.spawn('mmgen-tool',self.eth_args_noquiet + ['twimport',fn] + add_args)
 		t.expect('(y/N): ','y')
 		if expect_str:
@@ -1346,7 +1351,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	def tw_chktotal(self):
 		self.spawn('',msg_only=True)
 		from mmgen.tw.json import TwJSON
-		fn = joinpath( self.tmpdir, TwJSON.Base(self.proto).dump_fn )
+		fn = joinpath( self.tmpdir, TwJSON.Base(cfg,self.proto).dump_fn )
 		res = json.loads(read_from_file(fn))
 		cmp_or_die( res['data']['value'], vbal6, 'value in tracking wallet JSON dump' )
 		return 'ok'
@@ -1354,7 +1359,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	async def twcompare(self):
 		self.spawn('',msg_only=True)
 		from mmgen.tw.ctl import TwCtl
-		twctl = await TwCtl(self.proto)
+		twctl = await TwCtl(cfg,self.proto)
 		fn = twctl.tw_fn
 		imsg(f'Comparing imported tracking wallet with original')
 		data = [json.dumps(json.loads(read_from_file(f)),sort_keys=True) for f in (fn,fn+'.bak.json')]
@@ -1364,7 +1369,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 	def edit_json_twdump(self):
 		self.spawn('',msg_only=True)
 		from mmgen.tw.json import TwJSON
-		fn = TwJSON.Base(self.proto).dump_fn
+		fn = TwJSON.Base(cfg,self.proto).dump_fn
 		text = json.loads(self.read_from_tmpfile(fn))
 		token_addr = self.read_from_tmpfile('token_addr2').strip()
 		text['data']['entries']['tokens'][token_addr][2][3] = f'edited comment [фубар] [{gr_uc}]'
@@ -1373,7 +1378,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 
 	def stop(self):
 		self.spawn('',msg_only=True)
-		if not opt.no_daemon_stop:
+		if not cfg.no_daemon_stop:
 			if not stop_test_daemons(self.proto.coin+'_rt'):
 				return False
 		set_vt100()

+ 3 - 3
test/test_py_d/ts_input.py

@@ -352,7 +352,7 @@ class TestSuiteInput(TestSuiteBase):
 		mn = mn or sample_mn[fmt]['mn'].split()
 		t = self.spawn('mmgen-tool',['mn2hex_interactive','fmt='+fmt,'mn_len=12','print_mn=1'])
 		from mmgen.mn_entry import mn_entry
-		mne = mn_entry(fmt,entry_mode)
+		mne = mn_entry( cfg, fmt, entry_mode )
 		t.expect(
 			'Type a number.*: ',
 			('\n' if enter_for_dfl else str(mne.entry_modes.index(entry_mode)+1)),
@@ -381,7 +381,7 @@ class TestSuiteInput(TestSuiteBase):
 			t.expect('Type a number.*: ','6',regex=True)
 			t.expect('invalid')
 			from mmgen.mn_entry import mn_entry
-			mne = mn_entry(fmt,entry_mode)
+			mne = mn_entry( cfg, fmt, entry_mode )
 			t.expect('Type a number.*: ',str(mne.entry_modes.index(entry_mode)+1),regex=True)
 			t.expect('Using (.+) entry mode',regex=True)
 			mode = strip_ansi_escapes(t.p.match.group(1)).lower()
@@ -405,7 +405,7 @@ class TestSuiteInput(TestSuiteBase):
 	def mnemonic_entry_mmgen_minimal(self):
 		from mmgen.mn_entry import mn_entry
 		# erase_chars: '\b\x7f'
-		m = mn_entry('mmgen','minimal')
+		m = mn_entry( cfg, 'mmgen', 'minimal' )
 		np = 2
 		mn = (
 			'z',

+ 26 - 25
test/test_py_d/ts_main.py

@@ -20,8 +20,6 @@
 test.test_py_d.ts_main: Basic operations tests for the test.py test suite
 """
 
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.fileutil import get_data_from_file,write_data_to_file
 from mmgen.wallet import get_wallet_cls
 from mmgen.wallet.mmgen import wallet as MMGenWallet
@@ -41,9 +39,9 @@ def make_brainwallet_file(fn):
 		return ''.join([ws_list[getrandnum_range(1,200) % len(ws_list)] for i in range(nchars)])
 	rand_pairs = [wl[getrandnum_range(1,200) % len(wl)] + rand_ws_seq() for i in range(nwords)]
 	d = ''.join(rand_pairs).rstrip() + '\n'
-	if opt.verbose:
+	if cfg.verbose:
 		msg_r(f'Brainwallet password:\n{cyan(d)}')
-	write_data_to_file(fn,d,'brainwallet password',quiet=True,ignore_opt_outdir=True)
+	write_data_to_file(cfg,fn,d,'brainwallet password',quiet=True,ignore_opt_outdir=True)
 
 def verify_checksum_or_exit(checksum,chk):
 	chk = strip_ansi_escapes(chk)
@@ -194,7 +192,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		TestSuiteBase.__init__(self,trunner,cfgs,spawn)
 		if trunner == None or self.proto.coin.lower() not in self.networks:
 			return
-		self.rpc = async_run(rpc_init(self.proto))
+		self.rpc = async_run(rpc_init(cfg,self.proto))
 		self.lbl_id = ('account','label')['label_api' in self.rpc.caps]
 		if self.proto.coin in ('BTC','BCH','LTC'):
 			self.tx_fee     = {'btc':'0.0001','bch':'0.001','ltc':'0.01'}[self.proto.coin.lower()]
@@ -207,9 +205,9 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		addrfile = self.get_file_with_ext('addrs')
 		from mmgen.addrlist import AddrList
 		silence()
-		chk = AddrList( self.proto, addrfile ).chksum
+		chk = AddrList( cfg, self.proto, addrfile ).chksum
 		end_silence()
-		if opt.verbose and display:
+		if cfg.verbose and display:
 			msg(f'Checksum: {cyan(chk)}')
 		return chk
 
@@ -239,10 +237,10 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 
 	def delete_dfl_wallet(self,pf):
 		self.write_to_tmpfile('del_dw_run',b'',binary=True)
-		if opt.no_dw_delete:
+		if cfg.no_dw_delete:
 			return 'skip'
-		for wf in [f for f in os.listdir(g.data_dir) if f[-6:]=='.mmdat']:
-			os.unlink(joinpath(g.data_dir,wf))
+		for wf in [f for f in os.listdir(cfg.data_dir) if f[-6:]=='.mmdat']:
+			os.unlink(joinpath(cfg.data_dir,wf))
 		self.spawn('',msg_only=True)
 		self.have_dfl_wallet = False
 		return 'ok'
@@ -294,7 +292,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 
 	def passchg(self,wf,pf,label_action='cmdline',dfl_wallet=False,delete=False):
 		silence()
-		self.write_to_tmpfile(pwfile,get_data_from_file(pf))
+		self.write_to_tmpfile( pwfile, get_data_from_file(cfg,pf) )
 		end_silence()
 		add_args = {'cmdline': ['-d',self.tmpdir,'-L','Changed label (UTF-8) α'],
 					'keep':    ['-d',self.tr.trash_dir,'--keep-label'],
@@ -336,8 +334,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		return self.walletchk(wf,pf,wcls=wcls,dfl_wallet=dfl_wallet)
 
 	def _write_fake_data_to_file(self,d):
-		write_data_to_file(self.unspent_data_file,d,'Unspent outputs',quiet=True,ignore_opt_outdir=True)
-		if opt.verbose or opt.exact_output:
+		write_data_to_file(cfg,self.unspent_data_file,d,'Unspent outputs',quiet=True,ignore_opt_outdir=True)
+		if cfg.verbose or cfg.exact_output:
 			sys.stderr.write(f'Fake transaction wallet data written to file {self.unspent_data_file!r}\n')
 
 	def _create_fake_unspent_entry(self,coinaddr,al_id=None,idx=None,comment=None,non_mmgen=False,segwit=False):
@@ -373,7 +371,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		for d in tx_data.values():
 			al = adata.addrlist(al_id=d['al_id'])
 			for n,(idx,coinaddr) in enumerate(al.addrpairs()):
-				comment = get_comment(do_shuffle=not g.test_suite_deterministic)
+				comment = get_comment(do_shuffle=not cfg.test_suite_deterministic)
 				out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,comment,segwit=d['segwit']))
 				if n == 0:  # create a duplicate address. This means addrs_per_wallet += 1
 					out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,comment,segwit=d['segwit']))
@@ -387,11 +385,13 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 				pubkey_type = 'std' )
 			from mmgen.addrgen import KeyGenerator,AddrGenerator
 			rand_coinaddr = AddrGenerator(
+				cfg,
 				self.proto,
 				('legacy','compressed')[non_mmgen_input_compressed]
-				).to_addr(KeyGenerator(self.proto,'std').gen_data(privkey))
+				).to_addr(KeyGenerator( cfg, self.proto, 'std' ).gen_data(privkey))
 			of = joinpath(self.cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
 			write_data_to_file(
+				cfg               = cfg,
 				outfile           = of,
 				data              = privkey.wif + '\n',
 				desc              = f'compressed {self.proto.name} key',
@@ -406,14 +406,14 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		from mmgen.addrdata import AddrData
 		tx_data,ad = {},AddrData(self.proto)
 		for s in sources:
-			afile = get_file_with_ext(self.cfgs[s]['tmpdir'],'addrs')
-			al = AddrList(self.proto,afile)
+			addrfile = get_file_with_ext(self.cfgs[s]['tmpdir'],'addrs')
+			al = AddrList( cfg, self.proto, addrfile )
 			ad.add(al)
 			aix = AddrIdxList(fmt_str=self.cfgs[s]['addr_idx_list'])
 			if len(aix) != addrs_per_wallet:
 				die( 'TestSuiteFatalException', f'Address index list length != {addrs_per_wallet}: {repr(aix)}' )
 			tx_data[s] = {
-				'addrfile': afile,
+				'addrfile': addrfile,
 				'chk': al.chksum,
 				'al_id': al.al_id,
 				'addr_idxs': aix[-2:],
@@ -426,8 +426,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		privkey = PrivKey(self.proto,getrand(32),compressed=True,pubkey_type='std')
 		t = ('compressed','segwit')['S' in self.proto.mmtypes]
 		from mmgen.addrgen import KeyGenerator,AddrGenerator
-		rand_coinaddr = AddrGenerator(self.proto,t).to_addr(
-			KeyGenerator(self.proto,'std').gen_data(privkey)
+		rand_coinaddr = AddrGenerator( cfg, self.proto, t ).to_addr(
+			KeyGenerator( cfg, self.proto, 'std' ).gen_data(privkey)
 		)
 
 		# total of two outputs must be < 10 BTC (<1000 LTC)
@@ -472,7 +472,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 						cmdline_inputs             = False,
 						tweaks                     = [] ):
 
-		if opt.verbose or opt.exact_output:
+		if cfg.verbose or cfg.exact_output:
 			sys.stderr.write(green('Generating fake tracking wallet info\n'))
 
 		silence()
@@ -496,7 +496,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 
 		end_silence()
 
-		if opt.verbose or opt.exact_output:
+		if cfg.verbose or cfg.exact_output:
 			sys.stderr.write('\n')
 
 		t = self.spawn(
@@ -557,8 +557,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 			t.do_comment(False,has_label=True)
 			for cnum,wcls in (('1',IncogWallet),('3',MMGenWallet),('4',MMGenWallet)):
 				t.passphrase(wcls.desc,self.cfgs[cnum]['wpasswd'])
-			self._do_confirm_send(t,quiet=not g.debug,confirm_send=True)
-			if g.debug:
+			self._do_confirm_send(t,quiet=not cfg.debug,confirm_send=True)
+			if cfg.debug:
 				t.written_to_file('Transaction')
 		else:
 			t.do_comment(False)
@@ -617,7 +617,8 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		wcls = get_wallet_cls(fmt_code=out_fmt)
 		msg('==> {}: {}'.format(
 			wcls.desc,
-			cyan(get_data_from_file(f,wcls.desc)) ))
+			cyan(get_data_from_file( cfg, f, wcls.desc ))
+		))
 		end_silence()
 		return t
 

+ 3 - 3
test/test_py_d/ts_misc.py

@@ -20,7 +20,7 @@
 test.test_py_d.ts_misc: Miscellaneous test groups for the test.py test suite
 """
 
-from mmgen.globalvars import g,gc
+from mmgen.globalvars import gc
 from ..include.common import *
 from .common import *
 from .ts_base import *
@@ -41,7 +41,7 @@ class TestSuiteMisc(TestSuiteBase):
 	color = True
 
 	def rpc_backends(self):
-		backends = g.autoset_opts['rpc_backend'][1]
+		backends = cfg.autoset_opts['rpc_backend'][1]
 		for b in backends:
 			t = self.spawn_chk('mmgen-tool',[f'--rpc-backend={b}','daemon_version'],extra_desc=f'({b})')
 		return t
@@ -76,7 +76,7 @@ class TestSuiteMisc(TestSuiteBase):
 
 		t = self.spawn('test/misc/term_ni.py',['echo'],cmd_dir='.',pexpect_spawn=True,timeout=1)
 		t.p.logfile = None
-		t.p.logfile_read = sys.stdout if opt.verbose or opt.exact_output else None
+		t.p.logfile_read = sys.stdout if cfg.verbose or cfg.exact_output else None
 		t.p.logfile_send = None
 
 		test_noecho()

+ 17 - 20
test/test_py_d/ts_opts.py

@@ -45,7 +45,7 @@ class TestSuiteOpts(TestSuiteBase):
 
 	def opt_helpscreen(self):
 		expect = r'OPTS.PY: Opts test.*USAGE:\s+opts.py'
-		if not opt.pexpect_spawn:
+		if not cfg.pexpect_spawn:
 			expect += r'.*--minconf.*NOTES FOR THIS.*a note'
 		t = self.do_run( ['--help'], expect, 0, regex=True )
 		if t.pexpect_spawn:
@@ -57,17 +57,15 @@ class TestSuiteOpts(TestSuiteBase):
 		return self.check_vals(
 				[],
 				(
-					('opt.foo',               'None'),         # added opt
-					('opt.print_checksum',    'None'),         # sets 'quiet'
-					('opt.quiet',             'False'),        # init_opts, incompatible_opts
-					('opt.verbose',           'None'),         # init_opts, incompatible_opts
-					('opt.fee_estimate_mode', 'conservative'), # autoset_opts
-					('opt.passwd_file',       'None'),         # infile_opts - check_infile()
-					('opt.outdir',            'None'),         # check_outdir()
-					('opt.cached_balances',   'None'),         # opt_sets_global
-					('opt.minconf',           '1'),            # global_sets_opt
-					('g.cached_balances',     'None'),
-					('g.minconf',             '1'),
+					('cfg.foo',                 'None'),         # added opt
+					('cfg.print_checksum',      'None'),         # sets 'quiet'
+					('cfg.quiet',               'False'),        # init_opts, incompatible_opts
+					('cfg.verbose',             'False'),        # init_opts, incompatible_opts
+					('cfg.passwd_file',         ''),             # infile_opts - check_infile()
+					('cfg.outdir',              ''),             # check_outdir()
+					('cfg.cached_balances',     'False'),
+					('cfg.minconf',             '1'),
+					('cfg.fee_estimate_mode',   'conservative'), # autoset_opts
 				)
 			)
 
@@ -85,14 +83,13 @@ class TestSuiteOpts(TestSuiteBase):
 					f'--hidden-incog-input-params={pf},123',
 				],
 				(
-					('opt.print_checksum',    'True'),
-					('opt.quiet',             'True'), # set by print_checksum
-					('opt.fee_estimate_mode', 'economical'),
-					('opt.passwd_file',       pf),
-					('opt.outdir',            self.tmpdir),
-					('opt.cached_balances',   'True'),
-					('opt.hidden_incog_input_params', pf+',123'),
-					('g.cached_balances',     'True'),
+					('cfg.print_checksum',           'True'),
+					('cfg.quiet',                    'True'), # set by print_checksum
+					('cfg.passwd_file',              pf),
+					('cfg.outdir',                   self.tmpdir),
+					('cfg.cached_balances',          'True'),
+					('cfg.hidden_incog_input_params', pf+',123'),
+					('cfg.fee_estimate_mode',         'economical'),
 				)
 			)
 

+ 2 - 4
test/test_py_d/ts_ref.py

@@ -21,8 +21,6 @@ test.test_py_d.ts_ref: Reference file tests for the test.py test suite
 """
 
 import os
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.wallet import get_wallet_cls
 from ..include.common import *
 from .common import *
@@ -282,11 +280,11 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 
 	def ref_tool_decrypt(self):
 		f = joinpath(ref_dir,ref_enc_fn)
-		if not g.debug_utf8:
+		if not cfg.debug_utf8:
 			disable_debug()
 		dec_file = joinpath(self.tmpdir,'famous.txt')
 		t = self.spawn('mmgen-tool', ['-q','decrypt',f,'outfile='+dec_file,'hash_preset=1'])
-		if not g.debug_utf8:
+		if not cfg.debug_utf8:
 			restore_debug()
 		t.passphrase('data',tool_enc_passwd)
 		t.written_to_file('Decrypted data')

+ 4 - 6
test/test_py_d/ts_ref_3seed.py

@@ -21,8 +21,6 @@ test.test_py_d.ts_ref_3seed: Saved and generated reference file tests for 128,
                              192 and 256-bit seeds for the test.py test suite
 """
 
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.wallet import get_wallet_cls
 from ..include.common import *
 from .common import *
@@ -121,7 +119,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 			]
 			slarg = [f'-l{self.seed_len} ']
 			hparg = ['-p1']
-			if wtype == 'hic_wallet_old' and opt.profile:
+			if wtype == 'hic_wallet_old' and cfg.profile:
 				msg('')
 			t = self.spawn('mmgen-walletchk',
 				slarg + hparg + of_arg + ic_arg,
@@ -155,7 +153,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 		pat = r'{}-[0-9A-F]{{8}}\[{},1\]{}.mmdat'.format(
 			self.chk_data['sids'][idx],
 			self.chk_data['lens'][idx],
-			'-α' if g.debug_utf8 else '')
+			'-α' if cfg.debug_utf8 else '')
 		assert re.match(pat,fn), f'{pat} != {fn}'
 		sid = os.path.basename(fn.split('-')[0])
 		cmp_or_die(sid,self.seed_id,desc='Seed ID')
@@ -178,7 +176,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 			cmp_or_die('{}[{}]{}.{}'.format(
 				sid,
 				slen,
-				'-α' if g.debug_utf8 else '',
+				'-α' if cfg.debug_utf8 else '',
 				wcls.ext),
 				fn )
 		return t
@@ -192,7 +190,7 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 
 	def ref_walletconv_incog(self,ofmt='incog',ext='mmincog'):
 		args = ['-r0','-p1']
-		pat = r'{}-[0-9A-F]{{8}}-[0-9A-F]{{8}}\[{},1\]' + ('-α' if g.debug_utf8 else '') + '.' + ext
+		pat = r'{}-[0-9A-F]{{8}}-[0-9A-F]{{8}}\[{},1\]' + ('-α' if cfg.debug_utf8 else '') + '.' + ext
 		return self.ref_walletconv(ofmt=ofmt,extra_args=args,re_pat=pat)
 
 	def ref_walletconv_hexincog(self):

+ 2 - 3
test/test_py_d/ts_ref_altcoin.py

@@ -21,9 +21,8 @@ test.test_py_d.ts_ref_altcoin: Altcoin reference file tests for the test.py test
 """
 
 import os
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from .common import *
+from ..include.common import cfg
 from .ts_ref import *
 from .ts_base import *
 
@@ -101,7 +100,7 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase):
 					ref_dir,
 					self._get_ref_subdir_by_coin(coin),
 					fn )
-				proto = MMGenTxFile.get_proto(txfile,quiet_open=True)
+				proto = MMGenTxFile.get_proto(cfg,txfile,quiet_open=True)
 				if proto.sign_mode == 'daemon':
 					start_test_daemons(proto.network_id)
 					set_vt100()

+ 24 - 25
test/test_py_d/ts_regtest.py

@@ -22,8 +22,6 @@ test.test_py_d.ts_regtest: Regtest tests for the test.py test suite
 
 import os,json,time
 from decimal import Decimal
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.util import die,gmsg
 from mmgen.protocol import init_proto
 from mmgen.addrlist import AddrList
@@ -135,6 +133,7 @@ rt_data = {
 def make_burn_addr(proto):
 	from mmgen.tool.coin import tool_cmd
 	return tool_cmd(
+		cfg     = cfg,
 		cmdname = 'pubhash2addr',
 		proto   = proto,
 		mmtype  = 'compressed' ).pubhash2addr('00'*20)
@@ -416,7 +415,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			return
 		if self.proto.testnet:
 			die(2,'--testnet and --regtest options incompatible with regtest test suite')
-		self.proto = init_proto(self.proto.coin,network='regtest',need_amt=True)
+		self.proto = init_proto( cfg, self.proto.coin, network='regtest', need_amt=True )
 		coin = self.proto.coin.lower()
 
 		import test.test_py_d.ts_regtest as rt_mod
@@ -425,7 +424,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 		if self.proto.coin == 'BTC':
 			self.test_rbf = True # tests are non-coin-dependent, so run just once for BTC
-			if g.test_suite_deterministic:
+			if cfg.test_suite_deterministic:
 				self.deterministic = True
 				self.miner_addr = 'bcrt1qaq8t3pakcftpk095tnqfv5cmmczysls024atnd' # regtest.create_hdseed()
 				self.miner_wif = 'cTyMdQ2BgfAsjopRVZrj7AoEGp97pKfrC2NkqLuwHr4KHfPNAKwp'
@@ -443,7 +442,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def _add_comments_to_addr_file(self,addrfile,outfile,use_comments=False):
 		silence()
 		gmsg(f'Adding comments to address file {addrfile!r}')
-		a = AddrList(self.proto,addrfile)
+		a = AddrList( cfg, self.proto, addrfile )
 		for n,idx in enumerate(a.idxs(),1):
 			if use_comments:
 				a.set_comment(idx,get_comment())
@@ -452,7 +451,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		af = a.get_file()
 		af.format(add_comments=True)
 		from mmgen.fileutil import write_data_to_file
-		write_data_to_file(outfile,af.fmt_data,quiet=True,ignore_opt_outdir=True)
+		write_data_to_file(cfg,outfile,af.fmt_data,quiet=True,ignore_opt_outdir=True)
 		end_silence()
 
 	def setup(self):
@@ -499,7 +498,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def _get_user_subsid(self,user,subseed_idx):
 		fn = get_file_with_ext(self._user_dir(user),dfl_wcls.ext)
 		silence()
-		w = Wallet( fn=fn, passwd_file=os.path.join(self.tmpdir,'wallet_password') )
+		w = Wallet( cfg, fn=fn, passwd_file=os.path.join(self.tmpdir,'wallet_password') )
 		end_silence()
 		return w.seed.subseed(subseed_idx).sid
 
@@ -530,7 +529,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			addrfile = joinpath(self._user_dir(user),
 				'{}{}{}[{}]{x}.regtest.addrs'.format(
 					sid,self.altcoin_pfx,id_strs[desc],addr_range,
-					x='-α' if g.debug_utf8 else ''))
+					x='-α' if cfg.debug_utf8 else ''))
 			if mmtype == self.proto.mmtypes[0] and user == 'bob':
 				self._add_comments_to_addr_file(addrfile,addrfile,use_comments=True)
 			t = self.spawn(
@@ -541,7 +540,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 					(['--batch'] if batch else []) +
 					[addrfile] ),
 				extra_desc = f'({desc})' )
-			if g.debug:
+			if cfg.debug:
 				t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
 			t.expect('Importing')
 			if batch:
@@ -598,7 +597,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			return 'skip'
 		self.spawn('',msg_only=True)
 		from mmgen.proto.btc.regtest import MMGenRegtest
-		rt = MMGenRegtest(self.proto.coin)
+		rt = MMGenRegtest(cfg,self.proto.coin)
 		await rt.stop()
 		from shutil import rmtree
 		imsg(f'Deleting Bob’s old tracking wallet')
@@ -888,10 +887,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def get_addr_from_addrlist(self,user,sid,mmtype,idx,addr_range='1-5'):
 		id_str = { 'L':'', 'S':'-S', 'C':'-C', 'B':'-B' }[mmtype]
 		ext = '{}{}{}[{}]{x}.regtest.addrs'.format(
-			sid,self.altcoin_pfx,id_str,addr_range,x='-α' if g.debug_utf8 else '')
+			sid,self.altcoin_pfx,id_str,addr_range,x='-α' if cfg.debug_utf8 else '')
 		addrfile = get_file_with_ext(self._user_dir(user),ext,no_dot=True)
 		silence()
-		addr = AddrList(self.proto,addrfile).data[idx].addr
+		addr = AddrList( cfg, self.proto, addrfile ).data[idx].addr
 		end_silence()
 		return addr
 
@@ -909,7 +908,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def bob_rbf_1output_bump(self):
 		if not self.test_rbf:
 			return 'skip'
-		ext = '9343,3]{x}.regtest.rawtx'.format(x='-α' if g.debug_utf8 else '')
+		ext = '9343,3]{x}.regtest.rawtx'.format(x='-α' if cfg.debug_utf8 else '')
 		txfile = get_file_with_ext(self.tr.trash_dir,ext,delete=False,no_dot=True)
 		return self.user_txbump('bob',
 			self.tr.trash_dir,
@@ -958,7 +957,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		return t
 
 	def bob_rbf_bump(self):
-		ext = ',{}]{x}.regtest.sigtx'.format(rtFee[1][:-1],x='-α' if g.debug_utf8 else '')
+		ext = ',{}]{x}.regtest.sigtx'.format(rtFee[1][:-1],x='-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,delete=False,no_dot=True)
 		return self.user_txbump('bob',self.tmpdir,txfile,rtFee[2],add_args=['--send'])
 
@@ -969,10 +968,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		return t
 
 	def _get_mempool(self):
-		if not g.debug_utf8:
+		if not cfg.debug_utf8:
 			disable_debug()
 		ret = self.spawn('mmgen-regtest',['mempool']).read()
-		if not g.debug_utf8:
+		if not cfg.debug_utf8:
 			restore_debug()
 		m = re.search(r'(\[\s*"[a-f0-9]{64}"\s*])',ret) # allow for extra output by handler at end
 		return json.loads(m.group(1))
@@ -987,7 +986,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def bob_rbf_status(self,fee,exp1,exp2=''):
 		if not self.proto.cap('rbf'):
 			return 'skip'
-		ext = ',{}]{x}.regtest.sigtx'.format(fee[:-1],x='-α' if g.debug_utf8 else '')
+		ext = ',{}]{x}.regtest.sigtx'.format(fee[:-1],x='-α' if cfg.debug_utf8 else '')
 		txfile = self.get_file_with_ext(ext,delete=False,no_dot=True)
 		return self.user_txsend_status('bob',txfile,exp1,exp2)
 
@@ -1043,7 +1042,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 	def _gen_pairs(self,n):
 		from mmgen.tool.api import tool_api
-		t = tool_api()
+		t = tool_api(cfg)
 		t.init_coin(self.proto.coin,self.proto.network)
 
 		def gen_addr(Type):
@@ -1061,7 +1060,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 	def user_import(self,user,args,nAddr):
 		t = self.spawn('mmgen-addrimport',['--'+user]+args)
-		if g.debug:
+		if cfg.debug:
 			t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
 		t.expect(f'Importing {nAddr} address')
 		if '--rescan' in args:
@@ -1272,7 +1271,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def bob_edit_json_twdump(self):
 		self.spawn('',msg_only=True)
 		from mmgen.tw.json import TwJSON
-		fn = TwJSON.Base(self.proto).dump_fn
+		fn = TwJSON.Base(cfg,self.proto).dump_fn
 		text = json.loads(self.read_from_tmpfile(fn))
 		text['data']['entries'][3][3] = f'edited comment [фубар] [{gr_uc}]'
 		self.write_to_tmpfile( fn, json.dumps(text,indent=4) )
@@ -1280,7 +1279,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 	def carol_twimport(self,rpc_backend='http',add_parms=[],expect_str=None,expect_str2='Found 1 unspent output'):
 		from mmgen.tw.json import TwJSON
-		fn = joinpath( self.tmpdir, TwJSON.Base(self.proto).dump_fn )
+		fn = joinpath( self.tmpdir, TwJSON.Base(cfg,self.proto).dump_fn )
 		t = self.spawn(
 			'mmgen-tool',
 			([f'--rpc-backend={rpc_backend}'] if rpc_backend else [])
@@ -1313,7 +1312,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		t = self.spawn('mmgen-regtest',['cli','unloadwallet','carol'])
 		t.ok()
 		from mmgen.rpc import rpc_init
-		rpc = await rpc_init(self.proto)
+		rpc = await rpc_init(cfg,self.proto)
 		wdir = joinpath(rpc.daemon.network_datadir,'wallets','carol')
 		from shutil import rmtree
 		imsg(f'Deleting Carol’s tracking wallet')
@@ -1386,7 +1385,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 	def alice_add_comment_badaddr2(self):
 		# mainnet zero address:
-		addr = init_proto(self.proto.coin,network='mainnet').pubhash2addr(bytes(20),False) # mainnet zero address
+		addr = init_proto( cfg, self.proto.coin, network='mainnet' ).pubhash2addr(bytes(20),False)
 		return self.alice_add_comment_badaddr( addr, 'invalid address', 2 )
 
 	def alice_add_comment_badaddr3(self):
@@ -1513,7 +1512,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 				s,
 				regex=True )
 			if t.pexpect_spawn and s == 'w':
-				t.expect(r'Total.*','q',regex=True,delay=1 if opt.exact_output else t.send_delay)
+				t.expect(r'Total.*','q',regex=True,delay=1 if cfg.exact_output else t.send_delay)
 				time.sleep(t.send_delay)
 				t.send('e')
 		return t
@@ -1799,7 +1798,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			'contains no unused addresses of address type' )
 
 	def stop(self):
-		if opt.no_daemon_stop:
+		if cfg.no_daemon_stop:
 			self.spawn('',msg_only=True)
 			msg_r('(leaving daemon running by user request)')
 			return 'ok'

+ 0 - 2
test/test_py_d/ts_seedsplit.py

@@ -20,8 +20,6 @@
 test.test_py_d.ts_seedsplit: Seed split/join tests for the test.py test suite
 """
 
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.wallet import get_wallet_cls
 
 from .ts_base import *

+ 0 - 2
test/test_py_d/ts_shared.py

@@ -21,8 +21,6 @@ test.test_py_d.ts_shared: Shared methods for the test.py test suite
 """
 
 import os
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.util import ymsg
 from mmgen.wallet import get_wallet_cls
 

+ 2 - 2
test/test_py_d/ts_tool.py

@@ -87,7 +87,7 @@ class TestSuiteTool(TestSuiteMain,TestSuiteBase):
 		return t
 
 	def tool_extract_key_from_geth_wallet(self):
-		if opt.no_altcoin:
+		if cfg.no_altcoin:
 			return 'skip'
 		fn = 'test/ref/ethereum/geth-wallet.json'
 		key = '9627ddb68354f5e0ff45fb2da49d7a20a013b7257a83ef4adbbbd87aeaccc75e'
@@ -99,7 +99,7 @@ class TestSuiteTool(TestSuiteMain,TestSuiteBase):
 	def tool_api(self):
 		t = self.spawn(
 				'tool_api_test.py',
-				(['no_altcoin'] if opt.no_altcoin else []),
+				(['no_altcoin'] if cfg.no_altcoin else []),
 				cmd_dir = 'test/misc' )
 		t.expect('legacy.*compressed.*segwit.*bech32',regex=True)
 		return t

+ 2 - 3
test/test_py_d/ts_wallet.py

@@ -21,7 +21,6 @@ test.test_py_d.ts_wallet: Wallet conversion tests for the test.py test suite
 """
 
 import os
-from mmgen.opts import opt
 from mmgen.wallet import get_wallet_cls
 from .common import *
 from .ts_base import *
@@ -192,7 +191,7 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
 		wf = t.written_to_file(capfirst(ocls.desc),oo=oo)
 		t.p.wait()
 		# back check of result
-		msg('' if opt.profile else ' OK')
+		msg('' if cfg.profile else ' OK')
 		return self.walletchk(  wf,
 								pf         = None,
 								extra_desc = '(check)',
@@ -222,7 +221,7 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
 		if wcls.type == 'incog_hidden':
 			add_args += uopts_chk
 			wf = None
-		msg('' if opt.profile else ' OK')
+		msg('' if cfg.profile else ' OK')
 		return self.walletchk(  wf,
 								pf         = pf,
 								wcls       = wcls,

+ 15 - 11
test/test_py_d/ts_xmrwallet.py

@@ -24,7 +24,6 @@ import sys,os,atexit,asyncio,shutil
 from subprocess import run,PIPE
 
 from mmgen.globalvars import gc
-from mmgen.opts import opt
 from mmgen.obj import MMGenRange
 from mmgen.amt import XMRAmt
 from mmgen.addrlist import KeyAddrList,AddrIdxList
@@ -84,7 +83,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 			return
 
 		from mmgen.protocol import init_proto
-		self.proto = init_proto('XMR',network='mainnet')
+		self.proto = init_proto( cfg, 'XMR', network='mainnet' )
 		self.datadir_base  = os.path.join('test','daemons','xmrtest')
 		self.extra_opts = ['--wallet-rpc-password=passw0rd']
 		self.init_users()
@@ -99,11 +98,11 @@ class TestSuiteXMRWallet(TestSuiteBase):
 		self.tx_relay_daemon_proxy_parm = (
 			self.tx_relay_daemon_parm + f':127.0.0.1:{self.socks_port}' ) # must be IP, not 'localhost'
 
-		if not opt.no_daemon_stop:
+		if not cfg.no_daemon_stop:
 			atexit.register(self.stop_daemons)
 			atexit.register(self.stop_miner_wallet_daemon)
 
-		if not opt.no_daemon_autostart:
+		if not cfg.no_daemon_autostart:
 			self.stop_daemons()
 			shutil.rmtree(self.datadir_base,ignore_errors=True)
 			os.makedirs(self.datadir_base)
@@ -123,7 +122,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 			else: return True
 
 		def start_proxy():
-			if external_call or not opt.no_daemon_autostart:
+			if external_call or not cfg.no_daemon_autostart:
 				run(a+b2)
 				omsg(f'SSH SOCKS server started, listening at localhost:{cls.socks_port}')
 
@@ -174,7 +173,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 				""",indent='    ',strip_char='\t'))
 
 				from mmgen.ui import keypress_confirm
-				if keypress_confirm('Continue?'):
+				if keypress_confirm(cfg,'Continue?'):
 					start_proxy()
 				else:
 					die(1,'Exiting at user request')
@@ -187,7 +186,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 					Then restart the test.
 				""",indent='    '))
 
-		if not (external_call or opt.no_daemon_stop):
+		if not (external_call or cfg.no_daemon_stop):
 			atexit.register(kill_proxy)
 
 		return True
@@ -222,6 +221,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 			udir = os.path.join('test',f'tmp{n}',user)
 			datadir = os.path.join(self.datadir_base,user)
 			md = CoinDaemon(
+				cfg        = cfg,
 				proto      = self.proto,
 				test_suite = True,
 				port_shift = shift,
@@ -229,6 +229,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 				datadir    = datadir
 			)
 			md_rpc = MoneroRPCClient(
+				cfg    = cfg,
 				proto  = self.proto,
 				host   = md.host,
 				port   = md.rpc_port,
@@ -238,6 +239,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 				daemon = md,
 			)
 			wd = MoneroWalletDaemon(
+				cfg        = cfg,
 				proto      = self.proto,
 				test_suite = True,
 				wallet_dir = udir,
@@ -248,6 +250,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 				daemon_addr = f'127.0.0.1:{md.rpc_port}',
 			)
 			wd_rpc = MoneroWalletRPCClient(
+				cfg             = cfg,
 				daemon          = wd,
 				test_connection = False,
 			)
@@ -316,6 +319,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 			self.extra_opts + dir_opt + [ 'create', data.kafile, (wallet or data.kal_range) ] )
 		for i in MMGenRange(wallet or data.kal_range).items:
 			write_data_to_file(
+				cfg,
 				self.users[user].addrfile_fs.format(i),
 				t.expect_getend('Address: '),
 				quiet = True
@@ -570,16 +574,16 @@ class TestSuiteXMRWallet(TestSuiteBase):
 	async def open_wallet_user(self,user,wnum):
 		data = self.users[user]
 		silence()
-		kal = KeyAddrList(self.proto,data.kafile,key_address_validity_check=False)
+		kal = KeyAddrList( cfg, self.proto, data.kafile, key_address_validity_check=False )
 		end_silence()
-		self.users[user].wd.start(silent=not (opt.exact_output or opt.verbose))
+		self.users[user].wd.start(silent=not (cfg.exact_output or cfg.verbose))
 		return data.wd_rpc.call(
 			'open_wallet',
 			filename = os.path.basename(data.walletfile_fs.format(wnum)),
 			password = kal.entry(wnum).wallet_passwd )
 
 	async def stop_wallet_user(self,user):
-		await self.users[user].wd_rpc.stop_daemon(silent=not (opt.exact_output or opt.verbose))
+		await self.users[user].wd_rpc.stop_daemon(silent=not (cfg.exact_output or cfg.verbose))
 		return 'ok'
 
 	# mining methods
@@ -639,7 +643,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 
 		async def send_random_txs():
 			from mmgen.tool.api import tool_api
-			t = tool_api()
+			t = tool_api(cfg)
 			t.init_coin('XMR','mainnet')
 			t.usr_randchars = 0
 			imsg_r(f'Sending random transactions: ')

+ 43 - 39
test/tooltest.py

@@ -55,12 +55,15 @@ If no command is given, the whole suite of tests is run.
 
 sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-assert opt.type in (None,'zcash_z'), 'Only zcash-z permitted for --type argument'
+set_globals(cfg)
 
-from mmgen.protocol import init_proto_from_opts
-proto = init_proto_from_opts()
+vmsg = cfg._util.vmsg
+
+proto = cfg._proto
+
+assert cfg.type in (None,'zcash_z'), 'Only zcash-z permitted for --type argument'
 
 cmd_data = {
 	'cryptocoin': {
@@ -100,11 +103,11 @@ if proto.coin in ('BTC','LTC'):
 		'pipetest':             ('randpair','o3')
 	})
 
-if proto.coin == 'XMR' or opt.type == 'zcash_z':
+if proto.coin == 'XMR' or cfg.type == 'zcash_z':
 	del cmd_data['cryptocoin']['cmd_data']['pubhash2addr']
 	del cmd_data['cryptocoin']['cmd_data']['addr2pubhash']
 
-cfg = {
+tcfg = {
 	'name':          'the tool utility',
 	'enc_passwd':    'Ten Satoshis',
 	'tmpdir':        'test/tmp/10',
@@ -132,23 +135,24 @@ tn_ext = ('','.testnet')[proto.testnet]
 
 mmgen_cmd = 'mmgen-tool'
 
-if not opt.system:
+if not cfg.system:
 	os.environ['PYTHONPATH'] = repo_root
 	mmgen_cmd = os.path.relpath(os.path.join(repo_root,'cmds',mmgen_cmd))
 
 spawn_cmd = ['scripts/exec_wrapper.py',mmgen_cmd]
 
-if opt.coverage:
+if cfg.coverage:
 	d,f = init_coverage()
 	spawn_cmd = ['python3','-m','trace','--count','--coverdir='+d,'--file='+f] + spawn_cmd
 elif gc.platform == 'win':
 	spawn_cmd = ['python3'] + spawn_cmd
 
-add_spawn_args = ['--data-dir='+cfg['tmpdir']] + ['--{}{}'.format(
-		k.replace('_','-'),'='+getattr(opt,k) if getattr(opt,k) != True else '')
-			for k in ('testnet','rpc_host','regtest','coin','type') if getattr(opt,k)]
+add_spawn_args = ['--data-dir='+tcfg['tmpdir']] + ['--{}{}'.format(
+		k.replace('_','-'),
+		'='+getattr(cfg,k) if getattr(cfg,k) != True else '')
+			for k in ('testnet','rpc_host','regtest','coin','type') if getattr(cfg,k)]
 
-if opt.list_cmds:
+if cfg.list_cmds:
 	fs = '  {:<{w}} - {}'
 	Msg('Available commands:')
 	w = max(map(len,cmd_data))
@@ -157,7 +161,7 @@ if opt.list_cmds:
 	Msg('\nAvailable utilities:')
 	Msg(fs.format('clean','Clean the tmp directory',w=w))
 	sys.exit(0)
-if opt.list_names:
+if cfg.list_names:
 	tcmd = ['python3','test/tooltest2.py','--list-tested-cmds']
 	tested_in = {
 		'tooltest.py': [],
@@ -198,12 +202,12 @@ def is_coin_addr_loc(s):
 msg_w = 35
 def test_msg(m):
 	m2 = f'Testing {m}'
-	msg_r(green(m2+'\n') if opt.verbose else '{:{w}}'.format( m2, w=msg_w+8 ))
+	msg_r(green(m2+'\n') if cfg.verbose else '{:{w}}'.format( m2, w=msg_w+8 ))
 
-compressed = opt.type or ('','compressed')['C' in proto.mmtypes]
+compressed = cfg.type or ('','compressed')['C' in proto.mmtypes]
 segwit     = ('','segwit')['S' in proto.mmtypes]
 bech32     = ('','bech32')['B' in proto.mmtypes]
-type_compressed_arg = ([],['--type=' + (opt.type or 'compressed')])[bool(opt.type) or 'C' in proto.mmtypes]
+type_compressed_arg = ([],['--type=' + (cfg.type or 'compressed')])[bool(cfg.type) or 'C' in proto.mmtypes]
 type_segwit_arg     = ([],['--type=segwit'])['S' in proto.mmtypes]
 type_bech32_arg     = ([],['--type=bech32'])['B' in proto.mmtypes]
 
@@ -213,7 +217,7 @@ class MMGenToolTestUtils(object):
 		sys_cmd = (
 			spawn_cmd +
 			add_spawn_args +
-			['-r0','-d',cfg['tmpdir']] +
+			['-r0','-d',tcfg['tmpdir']] +
 			add_opts +
 			[name.lower()] +
 			tool_args +
@@ -222,7 +226,7 @@ class MMGenToolTestUtils(object):
 		if extra_msg: extra_msg = f'({extra_msg})'
 		full_name = ' '.join([name.lower()]+add_opts+kwargs.split()+extra_msg.split())
 		if not silent:
-			if opt.verbose:
+			if cfg.verbose:
 				sys.stderr.write(green(f'Testing {full_name}\nExecuting '))
 				sys.stderr.write(cyan(' '.join(sys_cmd)+'\n'))
 			else:
@@ -231,7 +235,7 @@ class MMGenToolTestUtils(object):
 		cp = run(sys_cmd,stdout=PIPE,stderr=PIPE)
 		out = cp.stdout
 		err = cp.stderr
-		if opt.debug:
+		if cfg.debug:
 			try: dmsg(err.decode())
 			except: dmsg(repr(err))
 		if not binary:
@@ -267,14 +271,14 @@ class MMGenToolTestUtils(object):
 	def run_cmd_out(self,name,carg=None,Return=False,kwargs='',fn_idx='',extra_msg='',
 						literal=False,chkdata='',hush=False,add_opts=[]):
 		if carg:
-			write_to_tmpfile(cfg,f'{name}{fn_idx}.in',carg+'\n')
+			write_to_tmpfile(tcfg,f'{name}{fn_idx}.in',carg+'\n')
 		ret = self.run_cmd(name,([],[carg])[bool(carg)],kwargs=kwargs,
 								extra_msg=extra_msg,add_opts=add_opts)
 		if carg:
 			vmsg('In:   ' + repr(carg))
 		vmsg('Out:  ' + (repr(ret),ret)[literal])
 		if ret or ret == '':
-			write_to_tmpfile(cfg,f'{name}{fn_idx}.out',ret+'\n')
+			write_to_tmpfile(tcfg,f'{name}{fn_idx}.out',ret+'\n')
 			if chkdata:
 				cmp_or_die(ret,chkdata)
 				return
@@ -287,10 +291,10 @@ class MMGenToolTestUtils(object):
 	def run_cmd_randinput(self,name,strip=True,add_opts=[]):
 		s = getrand(128)
 		fn = name+'.in'
-		write_to_tmpfile(cfg,fn,s,binary=True)
-		ret = self.run_cmd(name,[get_tmpfile(cfg,fn)],strip=strip,add_opts=add_opts)
+		write_to_tmpfile(tcfg,fn,s,binary=True)
+		ret = self.run_cmd(name,[get_tmpfile(tcfg,fn)],strip=strip,add_opts=add_opts)
 		fn = name+'.out'
-		write_to_tmpfile(cfg,fn,ret+'\n')
+		write_to_tmpfile(tcfg,fn,ret+'\n')
 		ok()
 		vmsg(f'Returned: {ret}')
 
@@ -342,7 +346,7 @@ class MMGenToolTestCmds(object):
 		for n,k in enumerate(('',compressed,segwit,bech32)):
 			ao = ['--type='+k] if k else []
 			ret = tu.run_cmd(name,[keys[n]],add_opts=ao).rstrip()
-			iaddr = read_from_tmpfile(cfg,f'randpair{n+1}.out').split()[-1]
+			iaddr = read_from_tmpfile(tcfg,f'randpair{n+1}.out').split()[-1]
 			vmsg(f'Out: {ret}')
 			cmp_or_die(iaddr,ret)
 			ok()
@@ -371,16 +375,16 @@ class MMGenToolTestCmds(object):
 	def pubhex2redeem_script(self,name,f1,f2,f3): # from above
 		addr = read_from_file(f3).strip()
 		tu.run_cmd_out(name,addr,add_opts=type_segwit_arg,fn_idx=3)
-		rs = read_from_tmpfile(cfg,'privhex2pubhex3.out').strip()
+		rs = read_from_tmpfile(tcfg,'privhex2pubhex3.out').strip()
 		tu.run_cmd_out('pubhex2addr',rs,add_opts=type_segwit_arg,fn_idx=3,hush=True)
-		addr1 = read_from_tmpfile(cfg,'pubhex2addr3.out').strip()
-		addr2 = read_from_tmpfile(cfg,'randpair3.out').split()[1]
+		addr1 = read_from_tmpfile(tcfg,'pubhex2addr3.out').strip()
+		addr2 = read_from_tmpfile(tcfg,'randpair3.out').split()[1]
 		cmp_or_die(addr1,addr2)
 		ok()
 	def wif2redeem_script(self,name,f1,f2,f3): # compare output with above
 		wif = read_from_file(f3).split()[0]
 		ret1 = tu.run_cmd_out(name,wif,add_opts=type_segwit_arg,fn_idx=3,Return=True)
-		ret2 = read_from_tmpfile(cfg,'pubhex2redeem_script3.out').strip()
+		ret2 = read_from_tmpfile(tcfg,'pubhex2redeem_script3.out').strip()
 		cmp_or_die(ret1,ret2)
 		ok()
 	def wif2segwit_pair(self,name,f1,f2): # does its own checking, so just run
@@ -400,10 +404,10 @@ class MMGenToolTestCmds(object):
 					a=' '.join(add_spawn_args),
 					wif=wif)
 		test_msg('command piping')
-		if opt.verbose:
+		if cfg.verbose:
 			sys.stderr.write(green('Executing ') + cyan(cmd) + '\n')
 		res = run(cmd,stdout=PIPE,shell=True).stdout.decode().strip()
-		addr = read_from_tmpfile(cfg,'wif2addr3.out').strip()
+		addr = read_from_tmpfile(tcfg,'wif2addr3.out').strip()
 		cmp_or_die(addr,res)
 		ok()
 
@@ -426,7 +430,7 @@ class MMGenToolTestCmds(object):
 # main()
 import time
 start_time = int(time.time())
-mk_tmpdir(cfg['tmpdir'])
+mk_tmpdir(tcfg['tmpdir'])
 
 def gen_deps_for_cmd(cmd,cdata):
 	fns = []
@@ -446,25 +450,25 @@ def do_cmds(cmd_group):
 	gdata = cmd_data[cmd_group]['cmd_data']
 	for cmd in gdata:
 		fns = gen_deps_for_cmd(cmd,gdata[cmd])
-		cmdline = [cmd] + [os.path.join(cfg['tmpdir'],fn) for fn in fns]
+		cmdline = [cmd] + [os.path.join(tcfg['tmpdir'],fn) for fn in fns]
 		getattr(tc,cmd)(*cmdline)
 
 try:
-	if cmd_args:
-		if len(cmd_args) != 1:
+	if cfg._args:
+		if len(cfg._args) != 1:
 			die(1,'Only one command may be specified')
-		cmd = cmd_args[0]
+		cmd = cfg._args[0]
 		if cmd in cmd_data:
-			cleandir(cfg['tmpdir'],do_msg=True)
+			cleandir(tcfg['tmpdir'],do_msg=True)
 			msg('Running tests for {}:'.format( cmd_data[cmd]['desc'] ))
 			do_cmds(cmd)
 		elif cmd == 'clean':
-			cleandir(cfg['tmpdir'],do_msg=True)
+			cleandir(tcfg['tmpdir'],do_msg=True)
 			sys.exit(0)
 		else:
 			die(1,f'{cmd!r}: unrecognized command')
 	else:
-		cleandir(cfg['tmpdir'],do_msg=True)
+		cleandir(tcfg['tmpdir'],do_msg=True)
 		for cmd in cmd_data:
 			msg('Running tests for {}:'.format( cmd_data[cmd]['desc'] ))
 			do_cmds(cmd)

+ 44 - 40
test/tooltest2.py

@@ -32,7 +32,7 @@ from test.overlay import overlay_setup
 sys.path.insert(0,overlay_setup(repo_root))
 
 from mmgen.common import *
-from test.include.common import *
+from test.include.common import set_globals,end_msg,sample_text
 from mmgen.bip39 import is_bip39_mnemonic
 from mmgen.baseconv import is_mmgen_mnemonic
 from mmgen.xmrseed import is_xmrseed
@@ -89,6 +89,18 @@ If no command is given, the whole suite of tests is run.
 	}
 }
 
+sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
+
+cfg = opts.init(
+	opts_data,
+	init_opts = {
+		'usr_randchars': 0,
+		'hash_preset': '1',
+		'passwd_file': 'test/ref/keyaddrfile_password',
+	})
+
+set_globals(cfg)
+
 sample_text_hexdump = (
 	'000000: 5468 6520 5469 6d65 7320 3033 2f4a 616e{n}' +
 	'000010: 2f32 3030 3920 4368 616e 6365 6c6c 6f72{n}' +
@@ -764,9 +776,9 @@ async def call_method(cls,method,cmd_name,args,out,opts,mmtype,stdin_input):
 			' '.join([cmd_name]+[repr(e) for e in args]),
 			' '+mmtype if mmtype else '' ))
 	aargs,kwargs = main_tool.process_args(cmd_name,args,cls)
-	oq_save = bool(opt.quiet)
-	if not opt.verbose:
-		opt.quiet = True
+	oq_save = bool(cfg.quiet)
+	if not cfg.verbose:
+		cfg.quiet = True
 	if stdin_input:
 		fd0,fd1 = os.pipe()
 		if os.fork(): # parent
@@ -776,7 +788,7 @@ async def call_method(cls,method,cmd_name,args,out,opts,mmtype,stdin_input):
 			cmd_out = method(*aargs,**kwargs)
 			os.dup2(stdin_save,0)
 			os.wait()
-			opt.quiet = oq_save
+			cfg.quiet = oq_save
 			return cmd_out
 		else: # child
 			os.close(fd0)
@@ -787,12 +799,12 @@ async def call_method(cls,method,cmd_name,args,out,opts,mmtype,stdin_input):
 		ret = method(*aargs,**kwargs)
 		if type(ret).__name__ == 'coroutine':
 			ret = await ret
-		opt.quiet = oq_save
+		cfg.quiet = oq_save
 		return ret
 
 def tool_api(cls,cmd_name,args,out,opts):
 	from mmgen.tool.api import tool_api
-	tool = tool_api()
+	tool = tool_api(cfg)
 	if opts:
 		for o in opts:
 			if o.startswith('--type='):
@@ -832,7 +844,7 @@ async def run_test(cls,gid,cmd_name):
 	# behavior is like test.py: run coin-dependent tests only if proto.testnet or proto.coin != BTC
 	if gid in coin_dependent_groups:
 		k = '{}_{}'.format(
-			( g.token.lower() if proto.tokensym else proto.coin.lower() ),
+			( cfg.token.lower() if proto.tokensym else proto.coin.lower() ),
 			('mainnet','testnet')[proto.testnet] )
 		if k in data:
 			data = data[k]
@@ -847,10 +859,10 @@ async def run_test(cls,gid,cmd_name):
 
 	m = '{} {}{}'.format(
 		purple('Testing'),
-		cmd_name if opt.names else docstring_head(getattr(cls,cmd_name)),
+		cmd_name if cfg.names else docstring_head(getattr(cls,cmd_name)),
 		m2 )
 
-	msg_r(green(m)+'\n' if opt.verbose else m)
+	msg_r(green(m)+'\n' if cfg.verbose else m)
 
 	for d in data:
 		args,out,opts,mmtype = d + tuple([None] * (4-len(d)))
@@ -859,17 +871,17 @@ async def run_test(cls,gid,cmd_name):
 			stdin_input = args[0]
 			args[0] = '-'
 
-		if opt.tool_api:
+		if cfg.tool_api:
 			if args and args[0 ]== '-':
 				continue
 			cmd_out = tool_api(cls,cmd_name,args,out,opts)
-		elif opt.fork:
+		elif cfg.fork:
 			cmd_out = fork_cmd(cmd_name,args,out,opts,stdin_input)
 		else:
 			if stdin_input and gc.platform == 'win':
 				msg('Skipping for MSWin - no os.fork()')
 				continue
-			method = getattr(cls(cmdname=cmd_name,proto=proto,mmtype=mmtype),cmd_name)
+			method = getattr(cls(cfg,cmdname=cmd_name,proto=proto,mmtype=mmtype),cmd_name)
 			cmd_out = await call_method(cls,method,cmd_name,args,out,opts,mmtype,stdin_input)
 
 		try:
@@ -886,15 +898,15 @@ async def run_test(cls,gid,cmd_name):
 					out[1],
 					func_out ))
 		elif isinstance(out,(list,tuple)):
-			for co,o in zip(cmd_out.split(NL) if opt.fork else cmd_out,out):
+			for co,o in zip(cmd_out.split(NL) if cfg.fork else cmd_out,out):
 				check_output(co,o)
 		else:
 			check_output(cmd_out,out)
 
-		if not opt.verbose:
+		if not cfg.verbose:
 			msg_r('.')
 
-	if not opt.verbose:
+	if not cfg.verbose:
 		msg('OK')
 
 def docstring_head(obj):
@@ -904,16 +916,16 @@ async def do_group(gid):
 	desc = f'command group {gid!r}'
 	cls = main_tool.get_mod_cls(gid.lower())
 	qmsg(blue('Testing ' +
-		desc if opt.names else
+		desc if cfg.names else
 		( docstring_head(cls) or desc )
 	))
 
-	for cmdname in cls().user_commands:
+	for cmdname in cls(cfg).user_commands:
 		if cmdname in skipped_tests:
 			continue
 		if cmdname not in tests[gid]:
 			m = f'No test for command {cmdname!r} in group {gid!r}!'
-			if opt.die_on_missing:
+			if cfg.die_on_missing:
 				die(1,m+'  Aborting')
 			else:
 				msg(m)
@@ -933,52 +945,44 @@ def list_tested_cmds():
 	for gid in tests:
 		Msg('\n'.join(tests[gid]))
 
-sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
-
-cmd_args = opts.init(
-	opts_data,
-	init_opts = {
-		'usr_randchars': 0,
-		'hash_preset': '1',
-		'passwd_file': 'test/ref/keyaddrfile_password',
-	})
+qmsg = cfg._util.qmsg
+vmsg = cfg._util.vmsg
 
-from mmgen.protocol import init_proto_from_opts
-proto = init_proto_from_opts(need_amt=True)
+proto = cfg._proto
 
-if opt.tool_api:
+if cfg.tool_api:
 	del tests['Wallet']
 	del tests['File']
 
 import mmgen.main_tool as main_tool
 
-if opt.list_tests:
+if cfg.list_tests:
 	Msg('Available tests:')
 	for modname,cmdlist in main_tool.mods.items():
 		cls = getattr(importlib.import_module(f'mmgen.tool.{modname}'),'tool_cmd')
 		Msg('  {:6} - {}'.format( modname, docstring_head(cls) ))
 	sys.exit(0)
 
-if opt.list_tested_cmds:
+if cfg.list_tested_cmds:
 	list_tested_cmds()
 	sys.exit(0)
 
-if opt.system:
+if cfg.system:
 	tool_exec = 'mmgen-tool'
 	sys.path.pop(0)
 else:
 	os.environ['PYTHONPATH'] = repo_root
 	tool_exec = os.path.relpath(os.path.join('cmds','mmgen-tool'))
 
-if opt.fork:
+if cfg.fork:
 	passthru_args = ['coin','type','testnet','token']
 	tool_cmd = [ tool_exec, '--skip-cfg-file' ] + [
 		'--{}{}'.format(
 			k.replace('_','-'),
-			'='+getattr(opt,k) if getattr(opt,k) != True else '')
-		for k in passthru_args if getattr(opt,k) ]
+			'='+getattr(cfg,k) if getattr(cfg,k) != True else '')
+		for k in passthru_args if getattr(cfg,k) ]
 
-	if opt.coverage:
+	if cfg.coverage:
 		d,f = init_coverage()
 		tool_cmd_preargs = ['python3','-m','trace','--count','--coverdir='+d,'--file='+f]
 	else:
@@ -988,8 +992,8 @@ start_time = int(time.time())
 
 async def main():
 	try:
-		if cmd_args:
-			for cmd in cmd_args:
+		if cfg._args:
+			for cmd in cfg._args:
 				if cmd in tests:
 					await do_group(cmd)
 				else:

+ 14 - 13
test/unit_tests.py

@@ -23,12 +23,12 @@ test/unit_tests.py: Unit tests for the MMGen suite
 import sys,os,time,importlib,platform
 
 from include.tests_header import repo_root
-from include.common import end_msg
 
 from mmgen.devinit import init_dev
 init_dev()
 
 from mmgen.common import *
+from test.include.common import set_globals,end_msg
 
 opts_data = {
 	'text': {
@@ -54,10 +54,11 @@ If no test is specified, all available tests are run
 
 sys.argv.insert(1,'--skip-cfg-file')
 
-opts.UserOpts._reset_ok += ('use_internal_keccak_module',)
-g._reset_ok += ('debug_addrlist',)
+cfg = opts.init(opts_data)
 
-cmd_args = opts.init(opts_data)
+type(cfg)._reset_ok += ('use_internal_keccak_module','debug_addrlist')
+
+set_globals(cfg)
 
 os.environ['PYTHONPATH'] = repo_root
 
@@ -66,7 +67,7 @@ file_pfx = 'ut_'
 all_tests = sorted(
 	[fn[3:-3] for fn in os.listdir(os.path.join(repo_root,'test','unit_tests_d')) if fn[:3] == file_pfx])
 
-exclude = opt.exclude.split(',') if opt.exclude else []
+exclude = cfg.exclude.split(',') if cfg.exclude else []
 
 for e in exclude:
 	if e not in all_tests:
@@ -74,7 +75,7 @@ for e in exclude:
 
 start_time = int(time.time())
 
-if opt.list:
+if cfg.list:
 	Msg(' '.join(all_tests))
 	sys.exit(0)
 
@@ -93,12 +94,12 @@ class UnitTestHelpers(object):
 		m_noraise = "\nillegal action 'bad {}' failed to raise exception {!r}"
 		for (desc,exc_chk,emsg_chk,func) in data:
 			try:
-				vmsg_r('  bad {:{w}}'.format( desc+':', w=desc_w+1 ))
+				cfg._util.vmsg_r('  bad {:{w}}'.format( desc+':', w=desc_w+1 ))
 				func()
 			except Exception as e:
 				exc = type(e).__name__
 				emsg = e.args[0]
-				vmsg(' {:{w}} [{}]'.format( exc, emsg, w=exc_w ))
+				cfg._util.vmsg(' {:{w}} [{}]'.format( exc, emsg, w=exc_w ))
 				assert exc == exc_chk, m_exc.format(exc,exc_chk)
 				assert re.search(emsg_chk,emsg), m_err.format(emsg,emsg_chk)
 			else:
@@ -139,14 +140,14 @@ def run_test(test,subtest=None):
 			subtests = [k for k,v in t.__dict__.items() if type(v).__name__ == 'function' and k[0] != '_']
 			for subtest in subtests:
 				subtest_disp = subtest.replace('_','-')
-				if opt.no_altcoin_deps and subtest in altcoin_deps:
-					qmsg(gray(f'Invoked with --no-altcoin-deps, so skipping {subtest_disp!r}'))
+				if cfg.no_altcoin_deps and subtest in altcoin_deps:
+					cfg._util.qmsg(gray(f'Invoked with --no-altcoin-deps, so skipping {subtest_disp!r}'))
 					continue
 				if gc.platform == 'win' and subtest in win_skip:
-					qmsg(gray(f'Skipping {subtest_disp!r} for Windows platform'))
+					cfg._util.qmsg(gray(f'Skipping {subtest_disp!r} for Windows platform'))
 					continue
 				elif platform.machine() == 'aarch64' and subtest in arm_skip:
-					qmsg(gray(f'Skipping {subtest_disp!r} for ARM platform'))
+					cfg._util.qmsg(gray(f'Skipping {subtest_disp!r} for ARM platform'))
 					continue
 				run_subtest(subtest)
 		else:
@@ -154,7 +155,7 @@ def run_test(test,subtest=None):
 				die(4,'Unit test {test!r} failed')
 
 try:
-	for test in (cmd_args or all_tests):
+	for test in (cfg._args or all_tests):
 		if '.' in test:
 			test,subtest = test.split('.')
 		else:

+ 3 - 3
test/unit_tests_d/__init__.py

@@ -7,17 +7,17 @@ test.unit_tests_d.__init__: shared data for unit tests for the MMGen suite
 import sys,os
 
 from mmgen.globalvars import gv
-from mmgen.opts import opt
+from ..include.common import cfg
 
 class unit_tests_base:
 
 	def _silence(self):
-		if not opt.verbose:
+		if not cfg.verbose:
 			self.stdout = sys.stdout
 			self.stderr = sys.stderr
 			sys.stdout = sys.stderr = gv.stdout = gv.stderr = open(os.devnull,'w')
 
 	def _end_silence(self):
-		if not opt.verbose:
+		if not cfg.verbose:
 			sys.stdout = gv.stdout = self.stdout
 			sys.stderr = gv.stderr = self.stderr

+ 11 - 9
test/unit_tests_d/ut_addrlist.py

@@ -10,17 +10,19 @@ from mmgen.addr import MMGenAddrType
 from mmgen.addrlist import AddrIdxList,AddrList,KeyList,KeyAddrList
 from mmgen.passwdlist import PasswordList
 from mmgen.protocol import init_proto
+from ..include.common import cfg,qmsg,vmsg
 
 def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None):
+
 	qmsg(blue(f'Testing {list_type.__name__}'))
-	proto = init_proto('btc')
-	seed = Seed(seed_bin=bytes.fromhex('feedbead'*8))
+	proto = init_proto( cfg, 'btc' )
+	seed = Seed(cfg,seed_bin=bytes.fromhex('feedbead'*8))
 	mmtype = MMGenAddrType(proto,'C')
 	idxs = AddrIdxList(idx_spec or '1-3')
 
-	if opt.verbose:
-		debug_addrlist_save = g.debug_addrlist
-		g.debug_addrlist = True
+	if cfg.verbose:
+		debug_addrlist_save = cfg.debug_addrlist
+		cfg.debug_addrlist = True
 
 	kwargs = {
 		'seed': seed,
@@ -36,7 +38,7 @@ def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None):
 	if add_kwargs:
 		kwargs.update(add_kwargs)
 
-	al = list_type( proto, **kwargs )
+	al = list_type( cfg, proto, **kwargs )
 
 	af = al.get_file()
 	af.format()
@@ -48,8 +50,8 @@ def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None):
 	if chksum:
 		assert al.chksum == chksum, f'{al.chksum} != {chksum}'
 
-	if opt.verbose:
-		g.debug_addrlist = debug_addrlist_save
+	if cfg.verbose:
+		cfg.debug_addrlist = debug_addrlist_save
 
 	return True
 
@@ -73,7 +75,7 @@ class unit_tests:
 				('',              ''),
 			):
 			l = AddrIdxList(i)
-			if opt.verbose:
+			if cfg.verbose:
 				msg('list: {}\nin:   {}\nout:  {}\n'.format(list(l),i,o))
 			assert l.id_str == o, f'{l.id_str} != {o}'
 

+ 2 - 1
test/unit_tests_d/ut_addrparse.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_addrparse: address parsing tests for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,vmsg
 
 vectors = {
 	'btc_mainnet': [
@@ -92,7 +93,7 @@ class unit_test(object):
 		for net_id,addrs in vectors.items():
 			coin,network = net_id.split('_')
 			test_network(
-				init_proto(coin,network=network),
+				init_proto( cfg, coin, network=network ),
 				addrs )
 
 		return True

+ 4 - 3
test/unit_tests_d/ut_baseconv.py

@@ -5,6 +5,7 @@ test.unit_tests_d.ut_baseconv: Base conversion unit test for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,qmsg,qmsg_r,vmsg,vmsg_r
 
 class unit_test(object):
 
@@ -164,7 +165,7 @@ class unit_test(object):
 		qmsg_r('\nChecking hex-to-base conversion:')
 		for base,data in self.vectors.items():
 			fs = "  {h:%s}  {p:<6} {r}" % max(len(d[0][0]) for d in data)
-			if not opt.verbose:
+			if not cfg.verbose:
 				qmsg_r(f' {base}')
 			vmsg(f'\nBase: {base}')
 			vmsg(fs.format(h='Input',p='Pad',r='Output'))
@@ -180,7 +181,7 @@ class unit_test(object):
 		qmsg_r('\nChecking base-to-hex conversion:')
 		for base,data in self.vectors.items():
 			fs = "  {h:%s}  {p:<6} {r}" % max(len(d[1]) for d in data)
-			if not opt.verbose:
+			if not cfg.verbose:
 				qmsg_r(f' {base}')
 			vmsg(f'\nBase: {base}')
 			vmsg(fs.format(h='Input',p='Pad',r='Output'))
@@ -200,7 +201,7 @@ class unit_test(object):
 
 		for wl_id in baseconv.constants['wl_chksum']:
 			vmsg_r(f'  {wl_id+":":9}')
-			baseconv(wl_id).check_wordlist()
+			baseconv(wl_id).check_wordlist(cfg)
 
 		qmsg('')
 

+ 2 - 1
test/unit_tests_d/ut_bip39.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_bip39: BIP39 unit test for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,qmsg,vmsg
 
 class unit_test(object):
 
@@ -91,7 +92,7 @@ class unit_test(object):
 		from mmgen.bip39 import bip39
 
 		b = bip39()
-		b.check_wordlist()
+		b.check_wordlist(cfg)
 
 		vmsg('')
 		qmsg('Checking seed to mnemonic conversion:')

+ 4 - 3
test/unit_tests_d/ut_daemon.py

@@ -8,9 +8,10 @@ from subprocess import run,DEVNULL
 from mmgen.common import *
 from mmgen.daemon import *
 from mmgen.protocol import init_proto
+from ..include.common import cfg,qmsg,qmsg_r,vmsg
 
 def test_flags():
-	d = CoinDaemon('eth')
+	d = CoinDaemon(cfg,'eth')
 	vmsg(f'Available opts:  {fmt_list(d.avail_opts,fmt="bare")}')
 	vmsg(f'Available flags: {fmt_list(d.avail_flags,fmt="bare")}')
 	vals = namedtuple('vals',['online','no_daemonize','keep_cfg_file'])
@@ -22,7 +23,7 @@ def test_flags():
 				(['online'],['keep_cfg_file'],                vals(True,False,True)),
 				(['online','no_daemonize'],['keep_cfg_file'], vals(True,True,True)),
 			):
-			d = CoinDaemon('eth',opts=opts,flags=flags)
+			d = CoinDaemon(cfg,'eth',opts=opts,flags=flags)
 			assert d.flag.keep_cfg_file == val.keep_cfg_file
 			assert d.opt.online == val.online
 			assert d.opt.no_daemonize == val.no_daemonize
@@ -56,7 +57,7 @@ def test_cmd(args_in,message):
 	qmsg_r(message)
 	args = ['python3', f'test/{args_in[0]}-coin-daemons.py'] + list(args_in[1:])
 	vmsg('\n' + orange(f"Running '{' '.join(args)}':"))
-	pipe = None if opt.verbose else PIPE
+	pipe = None if cfg.verbose else PIPE
 	cp = run( args, stdout=pipe, stderr=pipe, check=True )
 	qmsg('OK')
 	return True

+ 2 - 2
test/unit_tests_d/ut_dep.py

@@ -11,7 +11,7 @@ from subprocess import run,PIPE
 
 from mmgen.common import *
 from mmgen.exception import NoLEDSupport
-from ..include.common import check_solc_ver
+from ..include.common import cfg,vmsg,check_solc_ver
 
 class unit_tests:
 
@@ -127,7 +127,7 @@ class unit_tests:
 				'--supply=100000000000000000000000000',
 				'--decimals=18',
 				'--stdout',
-				init_proto('eth').checksummed_addr('deadbeef'*5),
+				init_proto( cfg, 'eth' ).checksummed_addr('deadbeef'*5),
 			]
 			cp = run(cmd,stdout=PIPE,stderr=PIPE)
 			vmsg(cp.stderr.decode())

+ 4 - 2
test/unit_tests_d/ut_devtools.py

@@ -78,10 +78,12 @@ class unit_tests(unit_tests_base):
 		from mmgen.protocol import init_proto
 		from mmgen.seed import Seed
 		from mmgen.addrlist import AddrList
+		from ..include.common import cfg
 		print_hdr('MMGenObject.pmsg()')
 		AddrList(
-			proto       = init_proto('btc'),
-			seed        = Seed(seed_bin=bytes.fromhex('bead'*16)),
+			cfg         = cfg,
+			proto       = init_proto( cfg, 'btc' ),
+			seed        = Seed(cfg,seed_bin=bytes.fromhex('bead'*16)),
 			addr_idxs   = '1',
 			mmtype      = 'B',
 			skip_chksum = True ).pmsg()

+ 1 - 0
test/unit_tests_d/ut_flags.py

@@ -6,6 +6,7 @@ test.unit_tests_d.ut_flags: unit test for the MMGen suite's ClassFlags class
 
 from mmgen.common import *
 from mmgen.flags import *
+from ..include.common import qmsg,qmsg_r,vmsg
 
 class unit_test(object):
 

+ 7 - 6
test/unit_tests_d/ut_gen.py

@@ -10,6 +10,7 @@ from mmgen.key import PrivKey
 from mmgen.addr import MMGenAddrType
 from mmgen.addrgen import KeyGenerator,AddrGenerator
 from mmgen.keygen import get_backends
+from ..include.common import cfg,qmsg
 
 # TODO: add viewkey checks
 vectors = { # from tooltest2
@@ -50,7 +51,7 @@ vectors = { # from tooltest2
 def do_test(proto,wif,addr_chk,addr_type,internal_keccak):
 
 	if internal_keccak:
-		opt.use_internal_keccak_module = True
+		cfg.use_internal_keccak_module = True
 		add_msg = ' (internal keccak module)'
 	else:
 		add_msg = ''
@@ -60,7 +61,7 @@ def do_test(proto,wif,addr_chk,addr_type,internal_keccak):
 
 	for n,backend in enumerate(get_backends(at.pubkey_type)):
 
-		kg = KeyGenerator(proto,at.pubkey_type,n+1)
+		kg = KeyGenerator( cfg, proto, at.pubkey_type, n+1 )
 		qmsg(blue(f'  Testing backend {backend!r} for addr type {addr_type!r}{add_msg}'))
 
 		data = kg.gen_data(privkey)
@@ -69,16 +70,16 @@ def do_test(proto,wif,addr_chk,addr_type,internal_keccak):
 			if v and k in ('pubkey','viewkey_bytes'):
 				qmsg(f'    {k+":":19} {v.hex()}')
 
-		ag = AddrGenerator(proto,addr_type)
+		ag = AddrGenerator( cfg, proto, addr_type )
 		addr = ag.to_addr(data)
 		qmsg(f'    addr:               {addr}\n')
 
 		assert addr == addr_chk, f'{addr} != {addr_chk}'
 
-	opt.use_internal_keccak_module = False
+	cfg.use_internal_keccak_module = False
 
 def do_tests(coin,internal_keccak=False):
-	proto = init_proto(coin)
+	proto = init_proto( cfg, coin )
 	for wif,addr,addr_type in vectors[coin]:
 		do_test(proto,wif,addr,addr_type,internal_keccak)
 	return True
@@ -95,7 +96,7 @@ class unit_tests:
 		return do_tests('eth',internal_keccak=True)
 
 	def xmr(self,name,ut):
-		if not opt.fast:
+		if not cfg.fast:
 			do_tests('xmr')
 		return do_tests('xmr',internal_keccak=True)
 

+ 1 - 0
test/unit_tests_d/ut_indexed_dict.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_indexed_dict: IndexedDict class unit test for the MMGen sui
 """
 
 from mmgen.common import *
+from ..include.common import vmsg
 
 class unit_test(object):
 

+ 1 - 0
test/unit_tests_d/ut_lockable.py

@@ -5,6 +5,7 @@ test.unit_tests_d.ut_lockable: unit test for the MMGen suite's Lockable class
 """
 
 from mmgen.common import *
+from ..include.common import qmsg,qmsg_r,vmsg
 
 class unit_test(object):
 

+ 4 - 3
test/unit_tests_d/ut_mn_entry.py

@@ -4,7 +4,8 @@
 test.unit_tests_d.ut_mn_entry: Mnemonic user entry unit test for the MMGen suite
 """
 
-from mmgen.util import msg,msg_r,qmsg,qmsg_r
+from mmgen.util import msg,msg_r
+from ..include.common import cfg,qmsg
 
 class unit_test(object):
 
@@ -49,7 +50,7 @@ class unit_test(object):
 		usl = {}
 		for wl_id in self.vectors:
 			for j,k in (('uniq_ss_len','usl'),('shortest_word','sw'),('longest_word','lw')):
-				a = getattr(mn_entry(wl_id),j)
+				a = getattr(mn_entry( cfg, wl_id ),j)
 				b = self.vectors[wl_id][k]
 				assert a == b, f'{wl_id}:{j} {a} != {b}'
 		msg('OK')
@@ -58,7 +59,7 @@ class unit_test(object):
 		qmsg('')
 		junk = 'a g z aa gg zz aaa ggg zzz aaaa gggg zzzz aaaaaaaaaaaaaa gggggggggggggg zzzzzzzzzzzzzz'
 		for wl_id in self.vectors:
-			m = mn_entry(wl_id)
+			m = mn_entry( cfg, wl_id )
 			qmsg('Wordlist: '+wl_id)
 			for entry_mode in ('full','short'):
 				for a,word in enumerate(m.wl):

+ 9 - 8
test/unit_tests_d/ut_msg.py

@@ -6,8 +6,7 @@ test.unit_tests_d.ut_msg: message signing unit tests for the MMGen suite
 
 import os
 
-from test.include.common import silence,end_silence,restart_test_daemons,stop_test_daemons
-from mmgen.opts import opt
+from ..include.common import cfg,silence,end_silence,restart_test_daemons,stop_test_daemons
 from mmgen.util import msg,bmsg,pumsg,suf
 from mmgen.protocol import CoinProtocol
 from mmgen.msg import NewMsg,UnsignedMsg,SignedMsg,SignedOnlineMsg,ExportedMsgSigs
@@ -24,6 +23,7 @@ def get_obj(coin,network,msghash_type):
 		addrlists = 'DEADBEEF:C:1-20 98831F3A:B:8,2 A091ABAA:S:10-11 A091ABAA:111 A091ABAA:C:1'
 
 	return NewMsg(
+		cfg       = cfg,
 		coin      = coin,
 		network   = network,
 		message   = '08/Jun/2021 Bitcoin Law Enacted by El Salvador Legislative Assembly',
@@ -37,7 +37,7 @@ async def run_test(network_id,chksum,msghash_type='raw'):
 
 	coin,network = CoinProtocol.Base.parse_network_id(network_id)
 
-	if not opt.verbose:
+	if not cfg.verbose:
 		silence()
 
 	bmsg(f'\nTesting {coin.upper()} {network.upper()}:\n')
@@ -61,17 +61,17 @@ async def run_test(network_id,chksum,msghash_type='raw'):
 
 	pumsg('\nTesting signing:\n')
 
-	m = UnsignedMsg( infile = os.path.join(tmpdir,get_obj(coin,network,msghash_type).filename) )
+	m = UnsignedMsg( cfg, infile = os.path.join(tmpdir,get_obj(coin,network,msghash_type).filename) )
 	await m.sign(wallet_files=['test/ref/98831F3A.mmwords'])
 
-	m = SignedMsg( data=m.__dict__ )
+	m = SignedMsg( cfg, data=m.__dict__ )
 	m.write_to_file(
 		outdir        = tmpdir,
 		ask_overwrite = False )
 
 	pumsg('\nTesting display:\n')
 
-	m = SignedOnlineMsg( infile = os.path.join(tmpdir,get_obj(coin,network,msghash_type).signed_filename) )
+	m = SignedOnlineMsg( cfg, infile = os.path.join(tmpdir,get_obj(coin,network,msghash_type).signed_filename) )
 
 	msg(m.format())
 
@@ -96,12 +96,13 @@ async def run_test(network_id,chksum,msghash_type='raw'):
 	from mmgen.fileutil import write_data_to_file
 	exported_sigs = os.path.join(tmpdir,'signatures.json')
 	write_data_to_file(
+		cfg     = cfg,
 		outfile = exported_sigs,
 		data    = m.get_json_for_export(),
 		desc    = 'signature data',
 		ask_overwrite = False )
 
-	m = ExportedMsgSigs( infile=exported_sigs )
+	m = ExportedMsgSigs( cfg, infile=exported_sigs )
 
 	pumsg('\nTesting verification (exported data):\n')
 	print_total( await m.verify() )
@@ -120,7 +121,7 @@ async def run_test(network_id,chksum,msghash_type='raw'):
 
 	msg('\n')
 
-	if not opt.verbose:
+	if not cfg.verbose:
 		end_silence()
 
 	return True

+ 1 - 0
test/unit_tests_d/ut_obj.py

@@ -7,6 +7,7 @@ test.unit_tests_d.ut_obj: data object unit tests for the MMGen suite
 from decimal import Decimal
 
 from mmgen.common import *
+from ..include.common import qmsg,qmsg_r,vmsg
 
 class unit_tests:
 

+ 23 - 20
test/unit_tests_d/ut_rpc.py

@@ -13,6 +13,7 @@ from mmgen.rpc import rpc_init
 from mmgen.daemon import CoinDaemon
 from mmgen.proto.xmr.rpc import MoneroRPCClient,MoneroWalletRPCClient
 from mmgen.proto.xmr.daemon import MoneroWalletDaemon
+from ..include.common import cfg,qmsg,vmsg
 
 def cfg_file_auth_test(proto,d,bad_auth=False):
 	m = 'missing credentials' if bad_auth else f'credentials from {d.cfg_file}'
@@ -32,14 +33,14 @@ def cfg_file_auth_test(proto,d,bad_auth=False):
 	if bad_auth:
 		os.rename(d.auth_cookie_fn,d.auth_cookie_fn+'.bak')
 		try:
-			async_run(rpc_init(proto))
+			async_run(rpc_init(cfg,proto))
 		except Exception as e:
 			vmsg(yellow(str(e)))
 		else:
 			die(3,'No error on missing credentials!')
 		os.rename(d.auth_cookie_fn+'.bak',d.auth_cookie_fn)
 	else:
-		rpc = async_run(rpc_init(proto))
+		rpc = async_run(rpc_init(cfg,proto))
 		assert rpc.auth.user == 'ut_rpc', f'{rpc.auth.user}: user is not ut_rpc!'
 
 	d.stop()
@@ -79,7 +80,7 @@ def do_msg(rpc,backend):
 class init_test:
 
 	async def btc(proto,backend,daemon):
-		rpc = await rpc_init(proto,backend,daemon)
+		rpc = await rpc_init(cfg,proto,backend,daemon)
 		do_msg(rpc,backend)
 
 		bh = (await rpc.call('getblockchaininfo',timeout=300))['bestblockhash']
@@ -88,14 +89,14 @@ class init_test:
 		return rpc
 
 	async def bch(proto,backend,daemon):
-		rpc = await rpc_init(proto,backend,daemon)
+		rpc = await rpc_init(cfg,proto,backend,daemon)
 		do_msg(rpc,backend)
 		return rpc
 
 	ltc = bch
 
 	async def eth(proto,backend,daemon):
-		rpc = await rpc_init(proto,backend,daemon)
+		rpc = await rpc_init(cfg,proto,backend,daemon)
 		do_msg(rpc,backend)
 		await rpc.call('eth_blockNumber',timeout=300)
 		return rpc
@@ -106,20 +107,20 @@ def run_test(network_ids,test_cf_auth=False,daemon_ids=None):
 
 	def do_test(d):
 
-		if not opt.no_daemon_stop:
+		if not cfg.no_daemon_stop:
 			d.stop()
 
-		if not opt.no_daemon_autostart:
+		if not cfg.no_daemon_autostart:
 			d.remove_datadir()
 			d.start()
 
-		for n,backend in enumerate(g.autoset_opts['rpc_backend'].choices):
+		for n,backend in enumerate(cfg.autoset_opts['rpc_backend'].choices):
 			test = getattr(init_test,d.proto.coin.lower())
 			rpc = async_run(test(d.proto,backend,d))
-			if not n and opt.verbose:
+			if not n and cfg.verbose:
 				print_daemon_info(rpc)
 
-		if not opt.no_daemon_stop:
+		if not cfg.no_daemon_stop:
 			d.stop()
 
 		if test_cf_auth and gc.platform != 'win':
@@ -129,12 +130,12 @@ def run_test(network_ids,test_cf_auth=False,daemon_ids=None):
 		qmsg('')
 
 	for network_id in network_ids:
-		proto = init_proto(network_id=network_id)
+		proto = init_proto( cfg, network_id=network_id )
 		ids = (lambda x:
 			set(daemon_ids) & set(x) if daemon_ids else x
-			)(CoinDaemon.get_daemon_ids(proto.coin))
+			)(CoinDaemon.get_daemon_ids(cfg,proto.coin))
 		for daemon_id in ids:
-			do_test( CoinDaemon(proto=proto,test_suite=True,daemon_id=daemon_id) )
+			do_test( CoinDaemon(cfg, proto=proto,test_suite=True,daemon_id=daemon_id) )
 
 	return True
 
@@ -169,6 +170,7 @@ class unit_tests:
 
 		def test_monerod_rpc(md):
 			rpc = MoneroRPCClient(
+				cfg    = cfg,
 				proto  = md.proto,
 				host   = md.host,
 				port   = md.rpc_port,
@@ -176,30 +178,31 @@ class unit_tests:
 				passwd = None,
 				daemon = md,
 			)
-			if opt.verbose:
+			if cfg.verbose:
 				print_daemon_info(rpc)
 			rpc.call_raw('get_height')
 			rpc.call('get_last_block_header')
 
 		async def run():
-			networks = init_proto('xmr').networks
+			networks = init_proto( cfg, 'xmr' ).networks
 			daemons = [(
-					CoinDaemon(proto=proto,test_suite=True),
+					CoinDaemon( cfg, proto=proto, test_suite=True ),
 					MoneroWalletDaemon(
+						cfg        = cfg,
 						proto      = proto,
 						test_suite = True,
 						wallet_dir = 'test/trash2',
 						passwd     = 'ut_rpc_passw0rd' )
-				) for proto in (init_proto( 'xmr', network=network ) for network in networks) ]
+				) for proto in (init_proto( cfg, 'xmr', network=network ) for network in networks) ]
 
 			for md,wd in daemons:
-				if not opt.no_daemon_autostart:
+				if not cfg.no_daemon_autostart:
 					md.start()
 				wd.start()
 
 				test_monerod_rpc(md)
 
-				c = MoneroWalletRPCClient(daemon=wd)
+				c = MoneroWalletRPCClient( cfg=cfg, daemon=wd )
 				fn = f'monero-{wd.network}-junk-wallet'
 				qmsg(f'Creating {wd.network} wallet')
 				c.call(
@@ -213,7 +216,7 @@ class unit_tests:
 			for md,wd in daemons:
 				wd.wait = False
 				await wd.rpc.stop_daemon()
-				if not opt.no_daemon_stop:
+				if not cfg.no_daemon_stop:
 					md.wait = False
 					await md.rpc.stop_daemon()
 

+ 11 - 10
test/unit_tests_d/ut_scrypt.py

@@ -4,7 +4,8 @@
 test.unit_tests_d.ut_scrypt: password hashing unit test for the MMGen suite
 """
 
-from ..include.common import *
+from ..include.common import cfg,qmsg,vmsg,omsg_r,silence,end_silence
+from mmgen.util import msg,msg_r
 
 class unit_test(object):
 
@@ -15,7 +16,7 @@ class unit_test(object):
 		qmsg('')
 
 		from mmgen.crypto import Crypto
-		crypto = Crypto()
+		crypto = Crypto(cfg)
 
 		salt = bytes.fromhex('f00f' * 16)
 
@@ -35,7 +36,7 @@ class unit_test(object):
 			for pw_base,res in pws:
 				for pw in (pw_base,pw_base.encode()):
 					pw_disp = "'"+pw+"'" if type(pw) == str else "b'"+pw.decode()+"'"
-					if opt.quiet:
+					if cfg.quiet:
 						omsg_r('.')
 					else:
 						msg_r(f'\n  password {pw_disp:9} ')
@@ -47,32 +48,32 @@ class unit_test(object):
 				hp = str(hp)
 				res = presets[hp]
 				pw = 'φυβαρ'
-				if opt.quiet:
+				if cfg.quiet:
 					omsg_r('.')
 				else:
 					msg_r(f'\n  {hp!r:3}: {crypto.hash_presets[hp]!r:12}  ')
 				st = time.time()
 				ret = crypto.scrypt_hash_passphrase(pw,salt,hp).hex()
 				t = time.time() - st
-				vmsg('' if g.test_suite_deterministic else f'  {t:0.4f} secs')
+				vmsg('' if cfg.test_suite_deterministic else f'  {t:0.4f} secs')
 				assert ret == res, ret
 
-		if opt.quiet:
+		if cfg.quiet:
 			silence()
 
-		g.force_standalone_scrypt_module = False
+		cfg.force_standalone_scrypt_module = False
 		vmsg('Passwords (auto module selection):')
 		test_passwords()
 		vmsg('Hash presets (auto module selection):')
-		test_presets((1,2,3,4) if opt.fast else (1,2,3,4,5,6,7))
+		test_presets((1,2,3,4) if cfg.fast else (1,2,3,4,5,6,7))
 
-		g.force_standalone_scrypt_module = True
+		cfg.force_standalone_scrypt_module = True
 		vmsg('Passwords (force standalone scrypt module):')
 		test_passwords()
 		vmsg('Hash presets (force standalone scrypt module):')
 		test_presets((1,2,3))
 
-		if opt.quiet:
+		if cfg.quiet:
 			end_silence()
 
 		msg('OK')

+ 7 - 6
test/unit_tests_d/ut_seedsplit.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_seedsplit: seed splitting unit test for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,vmsg,vmsg_r
 
 class unit_test(object):
 
@@ -12,7 +13,7 @@ class unit_test(object):
 		from mmgen.seed import Seed
 		from mmgen.seedsplit import SeedShareList,SeedShareIdx
 
-		g.debug_subseed = bool(opt.verbose)
+		cfg.debug_subseed = bool(cfg.verbose)
 
 		def basic_ops(master_idx):
 			test_data = {
@@ -56,7 +57,7 @@ class unit_test(object):
 
 				for a,b,c,d,e,f,h,i,p in test_data[id_str if id_str is not None else 'default']:
 					seed_bin = bytes.fromhex('deadbeef' * a)
-					seed = Seed(seed_bin)
+					seed = Seed( cfg, seed_bin )
 					assert seed.sid == b, seed.sid
 
 					for share_count,j,k,l,m in ((2,c,c,d,i),(5,e,f,h,p)):
@@ -91,7 +92,7 @@ class unit_test(object):
 
 						if master_idx:
 							slist = [shares.get_share_by_idx(i+1,base_seed=True) for i in range(len(shares))]
-							A = Seed.join_shares(slist,master_idx,id_str).sid
+							A = Seed.join_shares( cfg, slist, master_idx, id_str ).sid
 							assert A == b, A
 
 				msg('OK')
@@ -100,7 +101,7 @@ class unit_test(object):
 			msg_r('Testing defaults and limits...')
 
 			seed_bin = bytes.fromhex('deadbeef' * 8)
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 
 			shares = seed.split(SeedShareIdx.max_val)
 			s = shares.format()
@@ -124,7 +125,7 @@ class unit_test(object):
 			vmsg('')
 
 			seed_bin = bytes.fromhex(seed_hex)
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 
 			SeedShareIdx.max_val = ss_count
 			shares = seed.split(ss_count,master_idx=master_idx)
@@ -147,7 +148,7 @@ class unit_test(object):
 			msg_r('Testing last share collisions with shortened Seed IDs')
 			vmsg('')
 			seed_bin = bytes.fromhex('2eadbeef'*8)
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 			ssm_save = SeedShareIdx.max_val
 			ssm = SeedShareIdx.max_val = 2048
 			shares = SeedShareList(seed,count=ssm,id_str='foo',master_idx=1,debug_last_share=True)

+ 10 - 9
test/unit_tests_d/ut_subseed.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_subseed: subseed unit test for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,vmsg_r
 
 class unit_test(object):
 
@@ -23,7 +24,7 @@ class unit_test(object):
 				):
 
 				seed_bin = bytes.fromhex('deadbeef' * a)
-				seed = Seed(seed_bin)
+				seed = Seed( cfg, seed_bin )
 				assert seed.sid == b, seed.sid
 
 				subseed = seed.subseed('2s')
@@ -38,7 +39,7 @@ class unit_test(object):
 				assert subseed.idx == 10, subseed.idx
 				assert subseed.ss_idx == h, subseed.ss_idx
 
-				seed2 = Seed(seed_bin)
+				seed2 = Seed( cfg, seed_bin )
 				ss2_list = seed2.subseeds
 
 				seed2.subseeds._generate(1)
@@ -96,31 +97,31 @@ class unit_test(object):
 
 			seed_bin = bytes.fromhex('deadbeef' * 8)
 
-			seed = Seed(seed_bin,nSubseeds=11)
+			seed = Seed( cfg, seed_bin, nSubseeds=11 )
 			seed.subseeds._generate()
 			ss = seed.subseeds
 			assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
 			assert len(ss) == 11, len(ss)
 
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 			seed.subseeds._generate()
 			ss = seed.subseeds
 			assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
 			assert len(ss) == nSubseeds, len(ss)
 
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 			seed.subseed_by_seed_id('EEEEEEEE')
 			ss = seed.subseeds
 			assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
 			assert len(ss) == nSubseeds, len(ss)
 
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 			subseed = seed.subseed_by_seed_id('803B165C')
 			assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
 			assert subseed.sid == '803B165C', subseed.sid
 			assert subseed.idx == 3, subseed.idx
 
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 			subseed = seed.subseed_by_seed_id('803B165C',last_idx=1)
 			assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
 			assert subseed == None, subseed
@@ -160,14 +161,14 @@ class unit_test(object):
 			ss_count,ltr,last_sid,collisions_chk = (
 				(SubSeedIdxRange.max_idx,'S','2788F26B',470),
 				(49509,'L','8D1FE500',2)
-			)[bool(opt.fast)]
+			)[bool(cfg.fast)]
 
 			last_idx = str(ss_count) + ltr
 
 			msg_r(f'Testing Seed ID collisions ({ss_count} subseed pairs)...')
 
 			seed_bin = bytes.fromhex('12abcdef' * 8) # 95B3D78D
-			seed = Seed(seed_bin)
+			seed = Seed( cfg, seed_bin )
 
 			seed.subseeds._generate(ss_count)
 			ss = seed.subseeds

+ 6 - 5
test/unit_tests_d/ut_tx.py

@@ -11,20 +11,21 @@ from mmgen.tx import NewTX,CompletedTX
 from mmgen.tx.file import MMGenTxFile
 from mmgen.daemon import CoinDaemon
 from mmgen.protocol import init_proto
+from ..include.common import cfg,qmsg,vmsg
 
 async def do_txfile_test(desc,fns):
 	qmsg(f'  Testing CompletedTX initializer ({desc})')
 	for fn in fns:
 		qmsg(f'     parsing: {os.path.basename(fn)}')
 		fpath = os.path.join('test','ref',fn)
-		tx = await CompletedTX(filename=fpath,quiet_open=True)
+		tx = await CompletedTX( cfg=cfg, filename=fpath, quiet_open=True )
 
 		vmsg(tx.info.format())
 
 		f = MMGenTxFile(tx)
 		fn_gen = f.make_filename()
 
-		if g.debug_utf8:
+		if cfg.debug_utf8:
 			fn_gen = fn_gen.replace('-α','')
 
 		assert fn_gen == os.path.basename(fn), f'{fn_gen} != {fn}'
@@ -57,11 +58,11 @@ class unit_tests:
 
 	async def tx(self,name,ut):
 		qmsg('  Testing NewTX initializer')
-		d = CoinDaemon('btc',test_suite=True)
+		d = CoinDaemon( cfg, 'btc', test_suite=True )
 		d.start()
 
-		proto = init_proto('btc',need_amt=True)
-		tx = await NewTX(proto=proto)
+		proto = init_proto( cfg, 'btc', need_amt=True )
+		tx = await NewTX( cfg=cfg, proto=proto )
 
 		d.stop()
 		qmsg('  OK')

+ 10 - 10
test/unit_tests_d/ut_tx_deserialize.py

@@ -7,7 +7,7 @@ test/unit_tests_d/ut_tx_deserialize: TX deserialization unit tests for the MMGen
 import os,json
 
 from mmgen.common import *
-from ..include.common import *
+from ..include.common import cfg,start_test_daemons,stop_test_daemons
 from mmgen.protocol import init_proto
 from mmgen.tx import CompletedTX
 from mmgen.proto.btc.tx.base import DeserializeTX
@@ -15,14 +15,14 @@ from mmgen.rpc import rpc_init
 from mmgen.daemon import CoinDaemon
 
 def print_info(name,extra_desc):
-	if opt.names:
+	if cfg.names:
 		Msg_r('{} {} {}'.format(
 			purple('Testing'),
 			cyan(f'{name} ({extra_desc})'),
-			'' if opt.quiet else '\n'))
+			'' if cfg.quiet else '\n'))
 	else:
 		Msg_r(f'Testing {extra_desc}')
-		if not opt.quiet:
+		if not cfg.quiet:
 			Msg('')
 
 async def test_tx(tx_proto,tx_hex,desc,n):
@@ -34,17 +34,17 @@ async def test_tx(tx_proto,tx_hex,desc,n):
 				return True
 		return False
 
-	rpc = await rpc_init(proto=tx_proto)
+	rpc = await rpc_init( cfg, proto=tx_proto )
 	d = await rpc.call('decoderawtransaction',tx_hex)
 
 	if has_nonstandard_outputs(d['vout']): return False
 
 	dt = DeserializeTX(tx_proto,tx_hex)
 
-	if opt.verbose:
+	if cfg.verbose:
 		Msg('\n\n================================ Core vector: ==================================')
-	Msg_r('.' if opt.quiet else f'{n:>3}) {desc}\n')
-	if opt.verbose:
+	Msg_r('.' if cfg.quiet else f'{n:>3}) {desc}\n')
+	if cfg.verbose:
 		Pmsg(d)
 		Msg('\n------------------------------ MMGen deserialized: -----------------------------')
 		Pmsg(dt._asdict())
@@ -88,7 +88,7 @@ async def do_mmgen_ref(daemons,fns,name,desc):
 	start_test_daemons(*daemons)
 	print_info(name,desc)
 	for n,fn in enumerate(fns):
-		tx = await CompletedTX(filename=fn,quiet_open=True)
+		tx = await CompletedTX( cfg=cfg, filename=fn, quiet_open=True )
 		await test_tx(
 			tx_proto = tx.proto,
 			tx_hex   = tx.serialized,
@@ -119,7 +119,7 @@ class unit_tests:
 		for e in core_data:
 			if type(e[0]) == list:
 				await test_tx(
-					tx_proto = init_proto('btc',need_amt=True),
+					tx_proto = init_proto( cfg, 'btc', need_amt=True ),
 					tx_hex   = e[1],
 					desc     = desc,
 					n        = n )

+ 2 - 1
test/unit_tests_d/ut_xmrseed.py

@@ -5,6 +5,7 @@ test/unit_tests_d/ut_xmrseed: Monero mnemonic unit test for the MMGen suite
 """
 
 from mmgen.common import *
+from ..include.common import cfg,qmsg,vmsg
 
 class unit_test(object):
 
@@ -82,7 +83,7 @@ class unit_test(object):
 		from mmgen.xmrseed import xmrseed
 
 		b = xmrseed()
-		b.check_wordlist()
+		b.check_wordlist(cfg)
 
 		try:
 			from monero.wordlists.english import English