diff --git a/api/logics.py b/api/logics.py index 4f917a7f..9032a5bb 100644 --- a/api/logics.py +++ b/api/logics.py @@ -11,6 +11,7 @@ from api.lightning.node import LNNode from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, User from api.tasks import send_notification from api.utils import validate_onchain_address +from chat.models import Message FEE = float(config("FEE")) MAKER_FEE_SPLIT = float(config("MAKER_FEE_SPLIT")) @@ -428,6 +429,65 @@ class Logics: cls.publish_order(order) return True + @classmethod + def automatic_dispute_resolution(cls, order): + """Simple case where a dispute can be solved with a + priori knowledge. For example, a dispute that opens + at expiration on an order where one of the participants + never sent a message on the chat and never marked 'fiat + sent'. By solving the dispute automatically before + flagging it as dispute, we avoid having to settle the + bonds""" + + # If fiat has been marked as sent, automatic dispute + # resolution is not possible. + if order.is_fiat_sent: + return False + + # If the order has not entered dispute due to time expire + # (a user triggered it), automatic dispute resolution is + # not possible. + if order.expires_at >= timezone.now(): + return False + + num_messages_taker = len( + Message.objects.filter(order=order, sender=order.taker) + ) + num_messages_maker = len( + Message.objects.filter(order=order, sender=order.maker) + ) + + if num_messages_maker == num_messages_taker == 0: + cls.return_escrow(order) + cls.settle_bond(order.maker_bond) + cls.settle_bond(order.taker_bond) + order.status = Order.Status.DIS + + elif num_messages_maker == 0: + cls.return_escrow(order) + cls.settle_bond(order.maker_bond) + cls.return_bond(order.taker_bond) + cls.add_slashed_rewards(order.maker_bond, order.taker.profile) + order.status = Order.Status.MLD + + elif num_messages_maker == 0: + cls.return_escrow(order) + cls.settle_bond(order.maker_bond) + cls.return_bond(order.taker_bond) + cls.add_slashed_rewards(order.taker_bond, order.maker.profile) + order.status = Order.Status.TLD + else: + return False + + order.is_disputed = True + order.expires_at = timezone.now() + timedelta( + seconds=order.t_to_expire(Order.Status.DIS) + ) + order.save() + send_notification.delay(order_id=order.id, message="dispute_opened") + + return True + @classmethod def open_dispute(cls, order, user=None): @@ -446,6 +506,11 @@ class Logics: "bad_request": "You cannot open a dispute of this order at this stage" } + automatically_solved = cls.automatic_dispute_resolution(order) + + if automatically_solved: + return True, None + if not order.trade_escrow.status == LNPayment.Status.SETLED: cls.settle_escrow(order) cls.settle_bond(order.maker_bond) @@ -481,9 +546,9 @@ class Logics: "bad_request": "Only orders in dispute accept dispute statements" } - if len(statement) > 10000: + if len(statement) > 50000: return False, { - "bad_statement": "The statement is longer than 10000 characters" + "bad_statement": "The statement and chatlogs are longer than 50000 characters" } if len(statement) < 100: @@ -1601,7 +1666,6 @@ class Logics: def add_slashed_rewards(cls, bond, profile): """ When a bond is slashed due to overtime, rewards the user that was waiting. - If participants of the order were referred, the reward is given to the referees. """ reward_fraction = float(config("SLASHED_BOND_REWARD_SPLIT")) reward = int(bond.num_satoshis * reward_fraction) diff --git a/api/models.py b/api/models.py index e8715ccb..b26d6a41 100644 --- a/api/models.py +++ b/api/models.py @@ -444,10 +444,10 @@ class Order(models.Model): # in dispute is_disputed = models.BooleanField(default=False, null=False) maker_statement = models.TextField( - max_length=10000, null=True, default=None, blank=True + max_length=50000, null=True, default=None, blank=True ) taker_statement = models.TextField( - max_length=10000, null=True, default=None, blank=True + max_length=50000, null=True, default=None, blank=True ) # LNpayments diff --git a/api/serializers.py b/api/serializers.py index 6124d0e4..c1267f2d 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -492,7 +492,7 @@ class UpdateOrderSerializer(serializers.Serializer): routing_budget_ppm = serializers.IntegerField( default=0, min_value=0, - max_value=100000, + max_value=100001, allow_null=True, required=False, help_text="Max budget to allocate for routing in PPM", @@ -501,7 +501,7 @@ class UpdateOrderSerializer(serializers.Serializer): max_length=100, allow_null=True, allow_blank=True, default=None ) statement = serializers.CharField( - max_length=11000, allow_null=True, allow_blank=True, default=None + max_length=500000, allow_null=True, allow_blank=True, default=None ) action = serializers.ChoiceField( choices=(