def test_deposit_check_trustlines_horizon( mock_delay, client, acc1_usd_deposit_transaction_factory): """ Tests the `check_trustlines` function's various logical paths. Note that the Stellar deposit is created synchronously. This makes Horizon calls, so it is skipped by the CI. """ del mock_delay # Initiate a transaction with a new Stellar account. print("Creating initial deposit.") deposit = acc1_usd_deposit_transaction_factory() keypair = Keypair.random() deposit.stellar_account = keypair.public_key response = client.get( f"/deposit?asset_code=USD&account={deposit.stellar_account}", follow=True) content = json.loads(response.content) assert response.status_code == 403 assert content["type"] == "interactive_customer_info_needed" # Complete the interactive deposit. The transaction should be set # to pending_user_transfer_start, since wallet-side confirmation has not happened. print("Completing interactive deposit.") transaction_id = content["id"] url = content["url"] amount = 20 response = client.post(url, {"amount": amount}) assert response.status_code == 200 assert (Transaction.objects.get(id=transaction_id).status == Transaction.STATUS.pending_user_transfer_start) # As a result of this external confirmation, the transaction should # be `pending_trust`. This will trigger a synchronous call to # `create_stellar_deposit`, which will register the account on testnet. # Since the account will not have a trustline, the status will still # be `pending_trust`. response = client.get( f"/deposit/confirm_transaction?amount={amount}&transaction_id={transaction_id}", follow=True, ) assert response.status_code == 200 content = json.loads(response.content) transaction = content["transaction"] assert transaction assert transaction["status"] == Transaction.STATUS.pending_anchor assert float(transaction["amount_in"]) == amount # The Stellar account has not been registered, so # this should not change the status of the Transaction. print( "Check trustlines, try one. No trustline for account. Status should be pending_trust." ) check_trustlines() assert (Transaction.objects.get( id=transaction_id).status == Transaction.STATUS.pending_trust) # Add a trustline for the transaction asset from the server # source account to the transaction account. from stellar_sdk.asset import Asset from stellar_sdk.transaction_builder import TransactionBuilder print("Create trustline.") asset_code = deposit.asset.code asset_issuer = settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS Asset(code=asset_code, issuer=asset_issuer) server = settings.HORIZON_SERVER source_account = server.load_account(keypair.public_key) base_fee = 100 transaction = TransactionBuilder( source_account=source_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, base_fee=base_fee).append_change_trust_op(asset_code, asset_issuer).build() transaction.sign(keypair) response = server.submit_transaction(transaction) assert response[ "result_xdr"] == "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAA=" print("Check trustlines, try three. Status should be completed.") check_trustlines() completed_transaction = Transaction.objects.get(id=transaction_id) assert completed_transaction.status == Transaction.STATUS.completed assert (completed_transaction.stellar_transaction_id == HORIZON_SUCCESS_RESPONSE["hash"])
def get_or_create_transaction_destination_account( transaction, ) -> Tuple[Optional[Account], bool, bool]: """ Returns the stellar_sdk.account.Account for which this transaction's payment will be sent to as as well as whether or not the account was created as a result of calling this function. If the account exists, the function simply returns the account and False. If the account doesn't exist, Polaris must create the account using an account provided by the anchor. Polaris can use the distribution account of the anchored asset or a channel account if the asset's distribution account requires non-master signatures. If the transacted asset's distribution account does not require non-master signatures, Polaris can create the destination account using the distribution account. On successful creation, this function will return the account and True. On failure, a RuntimeError exception is raised. If the transacted asset's distribution account does require non-master signatures, the anchor should save a keypair of a pre-existing Stellar account to use as the channel account via DepositIntegration.create_channel_account(). This channel account must only be used as the source account for transactions related to the ``Transaction`` object passed. It also must not be used to submit transactions by any service other than Polaris. If it is, the outstanding transactions will be invalidated due to bad sequence numbers. Finally, the channel accounts must have a master signer with a weight greater than or equal to the medium threshold for the account. After the transaction for creating the destination account has been submitted to the stellar network and the transaction has been created, this function will return the account and True. If the transaction was not submitted successfully, a RuntimeError exception will be raised. """ try: account, json_resp = get_account_obj( Keypair.from_public_key(transaction.stellar_account)) return account, False, has_trustline(transaction, json_resp) except RuntimeError: master_signer = None if transaction.asset.distribution_account_master_signer: master_signer = json.loads( transaction.asset.distribution_account_master_signer) thresholds = json.loads( transaction.asset.distribution_account_thresholds) if master_signer and master_signer["weight"] >= thresholds[ "med_threshold"]: source_account_kp = Keypair.from_secret( transaction.asset.distribution_seed) source_account, _ = get_account_obj(source_account_kp) else: from polaris.integrations import registered_deposit_integration as rdi rdi.create_channel_account(transaction) source_account_kp = Keypair.from_secret(transaction.channel_seed) source_account, _ = get_account_obj(source_account_kp) base_fee = settings.HORIZON_SERVER.fetch_base_fee() builder = TransactionBuilder( source_account=source_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, base_fee=base_fee, ) transaction_envelope = builder.append_create_account_op( destination=transaction.stellar_account, starting_balance=settings.ACCOUNT_STARTING_BALANCE, ).build() transaction_envelope.sign(source_account_kp) try: settings.HORIZON_SERVER.submit_transaction(transaction_envelope) except BaseHorizonError as submit_exc: # pragma: no cover raise RuntimeError( "Horizon error when submitting create account to horizon: " f"{submit_exc.message}") transaction.status = Transaction.STATUS.pending_trust transaction.save() logger.info( f"Transaction {transaction.id} is now pending_trust of destination account" ) account, json_resp = get_account_obj( Keypair.from_public_key(transaction.stellar_account)) return account, True, has_trustline(transaction, json_resp) except BaseHorizonError as e: raise RuntimeError( f"Horizon error when loading stellar account: {e.message}")
def create_deposit_envelope(transaction, source_account) -> TransactionEnvelope: payment_amount = round( Decimal(transaction.amount_in) - Decimal(transaction.amount_fee), transaction.asset.significant_decimals, ) builder = TransactionBuilder( source_account=source_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, # only one operation, so base_fee will be multipled by 1 base_fee=settings.MAX_TRANSACTION_FEE_STROOPS or settings.HORIZON_SERVER.fetch_base_fee(), ) payment_op_kwargs = { "destination": transaction.stellar_account, "asset_code": transaction.asset.code, "asset_issuer": transaction.asset.issuer, "amount": str(payment_amount), "source": transaction.asset.distribution_account, } if transaction.claimable_balance_supported: _, json_resp = get_account_obj( Keypair.from_public_key(transaction.stellar_account)) if is_pending_trust(transaction, json_resp): claimant = Claimant(destination=transaction.stellar_account) asset = Asset(code=transaction.asset.code, issuer=transaction.asset.issuer) builder.append_create_claimable_balance_op( claimants=[claimant], asset=asset, amount=str(payment_amount), source=transaction.asset.distribution_account, ) else: builder.append_payment_op(**payment_op_kwargs) else: builder.append_payment_op(**payment_op_kwargs) if transaction.memo: builder.add_memo(make_memo(transaction.memo, transaction.memo_type)) return builder.build()
def test_to_txrep_full_tx(self): keypair = Keypair.from_secret( "SAHGKA7QJB6SRFDZSPZDEEIOEHUHTQS4XVN4IMR5YCKBPEN5A6YNKKUO") account = Account(keypair.public_key, 46489056724385792) transaction_builder = TransactionBuilder( source_account=account, network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE, base_fee=200, ) transaction_builder.add_text_memo("Enjoy this transaction") transaction_builder.add_time_bounds(1535756672, 1567292672) transaction_builder.append_create_account_op( destination= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", starting_balance="10", source=keypair.public_key, ) transaction_builder.append_payment_op( destination= "GBAF6NXN3DHSF357QBZLTBNWUTABKUODJXJYYE32ZDKA2QBM2H33IK6O", asset_code="USD", asset_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", amount="40.0004", source=keypair.public_key, ), transaction_builder.append_path_payment_strict_receive_op( destination= "GBAF6NXN3DHSF357QBZLTBNWUTABKUODJXJYYE32ZDKA2QBM2H33IK6O", send_code="USD", send_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", send_max="10", dest_code="XCN", dest_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", dest_amount="5.125", path=[ Asset( "Hello", "GD3RXMK2GHSXXEHPBZY5IL7VW5BXQEDJMCD4KVMXOH2GRFKDGZXR5PFO" ), Asset.native(), Asset( "MOEW", "GBR765FQTCAJLLJGZVYLXCFAOZI6ORTHPDPOOHJOHFRZ5GHNVYGK4IFM" ), ], source=keypair.public_key, ) transaction_builder.append_manage_sell_offer_op( selling_code="XCN", selling_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", buying_code="USD", buying_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", amount="100.123", price=Price(n=7, d=1), offer_id=12345, source=keypair.public_key, ) transaction_builder.append_create_passive_sell_offer_op( selling_code="XCN", selling_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", buying_code="USD", buying_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", amount="100.123", price="7.1", source=keypair.public_key, ) transaction_builder.append_set_options_op( inflation_dest= "GCVAZXCGX3HLHZ6P5WKEPE3U2YJMTLMPTZJFGY67MTNPSOA4COKVJ6AF", clear_flags=3, set_flags=3, master_weight=255, low_threshold=1, med_threshold=2, high_threshold=3, home_domain="stellar.org", signer=Signer.ed25519_public_key( "GAO3YIWNOBP4DN3ORDXYXTUMLF5S54OK4PKAFCWE23TTOML4COGQOIYA", 255), source=keypair.public_key, ) transaction_builder.append_set_options_op(home_domain="stellarcn.org") transaction_builder.append_pre_auth_tx_signer( "0ab0c76b1c661db0e829abfdd9e32e6ce3c11f756bdf71aa23846582106c1783", 5) transaction_builder.append_hashx_signer( "0d64fac556c1545616b3c915a4ec215239500bce287007cff038b6020950af46", 10) transaction_builder.append_change_trust_op( asset_code="XCN", asset_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", limit="1000", source=keypair.public_key, ) transaction_builder.append_allow_trust_op( trustor="GDU64QWPRTO3LW5VGZPTLRR6QROFWV36XG5QT4C6FZBPHQBBFYRCWZTZ", asset_code="CAT", authorize=True, source=keypair.public_key, ) transaction_builder.append_account_merge_op( destination= "GDU64QWPRTO3LW5VGZPTLRR6QROFWV36XG5QT4C6FZBPHQBBFYRCWZTZ", source=keypair.public_key, ) transaction_builder.append_manage_data_op("Hello", "Stellar", source=keypair.public_key) transaction_builder.append_manage_data_op("World", None, source=keypair.public_key) transaction_builder.append_bump_sequence_op(bump_to=46489056724385800, source=keypair.public_key) transaction_builder.append_manage_buy_offer_op( selling_code="XCN", selling_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", buying_code="USD", buying_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", amount="100.123", price="7.1", source=keypair.public_key, ) transaction_builder.append_path_payment_strict_send_op( destination= "GBAF6NXN3DHSF357QBZLTBNWUTABKUODJXJYYE32ZDKA2QBM2H33IK6O", send_code="USD", send_issuer= "GAZFEVBSEGJJ63WPVVIWXLZLWN2JYZECECGT6GUNP4FJDVZVNXWQWMYI", send_amount="10", dest_code="XCN", dest_issuer= "GAYE5SDEM5JIEMGQ7LBMQVRQRVJB6A5E7AZVLJYFL3CNHLZX24DFD35F", dest_min="5.125", path=[ Asset( "Hello", "GD3RXMK2GHSXXEHPBZY5IL7VW5BXQEDJMCD4KVMXOH2GRFKDGZXR5PFO" ), Asset.native(), Asset( "MOEW", "GBR765FQTCAJLLJGZVYLXCFAOZI6ORTHPDPOOHJOHFRZ5GHNVYGK4IFM" ), ], source=keypair.public_key, ) transaction_builder.append_inflation_op() te = transaction_builder.build() te.sign(keypair) te.sign_hashx( bytes.fromhex( "8b73f9e12fcc8cd9580a2a26aec14d6175aa1ff45e76b816618635d03f3256b8" )) txrep = to_txrep(te) assert txrep == get_txrep_file("test_to_txrep_full_tx.txt") assert (from_txrep( txrep, Network.TESTNET_NETWORK_PASSPHRASE).to_xdr() == te.to_xdr())
def get_or_create_destination_account( cls, transaction: Transaction, ) -> Tuple[Account, bool]: """ Returns: Account: The account found or created for the Transaction bool: True if trustline doesn't exist, False otherwise. If the account doesn't exist, Polaris must create the account using an account provided by the anchor. Polaris can use the distribution account of the anchored asset or a channel account if the asset's distribution account requires non-master signatures. If the transacted asset's distribution account does require non-master signatures, DepositIntegration.create_channel_account() will be called. See the function docstring for more info. On failure to create the destination account, a RuntimeError exception is raised. """ try: account, json_resp = get_account_obj( Keypair.from_public_key(transaction.stellar_account)) return account, is_pending_trust(transaction, json_resp) except RuntimeError: # account does not exist try: requires_multisig = PendingDeposits.requires_multisig( transaction) except NotFoundError: logger.error( f"{transaction.asset.code} distribution account " f"{transaction.asset.distribution_account} does not exist") raise RuntimeError("the distribution account does not exist") if requires_multisig: source_account_kp = cls.get_channel_keypair(transaction) source_account, _ = get_account_obj(source_account_kp) else: source_account_kp = Keypair.from_secret( transaction.asset.distribution_seed) source_account, _ = get_account_obj(source_account_kp) builder = TransactionBuilder( source_account=source_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, # this transaction contains one operation so base_fee will be multiplied by 1 base_fee=settings.MAX_TRANSACTION_FEE_STROOPS or settings.HORIZON_SERVER.fetch_base_fee(), ) transaction_envelope = builder.append_create_account_op( destination=transaction.stellar_account, starting_balance=settings.ACCOUNT_STARTING_BALANCE, ).build() transaction_envelope.sign(source_account_kp) try: settings.HORIZON_SERVER.submit_transaction( transaction_envelope) except BaseHorizonError as e: # pragma: no cover raise RuntimeError( "Horizon error when submitting create account " f"to horizon: {e.message}") account, _ = get_account_obj( Keypair.from_public_key(transaction.stellar_account)) return account, True except BaseHorizonError as e: raise RuntimeError( f"Horizon error when loading stellar account: {e.message}") except ConnectionError: raise RuntimeError("Failed to connect to Horizon")
def transaction_builder(): account = server().load_account(CONF['public_key']) return TransactionBuilder(source_account=account, network_passphrase=network_passphrase(), base_fee=fetch_base_fee())
builder.add_text_memo('For beers') # string length <= 28 bytes builder.sign() builder.submit()''' base_fee = server.fetch_base_fee() source_account = server.load_account(source_acc_id) # Start building the transaction. transaction = ( TransactionBuilder( source_account=source_account, network_passphrase='Test SDF Network ; September 2015', base_fee=base_fee, ) # Because Stellar allows transaction in many currencies, you must specify the asset type. # Here we are sending Lumens. .append_payment_op(destination=destination_acc_id, amount="220", asset_code="XLM") # A memo allows you to add your own metadata to a transaction. It's # optional and does not affect how Stellar treats the transaction. .add_text_memo("Test Transaction") # Wait a maximum of three minutes for the transaction .set_timeout(10).build()) # Sign the transaction to prove you are actually the person sending it. transaction.sign(source_acc_key) try: # And finally, send it off to Stellar! response = server.submit_transaction(transaction) print(f"Response: {response}")
def create_stellar_deposit(transaction_id): """Create and submit the Stellar transaction for the deposit.""" transaction = Transaction.objects.get(id=transaction_id) # We check the Transaction status to avoid double submission of a Stellar # transaction. The Transaction can be either `pending_anchor` if the task # is called from `GET deposit/confirm_transaction` or `pending_trust` if called # from the `check_trustlines()`. if transaction.status not in [ Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ]: logger.debug( "unexpected transaction status %s at top of create_stellar_deposit", transaction.status, ) return transaction.status = Transaction.STATUS.pending_stellar transaction.save() # We can assume transaction has valid stellar_account, amount_in, and asset # because this task is only called after those parameters are validated. stellar_account = transaction.stellar_account payment_amount = round(transaction.amount_in - transaction.amount_fee, 7) asset = transaction.asset.code # If the given Stellar account does not exist, create # the account with at least enough XLM for the minimum # reserve and a trust line (recommended 2.01 XLM), update # the transaction in our internal database, and return. server = settings.HORIZON_SERVER starting_balance = settings.ACCOUNT_STARTING_BALANCE server_account = server.load_account( settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) base_fee = server.fetch_base_fee() builder = TransactionBuilder( source_account=server_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, base_fee=base_fee) try: server.load_account(stellar_account) except BaseHorizonError as address_exc: # 404 code corresponds to Resource Missing. if address_exc.status != 404: logger.debug( "error with message %s when loading stellar account", address_exc.message, ) return transaction_envelope = builder \ .append_create_account_op(destination=stellar_account, starting_balance=starting_balance, source=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) \ .build() transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) try: server.submit_transaction(transaction_envelope) except BaseHorizonError as submit_exc: logger.debug( f"error with message {submit_exc.message} when submitting create account to horizon" ) return transaction.status = Transaction.STATUS.pending_trust transaction.save() return # If the account does exist, deposit the desired amount of the given # asset via a Stellar payment. If that payment succeeds, we update the # transaction to completed at the current time. If it fails due to a # trustline error, we update the database accordingly. Else, we do not update. transaction_envelope = builder \ .append_payment_op(destination=stellar_account, asset_code=asset, asset_issuer=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS, amount=str(payment_amount)) \ .build() transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) try: response = server.submit_transaction(transaction_envelope) # Functional errors at this stage are Horizon errors. except BaseHorizonError as exception: if TRUSTLINE_FAILURE_XDR not in exception.result_xdr: logger.debug( "error with message %s when submitting payment to horizon, non-trustline failure", exception.message, ) return logger.debug("trustline error when submitting transaction to horizon") transaction.status = Transaction.STATUS.pending_trust transaction.save() return # If this condition is met, the Stellar payment succeeded, so we # can mark the transaction as completed. if response["result_xdr"] != SUCCESS_XDR: logger.debug( "payment stellar transaction failed when submitted to horizon") return transaction.stellar_transaction_id = response["hash"] transaction.status = Transaction.STATUS.completed transaction.completed_at = now() transaction.status_eta = 0 # No more status change. transaction.amount_out = payment_amount transaction.save()
def create_stellar_deposit(transaction_id: str) -> bool: """ Create and submit the Stellar transaction for the deposit. :returns a boolean indicating whether or not the deposit was successfully completed. One reason a transaction may not be completed is if a trustline must be established. The transaction's status will be set as ``pending_stellar`` and the status_message will be populated with a description of the problem encountered. :raises ValueError: the transaction has an unexpected status """ transaction = Transaction.objects.get(id=transaction_id) # We check the Transaction status to avoid double submission of a Stellar # transaction. 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", ) transaction.status = Transaction.STATUS.pending_stellar transaction.save() # We can assume transaction has valid stellar_account, amount_in, and asset # because this task is only called after those parameters are validated. stellar_account = transaction.stellar_account payment_amount = round(transaction.amount_in - transaction.amount_fee, 7) asset = transaction.asset.code # If the given Stellar account does not exist, create # the account with at least enough XLM for the minimum # reserve and a trust line (recommended 2.01 XLM), update # the transaction in our internal database, and return. server = settings.HORIZON_SERVER starting_balance = settings.ACCOUNT_STARTING_BALANCE server_account = server.load_account( settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) base_fee = server.fetch_base_fee() builder = TransactionBuilder( source_account=server_account, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, base_fee=base_fee, ) try: server.load_account(stellar_account) except BaseHorizonError as address_exc: # 404 code corresponds to Resource Missing. if address_exc.status != 404: transaction.status = Transaction.STATUS.error transaction.status_message = ( "Horizon error when loading stellar account: " f"{address_exc.message}") transaction.save() return False transaction_envelope = builder.append_create_account_op( destination=stellar_account, starting_balance=starting_balance, source=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS, ).build() transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) try: server.submit_transaction(transaction_envelope) except BaseHorizonError as submit_exc: transaction.status = Transaction.STATUS.error transaction.status_message = ( "Horizon error when submitting create account to horizon: " f"{submit_exc.message}") transaction.save() return False transaction.status = Transaction.STATUS.pending_trust transaction.save() return False # If the account does exist, deposit the desired amount of the given # asset via a Stellar payment. If that payment succeeds, we update the # transaction to completed at the current time. If it fails due to a # trustline error, we update the database accordingly. Else, we do not update. transaction_envelope = builder.append_payment_op( destination=stellar_account, asset_code=asset, asset_issuer=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS, amount=str(payment_amount), ).build() transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) try: response = server.submit_transaction(transaction_envelope) # Functional errors at this stage are Horizon errors. except BaseHorizonError as exception: if TRUSTLINE_FAILURE_XDR not in exception.result_xdr: transaction.status = Transaction.STATUS.error transaction.status_message = ( "Unable to submit payment to horizon, " f"non-trustline failure: {exception.message}") transaction.save() return False transaction.status_message = ( "trustline error when submitting transaction to horizon") transaction.status = Transaction.STATUS.pending_trust transaction.save() return False if response["result_xdr"] != SUCCESS_XDR: transaction_result = TransactionResult.from_xdr(response["result_xdr"]) transaction.status_message = ( "Stellar transaction failed when submitted to horizon: " f"{transaction_result.result}") transaction.save() return False transaction.stellar_transaction_id = response["hash"] transaction.status = Transaction.STATUS.completed transaction.completed_at = now() transaction.status_eta = 0 # No more status change. transaction.amount_out = payment_amount transaction.save() return True