mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Add cancel taker penalty time
This commit is contained in:
parent
cf11583d6d
commit
de66040893
@ -5,6 +5,8 @@ MARKET_PRICE_API = 'https://blockchain.info/ticker'
|
|||||||
FEE = 0.002
|
FEE = 0.002
|
||||||
# Bond size in percentage %
|
# Bond size in percentage %
|
||||||
BOND_SIZE = 0.01
|
BOND_SIZE = 0.01
|
||||||
|
# Time out penalty for canceling takers in MINUTES
|
||||||
|
PENALTY_TIMEOUT = 2
|
||||||
|
|
||||||
# Trade limits in satoshis
|
# Trade limits in satoshis
|
||||||
MIN_TRADE = 10000
|
MIN_TRADE = 10000
|
||||||
|
@ -10,6 +10,7 @@ FEE = float(config('FEE'))
|
|||||||
BOND_SIZE = float(config('BOND_SIZE'))
|
BOND_SIZE = float(config('BOND_SIZE'))
|
||||||
MARKET_PRICE_API = config('MARKET_PRICE_API')
|
MARKET_PRICE_API = config('MARKET_PRICE_API')
|
||||||
ESCROW_USERNAME = config('ESCROW_USERNAME')
|
ESCROW_USERNAME = config('ESCROW_USERNAME')
|
||||||
|
PENALTY_TIMEOUT = int(config('PENALTY_TIMEOUT'))
|
||||||
|
|
||||||
MIN_TRADE = int(config('MIN_TRADE'))
|
MIN_TRADE = int(config('MIN_TRADE'))
|
||||||
MAX_TRADE = int(config('MAX_TRADE'))
|
MAX_TRADE = int(config('MAX_TRADE'))
|
||||||
@ -21,6 +22,7 @@ EXP_TRADE_ESCR_INVOICE = int(config('EXP_TRADE_ESCR_INVOICE'))
|
|||||||
BOND_EXPIRY = int(config('BOND_EXPIRY'))
|
BOND_EXPIRY = int(config('BOND_EXPIRY'))
|
||||||
ESCROW_EXPIRY = int(config('ESCROW_EXPIRY'))
|
ESCROW_EXPIRY = int(config('ESCROW_EXPIRY'))
|
||||||
|
|
||||||
|
|
||||||
class Logics():
|
class Logics():
|
||||||
|
|
||||||
def validate_already_maker_or_taker(user):
|
def validate_already_maker_or_taker(user):
|
||||||
@ -41,10 +43,16 @@ class Logics():
|
|||||||
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
|
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def take(order, user):
|
@classmethod
|
||||||
|
def take(cls, order, user):
|
||||||
|
is_penalized, time_out = cls.is_penalized(user)
|
||||||
|
if is_penalized:
|
||||||
|
return False, {'bad_request',f'You need to wait {time_out} seconds to take an order'}
|
||||||
|
else:
|
||||||
order.taker = user
|
order.taker = user
|
||||||
order.status = Order.Status.TAK
|
order.status = Order.Status.TAK
|
||||||
order.save()
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
def is_buyer(order, user):
|
def is_buyer(order, user):
|
||||||
is_maker = order.maker == user
|
is_maker = order.maker == user
|
||||||
@ -167,6 +175,18 @@ class Logics():
|
|||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
def is_penalized(user):
|
||||||
|
''' Checks if a user that is not participant of orders
|
||||||
|
has a limit on taking or making a order'''
|
||||||
|
|
||||||
|
if user.profile.penalty_expiration:
|
||||||
|
if user.profile.penalty_expiration > timezone.now():
|
||||||
|
time_out = (user.profile.penalty_expiration - timezone.now()).seconds
|
||||||
|
return True, time_out
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cancel_order(cls, order, user, state=None):
|
def cancel_order(cls, order, user, state=None):
|
||||||
|
|
||||||
@ -181,12 +201,24 @@ class Logics():
|
|||||||
|
|
||||||
# 2) When maker cancels after bond
|
# 2) When maker cancels after bond
|
||||||
'''The order dissapears from book and goes to cancelled.
|
'''The order dissapears from book and goes to cancelled.
|
||||||
Maker is charged a small amount of sats, to prevent DDOS
|
Maker is charged the bond to prevent DDOS
|
||||||
on the LN node and order book'''
|
on the LN node and order book. TODO Only charge a small part
|
||||||
|
of the bond (requires maker submitting an invoice)'''
|
||||||
|
|
||||||
|
|
||||||
# 3) When taker cancels before bond
|
# 3) When taker cancels before bond
|
||||||
''' The order goes back to the book as public.
|
''' The order goes back to the book as public.
|
||||||
LNPayment "order.taker_bond" is deleted() '''
|
LNPayment "order.taker_bond" is deleted() '''
|
||||||
|
elif order.status == Order.Status.TAK and order.taker == user:
|
||||||
|
# adds a timeout penalty
|
||||||
|
user.profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
order.taker = None
|
||||||
|
order.status = Order.Status.PUB
|
||||||
|
order.save()
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
# 4) When taker or maker cancel after bond (before escrow)
|
# 4) When taker or maker cancel after bond (before escrow)
|
||||||
'''The order goes into cancelled status if maker cancels.
|
'''The order goes into cancelled status if maker cancels.
|
||||||
|
@ -158,6 +158,9 @@ class Profile(models.Model):
|
|||||||
# RoboHash
|
# RoboHash
|
||||||
avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar', blank=True)
|
avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar', blank=True)
|
||||||
|
|
||||||
|
# Penalty expiration (only used then taking/cancelling repeatedly orders in the book before comitting bond)
|
||||||
|
penalty_expiration = models.DateTimeField(null=True)
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_user_profile(sender, instance, created, **kwargs):
|
def create_user_profile(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
|
@ -5,7 +5,7 @@ import ring
|
|||||||
|
|
||||||
storage = {}
|
storage = {}
|
||||||
|
|
||||||
@ring.dict(storage, expire=10) #keeps in cache for 10 seconds
|
@ring.dict(storage, expire=30) #keeps in cache for 30 seconds
|
||||||
def get_exchange_rate(currency):
|
def get_exchange_rate(currency):
|
||||||
# TODO Add fallback Public APIs and error handling
|
# TODO Add fallback Public APIs and error handling
|
||||||
# Think about polling price data in a different way (e.g. store locally every t seconds)
|
# Think about polling price data in a different way (e.g. store locally every t seconds)
|
||||||
|
@ -105,6 +105,11 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
data = ListOrderSerializer(order).data
|
data = ListOrderSerializer(order).data
|
||||||
|
|
||||||
|
# if user is under a limit (penalty), inform him
|
||||||
|
is_penalized, time_out = Logics.is_penalized(request.user)
|
||||||
|
if is_penalized:
|
||||||
|
data['penalty'] = time_out
|
||||||
|
|
||||||
# Add booleans if user is maker, taker, partipant, buyer or seller
|
# Add booleans if user is maker, taker, partipant, buyer or seller
|
||||||
data['is_maker'] = order.maker == request.user
|
data['is_maker'] = order.maker == request.user
|
||||||
data['is_taker'] = order.taker == request.user
|
data['is_taker'] = order.taker == request.user
|
||||||
@ -207,7 +212,9 @@ class OrderView(viewsets.ViewSet):
|
|||||||
valid, context = Logics.validate_already_maker_or_taker(request.user)
|
valid, context = Logics.validate_already_maker_or_taker(request.user)
|
||||||
if not valid: return Response(context, status=status.HTTP_409_CONFLICT)
|
if not valid: return Response(context, status=status.HTTP_409_CONFLICT)
|
||||||
|
|
||||||
Logics.take(order, request.user)
|
valid, context = Logics.take(order, request.user)
|
||||||
|
if not valid: return Response(context, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
else: Response({'bad_request':'This order is not public anymore.'}, status.HTTP_400_BAD_REQUEST)
|
else: Response({'bad_request':'This order is not public anymore.'}, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Any other action is only allowed if the user is a participant
|
# Any other action is only allowed if the user is a participant
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material"
|
import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material"
|
||||||
import TradeBox from "./TradeBox";
|
import TradeBox from "./TradeBox";
|
||||||
|
|
||||||
function msToTime(duration) {
|
function msToTime(duration) {
|
||||||
@ -101,6 +101,7 @@ export default class OrderPage extends Component {
|
|||||||
isTaker: data.is_taker,
|
isTaker: data.is_taker,
|
||||||
isBuyer: data.is_buyer,
|
isBuyer: data.is_buyer,
|
||||||
isSeller: data.is_seller,
|
isSeller: data.is_seller,
|
||||||
|
penalty: data.penalty,
|
||||||
expiresAt: data.expires_at,
|
expiresAt: data.expires_at,
|
||||||
badRequest: data.bad_request,
|
badRequest: data.bad_request,
|
||||||
bondInvoice: data.bond_invoice,
|
bondInvoice: data.bond_invoice,
|
||||||
@ -250,6 +251,18 @@ export default class OrderPage extends Component {
|
|||||||
<LinearDeterminate />
|
<LinearDeterminate />
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
{/* If the user has a penalty/limit */}
|
||||||
|
{this.state.penalty ?
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Alert severity="warning" sx={{maxWidth:360}}>
|
||||||
|
You cannot take an order yet! Wait {this.state.penalty} seconds
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user