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
2024-06-27 19:47:23 +03:00
from django . utils . dateparse import parse_datetime
from django . http import HttpResponseBadRequest
2022-10-20 12:55:24 +03:00
from drf_spectacular . utils import extend_schema
2022-01-07 00:36:22 +03:00
from rest_framework import status , viewsets
2023-05-05 13:12:38 +03:00
from rest_framework . authentication import TokenAuthentication
2023-05-06 16:42:48 +03:00
from rest_framework . generics import CreateAPIView , ListAPIView
2023-05-05 13:12:38 +03:00
from rest_framework . permissions import IsAuthenticated
2022-01-02 01:34:23 +03:00
from rest_framework . response import Response
2022-10-25 21:04:12 +03:00
from rest_framework . views import APIView
2022-01-05 03:13:08 +03:00
2022-10-25 21:04:12 +03:00
from api . logics import Logics
2024-06-27 19:47:23 +03:00
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 ,
2023-05-05 13:12:38 +03:00
RobotViewSchema ,
2022-10-20 12:56:10 +03:00
StealthViewSchema ,
TickViewSchema ,
2024-06-27 19:47:23 +03:00
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 ,
2024-06-27 19:47:23 +03:00
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 ,
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 ,
2023-05-17 16:06:04 +03:00
verify_signed_message ,
2022-10-20 12:56:10 +03:00
)
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
2022-01-07 21:22:52 +03:00
class MakerView ( CreateAPIView ) :
2022-02-17 22:50:10 +03:00
serializer_class = MakeOrderSerializer
2023-08-14 17:21:12 +03:00
authentication_classes = [ TokenAuthentication ]
2023-05-05 13:12:38 +03:00
permission_classes = [ IsAuthenticated ]
2022-01-02 01:34:23 +03:00
2022-10-02 21:02:35 +03:00
@extend_schema ( * * MakerViewSchema . post )
2022-02-17 22:50:10 +03:00
def post ( self , request ) :
2022-01-02 01:34:23 +03:00
serializer = self . serializer_class ( data = request . data )
2022-01-02 15:59:48 +03:00
2022-01-30 18:18:03 +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 " : " 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 ,
)
2022-03-22 02:27:36 +03:00
# 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 " )
2022-03-22 02:27:36 +03:00
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 " )
2022-04-29 21:54:20 +03:00
escrow_duration = serializer . data . get ( " escrow_duration " )
2022-03-19 01:09:38 +03:00
bond_size = serializer . data . get ( " bond_size " )
2023-10-10 15:47:22 +03:00
latitude = serializer . data . get ( " latitude " )
longitude = serializer . data . get ( " longitude " )
2022-03-19 01:09:38 +03:00
# Optional params
2022-10-20 23:53:51 +03:00
if public_duration is None :
2023-10-10 15:48:05 +03:00
public_duration = 60 * 60 * settings . DEFAULT_PUBLIC_ORDER_DURATION
2022-10-20 23:53:51 +03:00
if escrow_duration is None :
2023-10-10 15:48:05 +03:00
escrow_duration = 60 * settings . INVOICE_AND_ESCROW_DURATION
2022-10-20 23:53:51 +03:00
if bond_size is None :
2023-10-10 15:48:05 +03:00
bond_size = settings . DEFAULT_BOND_SIZE
2022-10-20 23:53:51 +03:00
if has_range is None :
2022-10-20 12:56:10 +03:00
has_range = False
2022-03-22 02:27:36 +03:00
# 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.
2022-10-20 23:53:51 +03:00
if has_range and ( min_amount is None or max_amount is None ) :
2022-03-22 02:27:36 +03:00
return Response (
{
2022-10-20 12:56:10 +03:00
" bad_request " : " You must specify min_amount and max_amount for a range order "
2022-03-22 02:27:36 +03:00
} ,
status . HTTP_400_BAD_REQUEST ,
)
2022-10-20 23:53:51 +03:00
elif not has_range and amount is None :
2022-03-22 02:27:36 +03:00
return Response (
2022-10-20 12:56:10 +03:00
{ " bad_request " : " You must specify an order amount " } ,
2022-03-22 02:27:36 +03:00
status . HTTP_400_BAD_REQUEST ,
)
2022-01-06 23:33:40 +03:00
# Creates a new order
order = Order (
type = type ,
2022-01-16 19:06:53 +03:00
currency = Currency . objects . get ( id = currency ) ,
2022-01-06 23:33:40 +03:00
amount = amount ,
2022-03-22 02:27:36 +03:00
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 ,
2022-04-29 21:54:20 +03:00
escrow_duration = escrow_duration ,
2022-03-19 01:09:38 +03:00
bond_size = bond_size ,
2023-10-10 15:47:22 +03:00
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 )
2024-04-30 01:58:03 +03:00
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 ( )
2023-08-06 20:48:20 +03:00
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 )
2022-01-02 15:59:48 +03:00
2022-01-05 03:13:08 +03:00
class OrderView ( viewsets . ViewSet ) :
2023-08-14 17:21:12 +03:00
authentication_classes = [ TokenAuthentication ]
2023-05-05 13:12:38 +03:00
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 "
2022-01-02 15:59:48 +03:00
2022-10-02 21:02:35 +03:00
@extend_schema ( * * OrderViewSchema . get )
2022-01-02 15:59:48 +03:00
def get ( self , request , format = None ) :
2022-02-17 22:50:10 +03:00
"""
2022-01-07 14:31:33 +03:00
Full trade pipeline takes place while looking / refreshing the order page .
2022-02-17 22:50:10 +03:00
"""
2022-01-02 15:59:48 +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
2022-10-20 23:53:51 +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 ,
)
2022-01-06 16:55:47 +03:00
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
2022-01-07 14:31:33 +03:00
# This is our order.
order = order [ 0 ]
2022-01-11 23:49:53 +03:00
# 2) If order has been cancelled
2022-01-07 14:31:33 +03:00
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 ,
)
2022-01-07 14:31:33 +03:00
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 ,
)
2022-01-07 14:31:33 +03:00
data = ListOrderSerializer ( order ) . data
2022-03-19 00:21:13 +03:00
data [ " total_secs_exp " ] = order . t_to_expire ( order . status )
2022-01-07 14:31:33 +03:00
2022-01-11 23:49:53 +03:00
# 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 :
2023-05-01 13:30:53 +03:00
data [ " penalty " ] = request . user . robot . penalty_expiration
2022-01-10 15:10:32 +03:00
2022-01-07 14:31:33 +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 (
2022-07-01 14:22:26 +03:00
{ " bad_request " : " This order is not available " } ,
2022-02-17 22:50:10 +03:00
status . HTTP_403_FORBIDDEN ,
)
2022-09-15 18:47:09 +03:00
data [ " maker_nick " ] = str ( order . maker )
2023-12-01 15:56:55 +03:00
data [ " maker_hash_id " ] = str ( order . maker . robot . hash_id )
2022-01-30 22:45:37 +03:00
# Add activity status of participants based on last_seen
2023-05-07 14:54:09 +03:00
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 )
2022-01-30 22:45:37 +03:00
2022-09-15 18:47:09 +03:00
# 3.b) Non participants can view details (but only if PUB)
if not data [ " is_participant " ] and order . status == Order . Status . PUB :
2023-04-21 14:10:47 +03:00
data [ " price_now " ] , data [ " premium_now " ] = Logics . price_and_premium_now ( order )
data [ " satoshis_now " ] = Logics . satoshis_now ( order )
2022-09-15 18:47:09 +03:00
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 :
2022-02-22 02:41:36 +03:00
data [ " price_now " ] , data [ " premium_now " ] = Logics . price_and_premium_now ( order )
2023-04-21 14:10:47 +03:00
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
2022-09-15 18:47:09 +03:00
# 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 )
2023-12-01 15:56:55 +03:00
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
2023-10-10 15:47:22 +03:00
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
2023-04-21 14:10:47 +03:00
data [ " satoshis_now " ] = order . last_satoshis
2022-01-07 21:22:52 +03:00
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 :
2022-09-15 18:47:09 +03:00
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 :
2022-09-15 18:47:09 +03:00
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 :
2022-09-15 18:47:09 +03:00
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
2022-01-11 23:49:53 +03:00
# If both bonds are locked, participants can see the final trade amount in sats.
2023-09-11 18:23:11 +03:00
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-07 14:31:33 +03:00
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 )
2022-01-07 14:31:33 +03:00
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 )
2022-01-07 14:31:33 +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 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-01-07 14:31:33 +03:00
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
) :
2022-06-14 01:27:09 +03:00
# 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
) :
2022-01-25 17:46:02 +03:00
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 )
2022-01-07 14:31:33 +03:00
2022-01-11 23:49:53 +03:00
# 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 ] :
2022-01-11 23:49:53 +03:00
# 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
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
2022-01-23 02:05:03 +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 :
2022-01-23 02:05:03 +03:00
# 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 " ] = (
2022-10-20 23:53:51 +03:00
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 " ] = (
2022-10-20 23:53:51 +03:00
order . taker_statement is not None and order . taker_statement != " "
2022-10-20 12:56:10 +03:00
)
2022-01-24 00:12:30 +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
2022-04-29 21:54:20 +03:00
data [ " next_retry_time " ] = order . payout . last_routing_time + timedelta (
2022-10-20 12:56:10 +03:00
minutes = RETRY_TIME
)
2022-05-19 17:00:55 +03:00
if order . payout . failure_reason :
2022-10-20 12:56:10 +03:00
data [ " failure_reason " ] = LNPayment . FailureReason (
order . payout . failure_reason
) . label
2022-01-25 01:53:55 +03:00
2022-01-25 17:46:02 +03:00
if order . payout . status == LNPayment . Status . EXPIRE :
2022-02-17 22:50:10 +03:00
data [ " invoice_expired " ] = True
2022-01-25 01:53:55 +03:00
# Add invoice amount once again if invoice was expired.
2022-11-26 03:06:40 +03:00
data [ " trade_satoshis " ] = Logics . payout_amount ( order , request . user ) [ 1 ] [
" invoice_amount "
]
2022-02-17 22:50:10 +03:00
2022-05-03 23:21:04 +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 ,
] :
2022-04-29 21:54:20 +03:00
data [ " public_duration " ] = order . public_duration
2023-11-16 16:28:53 +03:00
data [ " bond_size " ] = str ( order . bond_size )
2022-04-29 21:54:20 +03:00
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 ] :
2022-07-18 15:53:49 +03:00
valid , context = Logics . summarize_trade ( order , request . user )
if valid :
data = { * * data , * * context }
2022-07-16 14:15:00 +03:00
2022-05-03 23:21:04 +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
2023-03-14 20:23:11 +03:00
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
2023-03-14 20:23:11 +03:00
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
2022-01-07 14:31:33 +03:00
return Response ( data , status . HTTP_200_OK )
2022-01-02 15:59:48 +03:00
2022-10-02 21:02:35 +03:00
@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
"""
2022-01-15 17:22:07 +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
"""
2022-01-05 03:13:08 +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 )
2022-01-05 03:13:08 +03:00
order = Order . objects . get ( id = order_id )
2023-04-28 12:19:18 +03:00
# 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 " )
2023-05-17 16:06:04 +03:00
pgp_invoice = serializer . data . get ( " invoice " )
2022-11-24 20:42:30 +03:00
routing_budget_ppm = serializer . data . get ( " routing_budget_ppm " , 0 )
2023-05-17 16:06:04 +03:00
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 " )
2022-01-05 03:13:08 +03:00
2022-01-11 23:49:53 +03:00
# 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 )
2022-03-22 20:49:57 +03:00
# 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
2022-01-07 21:22:52 +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 ,
)
2022-01-07 21:22:52 +03:00
# 2) If action is 'update invoice'
2022-06-19 09:09:21 +03:00
elif action == " update_invoice " :
2023-05-17 16:06:04 +03:00
# DEPRECATE post v0.5.1.
2023-08-14 17:22:15 +03:00
valid_signature , invoice = verify_signed_message (
request . user . robot . public_key , pgp_invoice
)
2023-05-17 16:06:04 +03:00
if not valid_signature :
return Response (
{ " bad_request " : " The PGP signed cleartext message is not valid. " } ,
status . HTTP_400_BAD_REQUEST ,
)
2022-11-24 20:42:30 +03:00
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
2022-06-14 01:27:09 +03:00
# 2.b) If action is 'update address'
2022-06-19 09:09:21 +03:00
elif action == " update_address " :
2023-08-14 17:22:15 +03:00
valid_signature , address = verify_signed_message (
request . user . robot . public_key , pgp_address
)
2023-05-17 16:06:04 +03:00
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
2023-04-28 12:19:18 +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-01-17 00:54:42 +03:00
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
2022-01-07 21:22:52 +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
2022-04-29 21:54:20 +03:00
# 7) If action is rate_platform
2022-02-17 22:50:10 +03:00
elif action == " rate_platform " and rating :
2022-02-04 21:07:09 +03:00
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 )
2022-02-04 21:07:09 +03:00
2022-04-29 21:54:20 +03:00
# 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 )
2022-01-07 21:22:52 +03:00
# If nothing of the above... something else is going on. Probably not allowed!
2022-01-05 03:13:08 +03:00
else :
2022-01-07 21:22:52 +03:00
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 ,
)
2022-01-05 03:13:08 +03:00
return self . get ( request )
2022-10-02 21:02:35 +03:00
2023-05-05 13:12:38 +03:00
class RobotView ( APIView ) :
2023-08-14 17:21:12 +03:00
authentication_classes = [ TokenAuthentication ]
2023-05-05 13:12:38 +03:00
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
2023-12-01 15:56:55 +03:00
context [ " hash_id " ] = user . robot . hash_id
2023-05-05 13:12:38 +03:00
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
2024-06-27 19:47:23 +03:00
context = { * * context , * * Notifications . get_context ( user ) }
2023-05-05 13:12:38 +03:00
# 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 )
2022-01-05 03:13:08 +03:00
class BookView ( ListAPIView ) :
2022-10-02 21:02:35 +03:00
serializer_class = OrderPublicSerializer
2022-02-17 22:50:10 +03:00
queryset = Order . objects . filter ( status = Order . Status . PUB )
2022-10-02 21:02:35 +03:00
@extend_schema ( * * BookViewSchema . get )
2022-02-17 22:50:10 +03:00
def get ( self , request , format = None ) :
2022-09-11 18:04:03 +03:00
currency = request . GET . get ( " currency " , 0 )
type = request . GET . get ( " type " , 2 )
2022-01-03 17:27:25 +03:00
2022-01-10 00:54:13 +03:00
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 )
2022-01-10 00:54:13 +03:00
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 )
2022-01-10 00:54:13 +03:00
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 ,
)
2022-01-03 17:39:59 +03:00
2022-01-03 22:13:39 +03:00
book_data = [ ]
for order in queryset :
2022-01-05 03:13:08 +03:00
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 )
2023-05-07 14:54:09 +03:00
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
2022-01-05 03:13:08 +03:00
del data [ key ]
2022-02-17 22:50:10 +03:00
2022-01-03 22:13:39 +03:00
book_data . append ( data )
2022-02-17 22:50:10 +03:00
2022-01-03 17:39:59 +03:00
return Response ( book_data , status = status . HTTP_200_OK )
2022-01-07 21:22:52 +03:00
2022-10-02 21:02:35 +03:00
2024-06-27 19:47:23 +03:00
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 )
2023-11-11 18:48:54 +03:00
class InfoView ( viewsets . ViewSet ) :
2022-10-02 21:02:35 +03:00
serializer_class = InfoSerializer
@extend_schema ( * * InfoViewSchema . get )
2022-01-09 04:23:13 +03:00
def get ( self , request ) :
2022-01-07 21:22:52 +03:00
context = { }
2022-01-08 01:46:30 +03:00
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 " ] = (
2022-10-20 23:53:51 +03:00
0 if context [ " book_liquidity " ] is None else context [ " book_liquidity " ]
2022-10-20 12:56:10 +03:00
)
2022-03-05 20:32:27 +03:00
2022-01-11 04:36:34 +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-11 04:36:34 +03:00
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 )
2022-01-11 04:36:34 +03:00
if not len ( queryset ) == 0 :
2022-03-12 14:24:11 +03:00
avg_premium , total_volume = compute_avg_premium ( queryset )
# If no contracts, fallback to lifetime avg premium
2022-01-11 04:36:34 +03:00
else :
2022-03-12 14:24:11 +03:00
queryset = MarketTick . objects . all ( )
2022-03-13 15:00:21 +03:00
avg_premium , _ = compute_avg_premium ( queryset )
total_volume = 0
2022-01-11 04:36:34 +03:00
2022-01-26 21:45:24 +03:00
queryset = MarketTick . objects . all ( )
if not len ( queryset ) == 0 :
2022-02-19 20:18:47 +03:00
volume_contracted = [ ]
2022-01-26 21:45:24 +03:00
for tick in queryset :
2023-08-06 18:37:03 +03:00
volume_contracted . append ( tick . volume if tick . volume else 0 )
2022-02-19 20:18:47 +03:00
lifetime_volume = sum ( volume_contracted )
2022-01-26 21:45:24 +03:00
else :
2022-02-19 20:18:47 +03:00
lifetime_volume = 0
2022-01-26 21:45:24 +03:00
2022-03-13 15:00:21 +03:00
context [ " last_day_nonkyc_btc_premium " ] = round ( avg_premium , 2 )
2022-07-02 22:51:29 +03:00
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 ( )
2023-05-22 17:56:15 +03:00
context [ " cln_version " ] = get_cln_version ( )
2022-09-20 20:39:49 +03:00
context [ " robosats_running_commit_hash " ] = get_robosats_commit ( )
2023-05-05 13:12:38 +03:00
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 " )
2024-02-11 17:57:47 +03:00
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 " ) )
)
2023-10-10 15:48:05 +03:00
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 = " " )
2024-02-11 17:57:47 +03:00
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 )
2022-03-05 14:05:41 +03:00
2023-04-29 17:07:48 +03:00
try :
context [ " current_swap_fee_rate " ] = Logics . compute_swap_fee_rate (
BalanceLog . objects . latest ( " time " )
)
except BalanceLog . DoesNotExist :
context [ " current_swap_fee_rate " ] = 0
2022-06-16 23:01:10 +03:00
2022-01-08 01:46:30 +03:00
return Response ( context , status . HTTP_200_OK )
2022-03-06 19:08:28 +03:00
class RewardView ( CreateAPIView ) :
2023-08-14 17:21:12 +03:00
authentication_classes = [ TokenAuthentication ]
2023-05-05 13:12:38 +03:00
permission_classes = [ IsAuthenticated ]
2022-03-06 19:08:28 +03:00
serializer_class = ClaimRewardSerializer
2022-10-02 21:02:35 +03:00
@extend_schema ( * * RewardViewSchema . post )
2022-03-06 19:08:28 +03:00
def post ( self , request ) :
serializer = self . serializer_class ( data = request . data )
if not serializer . is_valid ( ) :
return Response ( status = status . HTTP_400_BAD_REQUEST )
2023-05-17 16:06:04 +03:00
pgp_invoice = serializer . data . get ( " invoice " )
2023-08-14 17:22:15 +03:00
valid_signature , invoice = verify_signed_message (
request . user . robot . public_key , pgp_invoice
)
2023-05-17 16:06:04 +03:00
if not valid_signature :
return Response (
{ " bad_request " : " The PGP signed cleartext message is not valid. " } ,
status . HTTP_400_BAD_REQUEST ,
)
2022-03-06 19:08:28 +03:00
valid , context = Logics . withdraw_rewards ( request . user , invoice )
if not valid :
2022-10-20 12:56:10 +03:00
context [ " successful_withdrawal " ] = False
2022-03-06 19:08:28 +03:00
return Response ( context , status . HTTP_400_BAD_REQUEST )
2022-03-12 14:24:11 +03:00
return Response ( { " successful_withdrawal " : True } , status . HTTP_200_OK )
2022-10-02 21:02:35 +03:00
2022-07-01 14:22:26 +03:00
class PriceView ( ListAPIView ) :
2022-03-12 14:24:11 +03:00
serializer_class = PriceSerializer
2022-10-02 21:02:35 +03:00
@extend_schema ( * * PriceViewSchema . get )
2022-03-12 14:24:11 +03:00
def get ( self , request ) :
payload = { }
2022-10-20 12:56:10 +03:00
queryset = Currency . objects . all ( ) . order_by ( " currency " )
2022-03-12 17:55:29 +03:00
2022-03-12 14:24:11 +03:00
for currency in queryset :
code = Currency . currency_dict [ str ( currency . currency ) ]
2022-03-12 17:55:29 +03:00
try :
2022-10-20 12:56:10 +03:00
last_tick = MarketTick . objects . filter ( currency = currency ) . latest (
" timestamp "
)
2022-03-12 17:55:29 +03:00
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 ,
2022-03-12 17:55:29 +03:00
}
2022-10-20 23:53:51 +03:00
except Exception :
2022-03-12 17:55:29 +03:00
payload [ code ] = None
2022-03-12 14:24:11 +03:00
2022-03-21 02:46:36 +03:00
return Response ( payload , status . HTTP_200_OK )
2022-10-02 21:02:35 +03:00
2022-07-01 14:22:26 +03:00
class TickView ( ListAPIView ) :
2022-06-21 22:29:07 +03:00
queryset = MarketTick . objects . all ( )
serializer_class = TickSerializer
2022-10-02 21:02:35 +03:00
@extend_schema ( * * TickViewSchema . get )
2022-06-21 22:29:07 +03:00
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
2022-06-21 22:29:07 +03:00
return Response ( data , status = status . HTTP_200_OK )
2022-10-02 21:02:35 +03:00
2022-03-21 02:46:36 +03:00
class LimitView ( ListAPIView ) :
2022-10-02 21:02:35 +03:00
@extend_schema ( * * LimitViewSchema . get )
2022-03-21 02:46:36 +03:00
def get ( self , request ) :
# Trade limits as BTC
2023-10-10 15:48:05 +03:00
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
}
2022-05-01 21:09:17 +03:00
return Response ( payload , status . HTTP_200_OK )
2022-05-09 20:35:04 +03:00
2022-10-02 21:02:35 +03:00
2022-05-09 20:35:04 +03:00
class HistoricalView ( ListAPIView ) :
2022-10-02 21:02:35 +03:00
@extend_schema ( * * HistoricalViewSchema . get )
2022-05-09 20:35:04 +03:00
def get ( self , request ) :
payload = { }
2022-10-20 12:56:10 +03:00
queryset = AccountingDay . objects . all ( ) . order_by ( " day " )
2022-05-09 20:35:04 +03:00
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 ,
2022-05-09 20:35:04 +03:00
}
return Response ( payload , status . HTTP_200_OK )
2022-08-12 20:41:06 +03:00
2022-10-02 21:02:35 +03:00
2023-05-06 16:42:48 +03:00
class StealthView ( APIView ) :
2023-08-14 17:21:12 +03:00
authentication_classes = [ TokenAuthentication ]
2023-05-05 13:12:38 +03:00
permission_classes = [ IsAuthenticated ]
2022-08-12 20:41:06 +03:00
serializer_class = StealthSerializer
2022-10-20 12:56:10 +03:00
2023-05-06 16:42:48 +03:00
@extend_schema ( * * StealthViewSchema . post )
def post ( self , request ) :
2022-08-12 20:41:06 +03:00
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 " )
2023-05-01 13:30:53 +03:00
request . user . robot . wants_stealth = stealth
2023-05-08 21:10:37 +03:00
request . user . robot . save ( update_fields = [ " wants_stealth " ] )
2022-08-12 20:41:06 +03:00
return Response ( { " wantsStealth " : stealth } )