mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-14 11:26:24 +00:00
Rework lightning module. Add version info
This commit is contained in:
parent
55c5f62078
commit
17df987630
@ -45,7 +45,7 @@ class LNNode():
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def settle_hold_invoice(cls, preimage):
|
def settle_hold_invoice(cls, preimage):
|
||||||
# SETTLING A HODL INVOICE
|
'''settles a hold invoice'''
|
||||||
request = invoicesrpc.SettleInvoiceMsg(preimage=preimage)
|
request = invoicesrpc.SettleInvoiceMsg(preimage=preimage)
|
||||||
response = invoicesstub.SettleInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
response = invoicesstub.SettleInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
||||||
# Fix this: tricky because settling sucessfully an invoice has no response. TODO
|
# Fix this: tricky because settling sucessfully an invoice has no response. TODO
|
||||||
@ -58,88 +58,111 @@ class LNNode():
|
|||||||
def gen_hold_invoice(cls, num_satoshis, description, expiry):
|
def gen_hold_invoice(cls, num_satoshis, description, expiry):
|
||||||
'''Generates hold invoice'''
|
'''Generates hold invoice'''
|
||||||
|
|
||||||
|
hold_payment = {}
|
||||||
# The preimage is a random hash of 256 bits entropy
|
# The preimage is a random hash of 256 bits entropy
|
||||||
preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest()
|
preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest()
|
||||||
|
|
||||||
# Its hash is used to generate the hold invoice
|
# Its hash is used to generate the hold invoice
|
||||||
preimage_hash = hashlib.sha256(preimage).digest()
|
r_hash = hashlib.sha256(preimage).digest()
|
||||||
|
|
||||||
request = invoicesrpc.AddHoldInvoiceRequest(
|
request = invoicesrpc.AddHoldInvoiceRequest(
|
||||||
memo=description,
|
memo=description,
|
||||||
value=num_satoshis,
|
value=num_satoshis,
|
||||||
hash=preimage_hash,
|
hash=r_hash,
|
||||||
expiry=expiry)
|
expiry=expiry)
|
||||||
response = cls.invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
response = cls.invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
||||||
|
|
||||||
invoice = response.payment_request
|
hold_payment['invoice'] = response.payment_request
|
||||||
payreq_decoded = cls.decode_payreq(invoice)
|
payreq_decoded = cls.decode_payreq(hold_payment['invoice'])
|
||||||
|
hold_payment['preimage'] = preimage.hex()
|
||||||
|
hold_payment['payment_hash'] = payreq_decoded.payment_hash
|
||||||
|
hold_payment['created_at'] = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
||||||
|
hold_payment['expires_at'] = hold_payment['created_at'] + timedelta(seconds=payreq_decoded.expiry)
|
||||||
|
|
||||||
preimage = preimage.hex()
|
return hold_payment
|
||||||
payment_hash = payreq_decoded.payment_hash
|
|
||||||
created_at = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
|
||||||
expires_at = created_at + timedelta(seconds=payreq_decoded.expiry)
|
|
||||||
|
|
||||||
return invoice, preimage, payment_hash, created_at, expires_at
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_hold_invoice_locked(cls, payment_hash):
|
def validate_hold_invoice_locked(cls, payment_hash):
|
||||||
'''Checks if hodl invoice is locked'''
|
'''Checks if hold invoice is locked'''
|
||||||
|
|
||||||
|
request = invoicesrpc.LookupInvoiceMsg(payment_hash=payment_hash)
|
||||||
|
response = invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
||||||
|
|
||||||
|
# What is the state for locked ???
|
||||||
|
if response.state == 'OPEN' or response.state == 'SETTLED':
|
||||||
|
return False
|
||||||
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_until_invoice_locked(cls, payment_hash, expiration):
|
def check_until_invoice_locked(cls, payment_hash, expiration):
|
||||||
'''Checks until hodl invoice is locked'''
|
'''Checks until hold invoice is locked.
|
||||||
|
When invoice is locked, returns true.
|
||||||
# request = ln.InvoiceSubscription()
|
If time expires, return False.'''
|
||||||
# When invoice is settled, return true. If time expires, return False.
|
|
||||||
# for invoice in stub.SubscribeInvoices(request):
|
|
||||||
# print(invoice)
|
|
||||||
|
|
||||||
|
request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
|
||||||
|
for invoice in invoicesstub.SubscribeSingleInvoice(request):
|
||||||
|
if timezone.now > expiration:
|
||||||
|
break
|
||||||
|
if invoice.state == 'LOCKED':
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_ln_invoice(cls, invoice, num_satoshis):
|
def validate_ln_invoice(cls, invoice, num_satoshis):
|
||||||
'''Checks if the submited LN invoice comforms to expectations'''
|
'''Checks if the submited LN invoice comforms to expectations'''
|
||||||
|
|
||||||
|
buyer_invoice = {
|
||||||
|
'valid': False,
|
||||||
|
'context': None,
|
||||||
|
'description': None,
|
||||||
|
'payment_hash': None,
|
||||||
|
'created_at': None,
|
||||||
|
'expires_at': None,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payreq_decoded = cls.decode_payreq(invoice)
|
payreq_decoded = cls.decode_payreq(invoice)
|
||||||
except:
|
except:
|
||||||
return False, {'bad_invoice':'Does not look like a valid lightning invoice'}, None, None, None, None
|
buyer_invoice['context'] = {'bad_invoice':'Does not look like a valid lightning invoice'}
|
||||||
|
return buyer_invoice
|
||||||
|
|
||||||
if not payreq_decoded.num_satoshis == num_satoshis:
|
if not payreq_decoded.num_satoshis == num_satoshis:
|
||||||
context = {'bad_invoice':'The invoice provided is not for '+'{:,}'.format(num_satoshis)+ ' Sats'}
|
buyer_invoice['context'] = {'bad_invoice':'The invoice provided is not for '+'{:,}'.format(num_satoshis)+ ' Sats'}
|
||||||
return False, context, None, None, None, None
|
return buyer_invoice
|
||||||
|
|
||||||
created_at = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
buyer_invoice['created_at'] = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
||||||
expires_at = created_at + timedelta(seconds=payreq_decoded.expiry)
|
buyer_invoice['expires_at'] = buyer_invoice['created_at'] + timedelta(seconds=payreq_decoded.expiry)
|
||||||
|
|
||||||
if expires_at < timezone.now():
|
if buyer_invoice['expires_at'] < timezone.now():
|
||||||
context = {'bad_invoice':f'The invoice provided has already expired'}
|
buyer_invoice['context'] = {'bad_invoice':f'The invoice provided has already expired'}
|
||||||
return False, context, None, None, None, None
|
return buyer_invoice
|
||||||
|
|
||||||
description = payreq_decoded.description
|
buyer_invoice['valid'] = True
|
||||||
payment_hash = payreq_decoded.payment_hash
|
buyer_invoice['description'] = payreq_decoded.description
|
||||||
|
buyer_invoice['payment_hash'] = payreq_decoded.payment_hash
|
||||||
|
|
||||||
return True, None, description, payment_hash, created_at, expires_at
|
return buyer_invoice
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pay_invoice(cls, invoice):
|
def pay_invoice(cls, invoice):
|
||||||
'''Sends sats to buyer, or cancelinvoices'''
|
'''Sends sats to buyer'''
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def check_if_hold_invoice_is_locked(cls, payment_hash):
|
|
||||||
'''Every hodl invoice that is in state INVGEN
|
|
||||||
Has to be checked for payment received until
|
|
||||||
the window expires'''
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def double_check_htlc_is_settled(cls, payment_hash):
|
def double_check_htlc_is_settled(cls, payment_hash):
|
||||||
''' Just as it sounds. Better safe than sorry!'''
|
''' Just as it sounds. Better safe than sorry!'''
|
||||||
|
request = invoicesrpc.LookupInvoiceMsg(payment_hash=payment_hash)
|
||||||
|
response = invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
||||||
|
|
||||||
|
if response.state == 'SETTLED':
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
135
api/logics.py
135
api/logics.py
@ -6,6 +6,8 @@ from .models import Order, LNPayment, MarketTick, User
|
|||||||
from decouple import config
|
from decouple import config
|
||||||
from .utils import get_exchange_rate
|
from .utils import get_exchange_rate
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
FEE = float(config('FEE'))
|
FEE = float(config('FEE'))
|
||||||
BOND_SIZE = float(config('BOND_SIZE'))
|
BOND_SIZE = float(config('BOND_SIZE'))
|
||||||
MARKET_PRICE_API = config('MARKET_PRICE_API')
|
MARKET_PRICE_API = config('MARKET_PRICE_API')
|
||||||
@ -80,7 +82,7 @@ class Logics():
|
|||||||
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
|
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
|
||||||
if not order.is_explicit:
|
if not order.is_explicit:
|
||||||
premium = order.premium
|
premium = order.premium
|
||||||
price = exchange_rate
|
price = exchange_rate * (1+float(premium)/100)
|
||||||
else:
|
else:
|
||||||
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
|
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
|
||||||
order_rate = float(order.amount) / (float(order.satoshis) / 100000000)
|
order_rate = float(order.amount) / (float(order.satoshis) / 100000000)
|
||||||
@ -88,6 +90,9 @@ class Logics():
|
|||||||
premium = int(premium*100) # 2 decimals left
|
premium = int(premium*100) # 2 decimals left
|
||||||
price = order_rate
|
price = order_rate
|
||||||
|
|
||||||
|
significant_digits = 6
|
||||||
|
price = round(price, significant_digits - int(math.floor(math.log10(abs(price)))) - 1)
|
||||||
|
|
||||||
return price, premium
|
return price, premium
|
||||||
|
|
||||||
def order_expires(order):
|
def order_expires(order):
|
||||||
@ -118,10 +123,10 @@ class Logics():
|
|||||||
return False, {'bad_request':'You cannot a invoice while bonds are not posted.'}
|
return False, {'bad_request':'You cannot a invoice while bonds are not posted.'}
|
||||||
|
|
||||||
num_satoshis = cls.buyer_invoice_amount(order, user)[1]['invoice_amount']
|
num_satoshis = cls.buyer_invoice_amount(order, user)[1]['invoice_amount']
|
||||||
valid, context, description, payment_hash, created_at, expires_at = LNNode.validate_ln_invoice(invoice, num_satoshis)
|
buyer_invoice = LNNode.validate_ln_invoice(invoice, num_satoshis)
|
||||||
|
|
||||||
if not valid:
|
if not buyer_invoice['valid']:
|
||||||
return False, context
|
return False, buyer_invoice['context']
|
||||||
|
|
||||||
order.buyer_invoice, _ = LNPayment.objects.update_or_create(
|
order.buyer_invoice, _ = LNPayment.objects.update_or_create(
|
||||||
concept = LNPayment.Concepts.PAYBUYER,
|
concept = LNPayment.Concepts.PAYBUYER,
|
||||||
@ -133,10 +138,10 @@ class Logics():
|
|||||||
'invoice' : invoice,
|
'invoice' : invoice,
|
||||||
'status' : LNPayment.Status.VALIDI,
|
'status' : LNPayment.Status.VALIDI,
|
||||||
'num_satoshis' : num_satoshis,
|
'num_satoshis' : num_satoshis,
|
||||||
'description' : description,
|
'description' : buyer_invoice['description'],
|
||||||
'payment_hash' : payment_hash,
|
'payment_hash' : buyer_invoice['payment_hash'],
|
||||||
'created_at' : created_at,
|
'created_at' : buyer_invoice['created_at'],
|
||||||
'expires_at' : expires_at}
|
'expires_at' : buyer_invoice['expires_at']}
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the order status is 'Waiting for escrow'. Move forward to 'chat'
|
# If the order status is 'Waiting for escrow'. Move forward to 'chat'
|
||||||
@ -206,7 +211,14 @@ class Logics():
|
|||||||
Maker is charged the bond to prevent DDOS
|
Maker is charged the bond to prevent DDOS
|
||||||
on the LN node and order book. TODO Only charge a small part
|
on the LN node and order book. TODO Only charge a small part
|
||||||
of the bond (requires maker submitting an invoice)'''
|
of the bond (requires maker submitting an invoice)'''
|
||||||
|
elif order.status == Order.Status.PUB and order.maker == user:
|
||||||
|
#Settle the maker bond (Maker loses the bond for a public order)
|
||||||
|
valid = cls.settle_maker_bond(order)
|
||||||
|
if valid:
|
||||||
|
order.maker = None
|
||||||
|
order.status = Order.Status.UCA
|
||||||
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
# 3) When taker cancels before bond
|
# 3) When taker cancels before bond
|
||||||
''' The order goes back to the book as public.
|
''' The order goes back to the book as public.
|
||||||
@ -227,6 +239,29 @@ class Logics():
|
|||||||
The order goes into the public book if taker cancels.
|
The order goes into the public book if taker cancels.
|
||||||
In both cases there is a small fee.'''
|
In both cases there is a small fee.'''
|
||||||
|
|
||||||
|
# 4.a) When maker cancel after bond (before escrow)
|
||||||
|
'''The order into cancelled status if maker cancels.'''
|
||||||
|
elif order.status > Order.Status.PUB and order.status < Order.Status.CHA and order.maker == user:
|
||||||
|
#Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||||
|
valid = cls.settle_maker_bond(order)
|
||||||
|
if valid:
|
||||||
|
order.maker = None
|
||||||
|
order.status = Order.Status.UCA
|
||||||
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
# 4.b) When taker cancel after bond (before escrow)
|
||||||
|
'''The order into cancelled status if maker cancels.'''
|
||||||
|
elif order.status > Order.Status.TAK and order.status < Order.Status.CHA and order.taker == user:
|
||||||
|
#Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||||
|
valid = cls.settle_taker_bond(order)
|
||||||
|
if valid:
|
||||||
|
order.taker = None
|
||||||
|
order.status = Order.Status.PUB
|
||||||
|
# order.taker_bond = None # TODO fix this, it overrides the information about the settled taker bond. Might make admin tasks hard.
|
||||||
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
# 5) When trade collateral has been posted (after escrow)
|
# 5) When trade collateral has been posted (after escrow)
|
||||||
'''Always goes to cancelled status. Collaboration is needed.
|
'''Always goes to cancelled status. Collaboration is needed.
|
||||||
When a user asks for cancel, 'order.is_pending_cancel' goes True.
|
When a user asks for cancel, 'order.is_pending_cancel' goes True.
|
||||||
@ -253,27 +288,28 @@ class Logics():
|
|||||||
|
|
||||||
order.last_satoshis = cls.satoshis_now(order)
|
order.last_satoshis = cls.satoshis_now(order)
|
||||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||||
description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat or unilaterally cancel'
|
|
||||||
|
description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond. It will automatically return if you do not cancel or cheat"
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
invoice, preimage, payment_hash, created_at, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||||
|
|
||||||
order.maker_bond = LNPayment.objects.create(
|
order.maker_bond = LNPayment.objects.create(
|
||||||
concept = LNPayment.Concepts.MAKEBOND,
|
concept = LNPayment.Concepts.MAKEBOND,
|
||||||
type = LNPayment.Types.hold,
|
type = LNPayment.Types.HOLD,
|
||||||
sender = user,
|
sender = user,
|
||||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||||
invoice = invoice,
|
invoice = hold_payment['invoice'],
|
||||||
preimage = preimage,
|
preimage = hold_payment['preimage'],
|
||||||
status = LNPayment.Status.INVGEN,
|
status = LNPayment.Status.INVGEN,
|
||||||
num_satoshis = bond_satoshis,
|
num_satoshis = bond_satoshis,
|
||||||
description = description,
|
description = description,
|
||||||
payment_hash = payment_hash,
|
payment_hash = hold_payment['payment_hash'],
|
||||||
created_at = created_at,
|
created_at = hold_payment['created_at'],
|
||||||
expires_at = expires_at)
|
expires_at = hold_payment['expires_at'])
|
||||||
|
|
||||||
order.save()
|
order.save()
|
||||||
return True, {'bond_invoice':invoice,'bond_satoshis':bond_satoshis}
|
return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_taker_hold_invoice(cls, order, user):
|
def gen_taker_hold_invoice(cls, order, user):
|
||||||
@ -294,28 +330,30 @@ class Logics():
|
|||||||
|
|
||||||
order.last_satoshis = cls.satoshis_now(order) # LOCKS THE AMOUNT OF SATOSHIS FOR THE TRADE
|
order.last_satoshis = cls.satoshis_now(order) # LOCKS THE AMOUNT OF SATOSHIS FOR THE TRADE
|
||||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||||
description = f'RoboSats - Taking {str(order)} - This bond will return to you if you do not cheat or unilaterally cancel'
|
description = f"RoboSats - Taking '{str(order)}' - This is a taker bond. It will automatically return if you do not cancel or cheat"
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||||
|
|
||||||
order.taker_bond = LNPayment.objects.create(
|
order.taker_bond = LNPayment.objects.create(
|
||||||
concept = LNPayment.Concepts.TAKEBOND,
|
concept = LNPayment.Concepts.TAKEBOND,
|
||||||
type = LNPayment.Types.hold,
|
type = LNPayment.Types.HOLD,
|
||||||
sender = user,
|
sender = user,
|
||||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||||
invoice = invoice,
|
invoice = hold_payment['invoice'],
|
||||||
|
preimage = hold_payment['preimage'],
|
||||||
status = LNPayment.Status.INVGEN,
|
status = LNPayment.Status.INVGEN,
|
||||||
num_satoshis = bond_satoshis,
|
num_satoshis = bond_satoshis,
|
||||||
description = description,
|
description = description,
|
||||||
payment_hash = payment_hash,
|
payment_hash = hold_payment['payment_hash'],
|
||||||
expires_at = expires_at)
|
created_at = hold_payment['created_at'],
|
||||||
|
expires_at = hold_payment['expires_at'])
|
||||||
|
|
||||||
# Extend expiry time to allow for escrow deposit
|
# Extend expiry time to allow for escrow deposit
|
||||||
## Not here, on func for confirming taker collar. order.expires_at = timezone.now() + timedelta(minutes=EXP_TRADE_ESCR_INVOICE)
|
## Not here, on func for confirming taker collar. order.expires_at = timezone.now() + timedelta(minutes=EXP_TRADE_ESCR_INVOICE)
|
||||||
|
|
||||||
order.save()
|
order.save()
|
||||||
return True, {'bond_invoice':invoice,'bond_satoshis': bond_satoshis}
|
return True, {'bond_invoice': hold_payment['invoice'], 'bond_satoshis': bond_satoshis}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_escrow_hold_invoice(cls, order, user):
|
def gen_escrow_hold_invoice(cls, order, user):
|
||||||
@ -334,38 +372,63 @@ class Logics():
|
|||||||
return False, None # Does not return any context of a healthy locked escrow
|
return False, None # Does not return any context of a healthy locked escrow
|
||||||
|
|
||||||
escrow_satoshis = order.last_satoshis # Trade sats amount was fixed at the time of taker bond generation (order.last_satoshis)
|
escrow_satoshis = order.last_satoshis # Trade sats amount was fixed at the time of taker bond generation (order.last_satoshis)
|
||||||
description = f'RoboSats - Escrow amount for {str(order)} - This escrow will be released to the buyer once you confirm you received the fiat.'
|
description = f"RoboSats - Escrow amount for '{str(order)}' - The escrow will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not pay."
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
hold_payment = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
||||||
|
|
||||||
order.trade_escrow = LNPayment.objects.create(
|
order.trade_escrow = LNPayment.objects.create(
|
||||||
concept = LNPayment.Concepts.TRESCROW,
|
concept = LNPayment.Concepts.TRESCROW,
|
||||||
type = LNPayment.Types.hold,
|
type = LNPayment.Types.HOLD,
|
||||||
sender = user,
|
sender = user,
|
||||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||||
invoice = invoice,
|
invoice = hold_payment['invoice'],
|
||||||
|
preimage = hold_payment['preimage'],
|
||||||
status = LNPayment.Status.INVGEN,
|
status = LNPayment.Status.INVGEN,
|
||||||
num_satoshis = escrow_satoshis,
|
num_satoshis = escrow_satoshis,
|
||||||
description = description,
|
description = description,
|
||||||
payment_hash = payment_hash,
|
payment_hash = hold_payment['payment_hash'],
|
||||||
expires_at = expires_at)
|
created_at = hold_payment['created_at'],
|
||||||
|
expires_at = hold_payment['expires_at'])
|
||||||
|
|
||||||
order.save()
|
order.save()
|
||||||
return True, {'escrow_invoice':invoice,'escrow_satoshis': escrow_satoshis}
|
return True, {'escrow_invoice':hold_payment['invoice'],'escrow_satoshis': escrow_satoshis}
|
||||||
|
|
||||||
def settle_escrow(order):
|
def settle_escrow(order):
|
||||||
''' Settles the trade escrow HTLC'''
|
''' Settles the trade escrow hold invoice'''
|
||||||
# TODO ERROR HANDLING
|
# TODO ERROR HANDLING
|
||||||
|
valid = LNNode.settle_hold_invoice(order.trade_escrow.preimage)
|
||||||
|
if valid:
|
||||||
|
order.trade_escrow.status = LNPayment.Status.SETLED
|
||||||
|
order.save()
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def settle_maker_bond(order):
|
||||||
|
''' Settles the maker bond hold invoice'''
|
||||||
|
# TODO ERROR HANDLING
|
||||||
|
valid = LNNode.settle_hold_invoice(order.maker_bond.preimage)
|
||||||
|
if valid:
|
||||||
|
order.maker_bond.status = LNPayment.Status.SETLED
|
||||||
|
order.save()
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def settle_taker_bond(order):
|
||||||
|
''' Settles the taker bond hold invoice'''
|
||||||
|
# TODO ERROR HANDLING
|
||||||
|
valid = LNNode.settle_hold_invoice(order.taker_bond.preimage)
|
||||||
|
if valid:
|
||||||
|
order.taker_bond.status = LNPayment.Status.SETLED
|
||||||
|
order.save()
|
||||||
|
|
||||||
valid = LNNode.settle_hold_htlcs(order.trade_escrow.payment_hash)
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def pay_buyer_invoice(order):
|
def pay_buyer_invoice(order):
|
||||||
''' Settles the trade escrow HTLC'''
|
''' Pay buyer invoice'''
|
||||||
# TODO ERROR HANDLING
|
# TODO ERROR HANDLING
|
||||||
|
|
||||||
valid = LNNode.pay_invoice(order.buyer_invoice.payment_hash)
|
valid = LNNode.pay_invoice(order.buyer_invoice.invoice)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -18,8 +18,8 @@ BOND_SIZE = float(config('BOND_SIZE'))
|
|||||||
class LNPayment(models.Model):
|
class LNPayment(models.Model):
|
||||||
|
|
||||||
class Types(models.IntegerChoices):
|
class Types(models.IntegerChoices):
|
||||||
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hold)
|
NORM = 0, 'Regular invoice' # Only outgoing buyer payment will be a regular invoice (Non-hold)
|
||||||
hold = 1, 'hold invoice'
|
HOLD = 1, 'hold invoice'
|
||||||
|
|
||||||
class Concepts(models.IntegerChoices):
|
class Concepts(models.IntegerChoices):
|
||||||
MAKEBOND = 0, 'Maker bond'
|
MAKEBOND = 0, 'Maker bond'
|
||||||
@ -38,7 +38,7 @@ class LNPayment(models.Model):
|
|||||||
FAILRO = 7, 'Failed routing'
|
FAILRO = 7, 'Failed routing'
|
||||||
|
|
||||||
# payment use details
|
# payment use details
|
||||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.hold)
|
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HOLD)
|
||||||
concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
|
concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
|
||||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
|
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
|
||||||
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
|
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
|
||||||
|
28
api/utils.py
28
api/utils.py
@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
|
import requests, ring, os
|
||||||
from decouple import config
|
from decouple import config
|
||||||
import requests
|
|
||||||
import ring
|
|
||||||
|
|
||||||
storage = {}
|
market_cache = {}
|
||||||
|
|
||||||
@ring.dict(storage, expire=30) #keeps in cache for 30 seconds
|
@ring.dict(market_cache, expire=30) #keeps in cache for 30 seconds
|
||||||
def get_exchange_rate(currency):
|
def get_exchange_rate(currency):
|
||||||
# TODO Add fallback Public APIs and error handling
|
# TODO Add fallback Public APIs and error handling
|
||||||
# Think about polling price data in a different way (e.g. store locally every t seconds)
|
# Think about polling price data in a different way (e.g. store locally every t seconds)
|
||||||
@ -14,3 +13,24 @@ def get_exchange_rate(currency):
|
|||||||
exchange_rate = float(market_prices[currency]['last'])
|
exchange_rate = float(market_prices[currency]['last'])
|
||||||
|
|
||||||
return exchange_rate
|
return exchange_rate
|
||||||
|
|
||||||
|
lnd_v_cache = {}
|
||||||
|
|
||||||
|
@ring.dict(lnd_v_cache, expire=3600) #keeps in cache for 3600 seconds
|
||||||
|
def get_lnd_version():
|
||||||
|
|
||||||
|
stream = os.popen('lnd --version')
|
||||||
|
lnd_version = stream.read()[:-1]
|
||||||
|
|
||||||
|
return lnd_version
|
||||||
|
|
||||||
|
robosats_commit_cache = {}
|
||||||
|
|
||||||
|
@ring.dict(robosats_commit_cache, expire=3600)
|
||||||
|
def get_commit_robosats():
|
||||||
|
|
||||||
|
stream = os.popen('git log -n 1 --pretty=format:"%H"')
|
||||||
|
lnd_version = stream.read()
|
||||||
|
|
||||||
|
return lnd_version
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from django.contrib.auth.models import User
|
|||||||
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
||||||
from .models import LNPayment, MarketTick, Order
|
from .models import LNPayment, MarketTick, Order
|
||||||
from .logics import Logics
|
from .logics import Logics
|
||||||
|
from .utils import get_lnd_version, get_commit_robosats
|
||||||
|
|
||||||
from .nick_generator.nick_generator import NickGenerator
|
from .nick_generator.nick_generator import NickGenerator
|
||||||
from robohash import Robohash
|
from robohash import Robohash
|
||||||
@ -415,8 +416,10 @@ class InfoView(ListAPIView):
|
|||||||
avg_premium = None
|
avg_premium = None
|
||||||
total_volume = None
|
total_volume = None
|
||||||
|
|
||||||
context['last_day_avg_btc_premium'] = avg_premium
|
context['today_avg_nonkyc_btc_premium'] = avg_premium
|
||||||
context['total_volume_today'] = total_volume
|
context['today_total_volume'] = total_volume
|
||||||
|
context['lnd_version'] = get_lnd_version()
|
||||||
|
context['robosats_running_commit_hash'] = get_commit_robosats()
|
||||||
|
|
||||||
return Response(context, status.HTTP_200_OK)
|
return Response(context, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user