6 Commits 4f1b16f489 ... 48edcf412c

Author SHA1 Message Date
  The MMGen Project 48edcf412c mmgen-txsend --status: support transaction ranges 2 months ago
  The MMGen Project 74acc44a2c tx.new: fix ETH contract creation regression (bugfix) 2 months ago
  The MMGen Project 51015089e6 tw.view: support Ctrl-R for balance refresh 2 months ago
  The MMGen Project 392b3700af mmgen-txsend: new `process_tx()` function: tx.online: cleanups 2 months ago
  The MMGen Project 41d705ec6e xmrwallet create_offline: use existing key-address file if present 2 months ago
  The MMGen Project 031ae051f9 CmdTestAutosignBase: simplify mnemonic entry 2 months ago

+ 9 - 6
mmgen/autosign.py

@@ -334,22 +334,25 @@ class Signable:
 				shred_file(self.cfg, fn, iterations=15)
 			sys.exit(0)
 
-		async def get_last_sent(self, *, idx=0):
+		async def get_last_sent(self, *, tx_range=None):
 			return await self.get_last_created(
 				# compat fallback - ‘sent_timestamp’ attr is missing in some old TX files:
 				sort_key = lambda x: x.sent_timestamp or x.timestamp,
-				idx = idx)
+				tx_range = tx_range)
 
-		async def get_last_created(self, *, sort_key=lambda x: x.timestamp, idx=0):
+		async def get_last_created(self, *, sort_key=lambda x: x.timestamp, tx_range=None):
 			from .tx import CompletedTX
 			fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)]
 			files = sorted(
 				[await CompletedTX(cfg=self.cfg, filename=str(txfile), quiet_open=True)
 					for txfile in fns],
 				key = sort_key)
-			if not (0 <= idx < len(files)):
-				die(2, f'{idx}: invalid transaction index (must be less than {len(files)})')
-			return files[-1 - idx]
+			if files:
+				return (
+					files[-1] if tx_range is None else
+					files[len(files) - 1 - tx_range.last:len(files) - tx_range.first])
+			else:
+				die(1, 'No sent automount transactions!')
 
 	class xmr_signable: # mixin class
 		automount = True

+ 1 - 1
mmgen/data/release_date

@@ -1 +1 @@
-January 2026
+February 2026

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-16.1.dev28
+16.1.dev29

+ 4 - 3
mmgen/main_txbump.py

@@ -17,10 +17,11 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-mmgen-txbump: Create, and optionally send and sign, a replacement transaction
-              on supporting networks
+mmgen-txbump: Create, and optionally send and sign, a replacement transaction on supported networks
 """
 
+import sys
+
 from .cfg import gc, Config
 from .util import msg, msg_r, die, async_run
 from .color import green
@@ -207,7 +208,7 @@ async def main():
 				TxKeys(cfg, tx2, seedfiles=seedfiles, keylist=kl, keyaddrlist=kal).keys):
 			tx4 = await OnlineSignedTX(cfg=cfg, data=tx3.__dict__)
 			tx4.file.write(ask_write=False)
-			await tx4.send(cfg, asi if cfg.autosign else None)
+			sys.exit(await tx4.send(cfg, asi if cfg.autosign else None))
 		else:
 			die(2, 'Transaction could not be signed')
 	else:

+ 4 - 2
mmgen/main_txdo.py

@@ -20,6 +20,8 @@
 mmgen-txdo: Create, sign and send an online MMGen transaction
 """
 
+import sys
+
 from .cfg import gc, Config
 from .util import die, fmt_list, async_run
 from .subseed import SubSeedIdxRange
@@ -192,8 +194,8 @@ async def main():
 	if tx3:
 		tx3.file.write(ask_write=False)
 		tx4 = await SentTX(cfg=cfg, data=tx3.__dict__)
-		await tx4.send(cfg, asi=None)
+		return await tx4.send(cfg, asi=None)
 	else:
 		die(2, 'Transaction could not be signed')
 
-async_run(cfg, main)
+sys.exit(async_run(cfg, main))

+ 53 - 40
mmgen/main_txsend.py

@@ -23,7 +23,7 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
 import sys
 
 from .cfg import gc, Config
-from .util import async_run, die, is_int
+from .util import msg, async_run, die
 
 opts_data = {
 	'sets': [
@@ -35,7 +35,7 @@ opts_data = {
 		'usage2': [
 			'[opts] <signed transaction file>',
 			'[opts] --autosign',
-			'[opts] --autosign (--status | --receipt) [IDX]',
+			'[opts] --autosign (--status | --receipt) [index or range]',
 		],
 		'options': """
 -h, --help       Print this help message
@@ -71,10 +71,13 @@ opts_data = {
 -y, --yes        Answer 'yes' to prompts, suppress non-essential output
 """,
 		'notes': """
-With --autosign, combined with --status or --receipt, the optional IDX arg
-represents an index into the list of sent transaction files on the removable
-device, in reverse chronological order.  ‘0’ (the default) specifies the
-last sent transaction, ‘1’ the next-to-last, and so on.
+With --autosign, combined with --status or --receipt, the optional index or
+range arg represents an index or range into the list of sent transaction files
+on the removable device, in reverse chronological order.  ‘0’ (the default)
+specifies the last sent transaction, ‘1’ the next-to-last, and so on.  Hyphen-
+separated ranges are also supported.  For example, specifying a range ‘0-3’
+would output data for the last four sent transactions, beginning with the most
+recent.
 """
 	},
 	'code': {
@@ -99,25 +102,25 @@ if cfg.dump_hex and cfg.dump_hex != '-':
 	check_outfile_dir(cfg.dump_hex)
 
 post_send_op = cfg.status or cfg.receipt
-
-asi = None
+asi, tx_range = (None, None)
 
 def init_autosign(arg):
-	global asi, si, infile, tx_idx
+	global asi, si, infile, tx_range
 	from .tx.util import mount_removable_device
+	from .tx.online import SentTXRange
 	from .autosign import Signable
 	asi = mount_removable_device(cfg)
 	si = Signable.automount_transaction(asi)
 	if cfg.abort:
 		si.shred_abortable() # prompts user, then raises exception or exits
+	elif post_send_op and (si.unsent or si.unsigned):
+		die(1, 'Transaction is {}'.format('unsent' if si.unsent else 'unsigned'))
 	elif post_send_op:
-		if si.unsent:
-			die(1, 'Transaction is unsent')
-		if si.unsigned:
-			die(1, 'Transaction is unsigned')
-		if not is_int(arg):
-			die(2, f'{arg}: invalid transaction index (must be a non-negative integer)')
-		tx_idx = int(arg)
+		try:
+			tx_range = SentTXRange(arg)
+		except:
+			die(2, f'{arg}: invalid transaction index arg '
+			'(must be a non-negative integer or hyphen-separated range)')
 	else:
 		infile = si.get_unsent()
 		cfg._util.qmsg(f'Got signed transaction file ‘{infile}’')
@@ -126,7 +129,7 @@ match cfg._args:
 	case [arg] if cfg.autosign and post_send_op:
 		init_autosign(arg)
 	case [] if cfg.autosign:
-		init_autosign(0)
+		init_autosign('0')
 	case [infile]:
 		from .fileutil import check_infile
 		check_infile(infile)
@@ -139,38 +142,35 @@ if not cfg.status:
 
 from .tx import OnlineSignedTX
 
-async def main():
+batch = tx_range and (tx_range.last != tx_range.first)
 
-	global cfg
+def do_sep():
+	if batch:
+		msg('-' * 74)
 
-	if cfg.autosign and post_send_op:
-		tx = await si.get_last_sent(idx=tx_idx)
-	else:
-		tx = await OnlineSignedTX(
-			cfg        = cfg,
-			filename   = infile,
-			automount  = cfg.autosign,
-			quiet_open = True)
+async def process_tx(tx):
+
+	do_sep()
+	cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
 
 	if tx.is_compat:
 		return await tx.compat_send()
 
-	cfg = Config({'_clone': cfg, 'proto': tx.proto, 'coin': tx.proto.coin})
+	txcfg = Config({'_clone': cfg, 'proto': tx.proto, 'coin': tx.proto.coin})
 
-	if cfg.tx_proxy:
-		from .tx.tx_proxy import check_client
-		check_client(cfg)
+	if not post_send_op:
+		if cfg.tx_proxy:
+			from .tx.tx_proxy import check_client
+			check_client(txcfg)
 
 	from .rpc import rpc_init
-	tx.rpc = await rpc_init(cfg)
-
-	cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
-
-	if cfg.mark_sent:
-		await tx.post_send(asi)
-		sys.exit(0)
+	tx.rpc = await rpc_init(txcfg)
 
 	if not post_send_op:
+		if cfg.mark_sent:
+			await tx.post_send(asi)
+			return 0
+
 		if tx.is_swap and not tx.check_swap_expiry():
 			die(1, 'Swap quote has expired. Please re-create the transaction')
 
@@ -180,6 +180,19 @@ async def main():
 				if not cfg.autosign:
 					tx.file.write(ask_write_default_yes=True)
 
-	await tx.send(cfg, asi)
+	return await tx.send(txcfg, asi, batch=batch)
+
+async def main():
+	if cfg.autosign and post_send_op:
+		exitvals = [await process_tx(tx)
+			for tx in reversed(await si.get_last_sent(tx_range=tx_range))]
+		do_sep()
+		return max(exitvals)
+	else:
+		return await process_tx(await OnlineSignedTX(
+			cfg        = cfg,
+			filename   = infile,
+			automount  = cfg.autosign,
+			quiet_open = True))
 
-async_run(cfg, main)
+sys.exit(async_run(cfg, main))

+ 2 - 1
mmgen/proto/xmr/tw/addresses.py

@@ -48,7 +48,8 @@ class MoneroTwAddresses(MoneroTwView, TwAddresses):
 		'N': 'a_acct_new',
 		'n': 'i_addr_new',
 		'u': 'd_showused',
-		'R': 'a_sync_wallets'}
+		'R': 'a_sync_wallets',
+		'\x12': 'a_sync_wallets'}
 	removed_key_mappings = {
 		'D': 'i_addr_delete'}
 

+ 1 - 0
mmgen/proto/xmr/tw/unspent.py

@@ -29,6 +29,7 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
 			'Actions: [q]uit menu, add [l]abel, r[e]draw, [R]efresh balances:']
 		self.extra_key_mappings = {
 			'R': 'a_sync_wallets',
+			'\x12': 'a_sync_wallets',
 			'A': 's_age'}
 		if tx and tx.is_sweep:
 			self.prompt_fs_in[-1] = 'Actions: [q]uit, add [l]abel, r[e]draw, [R]efresh balances:'

+ 1 - 0
mmgen/proto/xmr/tx/completed.py

@@ -26,6 +26,7 @@ class Completed(Base):
 			'network': proto.network})
 		self.proto = proto
 		self.filename = filename
+		self.infile = filename
 
 	@cached_property
 	def compat_tx(self):

+ 15 - 9
mmgen/proto/xmr/tx/online.py

@@ -15,17 +15,20 @@ proto.xmr.tx.online: Monero online signed transaction class
 from .completed import Completed
 
 class OnlineSigned(Completed):
+	desc = 'signed transaction'
 
 	async def compat_send(self):
+		"""
+		returns integer exit val to system
+		"""
 		from ....xmrwallet import op as xmrwallet_op
 		op_name = 'daemon' if self.cfg.status else 'submit'
 		op = xmrwallet_op(op_name, self.cfg, self.filename, None, compat_call=True)
 		if self.cfg.status:
-			from ....util import msg, ymsg, suf
+			from ....util import msg, msg_r, ymsg, suf
+			ret = 0
 			txid = self.compat_tx.data.txid
-			if self.cfg.verbose:
-				msg(self.compat_tx.get_info())
-			elif not self.cfg.quiet:
+			if not (self.cfg.verbose or self.cfg.quiet):
 				from ....obj import CoinTxID
 				msg('{} TxID: {}'.format(self.cfg.coin, CoinTxID(txid).hl()))
 			res = op.dc.call_raw('get_transactions', txs_hashes=[txid])
@@ -38,16 +41,19 @@ class OnlineSigned(Completed):
 					msg('Transaction has {} confirmation{}'.format(confs, suf(confs)))
 			else:
 				ymsg('An RPC error occurred while fetching transaction data')
-				return False
+				ret = 1
+			if self.cfg.verbose:
+				msg_r('\n' + self.compat_tx.get_info())
+			return ret
 		else:
 			await op.restart_wallet_daemon()
-			return await op.main()
+			return int(not await op.main())
 
 class Sent(OnlineSigned):
-	pass
+	desc = 'sent transaction'
 
 class AutomountOnlineSigned(OnlineSigned):
-	pass
+	desc = 'signed automount transaction'
 
 class AutomountSent(AutomountOnlineSigned):
-	pass
+	desc = 'sent automount transaction'

+ 2 - 1
mmgen/tw/addresses.py

@@ -55,7 +55,8 @@ class TwAddresses(TwView):
 		'w':'a_view_detail',
 		'p':'a_print_detail'}
 	extra_key_mappings = {
-		'R':'i_balance_refresh'}
+		'R':'i_balance_refresh',
+		'\x12':'i_balance_refresh'}
 
 	class display_type(TwView.display_type):
 

+ 2 - 1
mmgen/tw/unspent.py

@@ -66,7 +66,8 @@ class TwUnspentOutputs(TwView):
 		'l':'i_comment_add'}
 	extra_key_mappings = {
 		'D':'i_addr_delete',
-		'R':'i_balance_refresh'}
+		'R':'i_balance_refresh',
+		'\x12':'i_balance_refresh'}
 	disp_spc = 3
 	vout_w = 0
 

+ 1 - 1
mmgen/tx/new.py

@@ -456,7 +456,7 @@ class New(Base):
 		if self.cfg.comment_file:
 			self.add_comment(infile=self.cfg.comment_file)
 
-		if cmd_args and not do_info:
+		if not (do_info or self.is_sweep):
 			cmd_args, addrfile_args = self.get_addrfiles_from_cmdline(cmd_args)
 			if self.is_swap:
 				cmd_args = await self.process_swap_cmdline_args(cmd_args, addrfile_args)

+ 18 - 6
mmgen/tx/online.py

@@ -12,13 +12,18 @@
 tx.online: online signed transaction class
 """
 
-import sys, time, asyncio
+import time, asyncio
 
+from ..obj import MMGenRange
 from ..util import msg, Msg, gmsg, ymsg, make_timestr, die
 from ..color import pink, yellow
 
 from .signed import Signed, AutomountSigned
 
+class SentTXRange(MMGenRange):
+	min_idx = 0
+	max_idx = 1_000_000
+
 class OnlineSigned(Signed):
 
 	@property
@@ -62,8 +67,10 @@ class OnlineSigned(Signed):
 			ask_overwrite = False,
 			ask_write     = False)
 
-	async def send(self, cfg, asi):
-
+	async def send(self, cfg, asi, batch=False):
+		"""
+		returns integer exit val to system
+		"""
 		status_exitval = None
 		sent_status = None
 		all_ok = True
@@ -85,9 +92,10 @@ class OnlineSigned(Signed):
 			if coin_txid := getattr(self, f'coin_txid{idx}', None):
 				txhex = getattr(self, f'serialized{idx}')
 				if cfg.status:
-					cfg._util.qmsg(f'{self.proto.coin} TxID: {coin_txid.hl()}')
 					if cfg.verbose:
 						await self.post_network_send(coin_txid)
+					else:
+						cfg._util.qmsg(f'{self.proto.coin} TxID: {coin_txid.hl()}')
 					status_exitval = await self.status.display(idx=idx)
 				elif cfg.receipt:
 					if res := await self.get_receipt(coin_txid, receipt_only=True):
@@ -146,8 +154,12 @@ class OnlineSigned(Signed):
 
 		if status_exitval is not None:
 			if cfg.verbose:
-				self.info.view_with_prompt('View transaction details?', pause=False)
-			sys.exit(status_exitval)
+				if batch:
+					self.info.view(pause=False, terse=True)
+				else:
+					self.info.view_with_prompt('View transaction details?', pause=False)
+			return status_exitval
+		return 0
 
 class AutomountOnlineSigned(AutomountSigned, OnlineSigned):
 	pass

+ 9 - 3
mmgen/xmrwallet/ops/create.py

@@ -77,10 +77,16 @@ class OpCreateOffline(OpCreate):
 		vkf = vkal.file
 
 		# before writing viewkey-address file, shred any old ones in the directory:
+		do_write = True
 		for f in Path(self.asi.xmr_dir).iterdir():
-			if f.name.endswith(vkf.ext):
+			if f.name == vkf.filename:
+				do_write = False
+			elif f.name.endswith(vkf.ext):
 				from ...fileutil import shred_file
-				msg(f'\nShredding old viewkey-address file ‘{f}’')
+				msg(f'Shredding old viewkey-address file ‘{f}’')
 				shred_file(self.cfg, f, iterations=15)
 
-		vkf.write(outdir=self.asi.xmr_dir)
+		if do_write:
+			vkf.write(outdir=self.asi.xmr_dir)
+		else:
+			msg(f'Viewkey-address file ‘{vkf.filename}’ already exists, skipping creation')

+ 28 - 11
test/cmdtest_d/automount.py

@@ -82,9 +82,10 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		('alice_txbump5',                    'bumping the transaction (new outputs)'),
 		('alice_txsend5',                    'sending the bumped transaction'),
 		('alice_txstatus5',                  'getting transaction status (in mempool)'),
-		('alice_txstatus6',                  'getting transaction status (idx=0, in mempool)'),
-		('alice_txstatus7',                  'getting transaction status (idx=1, replaced)'),
-		('alice_txstatus8',                  'getting transaction status (idx=3, 2 confirmations)'),
+		('alice_txstatus6',                  'getting transaction status (tx_range=0, in mempool)'),
+		('alice_txstatus7',                  'getting transaction status (tx_range=1, replaced)'),
+		('alice_txstatus8',                  'getting transaction status (tx_range=3, 2 confirmations)'),
+		('alice_txstatus9',                  'getting transaction status (tx_range=0-3)'),
 		('generate',                         'mining a block'),
 		('alice_bal2',                       'checking Alice’s balance'),
 		('wait_loop_kill',                   'stopping autosign wait loop'),
@@ -224,7 +225,14 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 	def alice_txsend5(self):
 		return self._user_txsend('alice', need_rbf=True)
 
-	def _alice_txstatus(self, expect, exit_val=None, need_rbf=False, idx=None):
+	def _alice_txstatus(
+			self,
+			expect,
+			exit_val = None,
+			need_rbf = False,
+			tx_range = None,
+			verbose  = True,
+			batch    = False):
 
 		if need_rbf and not self.proto.cap('rbf'):
 			return 'skip'
@@ -232,12 +240,13 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		self.insert_device_online()
 		t = self.spawn(
 				'mmgen-txsend',
-				['--alice', '--autosign', '--status', '--verbose']
-				+ ([] if idx is None else [str(idx)]),
+				['--alice', '--autosign', '--status']
+				+ (['--verbose'] if verbose else [])
+				+ ([] if tx_range is None else [tx_range]),
 				no_passthru_opts = ['coin'],
 				exit_val = exit_val)
-		t.expect(expect)
-		if not exit_val:
+		t.expect(expect, regex=True)
+		if not (exit_val or batch):
 			t.expect('view: ', 'n')
 		t.read()
 		self.remove_device_online()
@@ -260,13 +269,21 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		return self._alice_txstatus('in mempool', need_rbf=True)
 
 	def alice_txstatus6(self):
-		return self._alice_txstatus('in mempool', need_rbf=True, idx=0)
+		return self._alice_txstatus('in mempool', need_rbf=True, tx_range='0')
 
 	def alice_txstatus7(self):
-		return self._alice_txstatus('replaced', need_rbf=True, idx=1)
+		return self._alice_txstatus('replaced', need_rbf=True, tx_range='1')
 
 	def alice_txstatus8(self):
-		return self._alice_txstatus('2 confirmations', need_rbf=True, idx=3)
+		return self._alice_txstatus('2 confirmations', need_rbf=True, tx_range='3')
+
+	def alice_txstatus9(self):
+		return self._alice_txstatus(
+			'in mempool.*replaced.*replaced.*2 confirmations',
+			need_rbf = True,
+			tx_range = '0-3',
+			verbose = False,
+			batch = True)
 
 	def alice_txsend_bad_no_unsent(self):
 		self.insert_device_online()

+ 8 - 2
test/cmdtest_d/autosign.py

@@ -47,7 +47,7 @@ from ..include.common import (
 from .include.common import ref_dir, dfl_words_file, dfl_bip39_file
 
 from .base import CmdTestBase
-from .include.input import stealth_mnemonic_entry
+from .include.input import mnemonic_entry
 
 class CmdTestAutosignBase(CmdTestBase):
 	networks     = ('btc',)
@@ -189,6 +189,12 @@ class CmdTestAutosignBase(CmdTestBase):
 		stop_test_daemons(*(self.network_ids + self.extra_daemons), remove_datadir=True)
 		return 'ok'
 
+	def delete_setup(self):
+		self.spawn(msg_only=True)
+		imsg(f'Deleting ‘{self.asi.wallet_dir}’')
+		shutil.rmtree(self.asi.wallet_dir, ignore_errors=True)
+		return 'ok'
+
 	def run_setup(
 			self,
 			mn_type        = None,
@@ -238,7 +244,7 @@ class CmdTestAutosignBase(CmdTestBase):
 					'Type a number.*: ',
 					str(mne.entry_modes.index(entry_mode) + 1),
 					regex = True)
-			stealth_mnemonic_entry(t, mne, mn, entry_mode)
+			mnemonic_entry(t, mne, mn)
 
 		t.written_to_file('Autosign wallet')
 

+ 5 - 0
test/cmdtest_d/include/input.py

@@ -14,6 +14,11 @@ import time
 from ...include.common import getrand
 from .common import randbool
 
+def mnemonic_entry(t, mne, mn):
+	p_ok, _ = mne.word_prompt
+	for wnum, word in enumerate(mn, 1):
+		t.expect(p_ok.format(wnum), word + ' ')
+
 def stealth_mnemonic_entry(t, mne, mn, entry_mode, pad_entry=False):
 
 	def pad_mnemonic(mn, ss_len):

+ 12 - 3
test/cmdtest_d/xmr_autosign.py

@@ -256,7 +256,10 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 			use_dfl_wallet = None,
 			expect_args    = ['Continue with Monero setup? (Y/n): ', 'n'])
 
-	def autosign_xmr_setup(self):
+	def autosign_xmr_setup_redo(self):
+		return self.autosign_xmr_setup(write_viewkeys=False)
+
+	def autosign_xmr_setup(self, write_viewkeys=True):
 		self.insert_device_online()
 		self.do_mount_online()
 		self.asi_online.xmr_dir.mkdir(exist_ok=True)
@@ -266,7 +269,10 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 
 		self.insert_device()
 		t = self.spawn('mmgen-autosign', self.opts + ['xmr_setup'], no_passthru_opts=True)
-		t.written_to_file('View keys')
+		if write_viewkeys:
+			t.written_to_file('View keys')
+		else:
+			t.expect('already exists, skipping')
 		t.read()
 		self.remove_device()
 		return t
@@ -514,6 +520,9 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 	cmd_group = (
 		('autosign_setup',           'autosign setup with Alice’s seed'),
 		('autosign_xmr_setup',       'autosign setup (creation of Monero signing wallets)'),
+		('delete_setup',             'deleting offline autosign setup'),
+		('autosign_setup',           'autosign setup with Alice’s seed'),
+		('autosign_xmr_setup_redo',  'autosign setup (creation of Monero signing wallets, redo)'),
 		('create_watchonly_wallets', 'creating Alice’s watch-only wallets'),
 		('gen_kafile_miner',         'generating key-address file for Miner'),
 		('create_wallet_miner',      'creating Monero wallet for Miner'),
@@ -807,7 +816,7 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 		return self._alice_txstatus(['--quiet'], expect_str='confirmations')
 
 	def alice_txstatus3(self):
-		return self._alice_txstatus(['--verbose'], expect_str='Info for transaction .* confirmations')
+		return self._alice_txstatus(['--verbose'], expect_str='confirmations.*Info for transaction')
 
 	def _alice_txstatus(self, add_opts=[], expect_str=None):
 		return self._alice_txops(