def info(request: Request) -> Response: info_data = { "deposit": {}, "withdraw": {}, "fee": {"enabled": True, "authentication_required": True}, "transactions": {"enabled": True, "authentication_required": True}, "transaction": {"enabled": True, "authentication_required": True}, } for asset in Asset.objects.filter(sep6_enabled=True): try: fields_and_types = registered_info_func(asset, request.GET.get("lang")) except ValueError: return render_error_response("unsupported 'lang'") try: validate_integration(fields_and_types) except ValueError as e: logger.error(f"info integration error: {str(e)}") return render_error_response( _("unable to process the request"), status_code=500 ) info_data["deposit"][asset.code] = get_asset_info( asset, "deposit", fields_and_types.get("fields", {}) ) info_data["withdraw"][asset.code] = get_asset_info( asset, "withdrawal", fields_and_types.get("types", {}) ) return Response(info_data)
def withdraw(account: str, request: Request) -> Response: """ POST /transactions/withdraw/interactive Creates an `incomplete` withdraw Transaction object in the database and returns the URL entry-point for the interactive flow. """ lang = request.POST.get("lang") asset_code = request.POST.get("asset_code") sep9_fields = extract_sep9_fields(request.POST) if lang: err_resp = validate_language(lang) if err_resp: return err_resp activate_lang_for_request(lang) if not asset_code: return render_error_response(_("'asset_code' is required")) elif request.POST.get("memo"): # Polaris SEP-24 doesn't support custodial wallets that depend on memos # to disambiguate users using the same stellar account. Support would # require new or adjusted integration points. return render_error_response(_("`memo` parameter is not supported")) # Verify that the asset code exists in our database, with withdraw enabled. asset = Asset.objects.filter(code=asset_code).first() if not (asset and asset.withdrawal_enabled and asset.sep24_enabled): return render_error_response( _("invalid operation for asset %s") % asset_code) elif not asset.distribution_account: return render_error_response( _("unsupported asset type: %s") % asset_code) try: rwi.save_sep9_fields(account, sep9_fields, lang) except ValueError as e: # The anchor found a validation error in the sep-9 fields POSTed by # the wallet. The error string returned should be in the language # specified in the request. return render_error_response(str(e)) transaction_id = create_transaction_id() Transaction.objects.create( id=transaction_id, stellar_account=account, asset=asset, kind=Transaction.KIND.withdrawal, status=Transaction.STATUS.incomplete, receiving_anchor_account=asset.distribution_account, memo_type=Transaction.MEMO_TYPES.hash, protocol=Transaction.PROTOCOL.sep24, ) logger.info(f"Created withdrawal transaction {transaction_id}") url = interactive_url(request, str(transaction_id), account, asset_code, settings.OPERATION_WITHDRAWAL) return Response({ "type": "interactive_customer_info_needed", "url": url, "id": transaction_id })
def transaction( request: Request, account: str, sep6: bool = False, ) -> Response: try: request_transaction = _get_transaction_from_request( request, account=account, sep6=sep6, ) except (AttributeError, ValidationError) as exc: return render_error_response(str(exc), status_code=status.HTTP_400_BAD_REQUEST) except Transaction.DoesNotExist: return render_error_response("transaction not found", status_code=status.HTTP_404_NOT_FOUND) serializer = TransactionSerializer( request_transaction, context={ "request": request, "sep6": sep6 }, ) return Response({"transaction": serializer.data})
def transactions( request: Request, account: str, sep6: bool = False, ) -> Response: try: limit = _validate_limit(request.GET.get("limit")) except ValueError: return render_error_response("invalid limit", status_code=status.HTTP_400_BAD_REQUEST) protocol_filter = { "sep6_enabled": True } if sep6 else { "sep24_enabled": True } if not request.GET.get("asset_code"): return render_error_response("asset_code is required") elif not Asset.objects.filter(code=request.GET.get("asset_code"), **protocol_filter).exists(): return render_error_response("invalid asset_code") translation_dict = { "asset_code": "asset__code", "no_older_than": "started_at__gte", "kind": "kind", } qset_filter = _compute_qset_filters(request.GET, translation_dict) qset_filter["stellar_account"] = account # Since the Transaction IDs are UUIDs, rather than in the chronological # order of their creation, we map the paging ID (if provided) to the # started_at field of a Transaction. paging_id = request.GET.get("paging_id") if paging_id: try: start_transaction = Transaction.objects.get(id=paging_id) except Transaction.DoesNotExist: return render_error_response( "invalid paging_id", status_code=status.HTTP_400_BAD_REQUEST) qset_filter["started_at__lt"] = start_transaction.started_at protocol = Transaction.PROTOCOL.sep6 if sep6 else Transaction.PROTOCOL.sep24 transactions_qset = Transaction.objects.filter(protocol=protocol, **qset_filter) if limit: transactions_qset = transactions_qset[:limit] serializer = TransactionSerializer( transactions_qset, many=True, context={ "request": request, "same_asset": True, "sep6": sep6 }, ) return Response({"transactions": serializer.data})
def get( account: str, _client_domain: Optional[str], _request: Request, transaction_id: str = None, ) -> Response: if not transaction_id: return render_error_response( _("GET requests must include a transaction ID in the URI"), ) elif not registered_sep31_receiver_integration.valid_sending_anchor( account): return render_error_response(_("invalid sending account."), status_code=403) elif not transaction_id: return render_error_response(_("missing 'id' in URI")) try: t = Transaction.objects.filter( id=transaction_id, stellar_account=account, ).first() except ValidationError: # bad id parameter return render_error_response(_("transaction not found"), status_code=404) if not t: return render_error_response(_("transaction not found"), status_code=404) return Response({"transaction": SEP31TransactionSerializer(t).data})
def post(account: str, request: Request, **kwargs): if kwargs: return render_error_response( _("POST requests should not specify subresources in the URI")) elif not registered_sep31_receiver_integration.valid_sending_anchor( account): return render_error_response("invalid sending account", status_code=403) try: params = validate_post_request(request) except ValueError as e: return render_error_response(str(e)) # validate fields separately since error responses need different format missing_fields = validate_post_fields(params.get("fields"), params.get("asset"), params.get("lang")) if missing_fields: return Response( { "error": "transaction_info_needed", "fields": missing_fields }, status=400, ) transaction_id = create_transaction_id() # create memo transaction_id_hex = transaction_id.hex padded_hex_memo = "0" * (64 - len(transaction_id_hex)) + transaction_id_hex transaction_memo = memo_hex_to_base64(padded_hex_memo) # create transaction object without saving to the DB transaction = Transaction( id=transaction_id, protocol=Transaction.PROTOCOL.sep31, kind=Transaction.KIND.send, status=Transaction.STATUS.pending_sender, stellar_account=account, asset=params["asset"], amount_in=params["amount"], memo=transaction_memo, memo_type=Transaction.MEMO_TYPES.hash, receiving_anchor_account=params["asset"].distribution_account, ) error_data = registered_sep31_receiver_integration.process_post_request( params, transaction) try: response_data = process_post_response(error_data, transaction) except ValueError as e: logger.error(str(e)) return render_error_response(_("unable to process the request"), status_code=500) else: transaction.save() return Response(response_data, status=400 if "error" in response_data else 200)
def complete_interactive_withdraw(request: Request) -> Response: """ GET /transactions/withdraw/interactive/complete Updates the transaction status to pending_user_transfer_start and redirects to GET /more_info. A `callback` can be passed in the URL to be used by the more_info template javascript. """ transaction_id = request.GET.get("transaction_id") callback = request.GET.get("callback") if not transaction_id: return render_error_response(_("Missing id parameter in URL"), content_type="text/html") try: Transaction.objects.filter(id=transaction_id).update( status=Transaction.STATUS.pending_user_transfer_start) except ValidationError: return render_error_response( _("ID passed is not a valid transaction ID"), content_type="text/html") logger.info( f"Hands-off interactive flow complete for transaction {transaction_id}" ) url, args = ( reverse("more_info"), urlencode({ "id": transaction_id, "callback": callback }), ) return redirect(f"{url}?{args}")
def complete_interactive_deposit(request: Request) -> Response: """ GET /transactions/deposit/interactive/complete Updates the transaction status to pending_user_transfer_start and redirects to GET /more_info. A `callback` can be passed in the URL to be used by the more_info template javascript. """ transaction_id = request.GET.get("transaction_id") callback = request.GET.get("callback") if not transaction_id: return render_error_response( _("Missing id parameter in URL"), content_type="text/html" ) try: Transaction.objects.filter(id=transaction_id).update( status=Transaction.STATUS.pending_user_transfer_start ) except ValidationError: return render_error_response( _("ID passed is not a valid transaction ID"), content_type="text/html" ) logger.info(f"Hands-off interactive flow complete for transaction {transaction_id}") args = {"id": transaction_id, "initialLoad": "true"} if callback: args["callback"] = callback return redirect(f"{reverse('more_info')}?{urlencode(args)}")
def parse_request_args(request: Request) -> Dict: asset = Asset.objects.filter(code=request.GET.get("asset_code"), sep6_enabled=True, deposit_enabled=True).first() if not asset: return {"error": render_error_response(_("invalid 'asset_code'"))} lang = request.GET.get("lang") if lang: err_resp = validate_language(lang) if err_resp: return {"error": err_resp} activate_lang_for_request(lang) memo_type = request.GET.get("memo_type") if memo_type and memo_type not in Transaction.MEMO_TYPES: return {"error": render_error_response(_("invalid 'memo_type'"))} try: memo = memo_str(request.GET.get("memo"), memo_type) except (ValueError, MemoInvalidException): return { "error": render_error_response(_("invalid 'memo' for 'memo_type'")) } return { "asset": asset, "memo_type": memo_type, "memo": memo, "lang": lang, "type": request.GET.get("type"), **extract_sep9_fields(request.GET), }
def withdraw( account: str, client_domain: Optional[str], request: Request, ) -> Response: args = parse_request_args(request) if "error" in args: return args["error"] args["account"] = account transaction_id = create_transaction_id() transaction_id_hex = transaction_id.hex padded_hex_memo = "0" * (64 - len(transaction_id_hex)) + transaction_id_hex transaction_memo = memo_hex_to_base64(padded_hex_memo) transaction = Transaction( id=transaction_id, stellar_account=account, asset=args["asset"], kind=Transaction.KIND.withdrawal, status=Transaction.STATUS.pending_user_transfer_start, receiving_anchor_account=args["asset"].distribution_account, memo=transaction_memo, memo_type=Transaction.MEMO_TYPES.hash, protocol=Transaction.PROTOCOL.sep6, more_info_url=request.build_absolute_uri( f"{SEP6_MORE_INFO_PATH}?id={transaction_id}"), on_change_callback=args["on_change_callback"], client_domain=client_domain, ) # All request arguments are validated in parse_request_args() # except 'type', 'dest', and 'dest_extra'. Since Polaris doesn't know # which argument was invalid, the anchor is responsible for raising # an exception with a translated message. try: integration_response = rwi.process_sep6_request(args, transaction) except ValueError as e: return render_error_response(str(e)) try: response, status_code = validate_response(args, integration_response, transaction) except ValueError as e: logger.error(str(e)) return render_error_response(_("unable to process the request"), status_code=500) if status_code == 200: response["memo"] = transaction.memo response["memo_type"] = transaction.memo_type logger.info(f"Created withdraw transaction {transaction.id}") transaction.save() elif Transaction.objects.filter(id=transaction.id).exists(): logger.error( "Do not save transaction objects for invalid SEP-6 requests") return render_error_response(_("unable to process the request"), status_code=500) return Response(response, status=status_code)
def validate_language( lang: str, content_type: str = "application/json") -> Optional[Response]: if not lang: return render_error_response(_("missing language code in request"), content_type=content_type) elif not _is_supported_language(lang): return render_error_response(_("unsupported language: %s" % lang), content_type=content_type)
def fee(request: Request, sep6: bool = False) -> Response: """ Definition of the /fee endpoint, in accordance with SEP-0024. See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#fee """ deposit_op = polaris_settings.OPERATION_DEPOSIT withdrawal_op = polaris_settings.OPERATION_WITHDRAWAL operation = request.GET.get("operation") op_type = request.GET.get("type") asset_code = request.GET.get("asset_code") amount_str = request.GET.get("amount") # Verify that the asset code exists in our database: protocol_filter = { "sep6_enabled": True } if sep6 else { "sep24_enabled": True } asset = Asset.objects.filter(code=asset_code, **protocol_filter).first() if not asset_code or not asset: return render_error_response("invalid 'asset_code'") # Verify that amount is provided, and that can be parsed into a decimal: try: amount = Decimal(amount_str) except (DecimalException, TypeError): return render_error_response("invalid 'amount'") error_resp = None # Verify that the requested operation is valid: if operation not in (deposit_op, withdrawal_op): error_resp = render_error_response( f"'operation' should be either '{deposit_op}' or '{withdrawal_op}'" ) # Verify asset is enabled and within the specified limits elif operation == deposit_op: error_resp = verify_valid_asset_operation(asset, amount, Transaction.KIND.deposit) elif operation == withdrawal_op: error_resp = verify_valid_asset_operation(asset, amount, Transaction.KIND.withdrawal) if error_resp: return error_resp else: return Response({ "fee": registered_fee_func({ "operation": operation, "type": op_type, "asset_code": asset_code, "amount": amount, }) })
def more_info(request: Request, sep6: bool = False) -> Response: try: request_transaction = _get_transaction_from_request(request, sep6=sep6) except (AttributeError, ValidationError) as exc: return render_error_response(str(exc), content_type="text/html") except Transaction.DoesNotExist: return render_error_response( _("transaction not found"), status_code=status.HTTP_404_NOT_FOUND, content_type="text/html", ) serializer = TransactionSerializer(request_transaction, context={ "request": request, "sep6": sep6 }) tx_json = json.dumps({"transaction": serializer.data}) context = { "tx_json": tx_json, "amount_in": serializer.data.get("amount_in"), "amount_out": serializer.data.get("amount_out"), "amount_fee": serializer.data.get("amount_fee"), "transaction": request_transaction, "asset_code": request_transaction.asset.code, } if request_transaction.kind == Transaction.KIND.deposit: content = rdi.content_for_template(Template.MORE_INFO, transaction=request_transaction) if request_transaction.status == Transaction.STATUS.pending_user_transfer_start: context.update(instructions=rdi.instructions_for_pending_deposit( request_transaction)) else: content = rwi.content_for_template(Template.MORE_INFO, transaction=request_transaction) if content: context.update(content) if registered_scripts_func is not scripts: logger.warning( "DEPRECATED: the `scripts` Polaris integration function will be " "removed in Polaris 2.0 in favor of allowing the anchor to override " "and extend Polaris' Django templates. See the Template Extensions " "documentation for more information.") context["scripts"] = registered_scripts_func(content) # more_info.html will update the 'callback' parameter value to 'success' after # making the callback. If the page is then reloaded, the callback is not included # in the rendering context, ensuring only one successful callback request is made. callback = request.GET.get("callback") if callback and callback != "success": context["callback"] = callback return Response(context, template_name="polaris/more_info.html")
def deposit(account: str, client_domain: Optional[str], request: Request,) -> Response: args = parse_request_args(request) if "error" in args: return args["error"] args["account"] = account transaction_id = create_transaction_id() transaction = Transaction( id=transaction_id, stellar_account=account, asset=args["asset"], kind=Transaction.KIND.deposit, status=Transaction.STATUS.pending_user_transfer_start, memo=args["memo"], memo_type=args["memo_type"] or Transaction.MEMO_TYPES.text, to_address=account, protocol=Transaction.PROTOCOL.sep6, more_info_url=request.build_absolute_uri( f"{SEP6_MORE_INFO_PATH}?id={transaction_id}" ), claimable_balance_supported=args["claimable_balance_supported"], on_change_callback=args["on_change_callback"], client_domain=client_domain, ) try: integration_response = rdi.process_sep6_request(args, transaction) except ValueError as e: return render_error_response(str(e)) except APIException as e: return render_error_response(str(e), status_code=e.status_code) try: response, status_code = validate_response( args, integration_response, transaction ) except (ValueError, KeyError) as e: logger.error(str(e)) return render_error_response( _("unable to process the request"), status_code=500 ) if status_code == 200: logger.info(f"Created deposit transaction {transaction.id}") transaction.save() elif Transaction.objects.filter(id=transaction.id).exists(): logger.error("Do not save transaction objects for invalid SEP-6 requests") return render_error_response( _("unable to process the request"), status_code=500 ) return Response(response, status=status_code)
def delete(account_from_auth: str, request: Request, account: str,) -> Response: if account_from_auth != account: return render_error_response(_("account not found"), status_code=404) try: make_memo(request.data.get("memo"), request.data.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) try: rci.delete(account, request.data.get("memo"), request.data.get("memo_type")) except ObjectDoesNotExist: return render_error_response(_("account not found"), status_code=404) else: return Response({"status": "success"}, status=200)
def delete(account_from_auth: str, request: Request, account: str) -> Response: if account_from_auth != account: return render_error_response("account not found", status_code=404) try: memo = memo_str(request.data.get("memo"), request.data.get("memo_type")) except ValueError as e: return render_error_response("invalid 'memo' for 'memo_type'") try: rci.delete(account, memo, request.data.get("memo_type")) except ValueError: return render_error_response("account not found", status_code=404) else: return Response({}, status=200)
def callback(account: str, request: Request) -> Response: if request.data.get("id"): if not isinstance(request.data.get("id"), str): return render_error_response(_("bad ID value, expected str")) elif ( request.data.get("account") or request.data.get("memo") or request.data.get("memo_type") ): return render_error_response( _( "requests with 'id' cannot also have 'account', 'memo', or 'memo_type'" ) ) elif account != request.data.get("account"): return render_error_response( _("The account specified does not match authorization token"), status_code=403, ) try: # validate memo and memo_type make_memo(request.data.get("memo"), request.data.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) callback_url = request.data.get("url") if not callback_url: return render_error_response(_("callback 'url' required")) schemes = ["https"] if not settings.LOCAL_MODE else ["https", "http"] try: URLValidator(schemes=schemes)(request.data.get("url")) except ValidationError: return render_error_response(_("'url' must be a valid URL")) try: rci.callback( { "id": request.data.get("id"), "account": account, "memo": request.data.get("memo"), "memo_type": request.data.get("memo_type"), "url": callback_url, } ) except ValueError as e: return render_error_response(str(e), status_code=400) except ObjectDoesNotExist as e: return render_error_response(str(e), status_code=404) except NotImplementedError: return render_error_response(_("not implemented"), status_code=501) return Response({"success": True})
def parse_request_args(request: Request) -> Dict: asset = Asset.objects.filter(code=request.GET.get("asset_code"), sep6_enabled=True, withdrawal_enabled=True).first() if not asset: return {"error": render_error_response(_("invalid 'asset_code'"))} lang = request.GET.get("lang") if lang: err_resp = validate_language(lang) if err_resp: return {"error": err_resp} activate_lang_for_request(lang) memo_type = request.GET.get("memo_type") if memo_type and memo_type not in Transaction.MEMO_TYPES: return {"error": render_error_response(_("invalid 'memo_type'"))} try: memo = memo_str(request.GET.get("memo"), memo_type) except (ValueError, MemoInvalidException): return { "error": render_error_response(_("invalid 'memo' for 'memo_type'")) } if not request.GET.get("type"): return {"error": render_error_response(_("'type' is required"))} if not request.GET.get("dest"): return {"error": render_error_response(_("'dest' is required"))} args = { "asset": asset, "memo_type": memo_type, "memo": memo, "lang": request.GET.get("lang"), "type": request.GET.get("type"), "dest": request.GET.get("dest"), "dest_extra": request.GET.get("dest_extra"), **extract_sep9_fields(request.GET), } # add remaining extra params, it's on the anchor to validate them for param, value in request.GET.items(): if param not in args: args[param] = value return args
def interactive_args_validation(request: Request) -> Dict: """ Validates the arguments passed to the /webapp endpoints Returns a dictionary, either containing an 'error' response object or the transaction and asset objects specified by the incoming request. """ transaction_id = request.GET.get("transaction_id") asset_code = request.GET.get("asset_code") callback = request.GET.get("callback") amount_str = request.GET.get("amount") asset = Asset.objects.filter(code=asset_code, sep24_enabled=True).first() if not transaction_id: return dict(error=render_error_response( _("no 'transaction_id' provided"), content_type="text/html")) elif not (asset_code and asset): return dict(error=render_error_response(_("invalid 'asset_code'"), content_type="text/html")) try: transaction = Transaction.objects.get(id=transaction_id, asset=asset) except (Transaction.DoesNotExist, ValidationError): return dict(error=render_error_response( _("Transaction with ID and asset_code not found"), content_type="text/html", status_code=status.HTTP_404_NOT_FOUND, )) # Verify that amount is provided, and that can be parsed into a decimal: amount = None if amount_str: try: amount = Decimal(amount_str) except (DecimalException, TypeError): return dict(error=render_error_response("invalid 'amount'")) err_resp = verify_valid_asset_operation(asset, amount, transaction.kind, content_type="text/html") if err_resp: return dict(error=err_resp) return dict(transaction=transaction, asset=asset, callback=callback, amount=amount)
def deposit(account: str, request: Request) -> Response: args = parse_request_args(request) if "error" in args: return args["error"] args["account"] = account transaction_id = create_transaction_id() transaction = Transaction( id=transaction_id, stellar_account=account, asset=args["asset"], kind=Transaction.KIND.deposit, status=Transaction.STATUS.pending_user_transfer_start, memo=args["memo"], memo_type=args["memo_type"] or Transaction.MEMO_TYPES.text, to_address=account, protocol=Transaction.PROTOCOL.sep6, ) try: integration_response = rdi.process_sep6_request(args, transaction) except ValueError as e: return render_error_response(str(e)) except APIException as e: return render_error_response(str(e), status_code=e.status_code) try: response, status_code = validate_response( args, integration_response, transaction ) except (ValueError, KeyError): return render_error_response( _("unable to process the request"), status_code=500 ) if status_code == 200: logger.info(f"Created deposit transaction {transaction.id}") transaction.save() elif Transaction.objects.filter(id=transaction.id).exists(): logger.error("Do not save transaction objects for invalid SEP-6 requests") return render_error_response( _("unable to process the request"), status_code=500 ) return Response(response, status=status_code)
def more_info(request: Request, sep6: bool = False) -> Response: try: request_transaction = _get_transaction_from_request(request, sep6=sep6) except (AttributeError, ValidationError) as exc: return render_error_response(str(exc), content_type="text/html") except Transaction.DoesNotExist: return render_error_response( _("transaction not found"), status_code=status.HTTP_404_NOT_FOUND, content_type="text/html", ) serializer = TransactionSerializer(request_transaction, context={ "request": request, "sep6": sep6 }) tx_json = json.dumps({"transaction": serializer.data}) resp_data = { "tx_json": tx_json, "amount_in": serializer.data.get("amount_in"), "amount_out": serializer.data.get("amount_out"), "amount_fee": serializer.data.get("amount_fee"), "transaction": request_transaction, "asset_code": request_transaction.asset.code, } if request_transaction.kind == Transaction.KIND.deposit: content = rdi.content_for_template(Template.MORE_INFO, transaction=request_transaction) if request_transaction.status == Transaction.STATUS.pending_user_transfer_start: resp_data["instructions"] = rdi.instructions_for_pending_deposit( request_transaction) else: content = rwi.content_for_template(Template.MORE_INFO, transaction=request_transaction) resp_data["scripts"] = registered_scripts_func(content) if content: resp_data.update(content) callback = request.GET.get("callback") if callback: resp_data["callback"] = callback return Response(resp_data, template_name="transaction/more_info.html")
def wrapper(request: Request, *args, **kwargs) -> Response: try: authenticate_session_helper(request) except ValueError as e: return render_error_response(str(e), content_type=content_type, status_code=403) else: return view(request)
def validate_url(url) -> Optional[Dict]: schemes = ["https"] if not settings.LOCAL_MODE else ["https", "http"] try: URLValidator(schemes=schemes)(url) except ValidationError: return dict(error=render_error_response( _("invalid callback URL provided"), content_type="text/html", ))
def confirm_email(request: Request) -> Response: if not (request.GET.get("token") and request.GET.get("email")): return render_error_response("email and token arguments required.", content_type="text/html") try: account = PolarisStellarAccount.objects.get( user__email=request.GET.get("email"), confirmation_token=request.GET.get("token"), ) except PolarisStellarAccount.DoesNotExist: return render_error_response( "User with email and token does not exist", content_type="text/html") account.confirmed = True account.save() return Response(template_name="email_confirmed.html")
def transactions(account: str, request: Request) -> Response: """ Definition of the /transactions endpoint, in accordance with SEP-0006. See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0006.md#transaction-history """ if account != request.GET.get("account"): return render_error_response( _("The account specified does not match authorization token"), status_code=403, ) return endpoints.transactions(request, account, sep6=True)
def post(self, request: Request, *_args, **_kwargs) -> Response: envelope_xdr = request.data.get("transaction") if not envelope_xdr: return render_error_response(gettext("'transaction' is required")) client_domain, error_response = self._validate_challenge_xdr( envelope_xdr) if error_response: return error_response else: return Response( {"token": self._generate_jwt(envelope_xdr, client_domain)})
def post(self, request: Request, *_args, **_kwargs) -> Response: envelope_xdr = request.data.get("transaction") if not envelope_xdr: return render_error_response(_("'transaction' is required")) try: self._validate_challenge_xdr(envelope_xdr) except ValueError as e: return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) else: return Response({"token": self._generate_jwt(envelope_xdr)})
def put_verification(account: str, _client_domain: Optional[str], request) -> Response: if not request.data.get("id") or not isinstance(request.data.get("id"), str): return render_error_response(_("bad ID value, expected str")) for key, value in request.data.items(): if key == "id": continue sep9_field, verification = key.rsplit("_", 1) if verification != "verification" or sep9_field not in SEP_9_FIELDS: return render_error_response( _("all request attributes other than 'id' must be a SEP-9 field followed " "by '_verification'")) try: response_data = rci.put_verification(account, dict(request.data)) except ObjectDoesNotExist: return render_error_response(_("customer not found"), status_code=404) except ValueError as e: return render_error_response(str(e), status_code=400) except NotImplementedError: return render_error_response(_("not implemented"), status_code=501) try: validate_response_data(response_data) except ValueError: logger.exception( _("An exception was raised validating PUT /customer/verification response" )) return render_error_response(_("unable to process request."), status_code=500) return Response(response_data)
def patch_transaction( account: str, _client_domain: Optional[str], request: Request, transaction_id: str ): try: t = Transaction.objects.get( id=transaction_id, stellar_account=account, protocol=Transaction.PROTOCOL.sep6, ) except (ValidationError, ObjectDoesNotExist): return render_error_response(_("transaction not found"), status_code=404) if t.status != Transaction.STATUS.pending_transaction_info_update: return render_error_response(_("update not required")) try: validate_patch_request_fields(request.data, t) except ValueError as e: return render_error_response(str(e)) except RuntimeError as e: logger.exception(str(e)) return render_error_response(_("unable to process request"), status_code=500) integration = rdi if t.kind == Transaction.KIND.deposit else rwi try: integration.patch_transaction(params=request.data, transaction=t) except ValueError as e: return render_error_response(str(e)) except NotImplementedError: return render_error_response(_("not implemented"), status_code=501) t.status = Transaction.STATUS.pending_anchor t.required_info_updates = None t.required_info_message = None t.save() return Response(status=200)
def info(request: Request) -> Response: info_data = { "receive": {}, } for asset in Asset.objects.filter(sep31_enabled=True): try: fields_and_types = registered_sep31_receiver_integration.info( asset, request.GET.get("lang")) except ValueError: return render_error_response("unsupported 'lang'") try: validate_info_response(fields_and_types) except ValueError as e: logger.error(f"info integration error: {str(e)}") return render_error_response( _("unable to process the request"), status_code=500, ) info_data["receive"][asset.code] = get_asset_info( asset, fields_and_types) return Response(info_data)