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 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 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 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 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 deposit(account: str, request: Request) -> Response: """ POST /transactions/deposit/interactive Creates an `incomplete` deposit Transaction object in the database and returns the URL entry-point for the interactive flow. """ asset_code = request.POST.get("asset_code") stellar_account = request.POST.get("account") lang = request.POST.get("lang") 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) # Verify that the request is valid. if not all([asset_code, stellar_account]): return render_error_response( _("`asset_code` and `account` are required parameters")) # Ensure memo won't cause stellar transaction to fail when submitted try: memo = memo_str(request.POST.get("memo"), request.POST.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) amount = None if request.POST.get("amount"): try: amount = Decimal(request.POST.get("amount")) except DecimalException as e: return render_error_response(_("Invalid 'amount'")) # Verify that the asset code exists in our database, with deposit enabled. asset = Asset.objects.filter(code=asset_code).first() if not asset: return render_error_response(_("unknown asset: %s") % asset_code) elif not (asset.deposit_enabled and asset.sep24_enabled): return render_error_response( _("invalid operation for asset %s") % asset_code) try: Keypair.from_public_key(stellar_account) except Ed25519PublicKeyInvalidError: return render_error_response(_("invalid 'account'")) try: rdi.save_sep9_fields(stellar_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)) # Construct interactive deposit pop-up URL. transaction_id = create_transaction_id() Transaction.objects.create( id=transaction_id, stellar_account=account, asset=asset, kind=Transaction.KIND.deposit, status=Transaction.STATUS.incomplete, to_address=account, protocol=Transaction.PROTOCOL.sep24, memo=memo, memo_type=request.POST.get("memo_type") or Transaction.MEMO_TYPES.hash, ) logger.info(f"Created deposit transaction {transaction_id}") url = interactive_url( request, str(transaction_id), account, asset_code, settings.OPERATION_DEPOSIT, amount, ) return Response( { "type": "interactive_customer_info_needed", "url": url, "id": transaction_id }, status=status.HTTP_200_OK, )
def deposit(account: str, client_domain: Optional[str], request: Request) -> Response: """ POST /transactions/deposit/interactive Creates an `incomplete` deposit Transaction object in the database and returns the URL entry-point for the interactive flow. """ asset_code = request.data.get("asset_code") stellar_account = request.data.get("account") lang = request.data.get("lang") sep9_fields = extract_sep9_fields(request.data) claimable_balance_supported = request.data.get( "claimable_balance_supported") if not claimable_balance_supported: claimable_balance_supported = False elif isinstance(claimable_balance_supported, str): if claimable_balance_supported.lower() not in ["true", "false"]: return render_error_response( _("'claimable_balance_supported' value must be 'true' or 'false'" )) claimable_balance_supported = claimable_balance_supported.lower( ) == "true" elif not isinstance(claimable_balance_supported, bool): return render_error_response( _("unexpected data type for 'claimable_balance_supprted'. Expected string or boolean." )) if lang: err_resp = validate_language(lang) if err_resp: return err_resp activate_lang_for_request(lang) # Verify that the request is valid. if not all([asset_code, stellar_account]): return render_error_response( _("`asset_code` and `account` are required parameters")) # Ensure memo won't cause stellar transaction to fail when submitted try: make_memo(request.data.get("memo"), request.data.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) # Verify that the asset code exists in our database, with deposit enabled. asset = Asset.objects.filter(code=asset_code).first() if not asset: return render_error_response(_("unknown asset: %s") % asset_code) elif not (asset.deposit_enabled and asset.sep24_enabled): return render_error_response( _("invalid operation for asset %s") % asset_code) amount = None if request.data.get("amount"): try: amount = Decimal(request.data.get("amount")) except DecimalException: return render_error_response(_("invalid 'amount'")) if not (asset.deposit_min_amount <= amount <= asset.deposit_max_amount): return render_error_response(_("invalid 'amount'")) try: Keypair.from_public_key(stellar_account) except Ed25519PublicKeyInvalidError: return render_error_response(_("invalid 'account'")) try: rdi.save_sep9_fields( stellar_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)) # Construct interactive deposit pop-up URL. transaction_id = create_transaction_id() Transaction.objects.create( id=transaction_id, stellar_account=account, asset=asset, kind=Transaction.KIND.deposit, status=Transaction.STATUS.incomplete, to_address=account, protocol=Transaction.PROTOCOL.sep24, claimable_balance_supported=claimable_balance_supported, memo=request.data.get("memo"), memo_type=request.data.get("memo_type") or Transaction.MEMO_TYPES.hash, more_info_url=request.build_absolute_uri( f"{reverse('more_info')}?id={transaction_id}"), client_domain=client_domain, ) logger.info(f"Created deposit transaction {transaction_id}") url = interactive_url( request, str(transaction_id), account, asset_code, settings.OPERATION_DEPOSIT, amount, ) return Response( { "type": "interactive_customer_info_needed", "url": url, "id": transaction_id }, status=status.HTTP_200_OK, )
def withdraw( account: str, client_domain: Optional[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.data.get("lang") asset_code = request.data.get("asset_code") sep9_fields = extract_sep9_fields(request.data) 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")) # 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 and asset.distribution_account): return render_error_response( _("invalid operation for asset %s") % asset_code) amount = None if request.data.get("amount"): try: amount = Decimal(request.data.get("amount")) except DecimalException: return render_error_response(_("invalid 'amount'")) if not (asset.withdrawal_min_amount <= amount <= asset.withdrawal_max_amount): return render_error_response(_("invalid 'amount'")) 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, more_info_url=request.build_absolute_uri( f"{reverse('more_info')}?id={transaction_id}"), client_domain=client_domain, ) logger.info(f"Created withdrawal transaction {transaction_id}") url = interactive_url( request, str(transaction_id), account, asset_code, settings.OPERATION_WITHDRAWAL, amount, ) return Response({ "type": "interactive_customer_info_needed", "url": url, "id": transaction_id })