mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-31 11:51:37 +00:00
Add automatic dispute resolution (#437)
* Add automatic dispute resolution logic * Small fixes
This commit is contained in:
@ -11,6 +11,7 @@ from api.lightning.node import LNNode
|
|||||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, User
|
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order, User
|
||||||
from api.tasks import send_notification
|
from api.tasks import send_notification
|
||||||
from api.utils import validate_onchain_address
|
from api.utils import validate_onchain_address
|
||||||
|
from chat.models import Message
|
||||||
|
|
||||||
FEE = float(config("FEE"))
|
FEE = float(config("FEE"))
|
||||||
MAKER_FEE_SPLIT = float(config("MAKER_FEE_SPLIT"))
|
MAKER_FEE_SPLIT = float(config("MAKER_FEE_SPLIT"))
|
||||||
@ -428,6 +429,65 @@ class Logics:
|
|||||||
cls.publish_order(order)
|
cls.publish_order(order)
|
||||||
return True
|
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
|
@classmethod
|
||||||
def open_dispute(cls, order, user=None):
|
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"
|
"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:
|
if not order.trade_escrow.status == LNPayment.Status.SETLED:
|
||||||
cls.settle_escrow(order)
|
cls.settle_escrow(order)
|
||||||
cls.settle_bond(order.maker_bond)
|
cls.settle_bond(order.maker_bond)
|
||||||
@ -481,9 +546,9 @@ class Logics:
|
|||||||
"bad_request": "Only orders in dispute accept dispute statements"
|
"bad_request": "Only orders in dispute accept dispute statements"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(statement) > 10000:
|
if len(statement) > 50000:
|
||||||
return False, {
|
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:
|
if len(statement) < 100:
|
||||||
@ -1601,7 +1666,6 @@ class Logics:
|
|||||||
def add_slashed_rewards(cls, bond, profile):
|
def add_slashed_rewards(cls, bond, profile):
|
||||||
"""
|
"""
|
||||||
When a bond is slashed due to overtime, rewards the user that was waiting.
|
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_fraction = float(config("SLASHED_BOND_REWARD_SPLIT"))
|
||||||
reward = int(bond.num_satoshis * reward_fraction)
|
reward = int(bond.num_satoshis * reward_fraction)
|
||||||
|
@ -444,10 +444,10 @@ class Order(models.Model):
|
|||||||
# in dispute
|
# in dispute
|
||||||
is_disputed = models.BooleanField(default=False, null=False)
|
is_disputed = models.BooleanField(default=False, null=False)
|
||||||
maker_statement = models.TextField(
|
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(
|
taker_statement = models.TextField(
|
||||||
max_length=10000, null=True, default=None, blank=True
|
max_length=50000, null=True, default=None, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# LNpayments
|
# LNpayments
|
||||||
|
@ -492,7 +492,7 @@ class UpdateOrderSerializer(serializers.Serializer):
|
|||||||
routing_budget_ppm = serializers.IntegerField(
|
routing_budget_ppm = serializers.IntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=100000,
|
max_value=100001,
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
required=False,
|
required=False,
|
||||||
help_text="Max budget to allocate for routing in PPM",
|
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
|
max_length=100, allow_null=True, allow_blank=True, default=None
|
||||||
)
|
)
|
||||||
statement = serializers.CharField(
|
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(
|
action = serializers.ChoiceField(
|
||||||
choices=(
|
choices=(
|
||||||
|
Reference in New Issue
Block a user