Add onchain logics pt1

This commit is contained in:
Reckless_Satoshi 2022-06-06 10:57:04 -07:00
parent f538d26355
commit 8d0b518222
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 130 additions and 8 deletions

View File

@ -8,7 +8,7 @@ from base64 import b64decode
from datetime import timedelta, datetime from datetime import timedelta, datetime
from django.utils import timezone from django.utils import timezone
from api.models import LNPayment
####### #######
# Should work with LND (c-lightning in the future if there are features that deserve the work) # Should work with LND (c-lightning in the future if there are features that deserve the work)
@ -176,6 +176,8 @@ class LNNode:
@classmethod @classmethod
def validate_hold_invoice_locked(cls, lnpayment): def validate_hold_invoice_locked(cls, lnpayment):
"""Checks if hold invoice is locked""" """Checks if hold invoice is locked"""
from api.models import LNPayment
request = invoicesrpc.LookupInvoiceMsg( request = invoicesrpc.LookupInvoiceMsg(
payment_hash=bytes.fromhex(lnpayment.payment_hash)) payment_hash=bytes.fromhex(lnpayment.payment_hash))
response = cls.invoicesstub.LookupInvoiceV2(request, response = cls.invoicesstub.LookupInvoiceV2(request,
@ -296,7 +298,8 @@ class LNNode:
@classmethod @classmethod
def pay_invoice(cls, lnpayment): def pay_invoice(cls, lnpayment):
"""Sends sats. Used for rewards payouts""" """Sends sats. Used for rewards payouts"""
from api.models import LNPayment
fee_limit_sat = int( fee_limit_sat = int(
max( max(
lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),

View File

@ -17,8 +17,11 @@ from decouple import config
from pathlib import Path from pathlib import Path
import json import json
from control.models import BalanceLog
MIN_TRADE = int(config("MIN_TRADE")) MIN_TRADE = int(config("MIN_TRADE"))
MAX_TRADE = int(config("MAX_TRADE")) MAX_TRADE = int(config("MAX_TRADE"))
MIN_SWAP_AMOUNT = int(config("MIN_SWAP_AMOUNT"))
FEE = float(config("FEE")) FEE = float(config("FEE"))
DEFAULT_BOND_SIZE = float(config("DEFAULT_BOND_SIZE")) DEFAULT_BOND_SIZE = float(config("DEFAULT_BOND_SIZE"))
@ -118,7 +121,7 @@ class LNPayment(models.Model):
blank=True) blank=True)
num_satoshis = models.PositiveBigIntegerField(validators=[ num_satoshis = models.PositiveBigIntegerField(validators=[
MinValueValidator(100), MinValueValidator(100),
MaxValueValidator(MAX_TRADE * (1 + DEFAULT_BOND_SIZE + FEE)), MaxValueValidator(1.5 * MAX_TRADE),
]) ])
# Fee in sats with mSats decimals fee_msat # Fee in sats with mSats decimals fee_msat
fee = models.DecimalField(max_digits=10, decimal_places=3, default=0, null=False, blank=False) fee = models.DecimalField(max_digits=10, decimal_places=3, default=0, null=False, blank=False)
@ -163,6 +166,96 @@ class LNPayment(models.Model):
# We created a truncated property for display 'hash' # We created a truncated property for display 'hash'
return truncatechars(self.payment_hash, 10) return truncatechars(self.payment_hash, 10)
class OnchainPayment(models.Model):
class Concepts(models.IntegerChoices):
PAYBUYER = 3, "Payment to buyer"
class Status(models.IntegerChoices):
CREAT = 0, "Created" # User was given platform fees and suggested mining fees
VALID = 1, "Valid" # Valid onchain address submitted
MEMPO = 2, "In mempool" # Tx is sent to mempool
CONFI = 3, "Confirmed" # Tx is confirme +2 blocks
# payment use details
concept = models.PositiveSmallIntegerField(choices=Concepts.choices,
null=False,
default=Concepts.PAYBUYER)
status = models.PositiveSmallIntegerField(choices=Status.choices,
null=False,
default=Status.VALID)
# payment info
address = models.CharField(max_length=100,
unique=False,
default=None,
null=True,
blank=True)
txid = models.CharField(max_length=64,
unique=True,
null=True,
default=None,
blank=True)
num_satoshis = models.PositiveBigIntegerField(validators=[
MinValueValidator(0.7 * MIN_SWAP_AMOUNT),
MaxValueValidator(1.5 * MAX_TRADE),
])
# fee in sats/vbyte with mSats decimals fee_msat
suggested_mining_fee_rate = models.DecimalField(max_digits=6,
decimal_places=3,
default=1.05,
null=False,
blank=False)
mining_fee_rate = models.DecimalField(max_digits=6,
decimal_places=3,
default=1.05,
null=False,
blank=False)
mining_fee_sats = models.PositiveBigIntegerField(default=0,
null=False,
blank=False)
# platform onchain/channels balance at creattion, swap fee rate as percent of total volume
node_balance = models.ForeignKey(BalanceLog,
related_name="balance",
on_delete=models.SET_NULL,
null=True)
swap_fee_rate = models.DecimalField(max_digits=4,
decimal_places=2,
default=2,
null=False,
blank=False)
created_at = models.DateTimeField(default=timezone.now)
# involved parties
receiver = models.ForeignKey(User,
related_name="tx_receiver",
on_delete=models.SET_NULL,
null=True,
default=None)
def __str__(self):
if self.txid:
txname = str(self.txid)[:8]
else:
txname = str(self.id)
return f"TX-{txname}: {self.Concepts(self.concept).label} - {self.Status(self.status).label}"
class Meta:
verbose_name = "Lightning payment"
verbose_name_plural = "Lightning payments"
@property
def hash(self):
# Payment hash is the primary key of LNpayments
# However it is too long for the admin panel.
# We created a truncated property for display 'hash'
return truncatechars(self.payment_hash, 10)
class Order(models.Model): class Order(models.Model):

View File

@ -79,7 +79,7 @@ def follow_send_payment(hash):
float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")), float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
)) # 1000 ppm or 10 sats )) # 1000 ppm or 10 sats
timeout_seconds = int(config("REWARRDS_TIMEOUT_SECONDS")) timeout_seconds = int(config("REWARDS_TIMEOUT_SECONDS"))
request = LNNode.routerrpc.SendPaymentRequest( request = LNNode.routerrpc.SendPaymentRequest(
payment_request=lnpayment.invoice, payment_request=lnpayment.invoice,

View File

@ -1,8 +1,7 @@
import requests, ring, os import requests, ring, os
from decouple import config from decouple import config
import numpy as np import numpy as np
import requests import coinaddrvalidator as addr
from api.models import Order from api.models import Order
def get_tor_session(): def get_tor_session():
@ -12,6 +11,24 @@ def get_tor_session():
'https': 'socks5://127.0.0.1:9050'} 'https': 'socks5://127.0.0.1:9050'}
return session return session
def validate_onchain_address(address):
'''
Validates an onchain address
'''
validation = addr.validate('btc', address.encode('utf-8'))
if not validation.valid:
return False
NETWORK = str(config('NETWORK'))
if NETWORK == 'mainnet':
if validation.network == 'main':
return True
elif NETWORK == 'testnet':
if validation.network == 'test':
return True
market_cache = {} market_cache = {}
@ring.dict(market_cache, expire=3) # keeps in cache for 3 seconds @ring.dict(market_cache, expire=3) # keeps in cache for 3 seconds
def get_exchange_rates(currencies): def get_exchange_rates(currencies):

View File

@ -416,9 +416,10 @@ class OrderView(viewsets.ViewSet):
order = Order.objects.get(id=order_id) order = Order.objects.get(id=order_id)
# action is either 1)'take', 2)'confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice' # action is either 1)'take', 2)'confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice'
# 6)'submit_statement' (in dispute), 7)'rate_user' , 'rate_platform' # 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform'
action = serializer.data.get("action") action = serializer.data.get("action")
invoice = serializer.data.get("invoice") invoice = serializer.data.get("invoice")
address = serializer.data.get("address")
statement = serializer.data.get("statement") statement = serializer.data.get("statement")
rating = serializer.data.get("rating") rating = serializer.data.get("rating")
@ -464,6 +465,13 @@ class OrderView(viewsets.ViewSet):
invoice) invoice)
if not valid: if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST) return Response(context, status.HTTP_400_BAD_REQUEST)
# 2.b) If action is 'update invoice'
if action == "update_address":
valid, context = Logics.update_address(order, request.user,
address)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 3) If action is cancel # 3) If action is cancel
elif action == "cancel": elif action == "cancel":

View File

@ -25,4 +25,5 @@ psycopg2==2.9.3
SQLAlchemy==1.4.31 SQLAlchemy==1.4.31
django-import-export==2.7.1 django-import-export==2.7.1
requests[socks] requests[socks]
python-gnupg==0.4.9 python-gnupg==0.4.9
coinaddrvalidator==1.1.3