Browse Source

mmgen-tool xmrwallet: new 'sweep' command

The MMGen Project 4 years ago
parent
commit
627d301076
1 changed files with 213 additions and 2 deletions
  1. 213 2
      mmgen/tool.py

+ 213 - 2
mmgen/tool.py

@@ -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)