mmgen-tool xmrwallet: new 'sweep' command

This commit is contained in:
The MMGen Project 2021-04-20 19:36:22 +00:00
commit 627d301076
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2

View file

@ -1020,7 +1020,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
op: str,
xmr_keyaddrfile: str,
blockheight: '(default: current height)' = 0,
wallets: '(integer range or list)' = '',
wallets: '(integer range or list, or sweep specifier)' = '',
start_wallet_daemon = True,
stop_wallet_daemon = True,
monerod_args = '',
@ -1033,11 +1033,30 @@ class MMGenToolCmdMonero(MMGenToolCmds):
create - create wallet for all or specified addresses in key-address file
sync - sync wallet for all or specified addresses in key-address file
sweep - sweep funds in specified wallet:account to new address in same
account or new account in another wallet
SWEEP OPERATION NOTES
For the sweep operation, the parameter to the 'wallets' arg has a different
format, known as a 'sweep specifier':
SOURCE:ACCOUNT[,DEST]
where SOURCE and DEST are wallet numbers and ACCOUNT an account index.
If DEST is omitted, a new address will be created in ACCOUNT of SOURCE and
all funds from ACCOUNT of SOURCE will be swept into it.
If DEST is included, a new account will be created in DEST and all funds
from ACCOUNT of SOURCE will be swept into the new account.
The user is prompted before addresses are created or funds are transferred.
"""
class MoneroWalletOps:
ops = ('create','sync')
ops = ('create','sync','sweep')
class base:
@ -1234,6 +1253,198 @@ class MMGenToolCmdMonero(MMGenToolCmds):
msg(fs.format( '-'*col1_w, '-'*18, '-'*18 ))
msg(fs.format( 'TOTAL:', fmtXMRamt(tbals[0]), fmtXMRamt(tbals[1]) ))
class sweep(base):
name = 'sweep'
desc = 'Sweep'
past = 'swept'
def create_addr_data(self):
import re
m = re.match('(\d+):(\d+)(?:,(\d+))?$',wallets,re.ASCII)
if not m:
die(1,
"{!r}: invalid 'wallets' arg: for sweep operation, it must have format {}".format(
wallets,
'SOURCE:ACCOUNT[,DEST]'
))
def gen():
for i,k in ( (1,'source'), (3,'dest') ):
if m[i] == None:
setattr(self,k,None)
else:
idx = int(m[i])
try:
res = [d for d in self.kal.data if d.idx == idx][0]
except:
die(1,'Supplied key-address file does not contain address {}:{}'.format(
self.kal.al_id.sid,
idx ))
else:
setattr(self,k,res)
yield res
self.addr_data = list(gen())
self.account = int(m[2])
async def process_wallets(self):
gmsg(f'\nSweeping account #{self.account} of wallet {self.source.idx}' + (
' to new address' if self.dest is None else
f' to new account in wallet {self.dest.idx}' ))
h = xmr_rpc_methods(self,self.source)
await h.open_wallet('source')
accts_data = await h.get_accts()
max_acct = len(accts_data['subaddress_accounts']) - 1
if self.account > max_acct:
die(1,f'{self.account}: requested account index out of bounds (>{max_acct})')
await h.get_addrs(accts_data,self.account)
if self.dest == None:
if keypress_confirm(f'\nCreate new address for account #{self.account}?'):
new_addr = await h.create_new_addr(self.account)
elif keypress_confirm(f'Sweep to last existing address of account #{self.account}?'):
new_addr = await h.get_last_addr(self.account)
else:
die(1,'Exiting at user request')
await h.get_addrs(accts_data,self.account)
else:
bn = os.path.basename(self.get_wallet_fn(self.dest))
h = xmr_rpc_methods(self,self.dest)
await h.open_wallet('destination')
accts_data = await h.get_accts()
if keypress_confirm(f'\nCreate new account for wallet {bn!r}?'):
new_addr = await h.create_acct()
await h.get_accts()
elif keypress_confirm(f'Sweep to last existing account of wallet {bn!r}?'):
new_addr = h.get_last_acct(accts_data)
else:
die(1,'Exiting at user request')
h = xmr_rpc_methods(self,self.source)
await h.open_wallet('source')
if keypress_confirm(
'\nSweep balance of wallet {}, account #{} to {}?'.format(
self.source.idx,
self.account,
cyan(new_addr),
)):
await h.do_sweep(self.account,new_addr)
else:
die(1,'Exiting at user request')
class xmr_rpc_methods:
def __init__(self,parent,d):
self.parent = parent
self.c = parent.c
self.d = d
self.fn = parent.get_wallet_fn(d)
async def open_wallet(self,desc):
gmsg_r(f'\n Opening {desc} wallet...')
ret = await self.c.call( # returns {}
'open_wallet',
filename=os.path.basename(self.fn),
password=self.d.wallet_passwd )
gmsg('done')
def print_accts(self,data,indent=' '):
d = data['subaddress_accounts']
msg('\n' + indent + f'Accounts of wallet {os.path.basename(self.fn)}:')
fs = indent + ' {:6} {:18} {:%s} {}' % max(len(e['label']) for e in d)
msg(fs.format('Index ','Base Address','Label','Balance'))
for e in d:
msg(fs.format(
str(e['account_index']),
e['base_address'][:15] + '...',
e['label'],
fmtXMRamt(e['balance']),
))
async def get_accts(self):
data = await self.c.call('get_accounts')
self.print_accts(data)
return data
async def create_acct(self):
msg('\n Creating new account...')
ret = await self.c.call(
'create_account',
label = f'Sweep from {self.parent.source.idx}:{self.parent.account}'
)
msg(' Index: {}'.format( pink(str(ret['account_index'])) ))
msg(' Address: {}'.format( cyan(ret['address']) ))
return ret['address']
def get_last_acct(self,accts_data):
msg('\n Getting last account...')
data = accts_data['subaddress_accounts'][-1]
msg(' Index: {}'.format( pink(str(data['account_index'])) ))
msg(' Address: {}'.format( cyan(data['base_address']) ))
return data['base_address']
async def get_addrs(self,accts_data,account):
ret = await self.c.call('get_address',account_index=account)
d = ret['addresses']
msg('\n Addresses of account #{} ({}):'.format(
account,
accts_data['subaddress_accounts'][account]['label']))
fs = ' {:6} {:18} {:%s} {}' % max(len(e['label']) for e in d)
msg(fs.format('Index ','Address','Label','Used'))
for e in d:
msg(fs.format(
str(e['address_index']),
e['address'][:15] + '...',
e['label'],
e['used']
))
return ret
async def create_new_addr(self,account):
msg_r('\n Creating new address: ')
ret = await self.c.call(
'create_address',
account_index = account,
label = 'Sweep from this account',
)
msg(cyan(ret['address']))
return ret['address']
async def get_last_addr(self,account):
msg('\n Getting last address:')
ret = (await self.c.call(
'get_address',
account_index = account,
))['addresses'][-1]['address']
msg(' ' + cyan(ret))
return ret
async def do_sweep(self,account,addr):
msg(f'\n Sweeping account balance...')
ret = { # debug
'amount_list': [322222330000],
'fee_list': [10600000],
'tx_hash_list': ['deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'],
}
ret = await self.c.call(
'sweep_all',
address = addr,
account_index = account,
)
from .obj import CoinTxID
msg(' TxID: {}\n Amount: {}\n Fee: {}'.format(
CoinTxID(ret['tx_hash_list'][0]).hl(),
hlXMRamt(ret['amount_list'][0]),
hlXMRamt(ret['fee_list'][0]),
))
return ret
def fmtXMRamt(amt):
from .obj import XMRAmt
return XMRAmt(amt,from_unit='min_coin_unit').fmt(fs='5.12',color=True)