mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Add onchain logics pt1
This commit is contained in:
parent
f538d26355
commit
8d0b518222
@ -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,6 +298,7 @@ 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(
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
21
api/utils.py
21
api/utils.py
@ -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):
|
||||||
|
10
api/views.py
10
api/views.py
@ -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")
|
||||||
|
|
||||||
@ -465,6 +466,13 @@ class OrderView(viewsets.ViewSet):
|
|||||||
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":
|
||||||
valid, context = Logics.cancel_order(order, request.user)
|
valid, context = Logics.cancel_order(order, request.user)
|
||||||
|
@ -26,3 +26,4 @@ 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
|
Loading…
Reference in New Issue
Block a user