Add HTLC model and placeholder functions

This commit is contained in:
Reckless_Satoshi 2022-01-05 02:30:38 -08:00
parent ed3605cca6
commit 635420c9dd
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
5 changed files with 121 additions and 35 deletions

View File

@ -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
View 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

View File

@ -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,36 +90,37 @@ 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")
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)])
is_explicit = models.BooleanField(default=False, null=False) # pricing method. A explicit amount of sats, or a relative premium above/below market.
# 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)])
t0_market_satoshis = models.PositiveBigIntegerField(null=True, validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)])
# 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):
user = models.OneToOneField(User,on_delete=models.CASCADE) user = models.OneToOneField(User,on_delete=models.CASCADE)

View File

@ -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...'})