Ejemplo n.º 1
0
def sign_transaction_from_xdr(wallet_file,
                              transaction_xdr,
                              test_mode=True,
                              trezor_mode=False,
                              just_sign=False,
                              vzero=False):
    network_settings = get_network_settings(test_mode=test_mode)
    if not trezor_mode:
        transaction = TransactionEnvelope.from_xdr(
            transaction_xdr,
            network_passphrase=network_settings.get("network_passphrase"))
        confirmation = show_transaction_data(transaction)
        if not confirmation:
            return
        (private_key, public_key) = load_wallet(wallet_file=wallet_file)
        k = Keypair.from_secret(secret=private_key)
        transaction.sign(k)
    else:
        transaction = TransactionEnvelope.from_xdr(
            transaction_xdr,
            network_passphrase=network_settings.get("network_passphrase"))
        confirmation = show_transaction_data(transaction)
        if not confirmation:
            return
        public_key = get_trezor_public_key()
        k = Keypair.from_public_key(public_key=public_key)
        transaction = sign_trezor_transaction(
            transaction=transaction,
            public_key=k,
            network_passphrase=network_settings.get("network_passphrase"))
    if just_sign:
        print("TX SIGNED DATA:\n{}".format(transaction.to_xdr()))
    else:
        broadcast_tx(transaction=transaction, test_mode=test_mode)
Ejemplo n.º 2
0
    def _append_fee_payment(self, txe: stellar_sdk.TransactionEnvelope) -> dict:

        txe.transaction.operations.append(
            self._create_fee_payment_operation(
                txe.transaction.operations[0].source, txe.transaction.operations[0].asset
            )
        )

        txe.transaction.fee = _MAX_FEE

        funding_wallet = self._get_slave_fundingwallet()

        source_public_kp = stellar_sdk.Keypair.from_public_key(funding_wallet.address)
        source_signing_kp = stellar_sdk.Keypair.from_secret(funding_wallet.secret)

        source_account = funding_wallet.load_account()
        source_account.increment_sequence_number()
        txe.transaction.source = source_public_kp

        txe.transaction.sequence = source_account.sequence
        txe.sign(source_signing_kp)

        transaction_xdr = txe.to_xdr()

        fund_if_needed(funding_wallet.instance_name)

        return {"transaction_xdr": transaction_xdr}
Ejemplo n.º 3
0
async def send_operation_details(destination, envelope: str, network_type):
    """
    Send information to sender on operations inside transaction
    """
    data = TransactionEnvelope.from_xdr(envelope, network_type)
    operations = data.transaction.operations

    count = 1
    for op in operations:
        op_info = Embed(title=f'Operation No.{count}',
                        colour=Colour.green())
        if isinstance(op, Payment):
            print("++++++++++++++++++++++")
            op_info.add_field(name=f'Payment To:',
                              value=f'```{op.destination}```',
                              inline=False)
            if isinstance(op.asset, Asset):
                op_info.add_field(name=f'Payment Value',
                                  value=f'`{op.amount} {op.asset.code}`')

        elif isinstance(op, CreateAccount):
            op_info.add_field(name=f'Create Account for',
                              value=f'{op.destination}')
            op_info.add_field(name=f'Starting Balance',
                              value=f'{op.starting_balance}')

        await destination.send(embed=op_info)
        count += 1
Ejemplo n.º 4
0
def test_deposit_authenticated_success(client, acc1_usd_deposit_transaction_factory):
    """`GET /deposit` succeeds with the SEP 10 authentication flow."""
    deposit = acc1_usd_deposit_transaction_factory()

    # SEP 10.
    response = client.get(f"/auth?account={client_address}", follow=True)
    content = json.loads(response.content)

    envelope_xdr = content["transaction"]
    envelope_object = TransactionEnvelope.from_xdr(
        envelope_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE
    )
    client_signing_key = Keypair.from_secret(client_seed)
    envelope_object.sign(client_signing_key)
    client_signed_envelope_xdr = envelope_object.to_xdr()

    response = client.post(
        "/auth",
        data={"transaction": client_signed_envelope_xdr},
        content_type="application/json",
    )
    content = json.loads(response.content)
    encoded_jwt = content["token"]
    assert encoded_jwt

    header = {"HTTP_AUTHORIZATION": f"Bearer {encoded_jwt}"}
    response = client.post(
        DEPOSIT_PATH,
        {"asset_code": "USD", "account": deposit.stellar_account},
        follow=True,
        **header,
    )
    content = json.loads(response.content)
    assert response.status_code == 200
    assert content["type"] == "interactive_customer_info_needed"
Ejemplo n.º 5
0
def test_post_success_account_doesnt_exist(mock_load_account, client):
    kp = Keypair.random()
    challenge_xdr = build_challenge_transaction(
        server_secret=settings.SIGNING_SEED,
        client_account_id=kp.public_key,
        home_domain=settings.SEP10_HOME_DOMAINS[0],
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
    )
    envelope = TransactionEnvelope.from_xdr(
        challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE)
    envelope.sign(kp)
    signed_challenge_xdr = envelope.to_xdr()
    mock_load_account.side_effect = NotFoundError(MagicMock())

    response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr})

    content = response.json()
    assert response.status_code == 200, json.dumps(content, indent=2)
    jwt_contents = jwt.decode(content["token"],
                              settings.SERVER_JWT_KEY,
                              algorithms=["HS256"])
    iat = jwt_contents.pop("iat")
    exp = jwt_contents.pop("exp")
    assert exp - iat == 24 * 60 * 60
    assert jwt_contents == {
        "iss": os.path.join(settings.HOST_URL, "auth"),
        "sub": kp.public_key,
        "jti": envelope.hash().hex(),
        "client_domain": None,
    }
Ejemplo n.º 6
0
def test_post_fails_account_doesnt_exist_no_client_attribution_signature(
        mock_load_account, client):
    kp = Keypair.random()
    client_domain_kp = Keypair.random()
    challenge_xdr = build_challenge_transaction(
        server_secret=settings.SIGNING_SEED,
        client_account_id=kp.public_key,
        home_domain=settings.SEP10_HOME_DOMAINS[0],
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        client_domain="test.com",
        client_signing_key=client_domain_kp.public_key,
    )
    envelope = TransactionEnvelope.from_xdr(
        challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE)
    envelope.sign(kp)
    signed_challenge_xdr = envelope.to_xdr()
    mock_load_account.side_effect = NotFoundError(MagicMock())

    response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr})

    content = response.json()
    assert response.status_code == 400, json.dumps(content, indent=2)
    assert (
        content["error"] == "error while validating challenge: "
        "Transaction not signed by the source account of the 'client_domain' ManageData operation"
    )
Ejemplo n.º 7
0
def create_stellar_deposit(transaction: Transaction) -> bool:
    """
    Performs final status and signature checks before calling submit_stellar_deposit().
    Returns true on successful submission, false otherwise. `transaction` will be placed
    in the error status if submission fails or if it is a multisig transaction and is not
    signed by the channel account.
    """
    if transaction.status not in [
            Transaction.STATUS.pending_anchor,
            Transaction.STATUS.pending_trust,
    ]:
        raise ValueError(
            f"unexpected transaction status {transaction.status} for "
            "create_stellar_deposit", )
    elif transaction.amount_in is None or transaction.amount_fee is None:
        transaction.status = Transaction.STATUS.error
        transaction.status_message = (
            "`amount_in` and `amount_fee` must be populated, skipping transaction"
        )
        transaction.save()
        raise ValueError(transaction.status_message)

    # if the distribution account's master signer's weight is great or equal to the its
    # medium threshold, verify the transaction is signed by it's channel account
    master_signer = None
    if transaction.asset.distribution_account_master_signer:
        master_signer = transaction.asset.distribution_account_master_signer
    thresholds = transaction.asset.distribution_account_thresholds
    if not master_signer or master_signer["weight"] < thresholds[
            "med_threshold"]:
        envelope = TransactionEnvelope.from_xdr(
            transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE)
        try:
            _verify_te_signed_by_account_id(envelope,
                                            transaction.channel_account)
        except InvalidSep10ChallengeError:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = gettext(
                "Multisig transaction's envelope was not signed by channel account"
            )
            transaction.save()
            return False
    # otherwise, create the envelope and sign it with the distribution account's secret
    else:
        distribution_acc, _ = get_account_obj(
            Keypair.from_public_key(transaction.asset.distribution_account))
        envelope = create_transaction_envelope(transaction, distribution_acc)
        envelope.sign(transaction.asset.distribution_seed)

    try:
        submit_stellar_deposit(transaction, envelope)
    except (RuntimeError, BaseHorizonError) as e:
        transaction.status_message = f"{e.__class__.__name__}: {e.message}"
        transaction.status = Transaction.STATUS.error
        transaction.save()
        logger.error(transaction.status_message)
        return False
    else:
        return True
Ejemplo n.º 8
0
def submit_transaction(transaction_xdr, test_mode=True, vzero=False):
    network_settings = get_network_settings(test_mode=test_mode)
    transaction = TransactionEnvelope.from_xdr(
        transaction_xdr,
        network_passphrase=network_settings.get("network_passphrase"))
    confirmation = show_transaction_data_before_submit(transaction)
    if not confirmation:
        return
    else:
        broadcast_tx(transaction=transaction, test_mode=test_mode)
Ejemplo n.º 9
0
    def set_unlock_transaction(self, unlock_transaction):
        """
        Adds a xdr encoded unlocktransaction
        :param unlock_transaction: xdr encoded unlocktransactionaddress of the destination.
        :type destination_address: str
        """
        txe = TransactionEnvelope.from_xdr(unlock_transaction, _NETWORK_PASSPHRASES[str(self.network)])
        tx_hash = txe.hash()
        unlock_hash = strkey.StrKey.encode_pre_auth_tx(tx_hash)

        self._create_unlockhash_transaction(unlock_hash=unlock_hash, transaction_xdr=txe.to_xdr())
Ejemplo n.º 10
0
def test_xdr(parameters, result):
    from stellar_sdk import TransactionEnvelope

    envelope = TransactionEnvelope.from_xdr(parameters["xdr"],
                                            parameters["network_passphrase"])
    tx, operations = stellar.from_envelope(envelope)
    tx.address_n = parse_path(parameters["address_n"])
    tx_expected, operations_expected = parameters_to_proto(parameters)
    assert tx == tx_expected
    for expected, actual in zip(operations_expected, operations):
        assert expected == actual
Ejemplo n.º 11
0
    def _set_unlock_conditions(self):
        for unlockhash in self.unlockhashes:
            unlockhash_tx = self._get_unlockhash_transaction(
                unlockhash=unlockhash)
            if unlockhash_tx is None:
                return

            txe = TransactionEnvelope.from_xdr(
                unlockhash_tx["transaction_xdr"], self.network_passphrase)
            tx = txe.transaction
            if tx.time_bounds is not None:
                self.unlock_time = tx.time_bounds.min_time
Ejemplo n.º 12
0
    def _set_unlock_conditions(self):
        unlockhash_tx_model = j.threebot.packages.threefoldfoundation.unlock_service.bcdb.model_get(
            url="threefoldfoundation.unlock_service.unlockhash_transaction")
        for unlockhash in self.unlockhashes:
            unlockhash_tx = unlockhash_tx_model.find(unlockhash=unlockhash)
            if len(unlockhash_tx) is 0:
                return

            unlockhash_tx = unlockhash_tx[0]
            txe = TransactionEnvelope.from_xdr(unlockhash_tx.transaction_xdr,
                                               self.network_passphrase)
            tx = txe.transaction
            if tx.time_bounds is not None:
                self.unlock_time = tx.time_bounds.min_time
Ejemplo n.º 13
0
def submit_stellar_deposit(transaction, multisig=False) -> bool:
    transaction.status = Transaction.STATUS.pending_stellar
    transaction.save()
    logger.info(f"Transaction {transaction.id} now pending_stellar")
    envelope = TransactionEnvelope.from_xdr(
        transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE)
    try:
        response = settings.HORIZON_SERVER.submit_transaction(envelope)
    except BaseHorizonError as e:
        logger.info(e.__class__.__name__)
        tx_result = TransactionResult.from_xdr(e.result_xdr)
        op_results = tx_result.result.results
        if isinstance(e, BadRequestError):
            if op_results[0].code == -1:  # Bad Auth
                handle_bad_signatures_error(e, transaction, multisig=multisig)
                return False  # handle_bad_signatures_error() saves transaction
            else:
                transaction.status = Transaction.STATUS.error
                transaction.status_message = (
                    f"tx failed with codes: {op_results}. Result XDR: {e.result_xdr}"
                )
        elif op_results[0].tr.paymentResult.code == const.PAYMENT_NO_TRUST:
            transaction.status = Transaction.STATUS.pending_trust
            transaction.status_message = (
                "trustline error when submitting transaction to horizon")
        else:
            raise RuntimeError("Unable to submit payment to horizon, "
                               f"non-trustline failure: {e.message}")
        transaction.save()
        logger.error(transaction.status_message)
        return False

    if not response.get("successful"):
        transaction_result = TransactionResult.from_xdr(response["result_xdr"])
        raise RuntimeError(
            "Stellar transaction failed when submitted to horizon: "
            f"{transaction_result.result.results}")

    transaction.paging_token = response["paging_token"]
    transaction.stellar_transaction_id = response["id"]
    transaction.status = Transaction.STATUS.completed
    transaction.completed_at = datetime.datetime.now(datetime.timezone.utc)
    transaction.status_eta = 0
    transaction.amount_out = round(
        Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
        transaction.asset.significant_decimals,
    )
    transaction.save()
    logger.info(f"Transaction {transaction.id} completed.")
    return True
 def sign_multisig_transactions(self):
     transactions = Transaction.objects.filter(
         pending_signatures=True,
         envelope_xdr__isnull=False,
         status=Transaction.STATUS.pending_anchor,
     ).all()
     for t in transactions:
         envelope = TransactionEnvelope.from_xdr(
             t.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE)
         envelope.sign(t.asset.distribution_seed)
         if not django_settings.MULT_ASSET_ADDITIONAL_SIGNING_SEED:
             raise CommandError(
                 "MULT's 2nd signer is not specified in the environment")
         envelope.sign(django_settings.MULT_ASSET_ADDITIONAL_SIGNING_SEED)
         t.envelope_xdr = envelope.to_xdr()
         t.pending_signatures = False
         t.save()
         logger.info(f"Transaction {t.id} signatures have been collected")
Ejemplo n.º 15
0
    def sign_multisig_transaction(self, tx_xdr):
        """sign_multisig_transaction signs a transaction xdr and tries to submit it to the network
        if it fails it returns the signed transaction xdr

        :param tx_xdr: transaction to sign in xdr format.
        :type tx_xdr: str
        """
        server = self._get_horizon_server()
        source_keypair = Keypair.from_secret(self.secret)
        tx = TransactionEnvelope.from_xdr(tx_xdr, _NETWORK_PASSPHRASES[str(self.network)])
        tx.sign(source_keypair)

        try:
            response = server.submit_transaction(tx)
            self._log_info(response)
            self._log_info("Multisig tx signed and sent")
        except BadRequestError:
            self._log_info("Transaction need additional signatures in order to send")
            return tx.to_xdr()
Ejemplo n.º 16
0
    def decode_transaction_envelope(self, envelope_xdr):
        """
        Decode envelope and get details
        Credits to overcat :
        https://stellar.stackexchange.com/questions/3022/how-can-i-get-the-value-of-the-stellar-transaction/3025#3025
        :param envelope_xdr: Xdr envelope from stellar network
        :return: Decoded transaction details
        """
        te = TransactionEnvelope.from_xdr(envelope_xdr, self.network_phrase)
        operations = te.transaction.operations

        # TODO make multiple payments inside one transaction
        for op in operations:
            if isinstance(op, Payment):
                asset = op.asset.to_dict()
                if asset.get('type') == 'native':
                    asset['code'] = 'XLM'  # Appending XLM code to asset incase if native
                asset["amount"] = op.to_xdr_amount(op.amount)
                # TODO count all deposits
                return asset
Ejemplo n.º 17
0
def test_post_success_account_exists_client_attribution(
        mock_load_account, client):
    kp = Keypair.random()
    client_domain_kp = Keypair.random()
    challenge_xdr = build_challenge_transaction(
        server_secret=settings.SIGNING_SEED,
        client_account_id=kp.public_key,
        home_domain=settings.SEP10_HOME_DOMAINS[0],
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        client_domain="test.com",
        client_signing_key=client_domain_kp.public_key,
    )
    envelope = TransactionEnvelope.from_xdr(
        challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE)
    envelope.sign(kp)
    envelope.sign(client_domain_kp)
    signed_challenge_xdr = envelope.to_xdr()
    mock_load_account.return_value = Mock(
        load_ed25519_public_key_signers=Mock(
            return_value=[Ed25519PublicKeySigner(kp.public_key, 0)]),
        thresholds=Mock(med_threshold=0),
    )

    response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr})

    content = response.json()
    assert response.status_code == 200, json.dumps(content, indent=2)
    jwt_contents = jwt.decode(content["token"],
                              settings.SERVER_JWT_KEY,
                              algorithms=["HS256"])
    iat = jwt_contents.pop("iat")
    exp = jwt_contents.pop("exp")
    assert exp - iat == 24 * 60 * 60
    assert jwt_contents == {
        "iss": os.path.join(settings.HOST_URL, "auth"),
        "sub": kp.public_key,
        "jti": envelope.hash().hex(),
        "client_domain": "test.com",
    }
Ejemplo n.º 18
0
def test_deposit_authenticated_success(client,
                                       acc1_usd_deposit_transaction_factory):
    """`GET /deposit` succeeds with the SEP 10 authentication flow."""
    client_address = "GDKFNRUATPH4BSZGVFDRBIGZ5QAFILVFRIRYNSQ4UO7V2ZQAPRNL73RI"
    client_seed = "SDKWSBERDHP3SXW5A3LXSI7FWMMO5H7HG33KNYBKWH2HYOXJG2DXQHQY"
    deposit = acc1_usd_deposit_transaction_factory()

    # SEP 10.
    response = client.get(f"/auth?account={client_address}", follow=True)
    content = json.loads(response.content)

    envelope_xdr = content["transaction"]
    envelope_object = TransactionEnvelope.from_xdr(
        envelope_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE)
    client_signing_key = Keypair.from_secret(client_seed)
    envelope_object.sign(client_signing_key)
    client_signed_envelope_xdr = envelope_object.to_xdr()

    response = client.post(
        "/auth",
        data={"transaction": client_signed_envelope_xdr},
        content_type="application/json",
    )
    content = json.loads(response.content)
    encoded_jwt = content["token"]
    assert encoded_jwt

    header = {"HTTP_AUTHORIZATION": f"Bearer {encoded_jwt}"}
    response = client.get(
        f"/deposit?asset_code=USD&account={deposit.stellar_account}",
        follow=True,
        **header,
    )
    content = json.loads(response.content)
    assert response.status_code == 403
    assert content["type"] == "interactive_customer_info_needed"
Ejemplo n.º 19
0
def create_stellar_deposit(transaction: Transaction,
                           destination_exists: bool = False) -> bool:
    """
    Create and submit the Stellar transaction for the deposit.

    The Transaction can be either `pending_anchor` if the task is called
    from `poll_pending_deposits()` or `pending_trust` if called from the
    `check_trustlines()`.
    """
    if transaction.status not in [
            Transaction.STATUS.pending_anchor,
            Transaction.STATUS.pending_trust,
    ]:
        raise ValueError(
            f"unexpected transaction status {transaction.status} for "
            "create_stellar_deposit", )
    elif transaction.amount_in is None or transaction.amount_fee is None:
        transaction.status = Transaction.STATUS.error
        transaction.status_message = (
            "`amount_in` and `amount_fee` must be populated, skipping transaction"
        )
        transaction.save()
        raise ValueError(transaction.status_message)

    # if we don't know if the destination account exists
    if not destination_exists:
        try:
            _, created, pending_trust = get_or_create_transaction_destination_account(
                transaction)
        except RuntimeError as e:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = str(e)
            transaction.save()
            logger.error(transaction.status_message)
            return False
        if created or pending_trust:
            # the account is pending_trust for the asset to be received
            if pending_trust and transaction.status != Transaction.STATUS.pending_trust:
                transaction.status = Transaction.STATUS.pending_trust
                transaction.save()
            return False

    # if the distribution account's master signer's weight is great or equal to the its
    # medium threshold, verify the transaction is signed by it's channel account
    master_signer = None
    if transaction.asset.distribution_account_master_signer:
        master_signer = transaction.asset.distribution_account_master_signer
    thresholds = transaction.asset.distribution_account_thresholds
    if not (master_signer
            and master_signer["weight"] >= thresholds["med_threshold"]):
        multisig = True
        envelope = TransactionEnvelope.from_xdr(
            transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE)
        try:
            _verify_te_signed_by_account_id(envelope,
                                            transaction.channel_account)
        except InvalidSep10ChallengeError:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = gettext(
                "Multisig transaction's envelope was not signed by channel account"
            )
            transaction.save()
            return False
    # otherwise, create the envelope and sign it with the distribution account's secret
    else:
        multisig = False
        distribution_acc, _ = get_account_obj(
            Keypair.from_public_key(transaction.asset.distribution_account))
        envelope = create_transaction_envelope(transaction, distribution_acc)
        envelope.sign(transaction.asset.distribution_seed)
        transaction.envelope_xdr = envelope.to_xdr()

    try:
        return submit_stellar_deposit(transaction, multisig=multisig)
    except RuntimeError as e:
        transaction.status_message = str(e)
        transaction.status = Transaction.STATUS.error
        transaction.save()
        logger.error(transaction.status_message)
        return False
Ejemplo n.º 20
0
 def signTransaction(txnXdr, signature):
     txnEnv = TransactionEnvelope.from_xdr(txnXdr)
     txnEnv.sign(signature)
     txnEnv.to_xdr()
Ejemplo n.º 21
0
    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
    def submit(cls, transaction: Transaction) -> bool:
        valid_statuses = [
            Transaction.STATUS.pending_user_transfer_start,
            Transaction.STATUS.pending_external,
            Transaction.STATUS.pending_anchor,
            Transaction.STATUS.pending_trust,
        ]
        if transaction.status not in valid_statuses:
            raise ValueError(
                f"Unexpected transaction status: {transaction.status}, expecting "
                f"{' or '.join(valid_statuses)}.")

        transaction.status = Transaction.STATUS.pending_anchor
        transaction.save()
        logger.info(f"Initiating Stellar deposit for {transaction.id}")
        maybe_make_callback(transaction)

        if transaction.envelope_xdr:
            try:
                envelope = TransactionEnvelope.from_xdr(
                    transaction.envelope_xdr,
                    settings.STELLAR_NETWORK_PASSPHRASE)
            except Exception:
                cls._handle_error(transaction,
                                  "Failed to decode transaction envelope")
                return False
        else:
            distribution_acc, _ = get_account_obj(
                Keypair.from_public_key(
                    transaction.asset.distribution_account))
            envelope = cls.create_deposit_envelope(transaction,
                                                   distribution_acc)
            envelope.sign(transaction.asset.distribution_seed)

        transaction.status = Transaction.STATUS.pending_stellar
        transaction.save()
        logger.info(f"Transaction {transaction.id} now pending_stellar")
        maybe_make_callback(transaction)

        try:
            response = settings.HORIZON_SERVER.submit_transaction(envelope)
        except BaseHorizonError as e:
            cls._handle_error(transaction,
                              f"{e.__class__.__name__}: {e.message}")
            return False

        if not response.get("successful"):
            cls._handle_error(
                transaction,
                f"Stellar transaction failed when submitted to horizon: {response['result_xdr']}",
            )
            return False
        elif transaction.claimable_balance_supported:
            transaction.claimable_balance_id = cls.get_balance_id(response)

        transaction.envelope_xdr = response["envelope_xdr"]
        transaction.paging_token = response["paging_token"]
        transaction.stellar_transaction_id = response["id"]
        transaction.status = Transaction.STATUS.completed
        transaction.completed_at = datetime.datetime.now(datetime.timezone.utc)
        transaction.amount_out = round(
            Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
            transaction.asset.significant_decimals,
        )
        transaction.save()
        logger.info(f"Transaction {transaction.id} completed.")
        maybe_make_callback(transaction)
        return True