mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 12:11:35 +00:00
Implement min_amount max_amount model props and validation
This commit is contained in:
parent
2998c265d6
commit
bf80986005
@ -87,24 +87,56 @@ class Logics:
|
||||
|
||||
return True, None, None
|
||||
|
||||
def validate_order_size(order):
|
||||
"""Validates if order is withing limits in satoshis at t0"""
|
||||
if order.t0_satoshis > MAX_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order is too big. It is worth " +
|
||||
"{:,}".format(order.t0_satoshis) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
if order.t0_satoshis < MIN_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order is too small. It is worth " +
|
||||
"{:,}".format(order.t0_satoshis) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
@classmethod
|
||||
def validate_order_size(cls, order):
|
||||
"""Validates if order size in Sats is within limits at t0"""
|
||||
if not order.has_range:
|
||||
if order.t0_satoshis > MAX_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order is too big. It is worth " +
|
||||
"{:,}".format(order.t0_satoshis) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
if order.t0_satoshis < MIN_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order is too small. It is worth " +
|
||||
"{:,}".format(order.t0_satoshis) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
elif order.has_range:
|
||||
min_sats = cls.calc_sats(order.min_amount, order.currency.exchange_rate, order.premium)
|
||||
max_sats = cls.calc_sats(order.max_amount, order.currency.exchange_rate, order.premium)
|
||||
if min_sats > max_sats/1.5:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"min_sats*1.5 has to be smaller than max_sats"
|
||||
}
|
||||
elif max_sats > MAX_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order upper range value (max_amount) is too big. It is worth " +
|
||||
"{:,}".format(max_sats) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
elif min_sats < MIN_TRADE:
|
||||
return False, {
|
||||
"bad_request":
|
||||
"Your order lower range value (min_mount) is too small. It is worth " +
|
||||
"{:,}".format(min_sats) +
|
||||
" Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
|
||||
" Sats"
|
||||
}
|
||||
elif min_sats < max_sats/5:
|
||||
return False, {
|
||||
"bad_request":
|
||||
f"Your order amount range is too large. Max amount can only be 5 times bigger than min amount"
|
||||
}
|
||||
|
||||
return True, None
|
||||
|
||||
def user_activity_status(last_seen):
|
||||
@ -144,15 +176,19 @@ class Logics:
|
||||
return (is_maker and order.type == Order.Types.SELL) or (
|
||||
is_taker and order.type == Order.Types.BUY)
|
||||
|
||||
def satoshis_now(order):
|
||||
def calc_sats(amount, exchange_rate, premium):
|
||||
exchange_rate = float(exchange_rate)
|
||||
premium_rate = exchange_rate * (1 + float(premium) / 100)
|
||||
return (float(amount) /premium_rate) * 100 * 1000 * 1000
|
||||
|
||||
@classmethod
|
||||
def satoshis_now(cls, order):
|
||||
"""checks trade amount in sats"""
|
||||
if order.is_explicit:
|
||||
satoshis_now = order.satoshis
|
||||
else:
|
||||
exchange_rate = float(order.currency.exchange_rate)
|
||||
premium_rate = exchange_rate * (1 + float(order.premium) / 100)
|
||||
satoshis_now = (float(order.amount) /
|
||||
premium_rate) * 100 * 1000 * 1000
|
||||
amount = order.amount if order.amount != None else order.max_amount
|
||||
satoshis_now = cls.calc_sats(amount, order.currency.exchange_rate, order.premium)
|
||||
|
||||
return int(satoshis_now)
|
||||
|
||||
|
@ -192,14 +192,15 @@ class Order(models.Model):
|
||||
currency = models.ForeignKey(Currency,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL)
|
||||
amount = models.DecimalField(max_digits=16,
|
||||
decimal_places=8,
|
||||
validators=[MinValueValidator(0.00000001)])
|
||||
amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
|
||||
has_range = models.BooleanField(default=False, null=False, blank=False)
|
||||
min_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
|
||||
max_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
|
||||
payment_method = models.CharField(max_length=35,
|
||||
null=False,
|
||||
default="not specified",
|
||||
blank=True)
|
||||
|
||||
bondless_taker = models.BooleanField(default=False, null=False, blank=False)
|
||||
# order pricing method. A explicit amount of sats, or a relative premium above/below market.
|
||||
is_explicit = models.BooleanField(default=False, null=False)
|
||||
# marked to market
|
||||
|
@ -14,10 +14,14 @@ class ListOrderSerializer(serializers.ModelSerializer):
|
||||
"type",
|
||||
"currency",
|
||||
"amount",
|
||||
"has_range",
|
||||
"min_amount",
|
||||
"max_amount",
|
||||
"payment_method",
|
||||
"is_explicit",
|
||||
"premium",
|
||||
"satoshis",
|
||||
"bondless_taker"
|
||||
"maker",
|
||||
"taker",
|
||||
)
|
||||
@ -31,12 +35,16 @@ class MakeOrderSerializer(serializers.ModelSerializer):
|
||||
"type",
|
||||
"currency",
|
||||
"amount",
|
||||
"has_range",
|
||||
"min_amount",
|
||||
"max_amount",
|
||||
"payment_method",
|
||||
"is_explicit",
|
||||
"premium",
|
||||
"satoshis",
|
||||
"public_duration",
|
||||
"bond_size",
|
||||
"bondless_taker",
|
||||
)
|
||||
|
||||
class UpdateOrderSerializer(serializers.Serializer):
|
||||
|
49
api/views.py
49
api/views.py
@ -66,33 +66,64 @@ class MakerView(CreateAPIView):
|
||||
},
|
||||
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)
|
||||
|
||||
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")
|
||||
payment_method = serializer.data.get("payment_method")
|
||||
premium = serializer.data.get("premium")
|
||||
satoshis = serializer.data.get("satoshis")
|
||||
is_explicit = serializer.data.get("is_explicit")
|
||||
public_duration = serializer.data.get("public_duration")
|
||||
bond_size = serializer.data.get("bond_size")
|
||||
bondless_taker = serializer.data.get("bondless_taker")
|
||||
|
||||
# Optional params
|
||||
if public_duration == None:
|
||||
public_duration = PUBLIC_DURATION
|
||||
if bond_size == None:
|
||||
bond_size = BOND_SIZE
|
||||
if public_duration == None: public_duration = PUBLIC_DURATION
|
||||
if bond_size == None: bond_size = BOND_SIZE
|
||||
if bondless_taker == None: bondless_taker = False
|
||||
if has_range == None: has_range = False
|
||||
|
||||
valid, context, _ = Logics.validate_already_maker_or_taker(
|
||||
request.user)
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_409_CONFLICT)
|
||||
# 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 == None or max_amount == None):
|
||||
return Response(
|
||||
{
|
||||
"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 == None:
|
||||
return Response(
|
||||
{
|
||||
"bad_request":
|
||||
"You must specify an order amount"
|
||||
},
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Creates a new order
|
||||
order = Order(
|
||||
type=type,
|
||||
currency=Currency.objects.get(id=currency),
|
||||
amount=amount,
|
||||
has_range=has_range,
|
||||
min_amount=min_amount,
|
||||
max_amount=max_amount,
|
||||
payment_method=payment_method,
|
||||
premium=premium,
|
||||
satoshis=satoshis,
|
||||
@ -102,9 +133,9 @@ class MakerView(CreateAPIView):
|
||||
maker=request.user,
|
||||
public_duration=public_duration,
|
||||
bond_size=bond_size,
|
||||
bondless_taker=bondless_taker,
|
||||
)
|
||||
|
||||
# TODO move to Order class method when new instance is created!
|
||||
order.last_satoshis = order.t0_satoshis = Logics.satoshis_now(order)
|
||||
|
||||
valid, context = Logics.validate_order_size(order)
|
||||
|
@ -78,9 +78,9 @@ export default class MakerPage extends Component {
|
||||
.then((data) => this.setState({
|
||||
limits:data,
|
||||
loadingLimits:false,
|
||||
minAmount: Number(data[this.state.currency]['max_amount']*0.25),
|
||||
maxAmount: Number(data[this.state.currency]['max_amount']*0.75),
|
||||
amount: ""})
|
||||
minAmount: parseFloat(Number(data[this.state.currency]['max_amount']*0.25).toPrecision(2)),
|
||||
maxAmount: parseFloat(Number(data[this.state.currency]['max_amount']*0.75).toPrecision(2))
|
||||
})
|
||||
& console.log(this.state.limits));
|
||||
}
|
||||
|
||||
@ -131,8 +131,8 @@ export default class MakerPage extends Component {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
minAmount: lowerValue,
|
||||
maxAmount: upperValue,
|
||||
minAmount: parseFloat(Number(lowerValue).toPrecision(2)),
|
||||
maxAmount: parseFloat(Number(upperValue).toPrecision(2)),
|
||||
});
|
||||
}
|
||||
|
||||
@ -192,12 +192,16 @@ export default class MakerPage extends Component {
|
||||
type: this.state.type,
|
||||
currency: this.state.currency,
|
||||
amount: this.state.amount,
|
||||
has_range: this.state.enableAmountRange,
|
||||
min_amount: this.state.minAmount,
|
||||
max_amount: this.state.maxAmount,
|
||||
payment_method: this.state.payment_method,
|
||||
is_explicit: this.state.is_explicit,
|
||||
premium: this.state.is_explicit ? null: this.state.premium,
|
||||
satoshis: this.state.is_explicit ? this.state.satoshis: null,
|
||||
public_duration: this.state.publicDuration,
|
||||
bond_size: this.state.bondSize,
|
||||
bondless_taker: this.state.allowBondless,
|
||||
}),
|
||||
};
|
||||
fetch("/api/make/",requestOptions)
|
||||
@ -253,6 +257,7 @@ export default class MakerPage extends Component {
|
||||
<Tooltip placement="top" enterTouchDelay="500" enterDelay="700" enterNextDelay="2000" title="Amount of fiat to exchange for bitcoin">
|
||||
<TextField
|
||||
disabled = {this.state.enableAmountRange}
|
||||
variant = {this.state.enableAmountRange ? 'filled' : 'outlined'}
|
||||
error={this.state.amount <= 0 & this.state.amount != "" }
|
||||
helperText={this.state.amount <= 0 & this.state.amount != "" ? 'Invalid' : null}
|
||||
label="Amount"
|
||||
@ -396,7 +401,7 @@ export default class MakerPage extends Component {
|
||||
}else{
|
||||
var max_amount = this.state.limits[this.state.currency]['max_amount']
|
||||
}
|
||||
return parseFloat(Number(max_amount).toPrecision(3))
|
||||
return parseFloat(Number(max_amount).toPrecision(2))
|
||||
}
|
||||
|
||||
getMinAmount = () => {
|
||||
@ -405,7 +410,7 @@ export default class MakerPage extends Component {
|
||||
}else{
|
||||
var min_amount = this.state.limits[this.state.currency]['min_amount']
|
||||
}
|
||||
return parseFloat(Number(min_amount).toPrecision(3))
|
||||
return parseFloat(Number(min_amount).toPrecision(2))
|
||||
}
|
||||
|
||||
|
||||
@ -417,14 +422,13 @@ export default class MakerPage extends Component {
|
||||
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<FormControl align="center">
|
||||
<Tooltip enterDelay="800" enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game, increase for higher safety assurance"}>
|
||||
<FormHelperText>
|
||||
<Tooltip enterTouchDelay="0" placement="top" title={"Set the skin-in-the-game (increase for higher safety assurance)"}>
|
||||
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
|
||||
Fidelity Bond Size <LockIcon sx={{height:20,width:20}}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormHelperText>
|
||||
|
||||
</Tooltip>
|
||||
<Slider
|
||||
sx={{width:220, align:"center"}}
|
||||
aria-label="Bond Size (%)"
|
||||
@ -462,7 +466,7 @@ export default class MakerPage extends Component {
|
||||
<Grid item xs={12} align="center" spacing={1}>
|
||||
<FormControl align="center">
|
||||
<FormHelperText>
|
||||
<Tooltip enterTouchDelay="0" title={"Let the taker chose an amount within a range"}>
|
||||
<Tooltip enterTouchDelay="0" placement="top" align="center"title={"Let the taker chose an amount within the range"}>
|
||||
<div align="center" style={{display:'flex',alignItems:'center', flexWrap:'wrap'}}>
|
||||
<Checkbox onChange={(e)=>this.setState({enableAmountRange:e.target.checked}) & (e.target.checked ? this.getLimits() : null)}/>
|
||||
Amount Range
|
||||
@ -475,12 +479,12 @@ export default class MakerPage extends Component {
|
||||
<div style={{ display: this.state.loadingLimits == false ? '':'none'}}>
|
||||
<Slider
|
||||
disableSwap={true}
|
||||
sx={{width:200, align:"center"}}
|
||||
sx={{width:190, align:"center"}}
|
||||
disabled={!this.state.enableAmountRange || this.state.loadingLimits}
|
||||
value={[this.state.minAmount, this.state.maxAmount]}
|
||||
step={this.getMaxAmount()/1000}
|
||||
//valueLabelDisplay="auto"
|
||||
valueLabelFormat={(x) => (Number(x).toPrecision(3)+" "+this.state.currencyCode)}
|
||||
step={(this.getMaxAmount()-this.getMinAmount())/100}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(x) => (parseFloat(Number(x).toPrecision(2))+" "+this.state.currencyCode)}
|
||||
marks={this.state.limits == null?
|
||||
null
|
||||
:
|
||||
@ -571,7 +575,8 @@ export default class MakerPage extends Component {
|
||||
: ""}
|
||||
<Typography component="subtitle2" variant="subtitle2">
|
||||
<div align='center'>
|
||||
Create a BTC {this.state.type==0 ? "buy":"sell"} order for {pn(this.state.amount)} {this.state.currencyCode}
|
||||
Create a BTC {this.state.type==0 ? "buy":"sell"} order for {this.state.enableAmountRange & this.state.minAmount != null?
|
||||
" "+this.state.minAmount+"-"+this.state.maxAmount : pn(this.state.amount)} {this.state.currencyCode}
|
||||
{this.state.is_explicit ? " of " + pn(this.state.satoshis) + " Satoshis" :
|
||||
(this.state.premium == 0 ? " at market price" :
|
||||
(this.state.premium > 0 ? " at a " + this.state.premium + "% premium":" at a " + -this.state.premium + "% discount")
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user