Browse Source

XMR compat: address/account creation

Example:

    # Press ‘N’ or ‘n’ at the prompt to create new account or address:
    $ mmgen-tool --coin=xmr listaddresses interactive=1

Testing/demo:

    $ test/cmdtest.py --coin=xmr -e xmr_compat
The MMGen Project 1 week ago
parent
commit
7b53f4337f

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-16.1.dev24
+16.1.dev25

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

@@ -16,6 +16,30 @@ from ....tw.addresses import TwAddresses
 
 from .view import MoneroTwView
 
+async def add_new_address(parent, spec, ok_msg):
+	from ....ui import line_input, keypress_confirm
+	from ....color import green, yellow
+	from ....xmrwallet import op as xmrwallet_op
+	lbl = line_input(
+		parent.cfg,
+		'Enter label text for new address (or ENTER for default label): ')
+	add_timestr = keypress_confirm(parent.cfg, 'Add timestamp to label?')
+	op = xmrwallet_op(
+		'new',
+		parent.cfg,
+		None,
+		None,
+		spec = spec + (',' + lbl if lbl else ''),
+		compat_call = True)
+	op.c.call('close_wallet')
+	if await op.main(add_timestr=add_timestr):
+		await parent.get_data()
+		parent.oneshot_msg = green(ok_msg)
+		return 'redraw'
+	else:
+		parent.oneshot_msg = yellow('Operation cancelled')
+		return False
+
 class MoneroTwAddresses(MoneroTwView, TwAddresses):
 
 	include_empty = True
@@ -23,8 +47,10 @@ class MoneroTwAddresses(MoneroTwView, TwAddresses):
 
 	prompt_fs_repl = {'XMR': (
 		(1, 'Filters: show [E]mpty addrs, [u]sed addrs, all [L]abels'),
-		(3, 'Actions: [q]uit menu, add [l]abel, [R]efresh balances:'))}
+		(3, 'Actions: [q]uit menu, add [l]abel, [N]ew acct, [n]ew addr, [R]efresh bals:'))}
 	extra_key_mappings = {
+		'N': 'a_acct_new',
+		'n': 'i_addr_new',
 		'u': 'd_showused',
 		'R': 'a_sync_wallets'}
 	removed_key_mappings = {
@@ -40,3 +66,36 @@ class MoneroTwAddresses(MoneroTwView, TwAddresses):
 
 	def get_disp_data(self):
 		return MoneroTwView.get_disp_data(self, input_data=tuple(TwAddresses.get_disp_data(self)))
+
+	class action(MoneroTwView.action):
+
+		async def a_acct_new(self, parent):
+			from ....obj import Int
+			from ....util import suf
+			from ....addr import MMGenID
+			from ....ui import item_chooser
+			def wallet_id(wnum):
+				return MMGenID(proto=parent.proto, id_str='{}:M:{}'.format(parent.sid, wnum))
+			res = item_chooser(
+				parent.cfg,
+				'Choose a wallet to add a new account to',
+				[(d['wallet_num'], len(d['data'].accts_data['subaddress_accounts']))
+					for d in parent.dump_data],
+				lambda d: '{a} [{b} account{c}]'.format(
+					a = wallet_id(d[0]).hl(),
+					b = Int(d[1]).hl(),
+					c = suf(d[1])))
+			return await add_new_address(
+				parent,
+				str(res.item[0]),
+				f'New account added to wallet {wallet_id(res.item[0]).hl()}')
+
+	class item_action(TwAddresses.item_action):
+		acct_methods = ('i_addr_new')
+
+		async def i_addr_new(self, parent, idx, acct_addr_idx=None):
+			e = parent.accts_data[idx-1]
+			return await add_new_address(
+				parent,
+				f'{e.idx}:{e.acct_idx}',
+				f'New address added to wallet {e.idx}, account #{e.acct_idx}')

+ 4 - 4
mmgen/proto/xmr/tw/view.py

@@ -57,16 +57,16 @@ class MoneroTwView:
 
 		op = xmrwallet_op('dump_data', self.cfg, None, None, compat_call=True)
 		await op.restart_wallet_daemon()
-		wallets_data = await op.main()
+		self.dump_data = await op.main()
 
-		if wallets_data:
-			self.sid = SeedID(sid=wallets_data[0]['seed_id'])
+		if self.dump_data:
+			self.sid = SeedID(sid=self.dump_data[0]['seed_id'])
 
 		self.total = self.unlocked_total = self.proto.coin_amt('0')
 
 		def gen_addrs():
 			bd = namedtuple('address_balance_data', ['bal', 'unlocked_bal', 'blocks_to_unlock'])
-			for wdata in wallets_data:
+			for wdata in self.dump_data:
 				bals_data = {i: {} for i in range(len(wdata['data'].accts_data['subaddress_accounts']))}
 
 				for d in wdata['data'].bals_data.get('per_subaddress', []):

+ 7 - 2
mmgen/xmrwallet/ops/new.py

@@ -27,14 +27,16 @@ class OpNew(OpMixinSpec, OpWallet):
 	spec_key = ((1, 'source'),)
 	wallet_offline = True
 
-	async def main(self):
+	async def main(self, add_timestr=True):
 		h = MoneroWalletRPC(self, self.source)
 		h.open_wallet('Monero', refresh=False)
 
 		desc = 'account' if self.account is None else 'address'
 		label = TwComment(
 			None if self.label == '' else
-			'{} [{}]'.format(self.label or f'xmrwallet new {desc}', make_timestr()))
+			'{a}{b}'.format(
+				a = self.label or (f'new {desc}' if self.compat_call else f'xmrwallet new {desc}'),
+				b = ' [{}]'.format(make_timestr()) if add_timestr else ''))
 
 		wallet_data = h.get_wallet_data()
 
@@ -58,11 +60,14 @@ class OpNew(OpMixinSpec, OpWallet):
 
 			if desc == 'address':
 				h.print_acct_addrs(wallet_data, self.account)
+			ret = True
 		else:
 			ymsg('\nOperation cancelled by user request')
+			ret = False
 
 		# wallet must be left open: otherwise the 'stop_wallet' RPC call used to stop the daemon will fail
 		if self.cfg.no_stop_wallet_daemon:
 			h.close_wallet('Monero')
 
 		msg('')
+		return ret

+ 62 - 2
test/cmdtest_d/xmr_autosign.py

@@ -508,6 +508,7 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 	Monero autosigning operations (compat mode)
 	"""
 	menu_prompt = 'efresh balances:\b'
+	listaddresses_menu_prompt = 'efresh bals:\b'
 	extra_daemons = ['ltc']
 
 	cmd_group = (
@@ -548,6 +549,14 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 		('wait_loop_start_ltc',      'starting autosign wait loop in XMR compat mode [--coins=ltc,xmr]'),
 		('alice_txsend1',            'sending the transaction'),
 		('wait_loop_kill',           'stopping autosign wait loop'),
+		('alice_newacct1',           'adding account to Alice’s tracking wallet (dfl label)'),
+		('alice_newacct2',           'adding account to Alice’s tracking wallet (no timestr)'),
+		('alice_newacct3',           'adding account to Alice’s tracking wallet'),
+		('alice_newacct4',           'adding account to Alice’s tracking wallet (dfl label, no timestr)'),
+		('alice_newaddr1',           'adding address to Alice’s tracking wallet'),
+		('alice_newaddr2',           'adding address to Alice’s tracking wallet (no timestr)'),
+		('alice_newaddr3',           'adding address to Alice’s tracking wallet (dfl label)'),
+		('alice_newaddr4',           'adding address to Alice’s tracking wallet (dfl label, no timestr)'),
 		('stop_daemons',             'stopping all wallet and coin daemons'),
 	)
 
@@ -600,6 +609,46 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 		addr_data = data['MoneroMMGenWalletDumpFile']['data']['wallet_metadata'][1]['addresses']
 		return await self.fund_alice(addr=addr_data[addr_num-1]['address'], amt=amt)
 
+	def alice_newacct1(self):
+		return self._alice_newacct(2, lbl_text='New Test Account', add_timestr=True)
+
+	def alice_newacct2(self):
+		return self._alice_newacct(1, lbl_text='New Test Account')
+
+	def alice_newacct3(self):
+		return self._alice_newacct(2, add_timestr=True)
+
+	def alice_newacct4(self):
+		return self._alice_newacct(2)
+
+	def _alice_newacct(self, wallet_num, lbl_text='', add_timestr=False):
+		return self._alice_twops(
+			'listaddresses',
+			newacct_wallet_num = wallet_num,
+			lbl_text = lbl_text,
+			add_timestr = add_timestr,
+			expect_str = (lbl_text or 'new account ') + (r'.*\[20.*\]' if add_timestr else ''))
+
+	def alice_newaddr1(self):
+		return self._alice_newaddr(1, lbl_text='New Test Address', add_timestr=True)
+
+	def alice_newaddr2(self):
+		return self._alice_newaddr(1, lbl_text='New Test Address')
+
+	def alice_newaddr3(self):
+		return self._alice_newaddr(2, add_timestr=True)
+
+	def alice_newaddr4(self):
+		return self._alice_newaddr(2)
+
+	def _alice_newaddr(self, acct_num, lbl_text='', add_timestr=False):
+		return self._alice_twops(
+			'listaddresses',
+			newaddr_acct_num = acct_num,
+			lbl_text = lbl_text,
+			add_timestr = add_timestr,
+			expect_str = (lbl_text or 'new address ') + (r'.*\[20.*\]' if add_timestr else ''))
+
 	def alice_listaddresses(self):
 		return self._alice_twops('listaddresses', menu='R')
 
@@ -646,6 +695,8 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 			*,
 			lbl_acct_num = None,
 			lbl_addr_idx = None,
+			newacct_wallet_num = None,
+			newaddr_acct_num = None,
 			lbl_text = '',
 			add_timestr = False,
 			menu = '',
@@ -660,16 +711,25 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 			+ self.autosign_opts
 			+ [op]
 			+ (['interactive=1'] if interactive else []))
-		menu_prompt = self.menu_prompt
-		have_lbl = lbl_acct_num
+		menu_prompt = self.listaddresses_menu_prompt if op == 'listaddresses' else self.menu_prompt
+		have_lbl = lbl_acct_num or newacct_wallet_num or newaddr_acct_num
+		have_new_addr = newacct_wallet_num or newaddr_acct_num
 		if interactive:
 			if lbl_acct_num:
 				t.expect(menu_prompt, 'l')
 				t.expect('main menu): ', str(lbl_acct_num))
 				t.expect('main menu): ', str(lbl_addr_idx))
+			elif newacct_wallet_num:
+				t.expect(menu_prompt, 'N')
+				t.expect('number> ', f'{newacct_wallet_num}\n')
+			elif newaddr_acct_num:
+				t.expect(menu_prompt, 'n')
+				t.expect('main menu): ', str(newaddr_acct_num))
 			if have_lbl:
 				t.expect(': ', lbl_text + '\n')                    # add label
 				t.expect('(y/N): ', ('y' if add_timestr else 'n')) # add timestr
+			if have_new_addr:
+				t.expect('(y/N): ', 'y')
 			for ch in menu:
 				t.expect(menu_prompt, ch)
 			if expect_str: