def transfer(
        self,
        destination_address,
        amount,
        asset="XLM",
        locked_until=None,
        memo_text=None,
        memo_hash=None,
        fund_transaction=True,
        from_address=None,
    ):
        """Transfer assets to another address
        :param destination_address: address of the destination.
        :type destination_address: str
        :param amount: amount, can be a floating point number with 7 numbers after the decimal point expressed as a Decimal or a string.
        :type amount: Union[str, Decimal]
        :param asset: asset to transfer (if none is specified the default 'XLM' is used),
        if you wish to specify an asset it should be in format 'assetcode:issuer'. Where issuer is the address of the
        issuer of the asset.
        :type asset: str
        :param locked_until: optional epoch timestamp indicating until when the tokens  should be locked.
        :type locked_until: float
        :param text_memo: optional memo text to add to the transaction, a string encoded using either ASCII or UTF-8, up to 28-bytes long
        :type: Union[str, bytes]
        :param memo_hash: optional memo hash to add to the transaction, A 32 byte hash
        :type: Union[str, bytes]
        :param fund_transaction: use the threefoldfoundation transaction funding service
        :type: fund_transaction: bool
        :param from_address: Use a different address to send the tokens from, useful in multisig use cases.
        :type from_address: str
        """
        issuer = None
        self._log_info(f"Sending {amount} {asset} to {destination_address}")
        if asset != "XLM":
            assetStr = asset.split(":")
            if len(assetStr) != 2:
                raise Exception("Wrong asset format")
            asset = assetStr[0]
            issuer = assetStr[1]

        if locked_until is not None:
            return self._transfer_locked_tokens(
                destination_address,
                amount,
                asset,
                issuer,
                locked_until,
                memo_text=memo_text,
                memo_hash=memo_hash,
                fund_transaction=fund_transaction,
                from_address=from_address)

        horizon_server = self._get_horizon_server()

        base_fee = horizon_server.fetch_base_fee()
        if from_address:
            source_account = horizon_server.load_account(from_address)
        else:
            source_account = self.load_account()

        transaction_builder = TransactionBuilder(
            source_account=source_account,
            network_passphrase=_NETWORK_PASSPHRASES[str(self.network)],
            base_fee=base_fee)
        transaction_builder.append_payment_op(
            destination=destination_address,
            amount=amount,
            asset_code=asset,
            asset_issuer=issuer,
            source=source_account.account_id,
        )
        transaction_builder.set_timeout(30)
        if memo_text is not None:
            transaction_builder.add_text_memo(memo_text)
        if memo_hash is not None:
            transaction_builder.add_hash_memo(memo_hash)

        transaction = transaction_builder.build()
        transaction = transaction.to_xdr()

        if asset in _NETWORK_KNOWN_TRUSTS[str(self.network)]:
            if fund_transaction:
                transaction = self._fund_transaction(transaction=transaction)
                transaction = transaction["transaction_xdr"]

        transaction = TransactionEnvelope.from_xdr(
            transaction, _NETWORK_PASSPHRASES[str(self.network)])

        my_keypair = Keypair.from_secret(self.secret)
        transaction.sign(my_keypair)

        try:
            response = horizon_server.submit_transaction(transaction)
            tx_hash = response["hash"]
            self._log_info("Transaction hash: {}".format(tx_hash))
            return tx_hash
        except BadRequestError as e:
            result_codes = e.extras.get("result_codes")
            operations = result_codes.get("operations")
            if operations is not None:
                for op in operations:
                    if op == "op_underfunded":
                        raise e
                    # if op_bad_auth is returned then we assume the transaction needs more signatures
                    # so we return the transaction as xdr
                    elif op == "op_bad_auth":
                        self._log_info(
                            "Transaction might need additional signatures in order to send"
                        )
                        return transaction.to_xdr()
            raise e
Example #2
0
    def transfer(self,
                 destination_address,
                 amount,
                 asset="XLM",
                 locked_until=None,
                 memo_text=None,
                 memo_hash=None):
        """Transfer assets to another address
        :param destination_address: address of the destination.
        :type destination_address: str
        :param amount: amount, can be a floating point number with 7 numbers after the decimal point expressed as a string.
        :type amount: str
        :param asset: asset to transfer (if none is specified the default 'XLM' is used),
        if you wish to specify an asset it should be in format 'assetcode:issuer'. Where issuer is the address of the
        issuer of the asset.
        :type asset: str
        :param locked_until: optional epoch timestamp indicating until when the tokens  should be locked.
        :type locked_until: float
        :param text_memo: optional memo text to add to the transaction, a string encoded using either ASCII or UTF-8, up to 28-bytes long
        :type: Union[str, bytes]
        :param memo_hash: optional memo hash to add to the transaction, A 32 byte hash
        :type: Union[str, bytes]
        """
        issuer = None
        self._log_info("Sending {} {} to {}".format(amount, asset,
                                                    destination_address))
        if asset != "XLM":
            assetStr = asset.split(":")
            if len(assetStr) != 2:
                raise Exception("Wrong asset format")
            asset = assetStr[0]
            issuer = assetStr[1]

        if locked_until is not None:
            return self._transfer_locked_tokens(destination_address, amount,
                                                asset, issuer, locked_until)

        server = self._get_horizon_server()
        source_keypair = Keypair.from_secret(self.secret)
        source_public_key = source_keypair.public_key
        source_account = server.load_account(source_public_key)

        base_fee = server.fetch_base_fee()

        transaction_builder = TransactionBuilder(
            source_account=source_account,
            network_passphrase=_NETWORK_PASSPHRASES[str(self.network)],
            base_fee=base_fee)
        transaction_builder.append_payment_op(destination=destination_address,
                                              amount=str(amount),
                                              asset_code=asset,
                                              asset_issuer=issuer)
        transaction_builder.set_timeout(30)
        if memo_text is not None:
            transaction_builder.add_text_memo(memo_text)
        if memo_hash is not None:
            transaction_builder.add_hash_memo(memo_hash)

        transaction = transaction_builder.build()

        transaction.sign(source_keypair)

        try:
            response = server.submit_transaction(transaction)
            tx_hash = response["hash"]
            self._log_info("Transaction hash: {}".format(tx_hash))
            return tx_hash
        except BadRequestError as e:
            self._log_debug(e)
            raise e