def unlockable_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, receiver: str, ) -> Tuple[int, int]: """Check unlockable balance on Aergo.""" token_origin = self.get_asset_address(asset_name, to_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format( token_origin)) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) hera = self.connect_aergo(to_chain) w3 = self.get_web3(from_chain) account_ref = receiver + token_origin position = b'\x07' # Burns eth_trie_key = keccak( account_ref.encode('utf-8') + position.rjust(32, b'\0')) aergo_storage_key = ('_sv__unlocks-' + account_ref).encode('utf-8') return eth_to_aergo.withdrawable(bridge_from, bridge_to, w3, hera, eth_trie_key, aergo_storage_key)
def unlock(aergo_to: herapy.Aergo, receiver: str, burn_proof: AttributeDict, token_origin: str, bridge_to: str, gas_limit: int, gas_price: int) -> Tuple[str, Transaction]: """ Unlock the receiver's deposit balance on aergo_to. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin)) ap = format_proof_for_lua(burn_proof.storageProof[0].proof) balance = int.from_bytes(burn_proof.storageProof[0].value, "big") ubig_balance = {'_bignum': str(balance)} # call unlock on aergo_to with the burn proof from aergo_from tx, result = aergo_to.call_sc( bridge_to, "unlock", args=[receiver, ubig_balance, token_origin, ap], gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Unlock asset Tx commit failed : {}".format(result)) result = aergo_to.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Unlock asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) return str(tx.tx_hash), result
def burn( aergo_from: herapy.Aergo, bridge_from: str, receiver: str, value: int, token_pegged: str, gas_limit: int, gas_price: int, ) -> Tuple[int, str, Transaction]: """ Burn a minted token on a sidechain. """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver)) if not is_aergo_address(token_pegged): raise InvalidArgumentsError( "token_pegged {} must be an Aergo address".format(token_pegged)) args = (receiver[2:].lower(), {"_bignum": str(value)}, token_pegged) tx, result = aergo_from.call_sc(bridge_from, "burn", args=args, gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Burn asset Tx commit failed : {}".format(result)) # Check burn success result = aergo_from.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Burn asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) # get precise burn height tx_detail = aergo_from.get_tx(tx.tx_hash) burn_height = tx_detail.block.height return burn_height, str(tx.tx_hash), tx_detail
def build_lock_proof( aergo_from: herapy.Aergo, w3: Web3, receiver: str, bridge_from: str, bridge_to: str, bridge_to_abi: str, lock_height: int, token_origin: str, ): """ Check the last anchored root includes the lock and build a lock proof for that root """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an ethereum address".format(receiver) ) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin) ) trie_key = "_sv__locks-".encode('utf-8') + bytes.fromhex(receiver[2:]) \ + token_origin.encode('utf-8') return _build_deposit_proof( aergo_from, w3, bridge_from, bridge_to, bridge_to_abi, lock_height, trie_key )
def mintable_to_eth( self, from_chain: str, to_chain: str, asset_name: str, receiver: str, ) -> Tuple[int, int]: """Check mintable balance on Ethereum.""" token_origin = self.get_asset_address(asset_name, from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format( token_origin)) if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver)) hera = self.connect_aergo(from_chain) w3 = self.get_web3(to_chain) account_ref_eth = \ bytes.fromhex(receiver[2:]) + token_origin.encode('utf-8') position = b'\x08' # Mints eth_trie_key = keccak(account_ref_eth + position.rjust(32, b'\0')) aergo_storage_key = '_sv__locks-'.encode('utf-8') \ + bytes.fromhex(receiver[2:]) + token_origin.encode('utf-8') return aergo_to_eth.withdrawable(bridge_from, bridge_to, hera, w3, aergo_storage_key, eth_trie_key)
def unfreezable( self, from_chain: str, to_chain: str, receiver: str, ) -> Tuple[int, int]: """Check unfreezable balance on Aergo.""" token_origin = self.get_asset_address('aergo_erc20', from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) if not is_ethereum_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Ethereum address".format( token_origin)) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) hera = self.connect_aergo(to_chain) w3 = self.get_web3(from_chain) account_ref_eth = \ receiver.encode('utf-8') + bytes.fromhex(token_origin[2:]) position = b'\x05' # Locks eth_trie_key = keccak(account_ref_eth + position.rjust(32, b'\0')) aergo_storage_key = ('_sv__unfreezes-' + receiver).encode('utf-8') \ + bytes.fromhex(token_origin[2:]) return eth_to_aergo.withdrawable(bridge_from, bridge_to, w3, hera, eth_trie_key, aergo_storage_key)
def burn(w3: Web3, signer_acct, receiver: str, amount: int, bridge_from: str, bridge_from_abi: str, token_pegged: str, gas_limit: int, gas_price: int) -> Tuple[int, str, AttributeDict]: """ Burn a token that was minted on ethereum. """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) if not is_ethereum_address(token_pegged): raise InvalidArgumentsError( "token_pegged {} must be an Ethereum address".format(token_pegged)) bridge_from = Web3.toChecksumAddress(bridge_from) eth_bridge = w3.eth.contract(address=bridge_from, abi=bridge_from_abi) construct_txn = eth_bridge.functions.burn( receiver, amount, token_pegged).buildTransaction({ 'chainId': w3.eth.chainId, 'from': signer_acct.address, 'nonce': w3.eth.getTransactionCount(signer_acct.address), 'gas': gas_limit, 'gasPrice': w3.toWei(gas_price, 'gwei') }) signed = signer_acct.sign_transaction(construct_txn) tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) receipt = w3.eth.waitForTransactionReceipt(tx_hash) if receipt.status != 1: raise TxError("Burn asset Tx execution failed: {}".format(receipt)) logger.info("\u26fd Eth Gas used: %s", receipt.gasUsed) return receipt.blockNumber, tx_hash.hex(), receipt
def RequestUnfreeze(self, account_ref, context): """ Create and broadcast unfreeze transactions if conditions are met: - the receiver is a valid aergo address - the unfreezable amount covers the unfreeze fee """ if not is_aergo_address(account_ref.receiver): logger.warning("\"Invalid receiver address %s\"", account_ref.receiver) return Status(error="Receiver must be an Aergo address") # format account references for Locks and Unfreezes account_ref_eth = \ account_ref.receiver.encode('utf-8') + self.aergo_erc20_bytes position = b'\x05' # Locks eth_trie_key = keccak(account_ref_eth + position.rjust(32, b'\0')) aergo_storage_key = \ ('_sv__unfreezes-' + account_ref.receiver).encode('utf-8') \ + self.aergo_erc20_bytes # check unfreezeable is larger that the fee unfreezeable, _ = withdrawable(self.bridge_eth, self.bridge_aergo, self.web3, self.hera, eth_trie_key, aergo_storage_key) if unfreezeable <= self.unfreeze_fee: logger.warning( "\"Unfreezable (%s aer) doesn't cover fee for: %s\"", unfreezeable, account_ref.receiver) return Status( error="Aergo native to unfreeze doesnt cover the fee") # build lock proof and arguments for unfreeze lock_proof = _build_deposit_proof(self.web3, self.hera, self.bridge_eth, self.bridge_aergo, 0, eth_trie_key) ap = format_proof_for_lua(lock_proof.storageProof[0].proof) balance = int.from_bytes(lock_proof.storageProof[0].value, "big") ubig_balance = {'_bignum': str(balance)} # call unfreeze tx gas_limit = 300000 gas_price = 0 tx, result = self.hera.call_sc( self.bridge_aergo, "unfreeze", args=[account_ref.receiver, ubig_balance, ap], gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: logger.warning("\"Error: tx failed: %s\"", result.json()) return Status(error="Unfreeze service error: tx failed") # all went well logger.info("\"Unfreeze success for: %s\"", account_ref.receiver) # Test unfreeze fee used # result = self.hera.wait_tx_result(tx.tx_hash) # logger.info("\"\u26fd Unfreeze tx fee paid: %s\"", result.fee_used) return Status(txHash=str(tx.tx_hash))
def unfreeze( self, from_chain: str, to_chain: str, receiver: str = None, lock_height: int = 0, privkey_name: str = 'default', privkey_pwd: str = None, ) -> str: """ Finalize ERC20Aergo transfer to Aergo Mainnet by unfreezing (aers are already minted and freezed in the bridge contract) """ logger.info(from_chain + ' -> ' + to_chain) asset_name = 'aergo_erc20' w3 = self.get_web3(from_chain) aergo_to = self.get_aergo(to_chain, privkey_name, privkey_pwd) tx_sender = str(aergo_to.account.address) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) asset_address = self.get_asset_address(asset_name, from_chain) if receiver is None: receiver = tx_sender if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) balance = aergo_u.get_balance(receiver, 'aergo', aergo_to) logger.info("\U0001f4b0 %s balance on destination before transfer: %s", asset_name, balance / 10**18) gas_limit = 300000 if receiver != tx_sender: aer_balance = aergo_u.get_balance(tx_sender, 'aergo', aergo_to) if aer_balance < gas_limit * self.aergo_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) lock_proof = eth_to_aergo.build_lock_proof(w3, aergo_to, receiver, bridge_from, bridge_to, lock_height, asset_address) logger.info("\u2699 Built lock proof") tx_hash, _ = eth_to_aergo.unfreeze(aergo_to, receiver, lock_proof, bridge_to, gas_limit, self.aergo_gas_price) logger.info('\U0001f4a7 Unfreeze success: %s', tx_hash) # new balance on destination balance = aergo_u.get_balance(receiver, 'aergo', aergo_to) logger.info("\U0001f4b0 %s balance on destination after transfer: %s", asset_name, balance / 10**18) aergo_to.disconnect() # record mint address in file return tx_hash
def lock_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, amount: int, receiver: str, privkey_name: str = 'default', privkey_pwd: str = None, ) -> Tuple[int, str]: """ Initiate ERC20 token or Ether transfer to Aergo sidechain """ logger.info(from_chain + ' -> ' + to_chain) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) bridge_from_abi = self.load_bridge_abi(from_chain, to_chain) erc20_abi = self.load_erc20_abi(from_chain, asset_name) w3 = self.get_web3(from_chain) signer_acct = self.get_signer(w3, privkey_name, privkey_pwd) token_owner = signer_acct.address bridge_from = self.get_bridge_contract_address(from_chain, to_chain) erc20_address = self.get_asset_address(asset_name, from_chain) balance = eth_u.get_balance(token_owner, erc20_address, w3, erc20_abi) logger.info("\U0001f4b0 %s balance on origin before transfer : %s", asset_name, balance / 10**18) if balance < amount: raise InsufficientBalanceError("not enough token balance") gas_limit = 500000 # estimation eth_balance = eth_u.get_balance(token_owner, 'ether', w3) if eth_balance * 10**9 < gas_limit * self.eth_gas_price: err = "not enough eth balance to pay tx fee" raise InsufficientBalanceError(err) next_nonce, tx_hash = eth_u.increase_approval(bridge_from, erc20_address, amount, w3, erc20_abi, signer_acct, gas_limit, self.eth_gas_price) logger.info("\u2b06 Increase approval success: %s", tx_hash) lock_height, tx_hash, _ = eth_to_aergo.lock( w3, signer_acct, receiver, amount, bridge_from, bridge_from_abi, erc20_address, gas_limit, self.eth_gas_price, next_nonce) logger.info('\U0001f512 Lock success: %s', tx_hash) balance = eth_u.get_balance(token_owner, erc20_address, w3, erc20_abi) logger.info( "\U0001f4b0 remaining %s balance on origin after transfer: %s", asset_name, balance / 10**18) return lock_height, tx_hash
def mint( w3: Web3, signer_acct, receiver: str, lock_proof: herapy.obj.sc_state.SCState, token_origin: str, bridge_to: str, bridge_to_abi: str, gas_limit: int, gas_price: int ) -> Tuple[str, str, AttributeDict]: """ Mint the receiver's deposit balance on aergo_to. """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Ethereum address".format(receiver) ) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin) ) receiver = Web3.toChecksumAddress(receiver) balance = int(lock_proof.var_proofs[0].value.decode('utf-8')[1:-1]) ap = lock_proof.var_proofs[0].auditPath bitmap = lock_proof.var_proofs[0].bitmap leaf_height = lock_proof.var_proofs[0].height # call mint on ethereum with the lock proof from aergo_from eth_bridge = w3.eth.contract( address=bridge_to, abi=bridge_to_abi ) construct_txn = eth_bridge.functions.mint( receiver, balance, token_origin, ap, bitmap, leaf_height ).buildTransaction({ 'chainId': w3.eth.chainId, 'from': signer_acct.address, 'nonce': w3.eth.getTransactionCount( signer_acct.address ), 'gas': gas_limit, 'gasPrice': w3.toWei(gas_price, 'gwei') }) signed = signer_acct.sign_transaction(construct_txn) tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) receipt = w3.eth.waitForTransactionReceipt(tx_hash) if receipt.status != 1: raise TxError("Mint asset Tx execution failed: {}".format(receipt)) logger.info("\u26fd Eth Gas used: %s", receipt.gasUsed) events = eth_bridge.events.mintEvent().processReceipt(receipt) token_pegged = events[0]['args']['tokenAddress'] return token_pegged, tx_hash.hex(), receipt
def burn_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, amount: int, receiver: str, privkey_name: str = 'default', privkey_pwd: str = None, ) -> Tuple[int, str]: """ Initiate minted Standard token transfer back to aergo origin""" logger.info(from_chain + ' -> ' + to_chain) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) bridge_from_abi = self.load_bridge_abi(from_chain, to_chain) minted_erc20_abi = self.load_minted_erc20_abi(from_chain, to_chain) w3 = self.get_web3(from_chain) signer_acct = self.get_signer(w3, privkey_name, privkey_pwd) token_owner = signer_acct.address bridge_from = self.get_bridge_contract_address(from_chain, to_chain) token_pegged = self.get_asset_address(asset_name, from_chain, asset_origin_chain=to_chain) balance = eth_u.get_balance(token_owner, token_pegged, w3, minted_erc20_abi) logger.info("\U0001f4b0 %s balance on origin before transfer : %s", asset_name, balance / 10**18) if balance < amount: raise InsufficientBalanceError("not enough token balance") gas_limit = 200000 eth_balance = eth_u.get_balance(token_owner, 'ether', w3) if eth_balance * 10**9 < gas_limit * self.eth_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) burn_height, tx_hash, _ = eth_to_aergo.burn(w3, signer_acct, receiver, amount, bridge_from, bridge_from_abi, token_pegged, gas_limit, self.eth_gas_price) logger.info('\U0001f525 Burn success: %s', tx_hash) balance = eth_u.get_balance(token_owner, token_pegged, w3, minted_erc20_abi) logger.info( "\U0001f4b0 remaining %s balance on origin after transfer: %s", asset_name, balance / 10**18) return burn_height, tx_hash
def build_burn_proof( w3: Web3, aergo_to: herapy.Aergo, receiver: str, bridge_from: str, bridge_to: str, burn_height: int, token_origin: str, ): """ Check the last anchored root includes the lock and build a lock proof for that root """ if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) if not is_aergo_address(token_origin): raise InvalidArgumentsError( "token_origin {} must be an Aergo address".format(token_origin)) account_ref = (receiver + token_origin).encode('utf-8') # 'Burns is the 8th state var defined in solitity contract position = b'\x07' trie_key = keccak(account_ref + position.rjust(32, b'\0')) return _build_deposit_proof(w3, aergo_to, bridge_from, bridge_to, burn_height, trie_key)
def unlock_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, receiver: str, burn_height: int = 0, privkey_name: str = 'default', privkey_pwd: str = None, ) -> str: """ Finalize Aergo Standard token transfer back to Aergo Origin""" logger.info(from_chain + ' -> ' + to_chain) if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) w3 = self.get_web3(from_chain) aergo_to = self.get_aergo(to_chain, privkey_name, privkey_pwd) tx_sender = str(aergo_to.account.address) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) asset_address = self.get_asset_address(asset_name, to_chain) burn_proof = eth_to_aergo.build_burn_proof(w3, aergo_to, receiver, bridge_from, bridge_to, burn_height, asset_address) logger.info("\u2699 Built burn proof") balance = aergo_u.get_balance(receiver, asset_address, aergo_to) logger.info("\U0001f4b0 %s balance on destination before transfer: %s", asset_name, balance / 10**18) gas_limit = 300000 aer_balance = aergo_u.get_balance(tx_sender, 'aergo', aergo_to) if aer_balance < gas_limit * self.aergo_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) tx_hash, _ = eth_to_aergo.unlock(aergo_to, receiver, burn_proof, asset_address, bridge_to, gas_limit, self.aergo_gas_price) logger.info('\U0001f513 Unlock success: %s', tx_hash) # new balance on origin balance = aergo_u.get_balance(receiver, asset_address, aergo_to) logger.info("\U0001f4b0 %s balance on destination after transfer: %s", asset_name, balance / 10**18) return tx_hash
def get_balance_aergo(self, asset_name: str, network_name: str, asset_origin_chain: str = None, account_name: str = 'default', account_addr: str = None) -> Tuple[int, str]: """ Get account name balance of asset_name on network_name, and specify asset_origin_chain for a pegged asset query, """ if account_addr is None: account_addr = self.config_data('wallet', account_name, 'addr') if not is_aergo_address(account_addr): raise InvalidArgumentsError( "Account {} must be an Aergo address".format(account_addr)) aergo = self.connect_aergo(network_name) asset_addr = self.get_asset_address(asset_name, network_name, asset_origin_chain) balance = aergo_u.get_balance(account_addr, asset_addr, aergo) aergo.disconnect() return balance, asset_addr
def lock( aergo_from: herapy.Aergo, bridge_from: str, receiver: str, value: int, asset: str, gas_limit: int, gas_price: int, ) -> Tuple[int, str, Transaction]: """ Lock can be called to lock aer or tokens. it supports delegated transfers when tx broadcaster is not the same as the token owner """ if not is_ethereum_address(receiver): raise InvalidArgumentsError( "receiver {} must be an Ethereum address".format(receiver)) if not is_aergo_address(asset): raise InvalidArgumentsError( "asset {} must be an Aergo address".format(asset)) args = (bridge_from, {"_bignum": str(value)}, receiver[2:].lower()) tx, result = aergo_from.call_sc(asset, "transfer", args=args, amount=0, gas_limit=gas_limit, gas_price=gas_price) if result.status != herapy.CommitStatus.TX_OK: raise TxError("Lock asset Tx commit failed : {}".format(result)) # Check lock success result = aergo_from.wait_tx_result(tx.tx_hash) if result.status != herapy.TxResultStatus.SUCCESS: raise TxError("Lock asset Tx execution failed : {}".format(result)) logger.info("\u26fd Aergo gas used: %s", result.gas_used) # get precise lock height tx_detail = aergo_from.get_tx(tx.tx_hash) lock_height = tx_detail.block.height return lock_height, str(tx.tx_hash), tx_detail
def mint_to_aergo( self, from_chain: str, to_chain: str, asset_name: str, receiver: str = None, lock_height: int = 0, privkey_name: str = 'default', privkey_pwd: str = None, ) -> str: """ Finalize ERC20 token or Ether transfer to Aergo sidechain """ logger.info(from_chain + ' -> ' + to_chain) w3 = self.get_web3(from_chain) aergo_to = self.get_aergo(to_chain, privkey_name, privkey_pwd) tx_sender = str(aergo_to.account.address) bridge_to = self.get_bridge_contract_address(to_chain, from_chain) bridge_from = self.get_bridge_contract_address(from_chain, to_chain) asset_address = self.get_asset_address(asset_name, from_chain) if receiver is None: receiver = tx_sender if not is_aergo_address(receiver): raise InvalidArgumentsError( "Receiver {} must be an Aergo address".format(receiver)) save_pegged_token_address = False try: token_pegged = self.get_asset_address( asset_name, to_chain, asset_origin_chain=from_chain) balance = aergo_u.get_balance(receiver, token_pegged, aergo_to) logger.info( "\U0001f4b0 %s balance on destination before transfer: %s", asset_name, balance / 10**18) except KeyError: logger.info("Pegged token unknow by wallet") save_pegged_token_address = True gas_limit = 300000 aer_balance = aergo_u.get_balance(tx_sender, 'aergo', aergo_to) if aer_balance < gas_limit * self.aergo_gas_price: err = "not enough aer balance to pay tx fee" raise InsufficientBalanceError(err) lock_proof = eth_to_aergo.build_lock_proof(w3, aergo_to, receiver, bridge_from, bridge_to, lock_height, asset_address) logger.info("\u2699 Built lock proof") token_pegged, tx_hash, _ = eth_to_aergo.mint(aergo_to, receiver, lock_proof, asset_address, bridge_to, gas_limit, self.aergo_gas_price) logger.info('\u26cf Mint success: %s', tx_hash) # new balance on destination balance = aergo_u.get_balance(receiver, token_pegged, aergo_to) logger.info("\U0001f4b0 %s balance on destination after transfer: %s", asset_name, balance / 10**18) aergo_to.disconnect() # record mint address in file if save_pegged_token_address: logger.info("------ Store mint address in config.json -----------") self.config_data('networks', from_chain, 'tokens', asset_name, 'pegs', to_chain, value=token_pegged) self.save_config() return tx_hash