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(),
     )
     _, json_resp = get_account_obj(
         Keypair.from_public_key(transaction.stellar_account))
     if transaction.claimable_balance_supported and is_pending_trust(
             transaction, json_resp):
         logger.debug(
             f"Crafting claimable balance operation for Transaction {transaction.id}"
         )
         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(
             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.memo:
         builder.add_memo(make_memo(transaction.memo,
                                    transaction.memo_type))
     return builder.build()
Exemplo n.º 2
0
def create_transaction_envelope(transaction,
                                source_account) -> TransactionEnvelope:
    payment_amount = round(
        Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
        transaction.asset.significant_decimals,
    )
    memo = make_memo(transaction.memo, transaction.memo_type)
    base_fee = settings.HORIZON_SERVER.fetch_base_fee()
    builder = TransactionBuilder(
        source_account=source_account,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        base_fee=base_fee,
    ).append_payment_op(
        destination=transaction.stellar_account,
        asset_code=transaction.asset.code,
        asset_issuer=transaction.asset.issuer,
        amount=str(payment_amount),
        source=transaction.asset.distribution_account,
    )
    if memo:
        builder.add_memo(memo)
    return builder.build()
Exemplo n.º 3
0
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",
        )
    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)
    transaction.status = Transaction.STATUS.pending_stellar
    transaction.save()
    logger.info(f"Transaction {transaction_id} now pending_stellar")

    # 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,
        transaction.asset.significant_decimals,
    )
    asset = transaction.asset
    memo = make_memo(transaction.memo, transaction.memo_type)

    # 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(asset.distribution_account)
    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:  # pragma: no cover
            msg = (
                "Horizon error when loading stellar account: " f"{address_exc.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False

        logger.info(f"Stellar account {stellar_account} does not exist. Creating.")
        transaction_envelope = builder.append_create_account_op(
            destination=stellar_account,
            starting_balance=starting_balance,
            source=asset.distribution_account,
        ).build()
        transaction_envelope.sign(asset.distribution_seed)
        try:
            server.submit_transaction(transaction_envelope)
        except BaseHorizonError as submit_exc:  # pragma: no cover
            msg = (
                "Horizon error when submitting create account to horizon: "
                f"{submit_exc.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False

        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        logger.info(f"Transaction for account {stellar_account} now pending_trust.")
        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.

    builder.append_payment_op(
        destination=stellar_account,
        asset_code=asset.code,
        asset_issuer=asset.issuer,
        amount=str(payment_amount),
    )
    if memo:
        builder.add_memo(memo)
    transaction_envelope = builder.build()
    transaction_envelope.sign(asset.distribution_seed)
    try:
        response = server.submit_transaction(transaction_envelope)
    # Functional errors at this stage are Horizon errors.
    except BaseHorizonError as exception:
        tx_result = TransactionResult.from_xdr(exception.result_xdr)
        op_result = tx_result.result.results[0]
        if op_result.tr.paymentResult.code != const.PAYMENT_NO_TRUST:
            msg = (
                "Unable to submit payment to horizon, "
                f"non-trustline failure: {exception.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False
        msg = "trustline error when submitting transaction to horizon"
        logger.error(msg)
        transaction.status_message = msg
        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        return False

    if not response.get("successful"):
        transaction_result = TransactionResult.from_xdr(response["result_xdr"])
        msg = (
            "Stellar transaction failed when submitted to horizon: "
            f"{transaction_result.result.results}"
        )
        logger.error(msg)
        transaction.status_message = msg
        transaction.status = Transaction.STATUS.error
        transaction.save()
        return False

    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  # No more status change.
    transaction.amount_out = payment_amount
    transaction.save()
    logger.info(f"Transaction {transaction.id} completed.")
    return True