mmgen-xmrwallet: new transfer operation
This commit is contained in:
parent
95e1354f69
commit
4f631a1068
3 changed files with 107 additions and 21 deletions
|
|
@ -29,9 +29,10 @@ opts_data = {
|
|||
'desc': """Perform various Monero wallet operations for addresses
|
||||
in an MMGen XMR key-address file""",
|
||||
'usage2': [
|
||||
'[opts] create <xmr_keyaddrfile> [wallets]',
|
||||
'[opts] sync <xmr_keyaddrfile> [wallets]',
|
||||
'[opts] sweep <xmr_keyaddrfile> <sweep_spec>',
|
||||
'[opts] create <xmr_keyaddrfile> [wallets]',
|
||||
'[opts] sync <xmr_keyaddrfile> [wallets]',
|
||||
'[opts] transfer <xmr_keyaddrfile> <transfer_spec>',
|
||||
'[opts] sweep <xmr_keyaddrfile> <sweep_spec>',
|
||||
],
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
|
|
@ -61,10 +62,12 @@ may point to a SOCKS proxy, in which case HOST may be a Tor onion address.
|
|||
|
||||
SUPPORTED OPERATIONS
|
||||
|
||||
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
|
||||
create - create wallet for all or specified addresses in key-address file
|
||||
sync - sync wallet for all or specified addresses in key-address file
|
||||
transfer - transfer specified XMR amount to specified address from specified
|
||||
wallet:account
|
||||
sweep - sweep funds in specified wallet:account to new address in same
|
||||
account or new account in another wallet
|
||||
|
||||
|
||||
CREATE AND SYNC OPERATION NOTES
|
||||
|
|
@ -75,6 +78,17 @@ key-address file, each corresponding to a Monero wallet to be created or
|
|||
synced. If omitted, all wallets are operated upon.
|
||||
|
||||
|
||||
TRANSFER OPERATION NOTES
|
||||
|
||||
The transfer operation takes a `transfer specifier` arg with the following
|
||||
format:
|
||||
|
||||
SOURCE:ACCOUNT:ADDRESS,AMOUNT
|
||||
|
||||
where SOURCE is a wallet index; ACCOUNT the source account index; and ADDRESS
|
||||
and AMOUNT the destination Monero address and XMR amount, respectively.
|
||||
|
||||
|
||||
SWEEP OPERATION NOTES
|
||||
|
||||
The sweep operation takes a `sweep specifier` arg with the following format:
|
||||
|
|
@ -126,7 +140,7 @@ if op in ('create','sync'):
|
|||
opts.usage()
|
||||
if cmd_args:
|
||||
wallets = cmd_args[0]
|
||||
elif op == 'sweep':
|
||||
elif op in ('transfer','sweep'):
|
||||
if len(cmd_args) != 1:
|
||||
opts.usage()
|
||||
spec = cmd_args[0]
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ from .common import *
|
|||
from .addr import KeyAddrList,AddrIdxList
|
||||
from .rpc import MoneroRPCClientRaw, MoneroWalletRPCClient
|
||||
from .daemon import MoneroWalletDaemon
|
||||
from .protocol import _b58a
|
||||
|
||||
xmrwallet_uarg_info = (
|
||||
lambda e,hp: {
|
||||
'daemon': e('HOST:PORT', hp),
|
||||
'tx_relay_daemon': e('HOST:PORT[:PROXY_HOST:PROXY_PORT]', r'({p})(?::({p}))?'.format(p=hp)),
|
||||
'transfer_spec': e('SOURCE_WALLET_NUM:ACCOUNT:ADDRESS,AMOUNT', rf'(\d+):(\d+):([{_b58a}]+),([0-9.]+)'),
|
||||
'sweep_spec': e('SOURCE_WALLET_NUM:ACCOUNT[,DEST_WALLET_NUM]', r'(\d+):(\d+)(?:,(\d+))?'),
|
||||
})(
|
||||
namedtuple('uarg_info_entry',['annot','pat']),
|
||||
|
|
@ -39,7 +41,7 @@ xmrwallet_uarg_info = (
|
|||
|
||||
class MoneroWalletOps:
|
||||
|
||||
ops = ('create','sync','sweep')
|
||||
ops = ('create','sync','transfer','sweep')
|
||||
|
||||
class base:
|
||||
|
||||
|
|
@ -155,6 +157,20 @@ class MoneroWalletOps:
|
|||
hl_amt(fee),
|
||||
))
|
||||
|
||||
async def make_transfer_tx(self,account,addr,amt):
|
||||
res = await self.c.call(
|
||||
'transfer',
|
||||
account_index = account,
|
||||
destinations = [{
|
||||
'amount': amt.to_unit('atomic'),
|
||||
'address': addr
|
||||
}],
|
||||
do_not_relay = True,
|
||||
get_tx_metadata = True
|
||||
)
|
||||
self.display_tx( res['tx_hash'], res['amount'], res['fee'] )
|
||||
return res['tx_metadata']
|
||||
|
||||
async def make_sweep_tx(self,account,addr):
|
||||
res = await self.c.call(
|
||||
'sweep_all',
|
||||
|
|
@ -438,6 +454,11 @@ class MoneroWalletOps:
|
|||
self.addr_data = list(gen())
|
||||
self.account = int(m[2])
|
||||
|
||||
if self.name == 'transfer':
|
||||
from mmgen.obj import CoinAddr
|
||||
self.dest_addr = CoinAddr(self.proto,m[3])
|
||||
self.amount = self.proto.coin_amt(m[4])
|
||||
|
||||
def post_init(self):
|
||||
|
||||
if uarg.tx_relay_daemon:
|
||||
|
|
@ -467,7 +488,8 @@ class MoneroWalletOps:
|
|||
|
||||
async def process_wallets(self):
|
||||
gmsg(f'\n{self.desc}ing account #{self.account} of wallet {self.source.idx}' + (
|
||||
' to new address' if self.dest is None
|
||||
f': {self.amount} XMR to {self.dest_addr}' if self.name == 'transfer'
|
||||
else ' to new address' if self.dest == None
|
||||
else f' to new account in wallet {self.dest.idx}' ))
|
||||
|
||||
h = self.rpc(self,self.source)
|
||||
|
|
@ -481,7 +503,9 @@ class MoneroWalletOps:
|
|||
|
||||
await h.get_addrs(accts_data,self.account)
|
||||
|
||||
if self.dest == None:
|
||||
if self.name == 'transfer':
|
||||
new_addr = self.dest_addr
|
||||
elif 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}?'):
|
||||
|
|
@ -507,12 +531,14 @@ class MoneroWalletOps:
|
|||
await h2.close_wallet('destination')
|
||||
await h.open_wallet('source')
|
||||
|
||||
msg('\n Creating sweep transaction: balance of wallet {}, account #{} => {}'.format(
|
||||
self.source.idx,
|
||||
self.account,
|
||||
cyan(new_addr),
|
||||
))
|
||||
tx_metadata = await h.make_sweep_tx(self.account,new_addr)
|
||||
msg_r(f'\n Creating {self.name} transaction: wallet {self.source.idx}, account #{self.account}')
|
||||
|
||||
if self.name == 'transfer':
|
||||
msg(f', {self.amount} XMR => {cyan(new_addr)}')
|
||||
tx_metadata = await h.make_transfer_tx(self.account,new_addr,self.amount)
|
||||
elif self.name == 'sweep':
|
||||
msg(f' => {cyan(new_addr)}')
|
||||
tx_metadata = await h.make_sweep_tx(self.account,new_addr)
|
||||
|
||||
if keypress_confirm(f'\nRelay {self.name} transaction?'):
|
||||
w_desc = 'source'
|
||||
|
|
@ -533,3 +559,10 @@ class MoneroWalletOps:
|
|||
die(1,'\nExiting at user request')
|
||||
|
||||
return True
|
||||
|
||||
class transfer(sweep):
|
||||
name = 'transfer'
|
||||
desc = 'Transfer'
|
||||
past = 'transferred'
|
||||
spec_id = 'transfer_spec'
|
||||
spec_key = ( (1,'source'), )
|
||||
|
|
|
|||
|
|
@ -61,6 +61,13 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
|
||||
('sweep_to_address_noproxy', 'sweeping to new address (via TX relay)'),
|
||||
('mine_blocks', 'mining blocks'),
|
||||
|
||||
('transfer_to_miner_proxy', 'transferring funds to Miner (via TX relay + proxy)'),
|
||||
('mine_blocks_extra', 'mining blocks'),
|
||||
('sync_wallet_2', 'syncing Alice’s wallet #2'),
|
||||
|
||||
('transfer_to_miner_noproxy', 'transferring funds to Miner (via TX relay)'),
|
||||
('mine_blocks', 'mining blocks'),
|
||||
)
|
||||
|
||||
def __init__(self,trunner,cfgs,spawn):
|
||||
|
|
@ -197,7 +204,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
'wd_rpc',
|
||||
])
|
||||
for user,sid,shift,kal_range in ( # kal_range must be None, a single digit, or a single hyphenated range
|
||||
('miner', '98831F3A', 130, '1'),
|
||||
('miner', '98831F3A', 130, '1-2'),
|
||||
('bob', '1378FC64', 140, None),
|
||||
('alice', 'FE3C6545', 150, '1-4'),
|
||||
):
|
||||
|
|
@ -320,6 +327,9 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
def sync_wallets_selected(self):
|
||||
return self.sync_wallets(wallets='1,3-4')
|
||||
|
||||
def sync_wallet_2(self):
|
||||
return self.sync_wallets(wallets='2')
|
||||
|
||||
def sync_wallets(self,wallets=None):
|
||||
data = self.users['alice']
|
||||
dir_opt = [f'--outdir={data.udir}']
|
||||
|
|
@ -376,6 +386,19 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
self.set_dest('alice',2,1,lambda x: x > 1,'unlocked balance > 1')
|
||||
return ret
|
||||
|
||||
def transfer_to_miner_proxy(self):
|
||||
addr = read_from_file(self.users['miner'].addrfile_fs.format(2))
|
||||
amt = '0.135'
|
||||
ret = self.do_op('transfer','alice',f'2:1:{addr},{amt}',self.tx_relay_daemon_proxy_parm)
|
||||
self.set_dest('miner',2,0,lambda x: str(x) == amt,f'unlocked balance == {amt}')
|
||||
return ret
|
||||
|
||||
def transfer_to_miner_noproxy(self):
|
||||
addr = read_from_file(self.users['miner'].addrfile_fs.format(2))
|
||||
ret = self.do_op('transfer','alice',f'2:1:{addr},0.0995',self.tx_relay_daemon_parm)
|
||||
self.set_dest('miner',2,0,lambda x: str(x) == '0.2345','unlocked balance == 0.2345')
|
||||
return ret
|
||||
|
||||
# wallet methods
|
||||
|
||||
async def open_wallet_user(self,user,wnum):
|
||||
|
|
@ -420,7 +443,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
ret = await self.users['miner'].md_rpc.call('stop_mining')
|
||||
return self.get_status(ret)
|
||||
|
||||
async def mine_blocks(self,random_txs=None):
|
||||
async def mine_blocks(self,random_txs=None,extra_blocks=None):
|
||||
"""
|
||||
- open destination wallet
|
||||
- optionally create and broadcast random TXs
|
||||
|
|
@ -447,6 +470,18 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
else:
|
||||
die(2,'Restart attempt limit exceeded')
|
||||
|
||||
async def mine_extra_blocks():
|
||||
h_start = await get_height()
|
||||
imsg_r(f'[+{extra_blocks} blocks]: ')
|
||||
oqmsg_r('|')
|
||||
while True:
|
||||
await asyncio.sleep(2)
|
||||
h = await get_height()
|
||||
imsg_r(f'{h} ')
|
||||
oqmsg_r('+')
|
||||
if h - h_start > extra_blocks:
|
||||
break
|
||||
|
||||
async def send_random_txs():
|
||||
from mmgen.tool import tool_api
|
||||
t = tool_api()
|
||||
|
|
@ -480,8 +515,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
|
||||
self.do_msg(extra_desc=f'+{random_txs} random TXs' if random_txs else None)
|
||||
|
||||
if self.dest.user != 'miner':
|
||||
await self.open_wallet_user(self.dest.user,self.dest.wnum)
|
||||
await self.open_wallet_user(self.dest.user,self.dest.wnum)
|
||||
|
||||
if random_txs:
|
||||
await send_random_txs()
|
||||
|
|
@ -494,6 +528,8 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
while True:
|
||||
ub = await get_balance(self.dest)
|
||||
if self.dest.test(ub):
|
||||
if extra_blocks:
|
||||
await mine_extra_blocks()
|
||||
imsg('')
|
||||
oqmsg_r('+')
|
||||
print_balance(self.dest,ub)
|
||||
|
|
@ -515,6 +551,9 @@ class TestSuiteXMRWallet(TestSuiteBase):
|
|||
async def mine_blocks_tx(self):
|
||||
return await self.mine_blocks(random_txs=self.dfl_random_txs)
|
||||
|
||||
async def mine_blocks_extra(self):
|
||||
return await self.mine_blocks(extra_blocks=100) # TODO: 100 is arbitrary value
|
||||
|
||||
# util methods
|
||||
|
||||
def get_status(self,ret):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue