def freeze_aergo( config_file_path: str, aergo_net: str, eth_net: str, bridge_vault: str, amount: int, privkey_pwd: str = None, ): """ Send aergo from the vault account to the bridge (freeze). NOTE: This is mainly for deploying test bridge as other wallets like aergo connect can be used to transfer aer """ if privkey_pwd is None: privkey_pwd = getpass( "Decrypt Aergo private key '{}'\nPassword: ".format(bridge_vault)) w = AergoWallet(config_file_path) aergo_bridge = w.config_data('networks', aergo_net, 'bridges', eth_net, 'addr') w.transfer(amount, aergo_bridge, 'aergo', aergo_net, privkey_name=bridge_vault, privkey_pwd=privkey_pwd)
def load_config(self): """Load the configuration file from path and create a wallet object.""" while 1: questions = [ { 'type': 'input', 'name': 'config_file_path', 'message': 'Path to config.json (path/to/config.json)' } ] config_file_path = inquirer.prompt( questions, style=aergo_style)['config_file_path'] try: self.wallet = AergoWallet(config_file_path) break except (IsADirectoryError, FileNotFoundError): print("Invalid path/to/config.json") except KeyError: return
from aergo_wallet.wallet import AergoWallet if __name__ == '__main__': print("\n\nDEPLOY TEST ARC1 on Aergo") wallet = AergoWallet("./test_config.json") # deploy test token total_supply = 500 * 10**6 * 10**18 with open("./contracts/lua/std_token_bytecode.txt", "r") as f: payload_str = f.read()[:-1] addr = wallet.deploy_token( payload_str, "token1", total_supply, "aergo-local", privkey_pwd='1234') print("ARC1 token address: " + addr)
class MerkleBridgeCli(): """CLI tool for interacting with the AergoWallet. First choose an existing config file or create one from scratch. Once a config file is chosen, the CLI provides an interface to the AergoWallet and has the following features: - edit config file settings - transfer assets between networks - check status of transfers - check balances for each asset on each network """ def __init__(self, root_path: str = './'): """Load the pending transfers.""" # root_path is the path from which files are tracked with open(root_path + 'aergo_cli/pending_transfers.json', 'r') as file: self.pending_transfers = json.load(file) self.root_path = root_path def start(self): """Entry point of cli : load a wallet configuration file of create a new one """ f = Figlet(font='speed') print(f.renderText('Merkel Bridge Cli')) print("Welcome to the Merkle Bridge Interactive CLI.\n" "This is a tool to transfer assets across the " "Aergo Merke bridge and manage wallet " "settings (config.json)\n") while 1: questions = [ { 'type': 'list', 'name': 'YesNo', 'message': "Do you have a config.json? ", 'choices': [ { 'name': 'Yes, find it with the path', 'value': 'Y' }, { 'name': 'No, create one from scratch', 'value': 'N' }, 'Quit'] } ] answers = inquirer.prompt(questions, style=aergo_style) try: if answers['YesNo'] == 'Y': self.load_config() self.menu() elif answers['YesNo'] == 'N': self.create_config() else: return except KeyError: return def load_config(self): """Load the configuration file from path and create a wallet object.""" while 1: questions = [ { 'type': 'input', 'name': 'config_file_path', 'message': 'Path to config.json (path/to/config.json)' } ] config_file_path = inquirer.prompt( questions, style=aergo_style)['config_file_path'] try: self.wallet = AergoWallet(config_file_path) break except (IsADirectoryError, FileNotFoundError): print("Invalid path/to/config.json") except KeyError: return def menu(self): """Menu for interacting with network. Users can change settings, query balances, check pending transfers, execute cross chain transactions """ while 1: questions = [ { 'type': 'list', 'name': 'action', 'message': "What would you like to do ? ", 'choices': [ { 'name': 'Check pending transfer', 'value': 'P' }, { 'name': 'Check balances', 'value': 'B' }, { 'name': 'Initiate transfer (Lock/Burn)', 'value': 'I' }, { 'name': 'Finalize transfer (Mint/Unlock)', 'value': 'F' }, { 'name': 'Settings (Register Assets and Networks)', 'value': 'S' }, 'Back' ] } ] answers = inquirer.prompt(questions, style=aergo_style) try: if answers['action'] == 'Back': return elif answers['action'] == 'P': self.check_withdrawable_balance() elif answers['action'] == 'B': self.check_balances() elif answers['action'] == 'I': self.initiate_transfer() elif answers['action'] == 'F': self.finalize_transfer() elif answers['action'] == 'S': self.edit_settings() except (TypeError, KeyboardInterrupt, InvalidArgumentsError, TxError, InsufficientBalanceError, KeyError) as e: print('Someting went wrong, check the status of your pending ' 'transfers\nError msg: {}'.format(e)) def check_balances(self): """Iterate every registered wallet, network and asset and query balances. """ col_widths = [24, 55, 23] for wallet, info in self.wallet.config_data('wallet').items(): print('\n' + wallet + ': ' + info['addr']) print_balance_table_header() for net_name, net in self.wallet.config_data('networks').items(): for token_name, token in net['tokens'].items(): lines = [] balance, addr = self.wallet.get_balance( token_name, net_name, account_name=wallet ) if balance != 0: line = [net_name, addr, str(balance / 10**18) + ' \U0001f4b0'] lines.append(line) for peg in token['pegs']: balance, addr = self.wallet.get_balance( token_name, peg, net_name, wallet ) if balance != 0: line = [peg, addr, str(balance / 10**18) + ' \U0001f4b0'] lines.append(line) print_balance_table_lines(lines, token_name, col_widths) aer_balance, _ = self.wallet.get_balance('aergo', net_name, account_name=wallet) if aer_balance != 0: line = [net_name, 'aergo', str(aer_balance / 10**18) + ' \U0001f4b0'] print_balance_table_lines([line], 'aergo', col_widths) print(' ' + '‾' * 120) def edit_settings(self): """Menu for editing the config file of the currently loaded wallet""" while 1: questions = [ { 'type': 'list', 'name': 'action', 'message': 'What would you like to do ? ', 'choices': [ { 'name': 'Register new asset', 'value': 'A' }, { 'name': 'Register new network', 'value': 'N' }, { 'name': 'Register new bridge', 'value': 'B' }, { 'name': 'Register new keystore account', 'value': 'K' }, { 'name': 'Update validators set', 'value': 'V' }, { 'name': 'Update anchoring periode', 'value': 'UA' }, { 'name': 'Update finality', 'value': 'UF' }, 'Back', ] } ] answers = inquirer.prompt(questions, style=aergo_style) try: if answers['action'] == 'Back': return elif answers['action'] == 'A': self.register_asset() elif answers['action'] == 'N': self.register_network() elif answers['action'] == 'V': self.register_new_validators() elif answers['action'] == 'B': self.register_bridge() elif answers['action'] == 'K': self.register_key() elif answers['action'] == 'UA': self.update_t_anchor() elif answers['action'] == 'UF': self.update_t_final() except (TypeError, KeyboardInterrupt, InvalidArgumentsError) as e: print('Someting went wrong, check the status of you pending ' 'transfers\nError msg: {}'.format(e)) def create_config(self): """Create a new configuration file from scratch. This tool registers 2 networks, bridge contracts, a private key for each network and bridge validators """ new_config = {} print("Let's register 2 networks, " "validators(optional) and a private key for interacting with " "each network.") # Register 2 networks answers = prompt_new_network() net1 = answers['name'] new_config['networks'] = {net1: { 'ip': answers['ip'], 'tokens': {}, 'bridges': {} }} answers = prompt_new_network() net2 = answers['name'] new_config['networks'][net2] = { 'ip': answers['ip'], 'tokens': {}, 'bridges': {} } # Register bridge contracts on each network if promptYN('Would you like to register a bridge ?', 'Yes', 'No'): answers = prompt_new_bridge(net1, net2) new_config['networks'][net1]['bridges'] = { net2: {'addr': answers['bridge1'], 't_anchor': int(answers['t_anchor1']), 't_final': int(answers['t_final1']), 'oracle': answers['oracle1'] } } new_config['networks'][net2]['bridges'] = { net1: {'addr': answers['bridge2'], 't_anchor': int(answers['t_anchor2']), 't_final': int(answers['t_final2']), 'oracle': answers['oracle2'] } } # Register bridge validators if promptYN('Would you like to register validators ? ' '(not needed for bridge users)', 'Yes', 'No'): validators = prompt_new_validators() new_config['validators'] = validators else: new_config['validators'] = {} # Register a new private key new_config['wallet'] = {} print("Register a keystore for transacting on Aergo") name, addr, keystore_path = prompt_aergo_keystore() new_config['wallet'][name] = {"addr": addr, "keystore": keystore_path} questions = [ { 'type': 'input', 'name': 'path', 'message': 'Path to save new config file' } ] path = inquirer.prompt(questions, style=aergo_style)['path'] with open(path, "w") as f: json.dump(new_config, f, indent=4, sort_keys=True) print("Config file stored in: {}".format(os.path.abspath(path))) def register_bridge(self): """Register bridge contracts between 2 already defined networks.""" net1, net2 = self.prompt_bridge_networks() answers = prompt_new_bridge(net1, net2) self.wallet.config_data( 'networks', net1, 'bridges', net2, value={'addr': answers['bridge1'], 't_anchor': int(answers['t_anchor1']), 't_final': int(answers['t_final1']) } ) self.wallet.config_data( 'networks', net2, 'bridges', net1, value={'addr': answers['bridge2'], 't_anchor': int(answers['t_anchor2']), 't_final': int(answers['t_final2']) } ) self.wallet.save_config() def register_asset(self): """Register a new asset and it's pegs on other networks in the wallet's config. """ networks = self.get_registered_networks() name, origin, origin_addr, pegs, peg_addrs = prompt_new_asset( networks.copy()) if name == 'aergo': print('Not allowed : aergo is reserved for aer native asset') return for net in networks: try: self.wallet.config_data('networks', net, 'tokens', name) print("Asset name already used") return except KeyError: pass self.wallet.config_data('networks', origin, 'tokens', name, value={'addr': {}, 'pegs': {}}) self.wallet.config_data( 'networks', origin, 'tokens', name, 'addr', value=origin_addr) for i, peg_net in enumerate(pegs): self.wallet.config_data( 'networks', origin, 'tokens', name, 'pegs', peg_net, value=peg_addrs[i]) self.wallet.save_config() def register_network(self): """Register a new network in the wallet's config.""" answers = prompt_new_network() net = answers['name'] ip = answers['ip'] self.wallet.config_data( 'networks', net, value={'ip': ip, 'tokens': {}, 'bridges': {}} ) self.wallet.save_config() def register_key(self): """Register new key in wallet's config.""" name, addr, keystore_path = prompt_aergo_keystore() keystore_path = os.path.relpath(keystore_path, self.root_path) try: self.wallet.config_data('wallet', name) print("Account name already used") return except KeyError: pass self.wallet.config_data( 'wallet', name, value={'addr': addr, 'keystore': keystore_path}) self.wallet.save_config() def register_new_validators(self): """Register new validators in the wallet's config.""" print("WARNING: current validators will be overridden in the config " "file") validators = prompt_new_validators() self.wallet.config_data('validators', value=validators) self.wallet.save_config() def update_t_anchor(self): from_chain, to_chain = self.prompt_transfer_networks() t_anchor = prompt_number("New anchoring periode (nb of blocks) of {} " "onto {}".format(from_chain, to_chain)) self.wallet.config_data('networks', to_chain, 'bridges', from_chain, 't_anchor', value=t_anchor) self.wallet.save_config() def update_t_final(self): from_chain, to_chain = self.prompt_transfer_networks() t_final = prompt_number("New finality (nb of blocks) of {}" .format(from_chain)) self.wallet.config_data('networks', to_chain, 'bridges', from_chain, 't_final', value=t_final) self.wallet.save_config() def get_asset_address(self, asset_name, from_chain, to_chain): try: addr = self.wallet.config_data( 'networks', from_chain, 'tokens', asset_name, 'addr') return addr except KeyError: pass try: addr = self.wallet.config_data( 'networks', to_chain, 'tokens', asset_name, 'pegs', from_chain) return addr except KeyError: pass raise InvalidArgumentsError( 'asset not properly registered in config.json') def initiate_transfer(self): """Initiate a new transfer of tokens between 2 networks.""" from_chain, to_chain, from_assets, to_assets, asset_name, \ receiver = self.prompt_commun_transfer_params() amount = prompt_amount() bridge_from = self.wallet.config_data( 'networks', from_chain, 'bridges', to_chain, 'addr') bridge_to = self.wallet.config_data( 'networks', to_chain, 'bridges', from_chain, 'addr') asset_addr = self.get_asset_address(asset_name, from_chain, to_chain) summary = "Departure chain: {} ({})\n" \ "Destination chain: {} ({})\n" \ "Asset name: {} ({})\n" \ "Receiver at destination: {}\n" \ "Amount: {}\n".format(from_chain, bridge_from, to_chain, bridge_to, asset_name, asset_addr, receiver, amount) deposit_height, tx_hash = 0, "" privkey_name = self.prompt_signing_key('wallet') if asset_name in from_assets: # if transfering a native asset Lock print("Lock transfer summary:\n{}".format(summary)) if not confirm_transfer(): print('Initialize transfer canceled') return deposit_height, tx_hash = self.wallet.initiate_transfer_lock( from_chain, to_chain, asset_name, amount, receiver, privkey_name ) elif (asset_name in to_assets and from_chain in self.wallet.config_data( 'networks', to_chain, 'tokens', asset_name, 'pegs')): # if transfering a pegged asset Burn print("Burn transfer summary:\n{}".format(summary)) if not confirm_transfer(): print('Initialize transfer canceled') return deposit_height, tx_hash = self.wallet.initiate_transfer_burn( from_chain, to_chain, asset_name, amount, receiver, privkey_name ) else: print('asset not properly registered in config.json') return print("Transaction Hash : {}\nBlock Height : {}\n" .format(tx_hash, deposit_height)) pending_id = hashlib.sha256( (from_chain + to_chain + asset_name + receiver).encode('utf-8') ).digest().hex() self.pending_transfers[pending_id] = \ [from_chain, to_chain, asset_name, receiver, deposit_height] self.store_pending_transfers() def finalize_transfer_arguments(self, prompt_last_deposit=True): """Prompt the arguments needed to finalize a transfer. The arguments can be taken from the pending transfers or inputed manually by users. Returns: List of transfer arguments """ choices = [ { 'name': '{}'.format(val), 'value': val } for _, val in self.pending_transfers.items() ] choices.extend(["Custom transfer", "Back"]) questions = [ { 'type': 'list', 'name': 'transfer', 'message': 'Choose a pending transfer', 'choices': choices } ] answers = inquirer.prompt(questions, style=aergo_style) if answers['transfer'] == 'Custom transfer': from_chain, to_chain, from_assets, to_assets, asset_name, \ receiver = self.prompt_commun_transfer_params() deposit_height = 0 if prompt_last_deposit: deposit_height = prompt_deposit_height() elif answers['transfer'] == 'Back': return None else: from_chain, to_chain, asset_name, receiver, deposit_height = \ answers['transfer'] from_assets, to_assets = self.get_registered_assets(from_chain, to_chain) return (from_chain, to_chain, from_assets, to_assets, asset_name, receiver, deposit_height) def finalize_transfer(self): """Finalize a token transfer between 2 chains.""" arguments = self.finalize_transfer_arguments() if arguments is None: return from_chain, to_chain, from_assets, to_assets, asset_name, receiver, \ deposit_height = arguments bridge_from = self.wallet.config_data( 'networks', from_chain, 'bridges', to_chain, 'addr') bridge_to = self.wallet.config_data( 'networks', to_chain, 'bridges', from_chain, 'addr') asset_addr = self.get_asset_address(asset_name, from_chain, to_chain) summary = "Departure chain: {} ({})\n" \ "Destination chain: {} ({})\n" \ "Asset name: {} ({})\n" \ "Receiver at destination: {}\n" \ "Block height of lock/burn/freeze: {}\n"\ .format(from_chain, bridge_from, to_chain, bridge_to, asset_name, asset_addr, receiver, deposit_height) privkey_name = self.prompt_signing_key('wallet') if asset_name in from_assets: # if transfering a native asset mint print("Mint transfer summary:\n{}".format(summary)) if not confirm_transfer(): print('Finalize transfer canceled') return self.wallet.finalize_transfer_mint( from_chain, to_chain, asset_name, receiver, deposit_height, privkey_name ) elif (asset_name in to_assets and from_chain in self.wallet.config_data( 'networks', to_chain, 'tokens', asset_name, 'pegs') ): # if transfering a pegged asset unlock print("Unlock transfer summary:\n{}".format(summary)) if not confirm_transfer(): print('Finalize transfer canceled') return self.wallet.finalize_transfer_unlock( from_chain, to_chain, asset_name, receiver, deposit_height, privkey_name ) else: print('asset not properly registered in config.json') return # remove pending id from pending transfers pending_id = hashlib.sha256( (from_chain + to_chain + asset_name + receiver).encode('utf-8') ).digest().hex() self.pending_transfers.pop(pending_id, None) self.store_pending_transfers() def check_withdrawable_balance(self): """Check the status of cross chain transfers.""" arguments = self.finalize_transfer_arguments( prompt_last_deposit=False) if arguments is None: return from_chain, to_chain, from_assets, to_assets, asset_name, receiver, \ _ = arguments if asset_name in from_assets: # if native asset check mintable withdrawable, pending = self.wallet.get_mintable_balance( from_chain, to_chain, asset_name, account_addr=receiver ) elif (asset_name in to_assets and from_chain in self.wallet.config_data( 'networks', to_chain, 'tokens', asset_name, 'pegs') ): # if pegged asset check unlockable withdrawable, pending = self.wallet.get_unlockable_balance( from_chain, to_chain, asset_name, account_addr=receiver ) else: print('asset not properly registered in config.json') return print("Withdrawable: {} Pending: {}" .format(withdrawable / 10**18, pending / 10**18)) def prompt_commun_transfer_params(self): """Prompt the common parameters necessary for all transfers. Returns: List of transfer parameters : from_chain, to_chain, from_assets, to_assets, asset_name, receiver """ from_chain, to_chain = self.prompt_transfer_networks() from_assets, to_assets = self.get_registered_assets(from_chain, to_chain) questions = [ { 'type': 'list', 'name': 'asset_name', 'message': 'Name of asset to transfer', 'choices': from_assets + to_assets }, { 'type': 'input', 'name': 'receiver', 'message': 'Receiver of assets on other side of bridge' } ] answers = inquirer.prompt(questions, style=aergo_style) receiver = answers['receiver'] asset_name = answers['asset_name'] return from_chain, to_chain, from_assets, to_assets, asset_name, \ receiver def prompt_signing_key(self, wallet_name): """Prompt user to select a private key. Note: Keys are displayed by name and should have been registered in wallet config. """ accounts = self.wallet.config_data(wallet_name) questions = [ { 'type': 'list', 'name': 'privkey_name', 'message': 'Choose account to sign transaction : ', 'choices': [name for name in accounts] } ] answers = inquirer.prompt(questions, style=aergo_style) return answers['privkey_name'] def prompt_bridge_networks(self): """Prompt user to choose 2 networks between registered networks.""" networks = self.get_registered_networks() questions = [ { 'type': 'list', 'name': 'from_chain', 'message': 'Departure network', 'choices': networks } ] answers = inquirer.prompt(questions, style=aergo_style) from_chain = answers['from_chain'] networks.remove(from_chain) questions = [ { 'type': 'list', 'name': 'to_chain', 'message': 'Destination network', 'choices': networks } ] answers = inquirer.prompt(questions, style=aergo_style) to_chain = answers['to_chain'] return from_chain, to_chain def prompt_transfer_networks(self): """Prompt user to choose 2 networks between registered bridged networks. """ networks = self.get_registered_networks() questions = [ { 'type': 'list', 'name': 'from_chain', 'message': 'Departure network', 'choices': networks } ] answers = inquirer.prompt(questions, style=aergo_style) from_chain = answers['from_chain'] networks = [net for net in self.wallet.config_data('networks', from_chain, 'bridges')] if len(networks) == 0: raise InvalidArgumentsError('No bridge registered to this network') questions = [ { 'type': 'list', 'name': 'to_chain', 'message': 'Destination network', 'choices': networks } ] answers = inquirer.prompt(questions, style=aergo_style) to_chain = answers['to_chain'] return from_chain, to_chain def get_registered_networks(self): """Get the list of networks registered in the wallet config.""" return [net for net in self.wallet.config_data('networks')] def get_registered_assets(self, from_chain, to_chain): """Get the list of registered assets on each network.""" from_assets = [ asset for asset in self.wallet.config_data( 'networks', from_chain, 'tokens') ] to_assets = [ asset for asset in self.wallet.config_data( 'networks', to_chain, 'tokens') ] return from_assets, to_assets def store_pending_transfers(self): """Record pending transfers in json file so they can be finalized later. """ with open(self.root_path + 'aergo_cli/pending_transfers.json', 'w') as file: json.dump(self.pending_transfers, file, indent=4)
def wallet(): wallet = AergoWallet("./test_config.json") return wallet