robosats/api/views.py

1010 lines
39 KiB
Python
Raw Permalink Normal View History

2022-10-25 21:04:12 +03:00
from datetime import datetime, timedelta
from decouple import config
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Q, Sum
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from django.http import HttpResponseBadRequest
from drf_spectacular.utils import extend_schema
2022-01-07 00:36:22 +03:00
from rest_framework import status, viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.generics import CreateAPIView, ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
2022-10-25 21:04:12 +03:00
from rest_framework.views import APIView
2022-10-25 21:04:12 +03:00
from api.logics import Logics
from api.models import (
Currency,
LNPayment,
MarketTick,
OnchainPayment,
Order,
Notification,
)
from api.notifications import Notifications
2022-10-20 12:56:10 +03:00
from api.oas_schemas import (
BookViewSchema,
HistoricalViewSchema,
InfoViewSchema,
LimitViewSchema,
MakerViewSchema,
OrderViewSchema,
PriceViewSchema,
RewardViewSchema,
RobotViewSchema,
2022-10-20 12:56:10 +03:00
StealthViewSchema,
TickViewSchema,
NotificationSchema,
2022-10-20 12:56:10 +03:00
)
from api.serializers import (
2022-10-25 21:04:12 +03:00
ClaimRewardSerializer,
2022-10-20 12:56:10 +03:00
InfoSerializer,
ListOrderSerializer,
MakeOrderSerializer,
OrderPublicSerializer,
PriceSerializer,
StealthSerializer,
2022-10-25 21:04:12 +03:00
TickSerializer,
UpdateOrderSerializer,
ListNotificationSerializer,
2022-10-20 12:56:10 +03:00
)
from api.utils import (
2022-10-25 21:04:12 +03:00
compute_avg_premium,
compute_premium_percentile,
Add core-lightning as backend lightning node vendor (#611) * Add CLN node backend image and service (#418) * Add cln service * Add hodlvoice Dockerfile and entrypoint * Add lnnode vendor switch (#431) * Add LNNode vendor switch * Add CLN version to frontend and other fixes * init * first draft * add unsettled_local_balance and unsettled_remote_balance * gen_hold_invoice now takes 3 more variables to build a label for cln * remove unneeded payment_hash from gen_hold_invoice * remove comment * add get_cln_version * first draft of clns follow_send_payment * fix name of get_lnd_version * enable flake8 * flake8 fixes * renaming cln file, class and get_version * remove lnd specific commented code * get_version: add try/except, refactor to top to mimic lnd.py * rename htlc_cltv to htlc_expiry * add clns lookup_invoice_status * refactored double_check_htlc_is_settled to the end to match lnds file * fix generate_rpc * Add sample environmental variables, small fixes * Fix CLN gRPC port * Fix gen_hold_invoice, plus some other tiny fixes (#435) * Fix channel_balance to use int object inside Amount (#438) * Add CLN/LND volume to celery-beat service * Add CLN/LND volume to celery-beat service * Bump CLN to v23.05 * changes for 0.5 and some small fixes * change invoice expiry from absolute to relative duration * add try/except to catch timeout error * fix failure_reason to be ln_payment failure reasons, albeit inaccurate sometimes * refactor follow_send_payment and add pending check to expired case * fix status comments * add send_keysend method * fix wrong state ints in cancel and settle * switch to use hodlinvoicelookup in double_check * move pay command after lnpayment status update * remove loop in follow_send_payment and add error result for edge case * fix typeerror for payment_hash * rework follow_send_payment logic and payment_hash, watch harder if pending * use fully qualified names for status instead of raw int * missed 2 status from prev commit * Always copy the cln-grpc-hodl plugin on start up * Fix ALLOW_SELF_KEYSEND linting error * Fix missing definition of failure_reason --------- Co-authored-by: daywalker90 <admin@noserver4u.de>
2023-05-22 17:56:15 +03:00
get_cln_version,
2022-10-20 12:56:10 +03:00
get_lnd_version,
get_robosats_commit,
verify_signed_message,
2022-10-20 12:56:10 +03:00
)
Add turtle chat component Squashed commit of the following: commit f60870fcfe574dc4ab1343e25241b6ef7cc2721b Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Thu Nov 10 10:30:42 2022 -0800 Fix internal error when entering chat commit f1eeb49f2a86575eb2e85cdff20460276e71b806 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 10:08:22 2022 -0800 Fix final serializer commit d0c08ba6ad4378a9539c0be83b6f4f8b958b532e Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:44:57 2022 -0800 Chat API changes commit a66bf64edc06d936612db6bf75476b54e6a84334 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:28:29 2022 -0800 Fix param on post commit 60b18d13c2ec625497323371a2a6f64c9c911e47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 08:56:25 2022 -0800 Fix serializer commit 11212d30eeffde37e07d2e6e5c1fb36df46916ad Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 21:07:18 2022 +0100 CR 2 commit c82790cb81dd9919de97c39f2553974784ffe92d Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 20:09:18 2022 +0100 Fix commit 605a3b69a1fcf795e45b2acba1e12436f8545f8a Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 14:44:42 2022 +0100 CR commit 09776e9c8fa85c253f28c75361829dab5df4d978 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 18:12:29 2022 +0100 translations commit 432e4d23991164b164d2ab3e4f31790a992dc601 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 17:39:02 2022 +0100 Switch and better UX commit df6e476613006f6a861bab68f8a4261bc8f641e0 Author: KoalaSat <yv1vtrul@duck.com> Date: Tue Nov 1 18:20:01 2022 +0100 Unused code commit 5b8d6b4d32980e31bb1d682444b53df1a8e16c47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Mon Oct 31 09:20:20 2022 -0700 Add Chat Turtle Mode
2022-11-11 12:28:09 +03:00
from chat.models import Message
2022-10-25 21:04:12 +03:00
from control.models import AccountingDay, BalanceLog
2022-01-02 01:13:27 +03:00
2022-02-17 22:50:10 +03:00
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
RETRY_TIME = int(config("RETRY_TIME"))
2022-02-07 16:37:16 +03:00
2022-02-17 22:50:10 +03:00
class MakerView(CreateAPIView):
2022-02-17 22:50:10 +03:00
serializer_class = MakeOrderSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
@extend_schema(**MakerViewSchema.post)
2022-02-17 22:50:10 +03:00
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not request.user.is_authenticated:
2022-02-17 22:50:10 +03:00
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "Woops! It seems you do not have a robot avatar"},
2022-02-17 22:50:10 +03:00
status.HTTP_400_BAD_REQUEST,
)
if not serializer.is_valid():
return Response(status=status.HTTP_400_BAD_REQUEST)
2022-02-25 23:08:22 +03:00
# In case it gets overwhelming. Limit the number of public orders.
2022-10-20 12:56:10 +03:00
if Order.objects.filter(status=Order.Status.PUB).count() >= int(
config("MAX_PUBLIC_ORDERS")
):
2022-02-25 23:08:22 +03:00
return Response(
{
2023-09-08 18:33:00 +03:00
"bad_request": f"The RoboSats {config('COORDINATOR_ALIAS', cast=str, default='NoAlias')} coordinator book is at full capacity! Current limit is {config('MAX_PUBLIC_ORDERS', cast=str)} orders"
2022-02-25 23:08:22 +03:00
},
status.HTTP_400_BAD_REQUEST,
)
# Only allow users who are not already engaged in an order
valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
if not valid:
return Response(context, status.HTTP_409_CONFLICT)
2022-02-25 23:08:22 +03:00
2022-02-17 22:50:10 +03:00
type = serializer.data.get("type")
currency = serializer.data.get("currency")
amount = serializer.data.get("amount")
has_range = serializer.data.get("has_range")
min_amount = serializer.data.get("min_amount")
max_amount = serializer.data.get("max_amount")
2022-02-17 22:50:10 +03:00
payment_method = serializer.data.get("payment_method")
premium = serializer.data.get("premium")
satoshis = serializer.data.get("satoshis")
is_explicit = serializer.data.get("is_explicit")
2022-03-19 00:21:13 +03:00
public_duration = serializer.data.get("public_duration")
escrow_duration = serializer.data.get("escrow_duration")
2022-03-19 01:09:38 +03:00
bond_size = serializer.data.get("bond_size")
latitude = serializer.data.get("latitude")
longitude = serializer.data.get("longitude")
2022-03-19 01:09:38 +03:00
# Optional params
if public_duration is None:
public_duration = 60 * 60 * settings.DEFAULT_PUBLIC_ORDER_DURATION
if escrow_duration is None:
escrow_duration = 60 * settings.INVOICE_AND_ESCROW_DURATION
if bond_size is None:
bond_size = settings.DEFAULT_BOND_SIZE
if has_range is None:
2022-10-20 12:56:10 +03:00
has_range = False
# An order can either have an amount or a range (min_amount and max_amount)
if has_range:
amount = None
else:
min_amount = None
max_amount = None
# Either amount or min_max has to be specified.
if has_range and (min_amount is None or max_amount is None):
return Response(
{
2022-10-20 12:56:10 +03:00
"bad_request": "You must specify min_amount and max_amount for a range order"
},
status.HTTP_400_BAD_REQUEST,
)
elif not has_range and amount is None:
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "You must specify an order amount"},
status.HTTP_400_BAD_REQUEST,
)
2022-01-06 23:33:40 +03:00
# Creates a new order
order = Order(
type=type,
currency=Currency.objects.get(id=currency),
2022-01-06 23:33:40 +03:00
amount=amount,
has_range=has_range,
min_amount=min_amount,
max_amount=max_amount,
2022-01-06 23:33:40 +03:00
payment_method=payment_method,
premium=premium,
satoshis=satoshis,
is_explicit=is_explicit,
2022-10-20 12:56:10 +03:00
expires_at=timezone.now() + timedelta(seconds=EXP_MAKER_BOND_INVOICE),
2022-02-17 22:50:10 +03:00
maker=request.user,
2022-03-19 00:21:13 +03:00
public_duration=public_duration,
escrow_duration=escrow_duration,
2022-03-19 01:09:38 +03:00
bond_size=bond_size,
latitude=latitude,
longitude=longitude,
2022-02-17 22:50:10 +03:00
)
2022-01-07 00:36:22 +03:00
order.last_satoshis = order.t0_satoshis = Logics.satoshis_now(order)
2022-01-06 23:33:40 +03:00
2022-01-07 00:36:22 +03:00
valid, context = Logics.validate_order_size(order)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
valid, context = Logics.validate_location(order)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-01-06 23:33:40 +03:00
order.save()
order.log(
f"Order({order.id},{order}) created by Robot({request.user.robot.id},{request.user})"
)
2022-10-20 12:56:10 +03:00
return Response(ListOrderSerializer(order).data, status=status.HTTP_201_CREATED)
class OrderView(viewsets.ViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
2022-01-06 23:33:40 +03:00
serializer_class = UpdateOrderSerializer
2022-02-17 22:50:10 +03:00
lookup_url_kwarg = "order_id"
@extend_schema(**OrderViewSchema.get)
def get(self, request, format=None):
2022-02-17 22:50:10 +03:00
"""
Full trade pipeline takes place while looking/refreshing the order page.
2022-02-17 22:50:10 +03:00
"""
order_id = request.GET.get(self.lookup_url_kwarg)
2022-01-28 17:30:45 +03:00
if not request.user.is_authenticated:
2022-02-17 22:50:10 +03:00
return Response(
{
2022-10-20 12:56:10 +03:00
"bad_request": "You must have a robot avatar to see the order details"
2022-02-17 22:50:10 +03:00
},
status=status.HTTP_400_BAD_REQUEST,
)
2022-01-28 17:30:45 +03:00
if order_id is None:
2022-02-17 22:50:10 +03:00
return Response(
{"bad_request": "Order ID parameter not found in request"},
status=status.HTTP_400_BAD_REQUEST,
)
order = Order.objects.filter(id=order_id)
# check if exactly one order is found in the db
2022-02-17 22:50:10 +03:00
if len(order) != 1:
2022-10-20 12:56:10 +03:00
return Response(
{"bad_request": "Invalid Order Id"}, status.HTTP_404_NOT_FOUND
)
2022-02-17 22:50:10 +03:00
# This is our order.
order = order[0]
# 2) If order has been cancelled
if order.status == Order.Status.UCA:
2022-02-17 22:50:10 +03:00
return Response(
{"bad_request": "This order has been cancelled by the maker"},
status.HTTP_400_BAD_REQUEST,
)
if order.status == Order.Status.CCA:
2022-02-17 22:50:10 +03:00
return Response(
2022-10-20 12:56:10 +03:00
{"bad_request": "This order has been cancelled collaborativelly"},
2022-02-17 22:50:10 +03:00
status.HTTP_400_BAD_REQUEST,
)
data = ListOrderSerializer(order).data
2022-03-19 00:21:13 +03:00
data["total_secs_exp"] = order.t_to_expire(order.status)
# if user is under a limit (penalty), inform him.
2022-01-10 15:10:32 +03:00
is_penalized, time_out = Logics.is_penalized(request.user)
if is_penalized:
data["penalty"] = request.user.robot.penalty_expiration
2022-01-10 15:10:32 +03:00
# Add booleans if user is maker, taker, partipant, buyer or seller
2022-02-17 22:50:10 +03:00
data["is_maker"] = order.maker == request.user
data["is_taker"] = order.taker == request.user
data["is_participant"] = data["is_maker"] or data["is_taker"]
2022-01-14 15:00:53 +03:00
# 3.a) If not a participant and order is not public, forbid.
2022-02-17 22:50:10 +03:00
if not data["is_participant"] and order.status != Order.Status.PUB:
return Response(
{"bad_request": "This order is not available"},
2022-02-17 22:50:10 +03:00
status.HTTP_403_FORBIDDEN,
)
data["maker_nick"] = str(order.maker)
data["maker_hash_id"] = str(order.maker.robot.hash_id)
# Add activity status of participants based on last_seen
data["maker_status"] = Logics.user_activity_status(order.maker.last_login)
if order.taker is not None:
data["taker_status"] = Logics.user_activity_status(order.taker.last_login)
# 3.b) Non participants can view details (but only if PUB)
if not data["is_participant"] and order.status == Order.Status.PUB:
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
data["satoshis_now"] = Logics.satoshis_now(order)
return Response(data, status=status.HTTP_200_OK)
# 4) If order is between public and WF2
2022-01-18 03:50:54 +03:00
if order.status >= Order.Status.PUB and order.status < Order.Status.WF2:
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
data["satoshis_now"] = Logics.satoshis_now(order)
2022-02-17 22:50:10 +03:00
2022-10-20 12:56:10 +03:00
# 4. a) If maker and Public/Paused, add premium percentile
# num similar orders, and maker information to enable telegram notifications.
2022-10-20 12:56:10 +03:00
if data["is_maker"] and order.status in [
Order.Status.PUB,
Order.Status.PAU,
]:
2022-02-17 22:50:10 +03:00
data["premium_percentile"] = compute_premium_percentile(order)
data["num_similar_orders"] = len(
2022-10-20 12:56:10 +03:00
Order.objects.filter(
currency=order.currency, status=Order.Status.PUB
)
)
2022-01-14 15:00:53 +03:00
2022-01-18 03:50:54 +03:00
# For participants add positions, nicks and status as a message and hold invoices status
2022-02-17 22:50:10 +03:00
data["is_buyer"] = Logics.is_buyer(order, request.user)
data["is_seller"] = Logics.is_seller(order, request.user)
data["taker_nick"] = str(order.taker)
if order.taker:
data["taker_hash_id"] = str(order.taker.robot.hash_id)
2022-02-17 22:50:10 +03:00
data["status_message"] = Order.Status(order.status).label
data["is_fiat_sent"] = order.is_fiat_sent
data["latitude"] = order.latitude
data["longitude"] = order.longitude
2022-02-17 22:50:10 +03:00
data["is_disputed"] = order.is_disputed
data["ur_nick"] = request.user.username
data["satoshis_now"] = order.last_satoshis
2022-01-18 03:50:54 +03:00
# Add whether hold invoices are LOCKED (ACCEPTED)
# Is there a maker bond? If so, True if locked, False otherwise
if order.maker_bond:
data["maker_locked"] = order.maker_bond.status == LNPayment.Status.LOCKED
2022-01-18 03:50:54 +03:00
else:
2022-02-17 22:50:10 +03:00
data["maker_locked"] = False
2022-01-18 03:50:54 +03:00
# Is there a taker bond? If so, True if locked, False otherwise
if order.taker_bond:
data["taker_locked"] = order.taker_bond.status == LNPayment.Status.LOCKED
2022-01-18 03:50:54 +03:00
else:
2022-02-17 22:50:10 +03:00
data["taker_locked"] = False
2022-01-18 03:50:54 +03:00
# Is there an escrow? If so, True if locked, False otherwise
if order.trade_escrow:
data["escrow_locked"] = order.trade_escrow.status == LNPayment.Status.LOCKED
2022-01-18 03:50:54 +03:00
else:
2022-02-17 22:50:10 +03:00
data["escrow_locked"] = False
2022-01-18 03:50:54 +03:00
# If both bonds are locked, participants can see the final trade amount in sats.
if order.status in [
Order.Status.WF2,
Order.Status.WFI,
Order.Status.WFE,
Order.Status.CCA,
Order.Status.FSE,
Order.Status.DIS,
Order.Status.PAY,
Order.Status.SUC,
Order.Status.FAI,
]:
2022-10-20 12:56:10 +03:00
if (
order.maker_bond.status
== order.taker_bond.status
== LNPayment.Status.LOCKED
):
2022-01-18 18:45:04 +03:00
# Seller sees the amount he sends
2022-02-17 22:50:10 +03:00
if data["is_seller"]:
2022-10-20 12:56:10 +03:00
data["trade_satoshis"] = Logics.escrow_amount(order, request.user)[
1
]["escrow_amount"]
2022-01-18 18:45:04 +03:00
# Buyer sees the amount he receives
2022-02-17 22:50:10 +03:00
elif data["is_buyer"]:
2022-10-20 12:56:10 +03:00
data["trade_satoshis"] = Logics.payout_amount(order, request.user)[
1
]["invoice_amount"]
2022-01-09 23:05:19 +03:00
# 5) If status is 'waiting for maker bond' and user is MAKER, reply with a MAKER hold invoice.
2022-02-17 22:50:10 +03:00
if order.status == Order.Status.WFB and data["is_maker"]:
2022-01-09 23:05:19 +03:00
valid, context = Logics.gen_maker_hold_invoice(order, request.user)
if valid:
data = {**data, **context}
else:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-02-17 22:50:10 +03:00
2022-01-09 23:05:19 +03:00
# 6) If status is 'waiting for taker bond' and user is TAKER, reply with a TAKER hold invoice.
2022-02-17 22:50:10 +03:00
elif order.status == Order.Status.TAK and data["is_taker"]:
2022-01-09 23:05:19 +03:00
valid, context = Logics.gen_taker_hold_invoice(order, request.user)
if valid:
data = {**data, **context}
else:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-02-17 22:50:10 +03:00
# 7 a. ) If seller and status is 'WF2' or 'WFE'
2022-10-20 12:56:10 +03:00
elif data["is_seller"] and (
order.status == Order.Status.WF2 or order.status == Order.Status.WFE
):
2022-01-09 23:05:19 +03:00
# If the two bonds are locked, reply with an ESCROW hold invoice.
2022-10-20 12:56:10 +03:00
if (
order.maker_bond.status
== order.taker_bond.status
== LNPayment.Status.LOCKED
):
valid, context = Logics.gen_escrow_hold_invoice(order, request.user)
2022-01-08 20:19:30 +03:00
if valid:
data = {**data, **context}
else:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-02-17 22:50:10 +03:00
# 7.b) If user is Buyer and status is 'WF2' or 'WFI'
2022-10-20 12:56:10 +03:00
elif data["is_buyer"] and (
order.status == Order.Status.WF2 or order.status == Order.Status.WFI
):
# If the two bonds are locked, reply with an AMOUNT and onchain swap cost so he can send the buyer invoice/address.
2022-10-20 12:56:10 +03:00
if (
order.maker_bond.status
== order.taker_bond.status
== LNPayment.Status.LOCKED
):
valid, context = Logics.payout_amount(order, request.user)
2022-01-08 20:19:30 +03:00
if valid:
data = {**data, **context}
else:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
2022-10-20 12:56:10 +03:00
elif order.status in [Order.Status.WFI, Order.Status.CHA, Order.Status.FSE]:
# If all bonds are locked.
2022-10-20 12:56:10 +03:00
if (
order.maker_bond.status
== order.taker_bond.status
== order.trade_escrow.status
== LNPayment.Status.LOCKED
):
2022-01-23 22:02:25 +03:00
# add whether a collaborative cancel is pending or has been asked
2022-02-17 22:50:10 +03:00
if (data["is_maker"] and order.taker_asked_cancel) or (
2022-10-20 12:56:10 +03:00
data["is_taker"] and order.maker_asked_cancel
):
2022-02-17 22:50:10 +03:00
data["pending_cancel"] = True
elif (data["is_maker"] and order.maker_asked_cancel) or (
2022-10-20 12:56:10 +03:00
data["is_taker"] and order.taker_asked_cancel
):
2022-02-17 22:50:10 +03:00
data["asked_for_cancel"] = True
2022-01-23 22:02:25 +03:00
else:
2022-02-17 22:50:10 +03:00
data["asked_for_cancel"] = False
2022-10-20 12:56:10 +03:00
Add turtle chat component Squashed commit of the following: commit f60870fcfe574dc4ab1343e25241b6ef7cc2721b Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Thu Nov 10 10:30:42 2022 -0800 Fix internal error when entering chat commit f1eeb49f2a86575eb2e85cdff20460276e71b806 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 10:08:22 2022 -0800 Fix final serializer commit d0c08ba6ad4378a9539c0be83b6f4f8b958b532e Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:44:57 2022 -0800 Chat API changes commit a66bf64edc06d936612db6bf75476b54e6a84334 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 09:28:29 2022 -0800 Fix param on post commit 60b18d13c2ec625497323371a2a6f64c9c911e47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Tue Nov 8 08:56:25 2022 -0800 Fix serializer commit 11212d30eeffde37e07d2e6e5c1fb36df46916ad Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 21:07:18 2022 +0100 CR 2 commit c82790cb81dd9919de97c39f2553974784ffe92d Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 20:09:18 2022 +0100 Fix commit 605a3b69a1fcf795e45b2acba1e12436f8545f8a Author: KoalaSat <yv1vtrul@duck.com> Date: Sun Nov 6 14:44:42 2022 +0100 CR commit 09776e9c8fa85c253f28c75361829dab5df4d978 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 18:12:29 2022 +0100 translations commit 432e4d23991164b164d2ab3e4f31790a992dc601 Author: KoalaSat <yv1vtrul@duck.com> Date: Wed Nov 2 17:39:02 2022 +0100 Switch and better UX commit df6e476613006f6a861bab68f8a4261bc8f641e0 Author: KoalaSat <yv1vtrul@duck.com> Date: Tue Nov 1 18:20:01 2022 +0100 Unused code commit 5b8d6b4d32980e31bb1d682444b53df1a8e16c47 Author: Reckless_Satoshi <reckless.satoshi@protonmail.com> Date: Mon Oct 31 09:20:20 2022 -0700 Add Chat Turtle Mode
2022-11-11 12:28:09 +03:00
# Add index of last chat message. To be used by client on Chat endpoint to fetch latest messages
messages = Message.objects.filter(order=order)
if len(messages) == 0:
data["chat_last_index"] = 0
else:
data["chat_last_index"] = messages.latest().index
2022-01-10 00:24:48 +03:00
# 9) If status is 'DIS' and all HTLCS are in LOCKED
2022-01-23 22:02:25 +03:00
elif order.status == Order.Status.DIS:
# add whether the dispute statement has been received
2022-02-17 22:50:10 +03:00
if data["is_maker"]:
2022-10-20 12:56:10 +03:00
data["statement_submitted"] = (
order.maker_statement is not None and order.maker_statement != ""
2022-10-20 12:56:10 +03:00
)
2022-02-17 22:50:10 +03:00
elif data["is_taker"]:
2022-10-20 12:56:10 +03:00
data["statement_submitted"] = (
order.taker_statement is not None and order.taker_statement != ""
2022-10-20 12:56:10 +03:00
)
# 9) If status is 'Failed routing', reply with retry amounts, time of next retry and ask for invoice at third.
2022-10-20 12:56:10 +03:00
elif (
order.status == Order.Status.FAI and order.payout.receiver == request.user
): # might not be the buyer if after a dispute where winner wins
2022-02-17 22:50:10 +03:00
data["retries"] = order.payout.routing_attempts
data["next_retry_time"] = order.payout.last_routing_time + timedelta(
2022-10-20 12:56:10 +03:00
minutes=RETRY_TIME
)
if order.payout.failure_reason:
2022-10-20 12:56:10 +03:00
data["failure_reason"] = LNPayment.FailureReason(
order.payout.failure_reason
).label
if order.payout.status == LNPayment.Status.EXPIRE:
2022-02-17 22:50:10 +03:00
data["invoice_expired"] = True
# Add invoice amount once again if invoice was expired.
data["trade_satoshis"] = Logics.payout_amount(order, request.user)[1][
"invoice_amount"
]
2022-02-17 22:50:10 +03:00
# 10) If status is 'Expired', "Sending", "Finished" or "failed routing", add info for renewal:
2022-10-20 12:56:10 +03:00
elif order.status in [
Order.Status.EXP,
Order.Status.SUC,
Order.Status.PAY,
Order.Status.FAI,
]:
data["public_duration"] = order.public_duration
data["bond_size"] = str(order.bond_size)
2022-07-16 14:15:00 +03:00
# Adds trade summary
2022-10-20 12:56:10 +03:00
if order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
valid, context = Logics.summarize_trade(order, request.user)
if valid:
data = {**data, **context}
2022-07-16 14:15:00 +03:00
# If status is 'Expired' add expiry reason
if order.status == Order.Status.EXP:
data["expiry_reason"] = order.expiry_reason
data["expiry_message"] = Order.ExpiryReasons(order.expiry_reason).label
2022-06-16 18:31:30 +03:00
# If status is 'Succes' add final stats and txid if it is a swap
if order.status == Order.Status.SUC:
# If buyer and is a swap, add TXID
2022-10-20 12:56:10 +03:00
if Logics.is_buyer(order, request.user):
2022-06-16 18:31:30 +03:00
if order.is_swap:
data["num_satoshis"] = order.payout_tx.num_satoshis
data["sent_satoshis"] = order.payout_tx.sent_satoshis
data["network"] = str(config("NETWORK"))
2022-10-20 12:56:10 +03:00
if order.payout_tx.status in [
OnchainPayment.Status.MEMPO,
OnchainPayment.Status.CONFI,
]:
2022-06-16 18:31:30 +03:00
data["txid"] = order.payout_tx.txid
elif order.payout_tx.status == OnchainPayment.Status.QUEUE:
data["tx_queued"] = True
data["address"] = order.payout_tx.address
2022-06-16 18:31:30 +03:00
return Response(data, status.HTTP_200_OK)
@extend_schema(**OrderViewSchema.take_update_confirm_dispute_cancel)
2022-01-06 23:33:40 +03:00
def take_update_confirm_dispute_cancel(self, request, format=None):
2022-02-17 22:50:10 +03:00
"""
Here takes place all of the updates to the order object.
2022-01-07 01:39:59 +03:00
That is: take, confim, cancel, dispute, update_invoice or rate.
2022-02-17 22:50:10 +03:00
"""
order_id = request.GET.get(self.lookup_url_kwarg)
2022-01-06 23:33:40 +03:00
serializer = UpdateOrderSerializer(data=request.data)
2022-02-17 22:50:10 +03:00
if not serializer.is_valid():
return Response(status=status.HTTP_400_BAD_REQUEST)
order = Order.objects.get(id=order_id)
# action is either 1)'take', 2)'confirm', 2.b)'undo_confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice'
2022-06-06 20:57:04 +03:00
# 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform'
2022-02-17 22:50:10 +03:00
action = serializer.data.get("action")
pgp_invoice = serializer.data.get("invoice")
routing_budget_ppm = serializer.data.get("routing_budget_ppm", 0)
pgp_address = serializer.data.get("address")
2022-06-06 23:37:51 +03:00
mining_fee_rate = serializer.data.get("mining_fee_rate")
2022-02-17 22:50:10 +03:00
statement = serializer.data.get("statement")
rating = serializer.data.get("rating")
# 1) If action is take, it is a taker request!
2022-02-17 22:50:10 +03:00
if action == "take":
if order.status == Order.Status.PUB:
2022-10-20 12:56:10 +03:00
valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status=status.HTTP_409_CONFLICT)
# For order with amount range, set the amount now.
if order.has_range:
amount = float(serializer.data.get("amount"))
valid, context = Logics.validate_amount_within_range(order, amount)
if not valid:
return Response(context, status=status.HTTP_400_BAD_REQUEST)
valid, context = Logics.take(order, request.user, amount)
else:
valid, context = Logics.take(order, request.user)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status=status.HTTP_403_FORBIDDEN)
2022-01-10 15:10:32 +03:00
2022-01-14 17:19:25 +03:00
return self.get(request)
2022-02-17 22:50:10 +03:00
else:
Response(
{"bad_request": "This order is not public anymore."},
status.HTTP_400_BAD_REQUEST,
)
2022-01-06 23:33:40 +03:00
# Any other action is only allowed if the user is a participant
if not (order.maker == request.user or order.taker == request.user):
2022-02-17 22:50:10 +03:00
return Response(
{"bad_request": "You are not a participant in this order"},
status.HTTP_403_FORBIDDEN,
)
# 2) If action is 'update invoice'
2022-06-19 09:09:21 +03:00
elif action == "update_invoice":
# DEPRECATE post v0.5.1.
valid_signature, invoice = verify_signed_message(
request.user.robot.public_key, pgp_invoice
)
if not valid_signature:
return Response(
{"bad_request": "The PGP signed cleartext message is not valid."},
status.HTTP_400_BAD_REQUEST,
)
valid, context = Logics.update_invoice(
order, request.user, invoice, routing_budget_ppm
)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-10-20 12:56:10 +03:00
# 2.b) If action is 'update address'
2022-06-19 09:09:21 +03:00
elif action == "update_address":
valid_signature, address = verify_signed_message(
request.user.robot.public_key, pgp_address
)
if not valid_signature:
return Response(
{"bad_request": "The PGP signed cleartext message is not valid."},
status.HTTP_400_BAD_REQUEST,
)
2022-10-20 12:56:10 +03:00
valid, context = Logics.update_address(
order, request.user, address, mining_fee_rate
)
2022-06-06 20:57:04 +03:00
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-02-17 22:50:10 +03:00
2022-01-06 23:33:40 +03:00
# 3) If action is cancel
2022-02-17 22:50:10 +03:00
elif action == "cancel":
valid, context = Logics.cancel_order(order, request.user)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-01-06 23:33:40 +03:00
# 4) If action is confirm
2022-02-17 22:50:10 +03:00
elif action == "confirm":
valid, context = Logics.confirm_fiat(order, request.user)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-01-06 23:33:40 +03:00
# 4.b) If action is confirm
elif action == "undo_confirm":
valid, context = Logics.undo_confirm_fiat_sent(order, request.user)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-01-06 23:33:40 +03:00
# 5) If action is dispute
2022-02-17 22:50:10 +03:00
elif action == "dispute":
valid, context = Logics.open_dispute(order, request.user)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-02-17 22:50:10 +03:00
elif action == "submit_statement":
2022-10-20 12:56:10 +03:00
valid, context = Logics.dispute_statement(order, request.user, statement)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
2022-01-06 23:33:40 +03:00
# 6) If action is rate
2022-02-17 22:50:10 +03:00
elif action == "rate_user" and rating:
2023-05-05 15:40:29 +03:00
"""No user rating"""
pass
2022-01-06 23:33:40 +03:00
# 7) If action is rate_platform
2022-02-17 22:50:10 +03:00
elif action == "rate_platform" and rating:
valid, context = Logics.rate_platform(request.user, rating)
2022-02-17 22:50:10 +03:00
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 8) If action is rate_platform
elif action == "pause":
valid, context = Logics.pause_unpause_public_order(order, request.user)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# If nothing of the above... something else is going on. Probably not allowed!
else:
return Response(
2022-02-17 22:50:10 +03:00
{
2022-10-20 12:56:10 +03:00
"bad_request": "The Robotic Satoshis working in the warehouse did not understand you. "
2023-05-09 16:07:16 +03:00
+ "Please, fill a Bug Issue in Github https://github.com/RoboSats/robosats/issues"
2022-02-17 22:50:10 +03:00
},
status.HTTP_501_NOT_IMPLEMENTED,
)
return self.get(request)
class RobotView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
@extend_schema(**RobotViewSchema.get)
def get(self, request, format=None):
"""
Respond with Nickname, pubKey, privKey.
"""
user = request.user
context = {}
context["nickname"] = user.username
context["hash_id"] = user.robot.hash_id
context["public_key"] = user.robot.public_key
context["encrypted_private_key"] = user.robot.encrypted_private_key
context["earned_rewards"] = user.robot.earned_rewards
context["wants_stealth"] = user.robot.wants_stealth
context["last_login"] = user.last_login
# Adds/generate telegram token and whether it is enabled
context = {**context, **Notifications.get_context(user)}
# return active order or last made order if any
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
request.user
)
if not has_no_active_order:
context["active_order_id"] = order.id
else:
last_order = Order.objects.filter(
Q(maker=request.user) | Q(taker=request.user)
).last()
if last_order:
context["last_order_id"] = last_order.id
# Robot was found, only if created +5 mins ago
if user.date_joined < (timezone.now() - timedelta(minutes=5)):
context["found"] = True
return Response(context, status=status.HTTP_200_OK)
class BookView(ListAPIView):
serializer_class = OrderPublicSerializer
2022-02-17 22:50:10 +03:00
queryset = Order.objects.filter(status=Order.Status.PUB)
@extend_schema(**BookViewSchema.get)
2022-02-17 22:50:10 +03:00
def get(self, request, format=None):
currency = request.GET.get("currency", 0)
type = request.GET.get("type", 2)
queryset = Order.objects.filter(status=Order.Status.PUB)
# Currency 0 and type 2 are special cases treated as "ANY". (These are not really possible choices)
if int(currency) == 0 and int(type) != 2:
2022-02-17 22:50:10 +03:00
queryset = Order.objects.filter(type=type, status=Order.Status.PUB)
elif int(type) == 2 and int(currency) != 0:
2022-10-20 12:56:10 +03:00
queryset = Order.objects.filter(currency=currency, status=Order.Status.PUB)
elif not (int(currency) == 0 and int(type) == 2):
2022-10-20 12:56:10 +03:00
queryset = Order.objects.filter(
currency=currency, type=type, status=Order.Status.PUB
)
2022-01-10 00:24:48 +03:00
2022-02-17 22:50:10 +03:00
if len(queryset) == 0:
return Response(
{"not_found": "No orders found, be the first to make one"},
status=status.HTTP_404_NOT_FOUND,
)
book_data = []
for order in queryset:
data = ListOrderSerializer(order).data
2022-02-17 22:50:10 +03:00
data["maker_nick"] = str(order.maker)
2023-11-28 14:13:40 +03:00
data["maker_hash_id"] = str(order.maker.robot.hash_id)
2022-02-17 22:50:10 +03:00
2022-09-10 16:51:15 +03:00
data["satoshis_now"] = Logics.satoshis_now(order)
2022-01-10 04:12:58 +03:00
# Compute current premium for those orders that are explicitly priced.
2023-11-18 19:33:39 +03:00
price, premium = Logics.price_and_premium_now(order)
data["price"], data["premium"] = price, str(premium)
data["maker_status"] = Logics.user_activity_status(order.maker.last_login)
2022-10-20 12:56:10 +03:00
for key in (
"status",
"taker",
): # Non participants should not see the status or who is the taker
del data[key]
2022-02-17 22:50:10 +03:00
book_data.append(data)
2022-02-17 22:50:10 +03:00
return Response(book_data, status=status.HTTP_200_OK)
class NotificationsView(ListAPIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = ListNotificationSerializer
@extend_schema(**NotificationSchema.get)
def get(self, request, format=None):
robot = request.user.robot
queryset = Notification.objects.filter(robot=robot).order_by("-created_at")
created_at = request.GET.get("created_at")
if created_at:
created_at = parse_datetime(created_at)
if not created_at:
return HttpResponseBadRequest("Invalid date format")
queryset = queryset.filter(created_at__gte=created_at)
notification_data = []
for notification in queryset:
data = self.serializer_class(notification).data
data["order_id"] = notification.order.id
notification_data.append(data)
return Response(notification_data, status=status.HTTP_200_OK)
class InfoView(viewsets.ViewSet):
serializer_class = InfoSerializer
@extend_schema(**InfoViewSchema.get)
2022-01-09 04:23:13 +03:00
def get(self, request):
context = {}
2022-02-17 22:50:10 +03:00
context["num_public_buy_orders"] = len(
2022-10-20 12:56:10 +03:00
Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB)
)
2022-02-17 22:50:10 +03:00
context["num_public_sell_orders"] = len(
2022-10-20 12:56:10 +03:00
Order.objects.filter(type=Order.Types.SELL, status=Order.Status.PUB)
)
context["book_liquidity"] = Order.objects.filter(
status=Order.Status.PUB
).aggregate(Sum("last_satoshis"))["last_satoshis__sum"]
context["book_liquidity"] = (
0 if context["book_liquidity"] is None else context["book_liquidity"]
2022-10-20 12:56:10 +03:00
)
# Number of active users (logged in in last 30 minutes)
today = datetime.today()
2022-02-17 22:50:10 +03:00
context["active_robots_today"] = len(
2022-10-20 12:56:10 +03:00
User.objects.filter(last_login__day=today.day)
)
2022-01-18 20:52:48 +03:00
# Compute average premium and volume of today
2022-03-13 15:00:21 +03:00
last_day = timezone.now() - timedelta(days=1)
queryset = MarketTick.objects.filter(timestamp__gt=last_day)
if not len(queryset) == 0:
avg_premium, total_volume = compute_avg_premium(queryset)
# If no contracts, fallback to lifetime avg premium
else:
queryset = MarketTick.objects.all()
2022-03-13 15:00:21 +03:00
avg_premium, _ = compute_avg_premium(queryset)
total_volume = 0
queryset = MarketTick.objects.all()
if not len(queryset) == 0:
volume_contracted = []
for tick in queryset:
volume_contracted.append(tick.volume if tick.volume else 0)
lifetime_volume = sum(volume_contracted)
else:
lifetime_volume = 0
2022-03-13 15:00:21 +03:00
context["last_day_nonkyc_btc_premium"] = round(avg_premium, 2)
context["last_day_volume"] = round(total_volume, 8)
context["lifetime_volume"] = round(lifetime_volume, 8)
2022-02-17 22:50:10 +03:00
context["lnd_version"] = get_lnd_version()
Add core-lightning as backend lightning node vendor (#611) * Add CLN node backend image and service (#418) * Add cln service * Add hodlvoice Dockerfile and entrypoint * Add lnnode vendor switch (#431) * Add LNNode vendor switch * Add CLN version to frontend and other fixes * init * first draft * add unsettled_local_balance and unsettled_remote_balance * gen_hold_invoice now takes 3 more variables to build a label for cln * remove unneeded payment_hash from gen_hold_invoice * remove comment * add get_cln_version * first draft of clns follow_send_payment * fix name of get_lnd_version * enable flake8 * flake8 fixes * renaming cln file, class and get_version * remove lnd specific commented code * get_version: add try/except, refactor to top to mimic lnd.py * rename htlc_cltv to htlc_expiry * add clns lookup_invoice_status * refactored double_check_htlc_is_settled to the end to match lnds file * fix generate_rpc * Add sample environmental variables, small fixes * Fix CLN gRPC port * Fix gen_hold_invoice, plus some other tiny fixes (#435) * Fix channel_balance to use int object inside Amount (#438) * Add CLN/LND volume to celery-beat service * Add CLN/LND volume to celery-beat service * Bump CLN to v23.05 * changes for 0.5 and some small fixes * change invoice expiry from absolute to relative duration * add try/except to catch timeout error * fix failure_reason to be ln_payment failure reasons, albeit inaccurate sometimes * refactor follow_send_payment and add pending check to expired case * fix status comments * add send_keysend method * fix wrong state ints in cancel and settle * switch to use hodlinvoicelookup in double_check * move pay command after lnpayment status update * remove loop in follow_send_payment and add error result for edge case * fix typeerror for payment_hash * rework follow_send_payment logic and payment_hash, watch harder if pending * use fully qualified names for status instead of raw int * missed 2 status from prev commit * Always copy the cln-grpc-hodl plugin on start up * Fix ALLOW_SELF_KEYSEND linting error * Fix missing definition of failure_reason --------- Co-authored-by: daywalker90 <admin@noserver4u.de>
2023-05-22 17:56:15 +03:00
context["cln_version"] = get_cln_version()
context["robosats_running_commit_hash"] = get_robosats_commit()
context["version"] = settings.VERSION
2022-02-17 22:50:10 +03:00
context["alternative_site"] = config("ALTERNATIVE_SITE")
context["alternative_name"] = config("ALTERNATIVE_NAME")
context["node_alias"] = config("NODE_ALIAS")
context["node_id"] = config("NODE_ID")
context["network"] = config("NETWORK", cast=str, default="mainnet")
context["maker_fee"] = float(config("FEE")) * float(config("MAKER_FEE_SPLIT"))
2022-10-20 12:56:10 +03:00
context["maker_fee"] = float(config("FEE")) * float(config("MAKER_FEE_SPLIT"))
context["taker_fee"] = float(config("FEE")) * (
1 - float(config("MAKER_FEE_SPLIT"))
)
context["bond_size"] = settings.DEFAULT_BOND_SIZE
2023-09-08 18:33:00 +03:00
context["notice_severity"] = config("NOTICE_SEVERITY", cast=str, default="none")
context["notice_message"] = config("NOTICE_MESSAGE", cast=str, default="")
context["min_order_size"] = config("MIN_ORDER_SIZE", cast=int, default=20000)
context["max_order_size"] = config("MAX_ORDER_SIZE", cast=int, default=250000)
context["swap_enabled"] = not config("DISABLE_ONCHAIN", cast=bool, default=True)
2024-02-11 20:19:43 +03:00
context["max_swap"] = config("MAX_SWAP_AMOUNT", cast=int, default=0)
try:
context["current_swap_fee_rate"] = Logics.compute_swap_fee_rate(
BalanceLog.objects.latest("time")
)
except BalanceLog.DoesNotExist:
context["current_swap_fee_rate"] = 0
return Response(context, status.HTTP_200_OK)
class RewardView(CreateAPIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = ClaimRewardSerializer
@extend_schema(**RewardViewSchema.post)
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response(status=status.HTTP_400_BAD_REQUEST)
pgp_invoice = serializer.data.get("invoice")
valid_signature, invoice = verify_signed_message(
request.user.robot.public_key, pgp_invoice
)
if not valid_signature:
return Response(
{"bad_request": "The PGP signed cleartext message is not valid."},
status.HTTP_400_BAD_REQUEST,
)
valid, context = Logics.withdraw_rewards(request.user, invoice)
if not valid:
2022-10-20 12:56:10 +03:00
context["successful_withdrawal"] = False
return Response(context, status.HTTP_400_BAD_REQUEST)
return Response({"successful_withdrawal": True}, status.HTTP_200_OK)
class PriceView(ListAPIView):
serializer_class = PriceSerializer
@extend_schema(**PriceViewSchema.get)
def get(self, request):
payload = {}
2022-10-20 12:56:10 +03:00
queryset = Currency.objects.all().order_by("currency")
for currency in queryset:
code = Currency.currency_dict[str(currency.currency)]
try:
2022-10-20 12:56:10 +03:00
last_tick = MarketTick.objects.filter(currency=currency).latest(
"timestamp"
)
payload[code] = {
2022-10-20 12:56:10 +03:00
"price": last_tick.price,
"volume": last_tick.volume,
"premium": last_tick.premium,
"timestamp": last_tick.timestamp,
}
except Exception:
payload[code] = None
2022-03-21 02:46:36 +03:00
return Response(payload, status.HTTP_200_OK)
class TickView(ListAPIView):
queryset = MarketTick.objects.all()
serializer_class = TickSerializer
@extend_schema(**TickViewSchema.get)
def get(self, request):
2023-07-23 22:27:37 +03:00
start_date_str = request.query_params.get("start")
end_date_str = request.query_params.get("end")
# Perform the query with date range filtering
try:
if start_date_str:
2023-11-18 19:06:22 +03:00
naive_start_date = datetime.strptime(start_date_str, "%d-%m-%Y")
aware_start_date = timezone.make_aware(
naive_start_date, timezone=timezone.get_current_timezone()
)
self.queryset = self.queryset.filter(timestamp__gte=aware_start_date)
2023-07-23 22:27:37 +03:00
if end_date_str:
2023-11-18 19:06:22 +03:00
naive_end_date = datetime.strptime(end_date_str, "%d-%m-%Y")
aware_end_date = timezone.make_aware(
naive_end_date, timezone=timezone.get_current_timezone()
)
self.queryset = self.queryset.filter(timestamp__lte=aware_end_date)
2023-07-23 22:27:37 +03:00
except ValueError:
return Response(
{"bad_request": "Invalid date format"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the number of ticks exceeds the limit
if self.queryset.count() > 5000:
return Response(
{
"bad_request": "More than 5000 market ticks have been found. Please, narrow the date range"
},
status=status.HTTP_400_BAD_REQUEST,
)
data = self.serializer_class(self.queryset, many=True, read_only=True).data
return Response(data, status=status.HTTP_200_OK)
2022-03-21 02:46:36 +03:00
class LimitView(ListAPIView):
@extend_schema(**LimitViewSchema.get)
2022-03-21 02:46:36 +03:00
def get(self, request):
# Trade limits as BTC
min_trade = config("MIN_ORDER_SIZE", cast=int, default=20_000) / 100_000_000
2024-01-09 17:36:27 +03:00
max_trade = config("MAX_ORDER_SIZE", cast=int, default=500_000) / 100_000_000
2022-03-21 02:46:36 +03:00
payload = {}
2022-10-20 12:56:10 +03:00
queryset = Currency.objects.all().order_by("currency")
2022-03-21 02:46:36 +03:00
for currency in queryset:
code = Currency.currency_dict[str(currency.currency)]
exchange_rate = float(currency.exchange_rate)
payload[currency.currency] = {
2022-10-20 12:56:10 +03:00
"code": code,
"price": exchange_rate,
"min_amount": min_trade * exchange_rate,
"max_amount": max_trade * exchange_rate,
2022-03-21 02:46:36 +03:00
}
return Response(payload, status.HTTP_200_OK)
class HistoricalView(ListAPIView):
@extend_schema(**HistoricalViewSchema.get)
def get(self, request):
payload = {}
2022-10-20 12:56:10 +03:00
queryset = AccountingDay.objects.all().order_by("day")
for accounting_day in queryset:
payload[str(accounting_day.day)] = {
2022-10-20 12:56:10 +03:00
"volume": accounting_day.contracted,
"num_contracts": accounting_day.num_contracts,
}
return Response(payload, status.HTTP_200_OK)
class StealthView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = StealthSerializer
2022-10-20 12:56:10 +03:00
@extend_schema(**StealthViewSchema.post)
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response(status=status.HTTP_400_BAD_REQUEST)
stealth = serializer.data.get("wantsStealth")
request.user.robot.wants_stealth = stealth
request.user.robot.save(update_fields=["wants_stealth"])
return Response({"wantsStealth": stealth})