XMR compat: support tracking wallet views
- all operations, including label editing and balance refresh, supported
- requires --autosign
- enables --xmrwallet-compat, meaning Monero watch-only wallets must be
located in ~/.mmgen/altcoins/xmr/tracking-wallets
This is a work in progress, UI will undergo changes.
Examples:
$ mmgen-tool --coin=xmr listaddresses interactive=1
$ mmgen-tool --coin=xmr twview interactive=1
Testing:
$ test/cmdtest.py -ne xmr_compat
This commit is contained in:
parent
937f0af9c9
commit
bdd7dd3393
18 changed files with 272 additions and 32 deletions
|
|
@ -147,12 +147,23 @@ class MMGenID(HiliteStr, InitErrors, MMGenObject):
|
|||
idx, ext = idx.split('-', 1)
|
||||
me.acct_idx, me.addr_idx = [MoneroIdx(e) for e in ext.split('/', 1)]
|
||||
else:
|
||||
ext = None
|
||||
me = str.__new__(cls, f'{sid}:{mmtype}:{idx}')
|
||||
me.sid = SeedID(sid=sid)
|
||||
me.mmtype = proto.addr_type(mmtype)
|
||||
me.idx = AddrIdx(idx)
|
||||
me.al_id = str.__new__(AddrListID, me.sid + ':' + me.mmtype) # checks already done
|
||||
me.sort_key = f'{me.sid}:{me.mmtype}:{me.idx:0{me.idx.max_digits}}'
|
||||
if ext:
|
||||
me.sort_key = '{}:{}:{:0{w1}}:{:0{w2}}:{:0{w2}}'.format(
|
||||
me.sid,
|
||||
me.mmtype,
|
||||
me.idx,
|
||||
me.acct_idx,
|
||||
me.addr_idx,
|
||||
w1 = me.idx.max_digits,
|
||||
w2 = MoneroIdx.max_digits)
|
||||
else:
|
||||
me.sort_key = '{}:{}:{:0{w}}'.format(me.sid, me.mmtype, me.idx, w=me.idx.max_digits)
|
||||
me.proto = proto
|
||||
return me
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ class Config(Lockable):
|
|||
# Monero:
|
||||
monero_wallet_rpc_user = 'monero'
|
||||
monero_wallet_rpc_password = ''
|
||||
monero_daemon = ''
|
||||
xmrwallet_compat = False
|
||||
priority = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
16.1.dev14
|
||||
16.1.dev15
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ opts_data = {
|
|||
-- -d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-- -h, --help Print this help message
|
||||
-- --, --longhelp Print help message for long (global) options
|
||||
x- -a, --autosign Operate on an autosigned transaction
|
||||
-- -e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
|
||||
-- -k, --use-internal-keccak-module Force use of the internal keccak module
|
||||
-- -K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
|
|
|
|||
|
|
@ -308,6 +308,9 @@ class UserOpts(Opts):
|
|||
br --rpc-user=USER Authenticate to coin daemon using username USER
|
||||
br --rpc-password=PASS Authenticate to coin daemon using password PASS
|
||||
Rr --rpc-backend=backend Use backend 'backend' for JSON-RPC communications
|
||||
mr --monero-wallet-rpc-user=USER Monero wallet RPC username
|
||||
mr --monero-wallet-rpc-password=USER Monero wallet RPC password
|
||||
mr --monero-daemon=HOST:PORT Connect to the monerod at HOST:PORT
|
||||
Rr --aiohttp-rpc-queue-len=N Use N simultaneous RPC connections with aiohttp
|
||||
-p --regtest=0|1 Disable or enable regtest mode
|
||||
-- --testnet=0|1 Disable or enable testnet
|
||||
|
|
@ -361,6 +364,7 @@ class UserOpts(Opts):
|
|||
'r' - local RPC coin
|
||||
'X' - remote RPC coin
|
||||
'x' - local or remote RPC coin
|
||||
'm' - Monero
|
||||
'-' - any coin
|
||||
Cmd codes:
|
||||
'p' - proto required
|
||||
|
|
@ -378,6 +382,7 @@ class UserOpts(Opts):
|
|||
['-', 'r', 'R', 'b', 'h', 'x'] if coin == 'bch' else
|
||||
['-', 'r', 'R', 'b', 'x'] if coin in gc.btc_fork_rpc_coins else
|
||||
['-', 'r', 'R', 'e', 'x'] if coin in gc.eth_fork_coins else
|
||||
['-', 'r', 'x', 'm'] if coin == 'xmr' else
|
||||
['-', 'r', 'x'] if coin in gc.local_rpc_coins else
|
||||
['-', 'X', 'x'] if coin in gc.remote_rpc_coins else
|
||||
['-']),
|
||||
|
|
|
|||
22
mmgen/proto/xmr/tw/addresses.py
Executable file
22
mmgen/proto/xmr/tw/addresses.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
proto.xmr.tw.addresses: Monero protocol tracking wallet address list class
|
||||
"""
|
||||
|
||||
from ....tw.addresses import TwAddresses
|
||||
|
||||
from .view import MoneroTwView
|
||||
|
||||
class MoneroTwAddresses(MoneroTwView, TwAddresses):
|
||||
|
||||
include_empty = True
|
||||
has_used = True
|
||||
|
|
@ -12,8 +12,34 @@
|
|||
proto.xmr.tw.ctl: Monero tracking wallet control class
|
||||
"""
|
||||
|
||||
from ....tw.ctl import write_mode
|
||||
from ....tw.store import TwCtlWithStore
|
||||
|
||||
class MoneroTwCtl(TwCtlWithStore):
|
||||
|
||||
tw_subdir = 'tracking-wallets'
|
||||
use_cached_balances = True
|
||||
|
||||
@write_mode
|
||||
async def set_comment(
|
||||
self,
|
||||
addrspec,
|
||||
comment = '',
|
||||
*,
|
||||
trusted_pair = None,
|
||||
silent = False):
|
||||
|
||||
from ....ui import keypress_confirm
|
||||
add_timestr = keypress_confirm(self.cfg, 'Add timestamp to label?')
|
||||
|
||||
m = trusted_pair[0].obj
|
||||
from ....xmrwallet import op as xmrwallet_op
|
||||
op = xmrwallet_op(
|
||||
'label',
|
||||
self.cfg,
|
||||
None,
|
||||
None,
|
||||
spec = f'{m.idx}:{m.acct_idx}:{m.addr_idx},{comment}',
|
||||
compat_call = True)
|
||||
await op.restart_wallet_daemon()
|
||||
return await op.main(add_timestr=add_timestr, auto=True)
|
||||
|
|
|
|||
23
mmgen/proto/xmr/tw/unspent.py
Executable file
23
mmgen/proto/xmr/tw/unspent.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
proto.xmr.tw.unspent: Monero protocol tracking wallet unspent outputs class
|
||||
"""
|
||||
|
||||
from ....tw.addresses import TwAddresses
|
||||
|
||||
from .view import MoneroTwView
|
||||
|
||||
class MoneroTwUnspentOutputs(MoneroTwView, TwAddresses):
|
||||
|
||||
hdr_lbl = 'spendable accounts'
|
||||
desc = 'address balances'
|
||||
include_empty = False
|
||||
81
mmgen/proto/xmr/tw/view.py
Executable file
81
mmgen/proto/xmr/tw/view.py
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
proto.xmr.tw.view: Monero protocol base class for tracking wallet view classes
|
||||
"""
|
||||
|
||||
from ....xmrwallet import op as xmrwallet_op
|
||||
from ....tw.view import TwView
|
||||
|
||||
class MoneroTwView:
|
||||
|
||||
class rpc:
|
||||
caps = ()
|
||||
is_remote = False
|
||||
|
||||
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:'))}
|
||||
extra_key_mappings = {
|
||||
'u': 'd_showused',
|
||||
'R': 'a_sync_wallets'}
|
||||
removed_key_mappings = {
|
||||
'D': 'i_addr_delete'}
|
||||
|
||||
async def get_rpc_data(self):
|
||||
from mmgen.tw.shared import TwMMGenID, TwLabel
|
||||
|
||||
op = xmrwallet_op('dump_data', self.cfg, None, None, compat_call=True)
|
||||
await op.restart_wallet_daemon()
|
||||
wallets_data = await op.main()
|
||||
|
||||
self.total = self.proto.coin_amt('0')
|
||||
|
||||
def gen_addrs():
|
||||
for wdata in wallets_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', []):
|
||||
bals_data[d['account_index']].update({d['address_index']: d['unlocked_balance']})
|
||||
|
||||
for acct_idx, acct_data in enumerate(wdata['data'].addrs_data):
|
||||
for addr_data in acct_data['addresses']:
|
||||
addr_idx = addr_data['address_index']
|
||||
self.total += (bal := self.proto.coin_amt(
|
||||
bals_data[acct_idx].get(addr_idx, 0),
|
||||
from_unit = 'atomic'))
|
||||
if self.include_empty or bal:
|
||||
mmid = '{}:M:{}-{}/{}'.format(
|
||||
wdata['seed_id'],
|
||||
wdata['wallet_num'],
|
||||
acct_idx,
|
||||
addr_idx)
|
||||
yield (TwMMGenID(self.proto, mmid), {
|
||||
'addr': addr_data['address'],
|
||||
'amt': bal,
|
||||
'recvd': bal,
|
||||
'is_used': addr_data['used'],
|
||||
'confs': 1,
|
||||
'lbl': TwLabel(self.proto, mmid + ' ' + addr_data['label'])})
|
||||
|
||||
return dict(gen_addrs())
|
||||
|
||||
class action(TwView.action):
|
||||
|
||||
async def a_sync_wallets(self, parent):
|
||||
from ....util import msg, msg_r
|
||||
from ....tw.view import CUR_HOME, ERASE_ALL
|
||||
msg('')
|
||||
op = xmrwallet_op('sync', parent.cfg, None, None, compat_call=True)
|
||||
await op.restart_wallet_daemon()
|
||||
await op.main()
|
||||
await parent.get_data()
|
||||
msg_r(CUR_HOME + ERASE_ALL)
|
||||
|
|
@ -192,10 +192,11 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
async def __init__(self, cfg, proto):
|
||||
self.cfg = cfg
|
||||
self.proto = proto
|
||||
if have_rpc := 'rpc_init' in proto.mmcaps:
|
||||
self.rpc = await rpc_init(cfg, proto)
|
||||
if self.has_wallet:
|
||||
from .ctl import TwCtl
|
||||
self.twctl = await TwCtl(cfg, proto, mode='w')
|
||||
self.twctl = await TwCtl(cfg, proto, mode='w', no_rpc=not have_rpc)
|
||||
self.amt_keys = {'amt':'iwidth', 'amt2':'iwidth2'} if self.has_amt2 else {'amt':'iwidth'}
|
||||
if repl_data := self.prompt_fs_repl.get(self.proto.coin):
|
||||
for repl in [repl_data] if isinstance(repl_data[0], int) else repl_data:
|
||||
|
|
@ -441,7 +442,7 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
yield 'Network: {}'.format(Green(
|
||||
self.proto.coin + ' ' + self.proto.chain_name.upper()))
|
||||
|
||||
if not self.rpc.is_remote:
|
||||
if hasattr(self.rpc, 'blockcount'):
|
||||
yield 'Block {} [{}]'.format(
|
||||
self.rpc.blockcount.hl(color=color),
|
||||
make_timestr(self.rpc.cur_date))
|
||||
|
|
@ -778,7 +779,7 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
a = 'for' if edited else 'added to' if comment else 'removed from',
|
||||
b = desc,
|
||||
c = ' edited' if edited else ''))
|
||||
return True
|
||||
return 'redraw' if parent.cfg.coin == 'XMR' else True
|
||||
else:
|
||||
await asyncio.sleep(3)
|
||||
parent.oneshot_msg = red('Label for {desc} could not be {action}'.format(
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ def op_cls(op_name):
|
|||
cls.name = op_name
|
||||
return cls
|
||||
|
||||
def op(op, cfg, infile, wallets, *, spec=None):
|
||||
if cfg.compat if cfg.compat is not None else cfg.xmrwallet_compat:
|
||||
def op(op, cfg, infile, wallets, *, spec=None, compat_call=False):
|
||||
if compat_call or (cfg.compat if cfg.compat is not None else cfg.xmrwallet_compat):
|
||||
if cfg.wallet_dir:
|
||||
die(1, '--wallet-dir can not be specified in xmrwallet compatibility mode')
|
||||
from ..tw.ctl import TwCtl
|
||||
|
|
@ -126,5 +126,8 @@ def op(op, cfg, infile, wallets, *, spec=None):
|
|||
cfg = Config({
|
||||
'_clone': cfg,
|
||||
'compat': True,
|
||||
'no_start_wallet_daemon': cfg.no_start_wallet_daemon or compat_call,
|
||||
'daemon': cfg.daemon or cfg.monero_daemon,
|
||||
'watch_only': cfg.watch_only or cfg.autosign or bool(cfg.autosign_mountpoint),
|
||||
'wallet_dir': twctl_cls.get_tw_dir(cfg, cfg._proto)})
|
||||
return op_cls(op)(cfg, uargs(infile, wallets, spec))
|
||||
|
|
|
|||
|
|
@ -111,12 +111,13 @@ class OpBase:
|
|||
Proxy: {blue(m[2] or 'None')}
|
||||
""", strip_char='\t', indent=indent))
|
||||
|
||||
def mount_removable_device(self):
|
||||
def mount_removable_device(self, registered=[]):
|
||||
if self.cfg.autosign:
|
||||
if not self.asi.device_inserted:
|
||||
die(1, 'Removable device not present!')
|
||||
if self.do_umount:
|
||||
if self.do_umount and not registered:
|
||||
atexit.register(lambda: self.asi.do_umount())
|
||||
registered.append(None)
|
||||
self.asi.do_mount()
|
||||
self.post_mount_action()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class OpLabel(OpMixinSpec, OpWallet):
|
|||
opts = ()
|
||||
wallet_offline = True
|
||||
|
||||
async def main(self, add_timestr='ask'):
|
||||
async def main(self, add_timestr='ask', auto=False):
|
||||
|
||||
gmsg('\n{a} label for wallet {b}, account #{c}, address #{d}'.format(
|
||||
a = 'Setting' if self.label else 'Removing',
|
||||
|
|
@ -40,13 +40,13 @@ class OpLabel(OpMixinSpec, OpWallet):
|
|||
h = MoneroWalletRPC(self, self.source)
|
||||
|
||||
h.open_wallet('source')
|
||||
wallet_data = h.get_wallet_data()
|
||||
wallet_data = h.get_wallet_data(print=not auto)
|
||||
|
||||
max_acct = len(wallet_data.accts_data['subaddress_accounts']) - 1
|
||||
if self.account > max_acct:
|
||||
die(2, f'{self.account}: requested account index out of bounds (>{max_acct})')
|
||||
|
||||
ret = h.print_acct_addrs(wallet_data, self.account)
|
||||
ret = h.print_acct_addrs(wallet_data, self.account, silent=auto)
|
||||
|
||||
if self.address_idx > len(ret) - 1:
|
||||
die(2, '{}: requested address index out of bounds (>{})'.format(
|
||||
|
|
@ -60,6 +60,7 @@ class OpLabel(OpMixinSpec, OpWallet):
|
|||
(self.label + (f' [{make_timestr()}]' if add_timestr else '')) if self.label
|
||||
else '')
|
||||
|
||||
if not auto:
|
||||
ca = CoinAddr(self.proto, addr['address'])
|
||||
from . import addr_width
|
||||
msg('\n {a} {b}\n {c} {d}\n {e} {f}'.format(
|
||||
|
|
@ -74,7 +75,7 @@ class OpLabel(OpMixinSpec, OpWallet):
|
|||
|
||||
if addr['label'] == new_label:
|
||||
ymsg('\nLabel is unchanged, operation cancelled')
|
||||
elif keypress_confirm(self.cfg, f' {op.capitalize()} label?'):
|
||||
elif auto or keypress_confirm(self.cfg, f' {op.capitalize()} label?'):
|
||||
h.set_label(self.account, self.address_idx, new_label)
|
||||
ret = h.print_acct_addrs(h.get_wallet_data(print=False), self.account)
|
||||
label_chk = ret[self.address_idx]['label']
|
||||
|
|
@ -83,5 +84,6 @@ class OpLabel(OpMixinSpec, OpWallet):
|
|||
return False
|
||||
else:
|
||||
msg(cyan('\nLabel successfully {}'.format('set' if op == 'set' else op+'d')))
|
||||
return new_label
|
||||
else:
|
||||
ymsg('\nOperation cancelled by user request')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ xmrwallet.ops.new: Monero wallet ops for the MMGen Suite
|
|||
|
||||
from ...color import red, pink
|
||||
from ...util import msg, ymsg, make_timestr
|
||||
|
||||
from ...obj import TwComment
|
||||
from ...ui import keypress_confirm
|
||||
|
||||
from ..rpc import MoneroWalletRPC
|
||||
|
|
@ -32,7 +32,7 @@ class OpNew(OpMixinSpec, OpWallet):
|
|||
h.open_wallet('Monero')
|
||||
|
||||
desc = 'account' if self.account is None else 'address'
|
||||
label = (
|
||||
label = TwComment(
|
||||
None if self.label == '' else
|
||||
'{} [{}]'.format(self.label or f'xmrwallet new {desc}', make_timestr()))
|
||||
|
||||
|
|
|
|||
|
|
@ -173,8 +173,10 @@ class OpWallet(OpBase):
|
|||
else:
|
||||
self.addr_data = self.kal.data
|
||||
|
||||
async def restart_wallet_daemon(self):
|
||||
async def restart_wallet_daemon(self, registered=[]):
|
||||
if not registered:
|
||||
atexit.register(lambda: asyncio.run(self.stop_wallet_daemon()))
|
||||
registered.append(None)
|
||||
await self.c.restart_daemon()
|
||||
|
||||
async def stop_wallet_daemon(self):
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ class MoneroWalletRPC:
|
|||
msg(' Address: {}'.format(cyan(ret['base_address'])))
|
||||
return (ret['account_index'], ret['base_address'])
|
||||
|
||||
def print_acct_addrs(self, wallet_data, account):
|
||||
def print_acct_addrs(self, wallet_data, account, silent=False):
|
||||
if not silent:
|
||||
msg('\n Addresses of account #{} ({}):'.format(
|
||||
account,
|
||||
wallet_data.accts_data['subaddress_accounts'][account]['label']))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ cmd_groups_dfl = {
|
|||
'runeswap': gd('CmdTestRuneSwap', {}),
|
||||
'xmrwallet': gd('CmdTestXMRWallet', {}),
|
||||
'xmr_autosign': gd('CmdTestXMRAutosign', {}),
|
||||
'xmr_compat': gd('CmdTestXMRCompat', {'modname': 'xmr_autosign'}),
|
||||
}
|
||||
|
||||
cmd_groups_extra = {
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
|
|||
async def fund_alice1(self):
|
||||
return await self.fund_alice(wallet=1)
|
||||
|
||||
fund_alice1b = fund_alice1
|
||||
|
||||
async def check_bal_alice1(self):
|
||||
return await self.check_bal_alice(wallet=1)
|
||||
|
||||
|
|
@ -492,3 +494,60 @@ class CmdTestXMRAutosignNoCompat(CmdTestXMRAutosign):
|
|||
Monero autosigning operations (non-xmrwallet compat mode)
|
||||
"""
|
||||
compat = False
|
||||
|
||||
class CmdTestXMRCompat(CmdTestXMRAutosign):
|
||||
"""
|
||||
Monero autosigning operations (compat mode)
|
||||
"""
|
||||
menu_prompt = 'efresh balances:\b'
|
||||
|
||||
cmd_group = (
|
||||
('autosign_setup', 'autosign setup with Alice’s seed'),
|
||||
('autosign_xmr_setup', 'autosign setup (creation of Monero signing wallets)'),
|
||||
('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'),
|
||||
('mine_initial_coins', 'mining initial coins'),
|
||||
('fund_alice1', 'sending funds to Alice (wallet #1)'),
|
||||
('mine_blocks', 'mining some blocks'),
|
||||
('alice_listaddresses', 'performing operations on Alice’s tracking wallets (listaddresses)'),
|
||||
('fund_alice1b', 'sending funds to Alice (wallet #1)'),
|
||||
('mine_blocks', 'mining some blocks'),
|
||||
('alice_twview', 'performing operations on Alice’s tracking wallets (twview)'),
|
||||
)
|
||||
|
||||
def __init__(self, cfg, trunner, cfgs, spawn):
|
||||
super().__init__(cfg, trunner, cfgs, spawn)
|
||||
if trunner is None:
|
||||
return
|
||||
self.alice_opts = [
|
||||
'--alice',
|
||||
'--coin=xmr',
|
||||
'--monero-wallet-rpc-password=passwOrd',
|
||||
f'--monero-daemon=localhost:{self.users["alice"].md.rpc_port}']
|
||||
|
||||
def create_watchonly_wallets(self):
|
||||
return self._create_wallets()
|
||||
|
||||
async def mine_blocks(self):
|
||||
self.spawn(msg_only=True)
|
||||
return await self.mine(10)
|
||||
|
||||
def alice_listaddresses(self):
|
||||
return self._alice_twops('listaddresses', 2, 'y', r'Primary account.*1\.234567891234')
|
||||
|
||||
def alice_twview(self):
|
||||
return self._alice_twops('twview', 1, 'n', r'New Label.*2\.469135782468')
|
||||
|
||||
def _alice_twops(self, op, addr_num, add_timestr_resp, expect_str):
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-tool', self.alice_opts + self.autosign_opts + [op, 'interactive=1'])
|
||||
t.expect(self.menu_prompt, 'l')
|
||||
t.expect('main menu): ', str(addr_num))
|
||||
t.expect(': ', 'New Label\n')
|
||||
t.expect('(y/N): ', add_timestr_resp)
|
||||
t.expect(self.menu_prompt, 'R')
|
||||
t.expect(expect_str, regex=True)
|
||||
t.expect(self.menu_prompt, 'q')
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue