XMR compat: sweep transactions
Example:
# Invoking `mmgen-txcreate` without arguments activates the sweep function-
# ality. Type ‘S’ (account sweep) or ‘s’ (address sweep) to begin creating
# the transaction:
$ mmgen-txcreate --coin=xmr
Testing/demo:
$ test/cmdtest.py --coin=xmr -e -X alice_twview_chk4 xmr_compat
This commit is contained in:
parent
c9e79dc630
commit
907bdc2bdf
11 changed files with 117 additions and 14 deletions
|
|
@ -1 +1 @@
|
|||
16.1.dev25
|
||||
16.1.dev26
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class help_notes:
|
|||
case 'Bitcoin':
|
||||
return '[ADDR,AMT ... | DATA_SPEC] ADDR [addr file ...]'
|
||||
case 'Monero':
|
||||
return 'ADDR,AMT'
|
||||
return '[ADDR,AMT]'
|
||||
case _:
|
||||
return 'ADDR,AMT [addr file ...]'
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ def help(proto, cfg):
|
|||
addr = t.privhex2addr('bead' * 16)
|
||||
sample_addr = addr.views[addr.view_pref]
|
||||
cmd_base = gc.prog_name + ('' if proto.coin == 'BTC' else f' --coin={proto.coin.lower()}')
|
||||
action = 'Create' if gc.prog_name == 'mmgen-txcreate' else 'Execute'
|
||||
|
||||
match proto.base_proto:
|
||||
case 'Bitcoin':
|
||||
|
|
@ -64,6 +65,10 @@ EXAMPLES:
|
|||
Send 0.123 {proto.coin} to an external {proto.name} address:
|
||||
|
||||
$ {cmd_base} {sample_addr},0.123
|
||||
|
||||
{action} a sweep transaction:
|
||||
|
||||
$ {cmd_base}
|
||||
"""
|
||||
|
||||
case _:
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ if cfg.list_assets:
|
|||
Msg('AVAILABLE SWAP ASSETS:\n' + sp.SwapAsset('BTC', 'send').fmt_assets_data(indent=' '))
|
||||
sys.exit(0)
|
||||
|
||||
if not (cfg.info or cfg.contract_data):
|
||||
if not (cfg.info or cfg.contract_data or cfg.coin == 'XMR'):
|
||||
if len(cfg._args) < {'tx': 1, 'swaptx': 2}[target]:
|
||||
cfg._usage()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,4 +30,40 @@ class MoneroTwUnspentOutputs(MoneroTwView, TwUnspentOutputs):
|
|||
self.extra_key_mappings = {
|
||||
'R': '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:'
|
||||
self.prompt_fs_in.insert(-1, 'Transaction ops: [s]weep to address, [S]weep to account')
|
||||
self.extra_key_mappings.update({
|
||||
's': 'i_addr_sweep',
|
||||
'S': 'i_acct_sweep'})
|
||||
await super().__init__(cfg, proto, minconf=minconf, addrs=addrs, tx=tx)
|
||||
|
||||
async def get_idx_from_user(self, method_name):
|
||||
if method_name in ('i_acct_sweep', 'i_addr_sweep'):
|
||||
from collections import namedtuple
|
||||
ret = []
|
||||
for acct_desc in {
|
||||
'i_acct_sweep': ['source', 'destination'],
|
||||
'i_addr_sweep': ['source']}[method_name]:
|
||||
if res := await self.get_idx(f'{acct_desc} account number', self.accts_data):
|
||||
ret.append(res)
|
||||
else:
|
||||
return None
|
||||
return namedtuple('usr_idx_data', 'idx acct_addr_idx', defaults=[None])(*ret)
|
||||
else:
|
||||
return await super().get_idx_from_user(method_name)
|
||||
|
||||
class item_action(TwUnspentOutputs.item_action):
|
||||
acct_methods = ('i_acct_sweep', 'i_addr_sweep')
|
||||
|
||||
async def i_acct_sweep(self, parent, idx, acct_addr_idx=None):
|
||||
d = parent.accts_data
|
||||
d1 = d[idx.idx - 1]
|
||||
d2 = d[acct_addr_idx.idx - 1]
|
||||
parent.tx.sweep_spec = f'{d1.idx}:{d1.acct_idx},{d2.idx}:{d2.acct_idx}'
|
||||
return 'quit_view'
|
||||
|
||||
async def i_addr_sweep(self, parent, idx, acct_addr_idx=None):
|
||||
d = parent.accts_data[idx.idx - 1]
|
||||
parent.tx.sweep_spec = f'{d.idx}:{d.acct_idx}'
|
||||
return 'quit_view'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ from .base import Base
|
|||
|
||||
class New(Base, TxNew):
|
||||
|
||||
async def create(self, cmd_args, **kwargs):
|
||||
self.is_sweep = not cmd_args
|
||||
self.sweep_spec = None
|
||||
return await super().create(cmd_args, **kwargs)
|
||||
|
||||
async def get_input_addrs_from_inputs_opt(self):
|
||||
return [] # TODO
|
||||
|
||||
|
|
@ -42,7 +47,20 @@ class New(Base, TxNew):
|
|||
|
||||
async def compat_create(self):
|
||||
|
||||
if True:
|
||||
if self.is_sweep:
|
||||
if not self.sweep_spec:
|
||||
from ....util import ymsg
|
||||
ymsg('No transaction operation specified. Exiting')
|
||||
return None
|
||||
from ....ui import item_chooser
|
||||
from ....color import pink
|
||||
op = item_chooser(
|
||||
self.cfg,
|
||||
'Choose the sweep operation type',
|
||||
('sweep', 'sweep_all'),
|
||||
lambda s: pink(s.upper())).item
|
||||
spec = self.sweep_spec
|
||||
else:
|
||||
op = 'transfer'
|
||||
i = self.inputs[0]
|
||||
o = self.outputs[0]
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ class TwUnspentOutputs(TwView):
|
|||
self.__dict__['proto'] = proto
|
||||
MMGenListItem.__init__(self, **kwargs)
|
||||
|
||||
async def __init__(self, cfg, proto, *, minconf=1, addrs=[]):
|
||||
async def __init__(self, cfg, proto, *, minconf=1, addrs=[], tx=None):
|
||||
self.tx = tx
|
||||
await super().__init__(cfg, proto)
|
||||
self.minconf = NonNegativeInt(minconf)
|
||||
self.addrs = addrs
|
||||
|
|
|
|||
|
|
@ -639,7 +639,9 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
case ch if ch in key_mappings:
|
||||
func = action_classes[ch].run
|
||||
arg = action_methods[ch]
|
||||
await func(self, arg) if isAsync(func) else func(self, arg)
|
||||
ret = await func(self, arg) if isAsync(func) else func(self, arg)
|
||||
if ret == 'quit_view':
|
||||
return cleanup()
|
||||
case 'q':
|
||||
return cleanup(add_nl=True)
|
||||
case _:
|
||||
|
|
@ -696,6 +698,8 @@ class TwView(MMGenObject, metaclass=AsyncInit):
|
|||
await do_error_msg()
|
||||
|
||||
async def post_action_cleanup(self, ret):
|
||||
if ret == 'quit_view':
|
||||
return ret
|
||||
if self.scroll and (ret is False or ret in ('redraw', 'erase')):
|
||||
# error messages could leave screen in messy state, so do complete redraw:
|
||||
msg_r(
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ def parse_fee_spec(proto, fee_arg):
|
|||
class New(Base):
|
||||
|
||||
fee_is_approximate = False
|
||||
is_sweep = False
|
||||
msg_wallet_low_coin = 'Wallet has insufficient funds for this transaction ({} {} needed)'
|
||||
msg_no_change_output = """
|
||||
ERROR: No change address specified. If you wish to create a transaction with
|
||||
|
|
@ -455,7 +456,7 @@ class New(Base):
|
|||
if self.cfg.comment_file:
|
||||
self.add_comment(infile=self.cfg.comment_file)
|
||||
|
||||
if not do_info:
|
||||
if cmd_args and not do_info:
|
||||
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)
|
||||
|
|
@ -475,7 +476,8 @@ class New(Base):
|
|||
self.cfg,
|
||||
self.proto,
|
||||
minconf = self.cfg.minconf,
|
||||
addrs = await self.get_input_addrs_from_inputs_opt())
|
||||
addrs = await self.get_input_addrs_from_inputs_opt(),
|
||||
tx = self if self.is_sweep else None)
|
||||
await self.twuo.get_data()
|
||||
self.twctl = self.twuo.twctl
|
||||
|
||||
|
|
@ -485,6 +487,11 @@ class New(Base):
|
|||
if not (self.is_bump or self.cfg.inputs):
|
||||
await self.twuo.view_filter_and_sort()
|
||||
|
||||
if self.is_sweep:
|
||||
del self.twctl
|
||||
del self.twuo.twctl
|
||||
return await self.compat_create()
|
||||
|
||||
if not self.is_bump:
|
||||
self.twuo.display_total()
|
||||
|
||||
|
|
|
|||
|
|
@ -169,10 +169,9 @@ class OpSweep(OpMixinSpec, OpWallet):
|
|||
die(2, f'{self.account}: requested account index out of bounds (>{max_acct})')
|
||||
|
||||
async def main(self):
|
||||
if not self.compat_call:
|
||||
gmsg(
|
||||
f'\n{self.stem.capitalize()}ing account #{self.account}'
|
||||
f' of wallet {self.source.idx}{self.add_desc}')
|
||||
gmsg(
|
||||
f'\n{self.stem.capitalize()}ing account #{self.account}'
|
||||
f' of wallet {self.source.idx}{self.add_desc}')
|
||||
|
||||
h = MoneroWalletRPC(self, self.source)
|
||||
|
||||
|
|
|
|||
|
|
@ -548,6 +548,16 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
('alice_txcreate3', 'recreating the transaction'),
|
||||
('wait_loop_start_ltc', 'starting autosign wait loop in XMR compat mode [--coins=ltc,xmr]'),
|
||||
('alice_txsend1', 'sending the transaction'),
|
||||
('mine_blocks_10', 'mining some blocks'),
|
||||
('alice_twview_chk2', 'viewing Alice’s tracking wallets (check balances)'),
|
||||
('alice_txcreate_sweep1', 'creating a sweep transaction (account sweep)'),
|
||||
('alice_txsend2', 'sending the transaction'),
|
||||
('mine_blocks_10', 'mining some blocks'),
|
||||
('alice_twview_chk3', 'viewing Alice’s tracking wallets (check balances)'),
|
||||
('alice_txcreate_sweep2', 'creating a sweep transaction (address sweep)'),
|
||||
('alice_txsend3', 'sending the transaction'),
|
||||
('mine_blocks_10', 'mining some blocks'),
|
||||
('alice_twview_chk4', 'viewing Alice’s tracking wallets (check balances)'),
|
||||
('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)'),
|
||||
|
|
@ -686,6 +696,15 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
'1 0.026296296417',
|
||||
'0.007654321098'])
|
||||
|
||||
def alice_twview_chk2(self):
|
||||
return self._alice_twview_chk(['Total XMR: 3.715053370119'], sync=True)
|
||||
|
||||
def alice_twview_chk3(self):
|
||||
return self._alice_twview_chk(['Total XMR: 3.713242570119', '1.232757091234'], sync=True)
|
||||
|
||||
def alice_twview_chk4(self):
|
||||
return self._alice_twview_chk(['Total XMR: 3.709050970119', '1.254861787651'], sync=True)
|
||||
|
||||
def _alice_twview_chk(self, expect_arr, sync=False):
|
||||
return self._alice_twops(
|
||||
'twview',
|
||||
|
|
@ -756,6 +775,12 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
def alice_txcreate1(self):
|
||||
return self._alice_txops('txcreate', [f'{self.burn_addr},0.012345'], acct_num=1)
|
||||
|
||||
def alice_txcreate_sweep1(self):
|
||||
return self._alice_txops('txcreate', menu='S', sweep_menu='23', sweep_type='sweep')
|
||||
|
||||
def alice_txcreate_sweep2(self):
|
||||
return self._alice_txops('txcreate', menu='s', sweep_menu='2', sweep_type='sweep_all')
|
||||
|
||||
alice_txcreate3 = alice_txcreate2 = alice_txcreate1
|
||||
|
||||
def _alice_txabort(self):
|
||||
|
|
@ -770,7 +795,7 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
add_opts = self.alice_daemon_opts,
|
||||
wait_signed = True)
|
||||
|
||||
alice_txsend1 = _alice_txsend
|
||||
alice_txsend1 = alice_txsend2 = alice_txsend3 = _alice_txsend
|
||||
|
||||
def wait_signed1(self):
|
||||
self.spawn(msg_only=True)
|
||||
|
|
@ -788,6 +813,8 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
menu = '',
|
||||
acct_num = None,
|
||||
wait_signed = False,
|
||||
sweep_type = None,
|
||||
sweep_menu = '',
|
||||
signable_desc = 'transaction'):
|
||||
if wait_signed:
|
||||
self._wait_signed(signable_desc)
|
||||
|
|
@ -796,7 +823,13 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
|
|||
if '--abort' in opts:
|
||||
t.expect('(y/N): ', 'y')
|
||||
elif op == 'txcreate':
|
||||
if True:
|
||||
if sweep_type:
|
||||
t.expect(self.menu_prompt, menu)
|
||||
for ch in sweep_menu:
|
||||
t.expect('main menu): ', ch)
|
||||
t.expect('number> ', {'sweep': '1', 'sweep_all': '2'}[sweep_type])
|
||||
t.expect('(y/N): ', 'y') # create new address?
|
||||
else:
|
||||
for ch in menu + 'q':
|
||||
t.expect(self.menu_prompt, ch)
|
||||
t.expect('to spend from: ', f'{acct_num}\n')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue