diff --git a/.env-sample b/.env-sample index 574c891b..a9362b4f 100644 --- a/.env-sample +++ b/.env-sample @@ -91,11 +91,6 @@ MAKER_FEE_SPLIT=0.125 # Leaving the default value (20%) will grant the DevFund contributor badge. DEVFUND = 0.2 -# Bond size as percentage (%) -DEFAULT_BOND_SIZE = 3 -MIN_BOND_SIZE = 1 -MAX_BOND_SIZE = 15 - # Time out penalty for canceling takers in SECONDS PENALTY_TIMEOUT = 60 # Time between routing attempts of buyer invoice in MINUTES @@ -109,9 +104,11 @@ DISABLE_ORDER_LOGS = False # Coordinator activity limits MAX_PUBLIC_ORDERS = 100 -# Trade limits in satoshis -MIN_TRADE = 20000 -MAX_TRADE = 5000000 +# Coordinator Order size limits in Satoshi +# Minimum order size (must be bigger than DB constrain in /robosats/settings.py MIN_TRADE, currently 20_000 Sats) +MIN_ORDER_SIZE = 20000 +# Minimum order size (must be smaller than DB constrain in /robosats/settings.py MAX_TRADE, currently 5_000_000 Sats) +MAX_ORDER_SIZE = 5000000 # For CLTV_expiry calculation # Assume 8 min/block assumed @@ -123,16 +120,6 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = 1.7 EXP_MAKER_BOND_INVOICE = 300 EXP_TAKER_BOND_INVOICE = 200 -# Time a order is public in the book HOURS -DEFAULT_PUBLIC_ORDER_DURATION = 24 -MAX_PUBLIC_ORDER_DURATION = 24 -MIN_PUBLIC_ORDER_DURATION = 0.166 - -# Default time to provide a valid invoice and the trade escrow MINUTES -INVOICE_AND_ESCROW_DURATION = 180 -# Time to confim chat and confirm fiat (time to Fiat Sent confirmation) HOURS -FIAT_EXCHANGE_DURATION = 24 - # ROUTING # Proportional routing fee limit (fraction of total payout: % / 100) PROPORTIONAL_ROUTING_FEE_LIMIT = 0.001 diff --git a/api/logics.py b/api/logics.py index ad556e3d..c37a7f99 100644 --- a/api/logics.py +++ b/api/logics.py @@ -18,8 +18,8 @@ MAKER_FEE_SPLIT = float(config("MAKER_FEE_SPLIT")) ESCROW_USERNAME = config("ESCROW_USERNAME") PENALTY_TIMEOUT = int(config("PENALTY_TIMEOUT")) -MIN_TRADE = int(config("MIN_TRADE")) -MAX_TRADE = int(config("MAX_TRADE")) +MIN_ORDER_SIZE = config("MIN_ORDER_SIZE", cast=int, default=20_000) +MAX_ORDER_SIZE = config("MAX_ORDER_SIZE", cast=int, default=5_000_000) EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE")) EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE")) @@ -29,9 +29,6 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float( config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED") ) -INVOICE_AND_ESCROW_DURATION = int(config("INVOICE_AND_ESCROW_DURATION")) -FIAT_EXCHANGE_DURATION = int(config("FIAT_EXCHANGE_DURATION")) - class Logics: @classmethod @@ -90,20 +87,20 @@ class Logics: 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: + if order.t0_satoshis > MAX_ORDER_SIZE: 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) + + "{:,}".format(MAX_ORDER_SIZE) + " Sats" } - if order.t0_satoshis < MIN_TRADE: + if order.t0_satoshis < MIN_ORDER_SIZE: 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) + + "{:,}".format(MIN_ORDER_SIZE) + " Sats" } elif order.has_range: @@ -117,20 +114,20 @@ class Logics: return False, { "bad_request": "Maximum range amount must be at least 50 percent higher than the minimum amount" } - elif max_sats > MAX_TRADE: + elif max_sats > MAX_ORDER_SIZE: return False, { "bad_request": "Your order maximum amount is too big. It is worth " + "{:,}".format(int(max_sats)) + " Sats now, but the limit is " - + "{:,}".format(MAX_TRADE) + + "{:,}".format(MAX_ORDER_SIZE) + " Sats" } - elif min_sats < MIN_TRADE: + elif min_sats < MIN_ORDER_SIZE: return False, { "bad_request": "Your order minimum amount is too small. It is worth " + "{:,}".format(int(min_sats)) + " Sats now, but the limit is " - + "{:,}".format(MIN_TRADE) + + "{:,}".format(MIN_ORDER_SIZE) + " Sats" } elif min_sats < max_sats / 15: @@ -590,7 +587,7 @@ class Logics: shape = str(config("SWAP_FEE_SHAPE")) if shape == "linear": - MIN_SWAP_FEE = float(config("MIN_SWAP_FEE")) + MIN_SWAP_FEE = config("MIN_SWAP_FEE", cast=float, default=0.01) MIN_POINT = float(config("MIN_POINT")) MAX_SWAP_FEE = float(config("MAX_SWAP_FEE")) MAX_POINT = float(config("MAX_POINT")) @@ -603,7 +600,7 @@ class Logics: ) elif shape == "exponential": - MIN_SWAP_FEE = float(config("MIN_SWAP_FEE")) + MIN_SWAP_FEE = config("MIN_SWAP_FEE", cast=float, default=0.01) MAX_SWAP_FEE = float(config("MAX_SWAP_FEE")) SWAP_LAMBDA = float(config("SWAP_LAMBDA")) swap_fee_rate = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * math.exp( diff --git a/api/models/ln_payment.py b/api/models/ln_payment.py index 949b1e21..76a73be3 100644 --- a/api/models/ln_payment.py +++ b/api/models/ln_payment.py @@ -1,4 +1,4 @@ -from decouple import config +from django.conf import settings from django.contrib.auth.models import User from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -78,7 +78,7 @@ class LNPayment(models.Model): num_satoshis = models.PositiveBigIntegerField( validators=[ MinValueValidator(100), - MaxValueValidator(1.5 * config("MAX_TRADE", cast=int, default=1_000_000)), + MaxValueValidator(1.5 * settings.MAX_TRADE), ] ) # Routing budget in PPM diff --git a/api/models/market_tick.py b/api/models/market_tick.py index 662080a6..ac09d360 100644 --- a/api/models/market_tick.py +++ b/api/models/market_tick.py @@ -51,7 +51,7 @@ class MarketTick(models.Model): fee = models.DecimalField( max_digits=4, decimal_places=4, - default=config("FEE", cast=float, default=0), + default=0, validators=[MinValueValidator(0), MaxValueValidator(1)], ) @@ -71,7 +71,11 @@ class MarketTick(models.Model): premium = 100 * (price / market_exchange_rate - 1) market_tick = MarketTick.objects.create( - price=price, volume=volume, premium=premium, currency=order.currency + price=price, + volume=volume, + premium=premium, + currency=order.currency, + fee=config("FEE", cast=float, default=0), ) return market_tick diff --git a/api/models/onchain_payment.py b/api/models/onchain_payment.py index f3efc947..c6959973 100644 --- a/api/models/onchain_payment.py +++ b/api/models/onchain_payment.py @@ -1,4 +1,4 @@ -from decouple import config +from django.conf import settings from django.contrib.auth.models import User from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -7,9 +7,6 @@ from django.utils import timezone from control.models import BalanceLog -MAX_TRADE = config("MAX_TRADE", cast=int, default=1_000_000) -MIN_SWAP_AMOUNT = config("MIN_SWAP_AMOUNT", cast=int, default=1_000_000) - class OnchainPayment(models.Model): class Concepts(models.IntegerChoices): @@ -48,17 +45,11 @@ class OnchainPayment(models.Model): num_satoshis = models.PositiveBigIntegerField( null=True, - validators=[ - MinValueValidator(0.5 * MIN_SWAP_AMOUNT), - MaxValueValidator(1.5 * MAX_TRADE), - ], + validators=[MinValueValidator(0), MaxValueValidator(1.5 * settings.MAX_TRADE)], ) sent_satoshis = models.PositiveBigIntegerField( null=True, - validators=[ - MinValueValidator(0.5 * MIN_SWAP_AMOUNT), - MaxValueValidator(1.5 * MAX_TRADE), - ], + validators=[MinValueValidator(0), MaxValueValidator(1.5 * settings.MAX_TRADE)], ) # fee in sats/vbyte with mSats decimals fee_msat suggested_mining_fee_rate = models.DecimalField( @@ -91,7 +82,7 @@ class OnchainPayment(models.Model): swap_fee_rate = models.DecimalField( max_digits=4, decimal_places=2, - default=config("MIN_SWAP_FEE", cast=float, default=0.01) * 100, + default=1, null=False, blank=False, ) diff --git a/api/models/order.py b/api/models/order.py index 4d568381..612024b9 100644 --- a/api/models/order.py +++ b/api/models/order.py @@ -1,6 +1,7 @@ import uuid from decouple import config +from django.conf import settings from django.contrib.auth.models import User from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -8,10 +9,6 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver from django.utils import timezone -MIN_TRADE = config("MIN_TRADE", cast=int, default=20_000) -MAX_TRADE = config("MAX_TRADE", cast=int, default=1_000_000) -FIAT_EXCHANGE_DURATION = config("FIAT_EXCHANGE_DURATION", cast=int, default=24) - class Order(models.Model): class Types(models.IntegerChoices): @@ -85,20 +82,22 @@ class Order(models.Model): # explicit satoshis = models.PositiveBigIntegerField( null=True, - validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)], + validators=[ + MinValueValidator(settings.MIN_TRADE), + MaxValueValidator(settings.MAX_TRADE), + ], blank=True, ) # optionally makers can choose the public order duration length (seconds) public_duration = models.PositiveBigIntegerField( - default=60 * 60 * config("DEFAULT_PUBLIC_ORDER_DURATION", cast=int, default=24) - - 1, + default=60 * 60 * settings.DEFAULT_PUBLIC_ORDER_DURATION - 1, null=False, validators=[ MinValueValidator( - 60 * 60 * config("MIN_PUBLIC_ORDER_DURATION", cast=float, default=0.166) + 60 * 60 * settings.MIN_PUBLIC_ORDER_DURATION ), # Min is 10 minutes MaxValueValidator( - 60 * 60 * config("MAX_PUBLIC_ORDER_DURATION", cast=float, default=24) + 60 * 60 * settings.MAX_PUBLIC_ORDER_DURATION ), # Max is 24 Hours ], blank=False, @@ -106,7 +105,7 @@ class Order(models.Model): # optionally makers can choose the escrow lock / invoice submission step length (seconds) escrow_duration = models.PositiveBigIntegerField( - default=60 * int(config("INVOICE_AND_ESCROW_DURATION")) - 1, + default=60 * settings.INVOICE_AND_ESCROW_DURATION - 1, null=False, validators=[ MinValueValidator(60 * 30), # Min is 30 minutes @@ -119,11 +118,11 @@ class Order(models.Model): bond_size = models.DecimalField( max_digits=4, decimal_places=2, - default=config("DEFAULT_BOND_SIZE", cast=float, default=3), + default=settings.DEFAULT_BOND_SIZE, null=False, validators=[ - MinValueValidator(config("MIN_BOND_SIZE", cast=float, default=1)), # 1 % - MaxValueValidator(config("MAX_BOND_SIZE", cast=float, default=1)), # 15 % + MinValueValidator(settings.MIN_BOND_SIZE), # 2 % + MaxValueValidator(settings.MAX_BOND_SIZE), # 15 % ], blank=False, ) @@ -153,12 +152,15 @@ class Order(models.Model): # how many sats at creation and at last check (relevant for marked to market) t0_satoshis = models.PositiveBigIntegerField( null=True, - validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)], + validators=[ + MinValueValidator(settings.MIN_TRADE), + MaxValueValidator(settings.MAX_TRADE), + ], blank=True, ) # sats at creation last_satoshis = models.PositiveBigIntegerField( null=True, - validators=[MinValueValidator(0), MaxValueValidator(MAX_TRADE * 2)], + validators=[MinValueValidator(0), MaxValueValidator(settings.MAX_TRADE * 2)], blank=True, ) # sats last time checked. Weird if 2* trade max... # timestamp of last_satoshis @@ -289,8 +291,10 @@ class Order(models.Model): ), # 'Waiting for trade collateral and buyer invoice' 7: int(self.escrow_duration), # 'Waiting only for seller trade collateral' 8: int(self.escrow_duration), # 'Waiting only for buyer invoice' - 9: 60 * 60 * FIAT_EXCHANGE_DURATION, # 'Sending fiat - In chatroom' - 10: 60 * 60 * FIAT_EXCHANGE_DURATION, # 'Fiat sent - In chatroom' + 9: 60 + * 60 + * settings.FIAT_EXCHANGE_DURATION, # 'Sending fiat - In chatroom' + 10: 60 * 60 * settings.FIAT_EXCHANGE_DURATION, # 'Fiat sent - In chatroom' 11: 1 * 24 * 60 * 60, # 'In dispute' 12: 0, # 'Collaboratively cancelled' 13: 100 * 24 * 60 * 60, # 'Sending satoshis to buyer' diff --git a/api/oas_schemas.py b/api/oas_schemas.py index bb2abfa4..ed59bcd5 100644 --- a/api/oas_schemas.py +++ b/api/oas_schemas.py @@ -1,6 +1,7 @@ import textwrap from decouple import config +from django.conf import settings from drf_spectacular.utils import OpenApiExample, OpenApiParameter from api.serializers import ( @@ -11,9 +12,6 @@ from api.serializers import ( EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE")) RETRY_TIME = int(config("RETRY_TIME")) -PUBLIC_DURATION = 60 * 60 * int(config("DEFAULT_PUBLIC_ORDER_DURATION")) - 1 -ESCROW_DURATION = 60 * int(config("INVOICE_AND_ESCROW_DURATION")) -BOND_SIZE = int(config("DEFAULT_BOND_SIZE")) class MakerViewSchema: @@ -25,9 +23,9 @@ class MakerViewSchema: Default values for the following fields if not specified: - - `public_duration` - **{PUBLIC_DURATION}** - - `escrow_duration` - **{ESCROW_DURATION}** - - `bond_size` - **{BOND_SIZE}** + - `public_duration` - **{settings.DEFAULT_PUBLIC_ORDER_DURATION}** + - `escrow_duration` - **{settings.INVOICE_AND_ESCROW_DURATION}** + - `bond_size` - **{settings.DEFAULT_BOND_SIZE}** - `has_range` - **false** - `premium` - **0** """ diff --git a/api/serializers.py b/api/serializers.py index ad909499..d2b0db2a 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -4,8 +4,6 @@ from rest_framework import serializers from .models import MarketTick, Order RETRY_TIME = int(config("RETRY_TIME")) -MIN_PUBLIC_ORDER_DURATION_SECS = 60 * 60 * float(config("MIN_PUBLIC_ORDER_DURATION")) -MAX_PUBLIC_ORDER_DURATION_SECS = 60 * 60 * float(config("MAX_PUBLIC_ORDER_DURATION")) class InfoSerializer(serializers.Serializer): diff --git a/api/views.py b/api/views.py index 8e458d5a..d03485ef 100644 --- a/api/views.py +++ b/api/views.py @@ -54,15 +54,10 @@ from control.models import AccountingDay, BalanceLog EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE")) RETRY_TIME = int(config("RETRY_TIME")) -PUBLIC_DURATION = 60 * 60 * int(config("DEFAULT_PUBLIC_ORDER_DURATION")) - 1 -ESCROW_DURATION = 60 * int(config("INVOICE_AND_ESCROW_DURATION")) -BOND_SIZE = int(config("DEFAULT_BOND_SIZE")) avatar_path = Path(settings.AVATAR_ROOT) avatar_path.mkdir(parents=True, exist_ok=True) -# Create your views here. - class MakerView(CreateAPIView): serializer_class = MakeOrderSerializer @@ -115,11 +110,11 @@ class MakerView(CreateAPIView): # Optional params if public_duration is None: - public_duration = PUBLIC_DURATION + public_duration = 60 * 60 * settings.DEFAULT_PUBLIC_ORDER_DURATION if escrow_duration is None: - escrow_duration = ESCROW_DURATION + escrow_duration = 60 * settings.INVOICE_AND_ESCROW_DURATION if bond_size is None: - bond_size = BOND_SIZE + bond_size = settings.DEFAULT_BOND_SIZE if has_range is None: has_range = False @@ -793,7 +788,7 @@ class InfoView(ListAPIView): context["taker_fee"] = float(config("FEE")) * ( 1 - float(config("MAKER_FEE_SPLIT")) ) - context["bond_size"] = float(config("DEFAULT_BOND_SIZE")) + context["bond_size"] = settings.DEFAULT_BOND_SIZE context["notice_severity"] = config("NOTICE_SEVERITY", cast=str, default="none") context["notice_message"] = config("NOTICE_MESSAGE", cast=str, default="") @@ -907,8 +902,8 @@ class LimitView(ListAPIView): @extend_schema(**LimitViewSchema.get) def get(self, request): # Trade limits as BTC - min_trade = float(config("MIN_TRADE")) / 100_000_000 - max_trade = float(config("MAX_TRADE")) / 100_000_000 + min_trade = config("MIN_ORDER_SIZE", cast=int, default=20_000) / 100_000_000 + max_trade = config("MAX_ORDER_SIZE", cast=int, default=5_000_000) / 100_000_000 payload = {} queryset = Currency.objects.all().order_by("currency") diff --git a/robosats/settings.py b/robosats/settings.py index 7ca1bf6c..492ac9cb 100644 --- a/robosats/settings.py +++ b/robosats/settings.py @@ -254,3 +254,32 @@ CACHES = { # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +##################################################################### +# RoboSats API settings. These should remain the same across +# all coordinators. Altering will require a DB migration. +# Do not change unless you know what you are doing. +# If there is a value here you would like to tweak or play with, +# there is possibly a better way to do it! E.g. the .env file + +# Trade limits in satoshis to be applied as DB validator/constrain +MIN_TRADE = 20_000 +MAX_TRADE = 5_000_000 + +# Time a order is public in the book HOURS +DEFAULT_PUBLIC_ORDER_DURATION = 24 +# Max value API will accept for public duration (cannot be higher than 24h, hardcoded as DB validator) +MAX_PUBLIC_ORDER_DURATION = 24 +# Max value API will accept for public duration (cannot be higher than 24h, hardcoded as DB validator) +MIN_PUBLIC_ORDER_DURATION = 0.166 + +# Bond size as percentage (%) +DEFAULT_BOND_SIZE = 3 +MIN_BOND_SIZE = 2 +MAX_BOND_SIZE = 15 + +# Default time to provide a valid invoice and the trade escrow MINUTES +INVOICE_AND_ESCROW_DURATION = 180 +# Time to confirm chat and confirm fiat (time to Fiat Sent confirmation) HOURS +FIAT_EXCHANGE_DURATION = 24