mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Add HTLC model and placeholder functions
This commit is contained in:
parent
ed3605cca6
commit
635420c9dd
10
api/admin.py
10
api/admin.py
@ -2,7 +2,7 @@ from django.contrib import admin
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from .models import Order, Profile
|
from .models import Order, LNPayment, Profile
|
||||||
|
|
||||||
admin.site.unregister(Group)
|
admin.site.unregister(Group)
|
||||||
admin.site.unregister(User)
|
admin.site.unregister(User)
|
||||||
@ -24,7 +24,13 @@ class EUserAdmin(UserAdmin):
|
|||||||
|
|
||||||
@admin.register(Order)
|
@admin.register(Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id','type','maker','taker','status','amount','currency','created_at','expires_at', 'invoice')
|
list_display = ('id','type','maker','taker','status','amount','currency','created_at','expires_at', 'buyer_invoice','maker_bond','taker_bond','trade_escrow')
|
||||||
|
list_display_links = ['id']
|
||||||
|
pass
|
||||||
|
|
||||||
|
@admin.register(LNPayment)
|
||||||
|
class LNPaymentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('id','concept','status','amount','type','invoice','secret','expires_at','sender','receiver')
|
||||||
list_display_links = ['id']
|
list_display_links = ['id']
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
41
api/lightning.py
Normal file
41
api/lightning.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
#######
|
||||||
|
# Placeholder functions
|
||||||
|
# Should work with LND (maybe c-lightning in the future)
|
||||||
|
|
||||||
|
class LNNode():
|
||||||
|
'''
|
||||||
|
Place holder functions to interact with Lightning Node
|
||||||
|
'''
|
||||||
|
|
||||||
|
def gen_hodl_invoice():
|
||||||
|
'''Generates hodl invoice to publish an order'''
|
||||||
|
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=80))
|
||||||
|
|
||||||
|
def validate_hodl_invoice_locked():
|
||||||
|
'''Generates hodl invoice to publish an order'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_ln_invoice(invoice):
|
||||||
|
'''Checks if a LN invoice is valid'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pay_buyer_invoice(invoice):
|
||||||
|
'''Sends sats to buyer'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
def charge_hodl_htlcs(invoice):
|
||||||
|
'''Charges a LN hodl invoice'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
def free_hodl_htlcs(invoice):
|
||||||
|
'''Returns sats'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,8 +11,50 @@ from pathlib import Path
|
|||||||
#############################
|
#############################
|
||||||
# TODO
|
# TODO
|
||||||
# Load hparams from .env file
|
# Load hparams from .env file
|
||||||
min_satoshis_trade = 10*1000
|
|
||||||
max_satoshis_trade = 500*1000
|
MIN_TRADE = 10*1000 #In sats
|
||||||
|
MAX_TRADE = 500*1000
|
||||||
|
FEE = 0.002 # Trade fee in %
|
||||||
|
BOND_SIZE = 0.01 # Bond in %
|
||||||
|
|
||||||
|
|
||||||
|
class LNPayment(models.Model):
|
||||||
|
|
||||||
|
class Types(models.IntegerChoices):
|
||||||
|
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hodl)
|
||||||
|
HODL = 1, 'Hodl invoice'
|
||||||
|
|
||||||
|
class Concepts(models.IntegerChoices):
|
||||||
|
MAKEBOND = 0, 'Maker bond'
|
||||||
|
TAKEBOND = 1, 'Taker-buyer bond'
|
||||||
|
TRESCROW = 2, 'Trade escrow'
|
||||||
|
PAYBUYER = 3, 'Payment to buyer'
|
||||||
|
|
||||||
|
class Status(models.IntegerChoices):
|
||||||
|
INVGEN = 0, 'Hodl invoice was generated'
|
||||||
|
LOCKED = 1, 'Hodl invoice has HTLCs locked'
|
||||||
|
CHRGED = 2, 'Hodl invoice was charged'
|
||||||
|
RETNED = 3, 'Hodl invoice was returned'
|
||||||
|
MISSNG = 4, 'Buyer invoice is missing'
|
||||||
|
IVALID = 5, 'Buyer invoice is valid'
|
||||||
|
INPAID = 6, 'Buyer invoice was paid'
|
||||||
|
INFAIL = 7, 'Buyer invoice routing failed'
|
||||||
|
|
||||||
|
# payment use case
|
||||||
|
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HODL)
|
||||||
|
concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
|
||||||
|
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
|
||||||
|
|
||||||
|
# payment details
|
||||||
|
invoice = models.CharField(max_length=300, unique=False, null=True, default=None)
|
||||||
|
secret = models.CharField(max_length=300, unique=False, null=True, default=None)
|
||||||
|
expires_at = models.DateTimeField()
|
||||||
|
amount = models.DecimalField(max_digits=9, decimal_places=4, validators=[MinValueValidator(MIN_TRADE*BOND_SIZE), MaxValueValidator(MAX_TRADE*(1+BOND_SIZE+FEE))])
|
||||||
|
|
||||||
|
# payment relationals
|
||||||
|
sender = models.ForeignKey(User, related_name='sender', on_delete=models.CASCADE, null=True, default=None)
|
||||||
|
receiver = models.ForeignKey(User, related_name='receiver', on_delete=models.CASCADE, null=True, default=None)
|
||||||
|
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
|
|
||||||
@ -25,7 +67,7 @@ class Order(models.Model):
|
|||||||
EUR = 2, 'EUR'
|
EUR = 2, 'EUR'
|
||||||
ETH = 3, 'ETH'
|
ETH = 3, 'ETH'
|
||||||
|
|
||||||
class Status(models.TextChoices):
|
class Status(models.IntegerChoices):
|
||||||
WFB = 0, 'Waiting for bond'
|
WFB = 0, 'Waiting for bond'
|
||||||
PUB = 1, 'Published in order book'
|
PUB = 1, 'Published in order book'
|
||||||
DEL = 2, 'Deleted from order book'
|
DEL = 2, 'Deleted from order book'
|
||||||
@ -48,35 +90,36 @@ class Order(models.Model):
|
|||||||
EXP = 19, 'Expired'
|
EXP = 19, 'Expired'
|
||||||
|
|
||||||
# order info
|
# order info
|
||||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=int(Status.WFB))
|
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.WFB)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
expires_at = models.DateTimeField()
|
expires_at = models.DateTimeField()
|
||||||
|
|
||||||
# order details
|
# order details
|
||||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
|
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
|
||||||
currency = models.PositiveSmallIntegerField(choices=Currencies.choices, null=False)
|
currency = models.PositiveSmallIntegerField(choices=Currencies.choices, null=False)
|
||||||
amount = models.DecimalField(max_digits=9, decimal_places=4, validators=[MinValueValidator(0.00001)])
|
amount = models.DecimalField(max_digits=9, decimal_places=4, validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)])
|
||||||
payment_method = models.CharField(max_length=30, null=False, default="Not specified")
|
payment_method = models.CharField(max_length=30, null=False, default="Not specified")
|
||||||
|
|
||||||
|
# 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 marked
|
||||||
premium = models.DecimalField(max_digits=5, decimal_places=2, default=0, null=True, validators=[MinValueValidator(-100), MaxValueValidator(999)])
|
premium = models.DecimalField(max_digits=5, decimal_places=2, default=0, null=True, validators=[MinValueValidator(-100), MaxValueValidator(999)])
|
||||||
satoshis = models.PositiveBigIntegerField(null=True, validators=[MinValueValidator(min_satoshis_trade), MaxValueValidator(max_satoshis_trade)])
|
t0_market_satoshis = models.PositiveBigIntegerField(null=True, validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)])
|
||||||
is_explicit = models.BooleanField(default=False, null=False) # pricing method. A explicit amount of sats, or a relative premium above/below market.
|
# explicit
|
||||||
|
satoshis = models.PositiveBigIntegerField(null=True, validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)])
|
||||||
|
|
||||||
# order participants
|
# order participants
|
||||||
maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order
|
maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order
|
||||||
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a taker can only take one order
|
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a taker can only take one order
|
||||||
|
|
||||||
# order collateral
|
# order collateral
|
||||||
has_maker_bond = models.BooleanField(default=False, null=False)
|
maker_bond = models.ForeignKey(LNPayment, related_name='maker_bond', on_delete=models.SET_NULL, null=True, default=None)
|
||||||
has_taker_bond = models.BooleanField(default=False, null=False)
|
taker_bond = models.ForeignKey(LNPayment, related_name='taker_bond', on_delete=models.SET_NULL, null=True, default=None)
|
||||||
has_trade_collat = models.BooleanField(default=False, null=False)
|
trade_escrow = models.ForeignKey(LNPayment, related_name='trade_escrow', on_delete=models.SET_NULL, null=True, default=None)
|
||||||
|
|
||||||
maker_bond_secret = models.CharField(max_length=300, unique=False, null=True, default=None)
|
|
||||||
taker_bond_secret = models.CharField(max_length=300, unique=False, null=True, default=None)
|
|
||||||
trade_collat_secret = models.CharField(max_length=300, unique=False, null=True, default=None)
|
|
||||||
|
|
||||||
# buyer payment LN invoice
|
# buyer payment LN invoice
|
||||||
has_invoice = models.BooleanField(default=False, null=False) # has invoice and is valid
|
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None)
|
||||||
invoice = models.CharField(max_length=300, unique=False, null=True, default=None)
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
class Profile(models.Model):
|
||||||
|
|
||||||
|
26
api/views.py
26
api/views.py
@ -8,7 +8,8 @@ from django.contrib.auth import authenticate, login, logout
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
||||||
from .models import Order
|
from .models import Order, LNPayment
|
||||||
|
from .lightning import LNNode
|
||||||
|
|
||||||
from .nick_generator.nick_generator import NickGenerator
|
from .nick_generator.nick_generator import NickGenerator
|
||||||
from robohash import Robohash
|
from robohash import Robohash
|
||||||
@ -39,11 +40,6 @@ def validate_already_maker_or_taker(request):
|
|||||||
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def validate_ln_invoice(invoice):
|
|
||||||
'''Checks if a LN invoice is valid'''
|
|
||||||
#TODO
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
class OrderMakerView(CreateAPIView):
|
class OrderMakerView(CreateAPIView):
|
||||||
@ -68,7 +64,7 @@ class OrderMakerView(CreateAPIView):
|
|||||||
# Creates a new order in db
|
# Creates a new order in db
|
||||||
order = Order(
|
order = Order(
|
||||||
type=otype,
|
type=otype,
|
||||||
status=int(Order.Status.PUB), # TODO orders are public by default for the moment. Future it will be WFB (waiting for bond)
|
status=Order.Status.PUB, # TODO orders are public by default for the moment. Future it will be WFB (waiting for bond)
|
||||||
currency=currency,
|
currency=currency,
|
||||||
amount=amount,
|
amount=amount,
|
||||||
payment_method=payment_method,
|
payment_method=payment_method,
|
||||||
@ -105,11 +101,11 @@ class OrderView(viewsets.ViewSet):
|
|||||||
data['is_maker'] = str(order.maker) == nickname
|
data['is_maker'] = str(order.maker) == nickname
|
||||||
data['is_taker'] = str(order.taker) == nickname
|
data['is_taker'] = str(order.taker) == nickname
|
||||||
data['is_participant'] = data['is_maker'] or data['is_taker']
|
data['is_participant'] = data['is_maker'] or data['is_taker']
|
||||||
data['is_buyer'] = (data['is_maker'] and order.type == int(Order.Types.BUY)) or (data['is_taker'] and order.type == int(Order.Types.SELL))
|
data['is_buyer'] = (data['is_maker'] and order.type == Order.Types.BUY) or (data['is_taker'] and order.type == Order.Types.SELL)
|
||||||
data['is_seller'] = (data['is_maker'] and order.type == int(Order.Types.SELL)) or (data['is_taker'] and order.type == int(Order.Types.BUY))
|
data['is_seller'] = (data['is_maker'] and order.type == Order.Types.SELL) or (data['is_taker'] and order.type == Order.Types.BUY)
|
||||||
|
|
||||||
# If not a participant and order is not public, forbid.
|
# If not a participant and order is not public, forbid.
|
||||||
if not data['is_participant'] and order.status != int(Order.Status.PUB):
|
if not data['is_participant'] and order.status != Order.Status.PUB:
|
||||||
return Response({'bad_request':'Not allowed to see this order'},status.HTTP_403_FORBIDDEN)
|
return Response({'bad_request':'Not allowed to see this order'},status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# return nicks too
|
# return nicks too
|
||||||
@ -142,28 +138,28 @@ class OrderView(viewsets.ViewSet):
|
|||||||
invoice = serializer.data.get('invoice')
|
invoice = serializer.data.get('invoice')
|
||||||
|
|
||||||
# If this is an empty POST request (no invoice), it must be taker request!
|
# If this is an empty POST request (no invoice), it must be taker request!
|
||||||
if not invoice and order.status == int(Order.Status.PUB):
|
if not invoice and order.status == Order.Status.PUB:
|
||||||
|
|
||||||
valid, response = validate_already_maker_or_taker(request)
|
valid, response = validate_already_maker_or_taker(request)
|
||||||
if not valid:
|
if not valid:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
order.taker = self.request.user
|
order.taker = self.request.user
|
||||||
order.status = int(Order.Status.TAK)
|
order.status = Order.Status.TAK
|
||||||
|
|
||||||
#TODO REPLY WITH HODL INVOICE
|
#TODO REPLY WITH HODL INVOICE
|
||||||
data = ListOrderSerializer(order).data
|
data = ListOrderSerializer(order).data
|
||||||
|
|
||||||
# An invoice came in! update it
|
# An invoice came in! update it
|
||||||
elif invoice:
|
elif invoice:
|
||||||
if validate_ln_invoice(invoice):
|
if LNNode.validate_ln_invoice(invoice):
|
||||||
order.invoice = invoice
|
order.invoice = invoice
|
||||||
|
|
||||||
#TODO Validate if request comes from PARTICIPANT AND BUYER
|
#TODO Validate if request comes from PARTICIPANT AND BUYER
|
||||||
|
|
||||||
#If the order status was Payment Failed. Move foward to invoice Updated.
|
#If the order status was Payment Failed. Move foward to invoice Updated.
|
||||||
if order.status == int(Order.Status.FAI):
|
if order.status == Order.Status.FAI:
|
||||||
order.status = int(Order.Status.UPI)
|
order.status = Order.Status.UPI
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return Response({'bad_request':'Invalid Lightning Network Invoice. It starts by LNTB...'})
|
return Response({'bad_request':'Invalid Lightning Network Invoice. It starts by LNTB...'})
|
||||||
|
0
frontend/src/components/TradePipelineBox.js
Normal file
0
frontend/src/components/TradePipelineBox.js
Normal file
Loading…
Reference in New Issue
Block a user