mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21: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.contrib.auth.models import Group, User
|
||||
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(User)
|
||||
@ -24,7 +24,13 @@ class EUserAdmin(UserAdmin):
|
||||
|
||||
@admin.register(Order)
|
||||
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']
|
||||
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
|
||||
# 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):
|
||||
|
||||
@ -25,7 +67,7 @@ class Order(models.Model):
|
||||
EUR = 2, 'EUR'
|
||||
ETH = 3, 'ETH'
|
||||
|
||||
class Status(models.TextChoices):
|
||||
class Status(models.IntegerChoices):
|
||||
WFB = 0, 'Waiting for bond'
|
||||
PUB = 1, 'Published in order book'
|
||||
DEL = 2, 'Deleted from order book'
|
||||
@ -48,35 +90,36 @@ class Order(models.Model):
|
||||
EXP = 19, 'Expired'
|
||||
|
||||
# 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)
|
||||
expires_at = models.DateTimeField()
|
||||
|
||||
# order details
|
||||
type = models.PositiveSmallIntegerField(choices=Types.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")
|
||||
|
||||
# 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)])
|
||||
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.
|
||||
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
|
||||
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
|
||||
|
||||
# order collateral
|
||||
has_maker_bond = models.BooleanField(default=False, null=False)
|
||||
has_taker_bond = models.BooleanField(default=False, null=False)
|
||||
has_trade_collat = models.BooleanField(default=False, null=False)
|
||||
|
||||
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)
|
||||
maker_bond = models.ForeignKey(LNPayment, related_name='maker_bond', on_delete=models.SET_NULL, null=True, default=None)
|
||||
taker_bond = models.ForeignKey(LNPayment, related_name='taker_bond', on_delete=models.SET_NULL, null=True, default=None)
|
||||
trade_escrow = models.ForeignKey(LNPayment, related_name='trade_escrow', on_delete=models.SET_NULL, null=True, default=None)
|
||||
|
||||
# buyer payment LN invoice
|
||||
has_invoice = models.BooleanField(default=False, null=False) # has invoice and is valid
|
||||
invoice = models.CharField(max_length=300, unique=False, null=True, default=None)
|
||||
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None)
|
||||
|
||||
|
||||
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 .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 robohash import Robohash
|
||||
@ -39,11 +40,6 @@ def validate_already_maker_or_taker(request):
|
||||
|
||||
return True, None
|
||||
|
||||
def validate_ln_invoice(invoice):
|
||||
'''Checks if a LN invoice is valid'''
|
||||
#TODO
|
||||
return True
|
||||
|
||||
# Create your views here.
|
||||
|
||||
class OrderMakerView(CreateAPIView):
|
||||
@ -68,7 +64,7 @@ class OrderMakerView(CreateAPIView):
|
||||
# Creates a new order in db
|
||||
order = Order(
|
||||
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,
|
||||
amount=amount,
|
||||
payment_method=payment_method,
|
||||
@ -105,11 +101,11 @@ class OrderView(viewsets.ViewSet):
|
||||
data['is_maker'] = str(order.maker) == nickname
|
||||
data['is_taker'] = str(order.taker) == nickname
|
||||
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_seller'] = (data['is_maker'] and order.type == int(Order.Types.SELL)) or (data['is_taker'] and order.type == int(Order.Types.BUY))
|
||||
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 == 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 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 nicks too
|
||||
@ -142,28 +138,28 @@ class OrderView(viewsets.ViewSet):
|
||||
invoice = serializer.data.get('invoice')
|
||||
|
||||
# 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)
|
||||
if not valid:
|
||||
return response
|
||||
|
||||
order.taker = self.request.user
|
||||
order.status = int(Order.Status.TAK)
|
||||
order.status = Order.Status.TAK
|
||||
|
||||
#TODO REPLY WITH HODL INVOICE
|
||||
data = ListOrderSerializer(order).data
|
||||
|
||||
# An invoice came in! update it
|
||||
elif invoice:
|
||||
if validate_ln_invoice(invoice):
|
||||
if LNNode.validate_ln_invoice(invoice):
|
||||
order.invoice = invoice
|
||||
|
||||
#TODO Validate if request comes from PARTICIPANT AND BUYER
|
||||
|
||||
#If the order status was Payment Failed. Move foward to invoice Updated.
|
||||
if order.status == int(Order.Status.FAI):
|
||||
order.status = int(Order.Status.UPI)
|
||||
if order.status == Order.Status.FAI:
|
||||
order.status = Order.Status.UPI
|
||||
|
||||
else:
|
||||
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