Esempio n. 1
0
def build_burn_proof(
    aergo_from: herapy.Aergo,
    w3: Web3,
    receiver: str,
    bridge_from: str,
    bridge_to: str,
    bridge_to_abi: str,
    burn_height: int,
    token_origin: str,
):
    """ Check the last anchored root includes the burn and build
    a burn proof for that root
    """
    if not is_ethereum_address(receiver):
        raise InvalidArgumentsError(
            "Receiver {} must be an Ethereum address".format(receiver)
        )
    if not is_ethereum_address(token_origin):
        raise InvalidArgumentsError(
            "token_origin {} must be an Ethereum address".format(token_origin)
        )
    trie_key = "_sv__burns-".encode('utf-8') + bytes.fromhex(receiver[2:]) \
        + bytes.fromhex(token_origin[2:])
    return _build_deposit_proof(
        aergo_from, w3, bridge_from, bridge_to, bridge_to_abi, burn_height,
        trie_key
    )
Esempio n. 2
0
 def unlockable_to_eth(
     self,
     from_chain: str,
     to_chain: str,
     asset_name: str,
     receiver: str,
 ) -> Tuple[int, int]:
     """Check unlockable balance on Ethereum."""
     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_ethereum_address(token_origin):
         raise InvalidArgumentsError(
             "token_origin {} must be an Ethereum 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 = receiver[2:] + token_origin[2:]
     position = b'\x06'  # Unlocks
     eth_trie_key = keccak(
         bytes.fromhex(account_ref) + position.rjust(32, b'\0'))
     aergo_storage_key = '_sv__burns-'.encode('utf-8') \
         + bytes.fromhex(account_ref)
     return aergo_to_eth.withdrawable(bridge_from, bridge_to, hera, w3,
                                      aergo_storage_key, eth_trie_key)
Esempio n. 3
0
def unlock(
    w3: Web3,
    signer_acct,
    receiver: str,
    burn_proof: herapy.obj.sc_state.SCState,
    token_origin: str,
    bridge_to: str,
    bridge_to_abi: str,
    gas_limit: int,
    gas_price: int
) -> Tuple[str, AttributeDict]:
    """ Unlock the receiver's burnt balance on aergo_to. """
    if not is_ethereum_address(receiver):
        raise InvalidArgumentsError(
            "Receiver {} must be an Ethereum address".format(receiver)
        )
    if not is_ethereum_address(token_origin):
        raise InvalidArgumentsError(
            "token_origin {} must be an Ethereum address".format(token_origin)
        )
    receiver = Web3.toChecksumAddress(receiver)
    balance = int(burn_proof.var_proofs[0].value.decode('utf-8')[1:-1])
    ap = burn_proof.var_proofs[0].auditPath
    bitmap = burn_proof.var_proofs[0].bitmap
    leaf_height = burn_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.unlock(
        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("Unlock asset Tx execution failed: {}".format(receipt))
    logger.info("\u26fd Eth Gas used: %s", receipt.gasUsed)
    return tx_hash.hex(), receipt
Esempio n. 4
0
def mint(aergo_to: herapy.Aergo, receiver: str, lock_proof: AttributeDict,
         token_origin: str, bridge_to: str, gas_limit: int,
         gas_price: int) -> Tuple[str, 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_ethereum_address(token_origin):
        raise InvalidArgumentsError(
            "token_origin {} must be an Ethereum address".format(token_origin))
    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 unlock on aergo_to with the burn proof from aergo_from
    tx, result = aergo_to.call_sc(
        bridge_to,
        "mint",
        args=[receiver, ubig_balance, token_origin[2:].lower(), ap],
        gas_limit=gas_limit,
        gas_price=gas_price)
    if result.status != herapy.CommitStatus.TX_OK:
        raise TxError("Mint asset Tx commit failed : {}".format(result))

    result = aergo_to.wait_tx_result(tx.tx_hash)
    if result.status != herapy.TxResultStatus.SUCCESS:
        raise TxError("Mint asset Tx execution failed : {}".format(result))
    logger.info("\u26fd Aergo gas used: %s", result.gas_used)
    token_pegged = json.loads(result.detail)[0]
    return token_pegged, str(tx.tx_hash), result
Esempio n. 5
0
 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)
Esempio n. 6
0
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
Esempio n. 7
0
    def get_balance_eth(
        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.get_eth_wallet_address(account_name)
        if not is_ethereum_address(account_addr):
            raise InvalidArgumentsError(
                "Account {} must be an Ethereum address".format(account_addr))
        w3 = self.get_web3(network_name)
        asset_addr = self.get_asset_address(asset_name, network_name,
                                            asset_origin_chain)
        if asset_origin_chain is None:
            if asset_addr == 'ether':
                balance = eth_u.get_balance(account_addr, asset_addr, w3)
                return balance, asset_addr
            abi = self.load_erc20_abi(network_name, asset_name)
        else:
            abi = self.load_minted_erc20_abi(network_name, asset_origin_chain)

        balance = eth_u.get_balance(account_addr, asset_addr, w3, abi)
        return balance, asset_addr
Esempio n. 8
0
def freeze(
    aergo_from: herapy.Aergo,
    bridge_from: str,
    receiver: str,
    value: int,
    gas_limit: int,
    gas_price: int,
) -> Tuple[int, str, Transaction]:
    """ Freeze aergo native """
    if not is_ethereum_address(receiver):
        raise InvalidArgumentsError(
            "Receiver {} must be an Ethereum address".format(receiver))
    args = (receiver[2:].lower(), {"_bignum": str(value)})
    tx, result = aergo_from.call_sc(bridge_from,
                                    "freeze",
                                    amount=value,
                                    args=args,
                                    gas_limit=gas_limit,
                                    gas_price=gas_price)

    if result.status != herapy.CommitStatus.TX_OK:
        raise TxError("Freeze asset Tx commit failed : {}".format(result))

    # Check freeze success
    result = aergo_from.wait_tx_result(tx.tx_hash)
    if result.status != herapy.TxResultStatus.SUCCESS:
        raise TxError("Freeze Aer 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)
    freeze_height = tx_detail.block.height
    return freeze_height, str(tx.tx_hash), tx_detail
Esempio n. 9
0
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
Esempio n. 10
0
    def unlock_to_eth(
        self,
        from_chain: str,
        to_chain: str,
        asset_name: str,
        receiver: str = None,
        burn_height: int = 0,
        privkey_name: str = 'default',
        privkey_pwd: str = None,
    ) -> str:
        """ Finalize ERC20 or Eth transfer back to Ethereum origin """
        logger.info(from_chain + ' -> ' + to_chain)
        bridge_to_abi = self.load_bridge_abi(to_chain, from_chain)
        erc20_abi = self.load_erc20_abi(to_chain, asset_name)
        aergo_from = self.connect_aergo(from_chain)
        # get ethereum tx signer
        w3 = self.get_web3(to_chain)
        signer_acct = self.get_signer(w3, privkey_name, privkey_pwd)
        tx_sender = signer_acct.address

        if receiver is None:
            receiver = tx_sender
        if not is_ethereum_address(receiver):
            raise InvalidArgumentsError(
                "receiver {} must be an Ethereum address".format(receiver))

        bridge_from = self.get_bridge_contract_address(from_chain, to_chain)
        bridge_to = self.get_bridge_contract_address(to_chain, from_chain)
        asset_address = self.get_asset_address(asset_name, to_chain)
        balance = eth_u.get_balance(receiver, asset_address, w3, erc20_abi)
        logger.info(
            "\U0001f4b0 %s balance on destination before transfer : %s",
            asset_name, balance / 10**18)

        gas_limit = 200000
        eth_balance = eth_u.get_balance(tx_sender, '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_proof = aergo_to_eth.build_burn_proof(aergo_from, w3, receiver,
                                                   bridge_from, bridge_to,
                                                   bridge_to_abi, burn_height,
                                                   asset_address)
        logger.info("\u2699 Built burn proof")

        tx_hash, _ = aergo_to_eth.unlock(w3, signer_acct, receiver, burn_proof,
                                         asset_address, bridge_to,
                                         bridge_to_abi, gas_limit,
                                         self.eth_gas_price)
        logger.info('\U0001f513 Unlock success: %s', tx_hash)

        # new balance on origin
        balance = eth_u.get_balance(receiver, asset_address, w3, erc20_abi)
        logger.info("\U0001f4b0 %s balance on destination after transfer : %s",
                    asset_name, balance / 10**18)

        aergo_from.disconnect()
        return tx_hash
Esempio n. 11
0
    def burn_to_eth(
        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 token transfer back to ethereum origin"""
        logger.info(from_chain + ' -> ' + to_chain)
        if not is_ethereum_address(receiver):
            raise InvalidArgumentsError(
                "receiver {} must be an Ethereum address".format(receiver))
        aergo_from = self.get_aergo(from_chain, privkey_name, privkey_pwd)
        sender = str(aergo_from.account.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)

        gas_limit = 300000
        balance = aergo_u.get_balance(sender, token_pegged, aergo_from)
        if balance < amount:
            raise InsufficientBalanceError("not enough token balance")
        logger.info("\U0001f4b0 %s balance on origin before transfer: %s",
                    asset_name, balance / 10**18)

        aer_balance = aergo_u.get_balance(sender, 'aergo', aergo_from)
        if aer_balance < gas_limit * self.aergo_gas_price:
            err = "not enough aer balance to pay tx fee"
            raise InsufficientBalanceError(err)

        burn_height, tx_hash, _ = aergo_to_eth.burn(aergo_from, bridge_from,
                                                    receiver, amount,
                                                    token_pegged, gas_limit,
                                                    self.aergo_gas_price)
        logger.info('\U0001f525 Burn success: %s', tx_hash)

        # remaining balance on origin : aer or asset
        balance = aergo_u.get_balance(sender, token_pegged, aergo_from)
        logger.info(
            "\U0001f4b0 remaining %s balance on origin after transfer: %s",
            asset_name, balance / 10**18)

        aergo_from.disconnect()
        return burn_height, tx_hash
Esempio n. 12
0
    def lock_to_eth(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 Aergo Standard Token transfer to Ethereum sidechain"""
        logger.info(from_chain + ' -> ' + to_chain)
        if not is_ethereum_address(receiver):
            raise InvalidArgumentsError(
                "receiver {} must be an Ethereum address".format(receiver))
        if asset_name == 'aergo':
            raise InvalidArgumentsError(
                'aer cannot be locked on Aergo, must be frozen')
        aergo_from = self.get_aergo(from_chain, privkey_name, privkey_pwd)
        sender = str(aergo_from.account.address)
        bridge_from = self.get_bridge_contract_address(from_chain, to_chain)
        asset_address = self.get_asset_address(asset_name, from_chain)

        gas_limit = 300000
        balance = aergo_u.get_balance(sender, asset_address, aergo_from)
        if balance < amount:
            raise InsufficientBalanceError("not enough token balance")
        logger.info("\U0001f4b0 %s balance on origin before transfer: %s",
                    asset_name, balance / 10**18)

        aer_balance = aergo_u.get_balance(sender, 'aergo', aergo_from)
        if aer_balance < gas_limit * self.aergo_gas_price:
            err = "not enough aer balance to pay tx fee"
            raise InsufficientBalanceError(err)

        lock_height, tx_hash, _ = aergo_to_eth.lock(aergo_from, bridge_from,
                                                    receiver, amount,
                                                    asset_address, gas_limit,
                                                    self.aergo_gas_price)
        logger.info('\U0001f512 Lock success: %s', tx_hash)

        # remaining balance on origin : aer or asset
        balance = aergo_u.get_balance(sender, asset_address, aergo_from)
        logger.info(
            "\U0001f4b0 remaining %s balance on origin after transfer: %s",
            asset_name, balance / 10**18)

        aergo_from.disconnect()
        return lock_height, tx_hash
Esempio n. 13
0
def get_balance(account_addr: str,
                asset_addr: str,
                w3: Web3,
                erc20_abi: str = None) -> int:
    if not is_ethereum_address(account_addr):
        raise InvalidArgumentsError(
            "Account {} must be an Ethereum address".format(account_addr))
    account_addr = Web3.toChecksumAddress(account_addr)
    balance = 0
    if asset_addr == "ether":
        balance = w3.eth.getBalance(account_addr)
    else:
        asset_addr = Web3.toChecksumAddress(asset_addr)
        if erc20_abi is None:
            raise InvalidArgumentsError("Provide token abi to query balance")
        token_contract = w3.eth.contract(address=asset_addr, abi=erc20_abi)
        try:
            balance = token_contract.functions.balanceOf(account_addr).call()
        except BadFunctionCallOutput as e:
            raise InvalidArgumentsError(e, asset_addr)
    return balance
Esempio n. 14
0
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
Esempio n. 15
0
    def freeze(
        self,
        from_chain: str,
        to_chain: str,
        amount: int,
        receiver: str,
        privkey_name: str = 'default',
        privkey_pwd: str = None,
    ) -> Tuple[int, str]:
        """ Initiate Aer transfer back to Ethereum AergoERC20 sidechain"""
        logger.info(from_chain + ' -> ' + to_chain)
        if not is_ethereum_address(receiver):
            raise InvalidArgumentsError(
                "receiver {} must be an Ethereum address".format(receiver))
        asset_name = 'aergo_erc20'
        aergo_from = self.get_aergo(from_chain, privkey_name, privkey_pwd)
        sender = str(aergo_from.account.address)
        bridge_from = self.get_bridge_contract_address(from_chain, to_chain)

        gas_limit = 300000
        balance = aergo_u.get_balance(sender, 'aergo', aergo_from)
        if balance < amount + gas_limit * self.aergo_gas_price:
            raise InsufficientBalanceError("not enough token balance")
        logger.info("\U0001f4b0 %s balance on origin before transfer: %s",
                    asset_name, balance / 10**18)

        freeze_height, tx_hash, _ = aergo_to_eth.freeze(
            aergo_from, bridge_from, receiver, amount, gas_limit,
            self.aergo_gas_price)
        logger.info('\u2744 Freeze success: %s', tx_hash)

        # remaining balance on origin : aer or asset
        balance = aergo_u.get_balance(sender, 'aergo', aergo_from)
        logger.info(
            "\U0001f4b0 remaining %s balance on origin after transfer: %s",
            asset_name, balance / 10**18)

        aergo_from.disconnect()
        return freeze_height, tx_hash
Esempio n. 16
0
def build_lock_proof(
    w3: Web3,
    aergo_to: herapy.Aergo,
    receiver: str,
    bridge_from: str,
    bridge_to: 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_aergo_address(receiver):
        raise InvalidArgumentsError(
            "Receiver {} must be an Aergo address".format(receiver))
    if not is_ethereum_address(token_origin):
        raise InvalidArgumentsError(
            "token_origin {} must be an Ethereum address".format(token_origin))
    account_ref = receiver.encode('utf-8') + bytes.fromhex(token_origin[2:])
    # 'Locks is the 6th state var defined in solitity contract
    position = b'\x05'
    trie_key = keccak(account_ref + position.rjust(32, b'\0'))
    return _build_deposit_proof(w3, aergo_to, bridge_from, bridge_to,
                                lock_height, trie_key)
Esempio n. 17
0
    def mint_to_eth(
        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,
    ) -> Tuple[str, str]:
        """ Finalize Aergo Standard Token transfer to Ethereum sidechain
        NOTE anybody can mint so sender is not necessary.
        The amount to mint is the difference between total deposit and
        already minted amount.
        Bridge tempo is taken from config_data
        """
        logger.info(from_chain + ' -> ' + to_chain)
        bridge_to_abi = self.load_bridge_abi(to_chain, from_chain)
        minted_erc20_abi = self.load_minted_erc20_abi(to_chain, from_chain)
        aergo_from = self.connect_aergo(from_chain)
        w3 = self.get_web3(to_chain)
        signer_acct = self.get_signer(w3, privkey_name, privkey_pwd)
        tx_sender = signer_acct.address
        if receiver is None:
            receiver = tx_sender
        if not is_ethereum_address(receiver):
            raise InvalidArgumentsError(
                "receiver {} must be an Ethereum address".format(receiver))
        bridge_from = self.get_bridge_contract_address(from_chain, to_chain)
        bridge_to = self.get_bridge_contract_address(to_chain, from_chain)
        asset_address = self.get_asset_address(asset_name, from_chain)

        save_pegged_token_address = False
        try:
            token_pegged = self.get_asset_address(
                asset_name, to_chain, asset_origin_chain=from_chain)
            balance = eth_u.get_balance(receiver, token_pegged, w3,
                                        minted_erc20_abi)
            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 = 2000000
        eth_balance = eth_u.get_balance(tx_sender, '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)

        lock_proof = aergo_to_eth.build_lock_proof(aergo_from, w3, receiver,
                                                   bridge_from, bridge_to,
                                                   bridge_to_abi, lock_height,
                                                   asset_address)
        logger.info("\u2699 Built lock proof")

        token_pegged, tx_hash, _ = aergo_to_eth.mint(w3, signer_acct, receiver,
                                                     lock_proof, asset_address,
                                                     bridge_to, bridge_to_abi,
                                                     gas_limit,
                                                     self.eth_gas_price)
        logger.info('\u26cf Mint success: %s', tx_hash)

        # new balance on sidechain
        balance = eth_u.get_balance(receiver, token_pegged, w3,
                                    minted_erc20_abi)
        logger.info("\U0001f4b0 %s balance on destination after transfer : %s",
                    asset_name, balance / 10**18)

        aergo_from.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 token_pegged, tx_hash