mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 20:21:35 +00:00
Work on LN bonds. Maker bond works. Yet, this is not the best way probably.
This commit is contained in:
parent
17df987630
commit
8bc8f539d0
1
.gitignore
vendored
1
.gitignore
vendored
@ -643,4 +643,5 @@ frontend/static/frontend/main*
|
|||||||
frontend/static/assets/avatars*
|
frontend/static/assets/avatars*
|
||||||
api/lightning/lightning*
|
api/lightning/lightning*
|
||||||
api/lightning/invoices*
|
api/lightning/invoices*
|
||||||
|
api/lightning/router*
|
||||||
api/lightning/googleapis*
|
api/lightning/googleapis*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import grpc, os, hashlib, secrets, json
|
import grpc, os, hashlib, secrets, json
|
||||||
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
|
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
|
||||||
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
|
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
|
||||||
|
from . import router_pb2 as routerrpc, router_pb2_grpc as routerstub
|
||||||
|
|
||||||
from decouple import config
|
from decouple import config
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
@ -19,10 +20,13 @@ LND_GRPC_HOST = config('LND_GRPC_HOST')
|
|||||||
class LNNode():
|
class LNNode():
|
||||||
|
|
||||||
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
|
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
|
||||||
|
|
||||||
creds = grpc.ssl_channel_credentials(CERT)
|
creds = grpc.ssl_channel_credentials(CERT)
|
||||||
channel = grpc.secure_channel(LND_GRPC_HOST, creds)
|
channel = grpc.secure_channel(LND_GRPC_HOST, creds)
|
||||||
|
|
||||||
lightningstub = lightningstub.LightningStub(channel)
|
lightningstub = lightningstub.LightningStub(channel)
|
||||||
invoicesstub = invoicesstub.InvoicesStub(channel)
|
invoicesstub = invoicesstub.InvoicesStub(channel)
|
||||||
|
routerstub = routerstub.RouterStub(channel)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def decode_payreq(cls, invoice):
|
def decode_payreq(cls, invoice):
|
||||||
@ -46,8 +50,8 @@ class LNNode():
|
|||||||
@classmethod
|
@classmethod
|
||||||
def settle_hold_invoice(cls, preimage):
|
def settle_hold_invoice(cls, preimage):
|
||||||
'''settles a hold invoice'''
|
'''settles a hold invoice'''
|
||||||
request = invoicesrpc.SettleInvoiceMsg(preimage=preimage)
|
request = invoicesrpc.SettleInvoiceMsg(preimage=bytes.fromhex(preimage))
|
||||||
response = invoicesstub.SettleInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
response = cls.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
|
||||||
if response == None:
|
if response == None:
|
||||||
return True
|
return True
|
||||||
@ -84,32 +88,30 @@ class LNNode():
|
|||||||
@classmethod
|
@classmethod
|
||||||
def validate_hold_invoice_locked(cls, payment_hash):
|
def validate_hold_invoice_locked(cls, payment_hash):
|
||||||
'''Checks if hold invoice is locked'''
|
'''Checks if hold invoice is locked'''
|
||||||
|
request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
|
||||||
request = invoicesrpc.LookupInvoiceMsg(payment_hash=payment_hash)
|
response = cls.invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
||||||
response = invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
print('status here')
|
||||||
|
print(response.state) # LND states: 0 OPEN, 1 SETTLED, 3 ACCEPTED, GRPC_ERROR status 5 when cancelled
|
||||||
# What is the state for locked ???
|
return response.state == 3 # True if hold invoice is accepted.
|
||||||
if response.state == 'OPEN' or response.state == 'SETTLED':
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_until_invoice_locked(cls, payment_hash, expiration):
|
def check_until_invoice_locked(cls, payment_hash, expiration):
|
||||||
'''Checks until hold invoice is locked.
|
'''Checks until hold invoice is locked.
|
||||||
When invoice is locked, returns true.
|
When invoice is locked, returns true.
|
||||||
If time expires, return False.'''
|
If time expires, return False.'''
|
||||||
|
# Experimental, needs asyncio
|
||||||
request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
|
# Maybe best to pass LNpayment object and change status live.
|
||||||
for invoice in invoicesstub.SubscribeSingleInvoice(request):
|
|
||||||
|
request = cls.invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
|
||||||
|
for invoice in cls.invoicesstub.SubscribeSingleInvoice(request):
|
||||||
|
print(invoice)
|
||||||
if timezone.now > expiration:
|
if timezone.now > expiration:
|
||||||
break
|
break
|
||||||
if invoice.state == 'LOCKED':
|
if invoice.state == 'ACCEPTED':
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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'''
|
||||||
@ -125,10 +127,15 @@ class LNNode():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
payreq_decoded = cls.decode_payreq(invoice)
|
payreq_decoded = cls.decode_payreq(invoice)
|
||||||
|
print(payreq_decoded)
|
||||||
except:
|
except:
|
||||||
buyer_invoice['context'] = {'bad_invoice':'Does not look like a valid lightning invoice'}
|
buyer_invoice['context'] = {'bad_invoice':'Does not look like a valid lightning invoice'}
|
||||||
return buyer_invoice
|
return buyer_invoice
|
||||||
|
|
||||||
|
if payreq_decoded.num_satoshis == 0:
|
||||||
|
buyer_invoice['context'] = {'bad_invoice':'The invoice provided has no explicit amount'}
|
||||||
|
return buyer_invoice
|
||||||
|
|
||||||
if not payreq_decoded.num_satoshis == num_satoshis:
|
if not payreq_decoded.num_satoshis == num_satoshis:
|
||||||
buyer_invoice['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 buyer_invoice
|
return buyer_invoice
|
||||||
@ -147,11 +154,28 @@ class LNNode():
|
|||||||
return buyer_invoice
|
return buyer_invoice
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pay_invoice(cls, invoice):
|
def pay_invoice(cls, invoice, num_satoshis):
|
||||||
'''Sends sats to buyer'''
|
'''Sends sats to buyer'''
|
||||||
|
# Needs router subservice
|
||||||
|
# Maybe best to pass order and change status live.
|
||||||
|
|
||||||
|
fee_limit_sat = max(num_satoshis * 0.0002, 10) # 200 ppm or 10 sats
|
||||||
|
|
||||||
return True
|
request = routerrpc.SendPaymentRequest(
|
||||||
|
payment_request=invoice,
|
||||||
|
amt_msat=num_satoshis,
|
||||||
|
fee_limit_sat=fee_limit_sat,
|
||||||
|
timeout_seconds=60,
|
||||||
|
)
|
||||||
|
|
||||||
|
for response in routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]):
|
||||||
|
print(response)
|
||||||
|
print(response.status)
|
||||||
|
|
||||||
|
if response.status == True:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def double_check_htlc_is_settled(cls, payment_hash):
|
def double_check_htlc_is_settled(cls, payment_hash):
|
||||||
|
@ -149,7 +149,6 @@ class Logics():
|
|||||||
|
|
||||||
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
||||||
if order.status == Order.Status.WF2:
|
if order.status == Order.Status.WF2:
|
||||||
print(order.trade_escrow)
|
|
||||||
if order.trade_escrow:
|
if order.trade_escrow:
|
||||||
if order.trade_escrow.status == LNPayment.Status.LOCKED:
|
if order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||||
order.status = Order.Status.CHA
|
order.status = Order.Status.CHA
|
||||||
@ -159,27 +158,36 @@ class Logics():
|
|||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
def add_profile_rating(profile, rating):
|
||||||
|
''' adds a new rating to a user profile'''
|
||||||
|
|
||||||
|
profile.total_ratings = profile.total_ratings + 1
|
||||||
|
latest_ratings = profile.latest_ratings
|
||||||
|
if len(latest_ratings) <= 1:
|
||||||
|
profile.latest_ratings = [rating]
|
||||||
|
profile.avg_rating = rating
|
||||||
|
|
||||||
|
else:
|
||||||
|
latest_ratings = list(latest_ratings).append(rating)
|
||||||
|
profile.latest_ratings = latest_ratings
|
||||||
|
profile.avg_rating = sum(latest_ratings) / len(latest_ratings)
|
||||||
|
|
||||||
|
profile.save()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rate_counterparty(cls, order, user, rating):
|
def rate_counterparty(cls, order, user, rating):
|
||||||
|
|
||||||
# If the trade is finished
|
# If the trade is finished
|
||||||
if order.status > Order.Status.PAY:
|
if order.status > Order.Status.PAY:
|
||||||
|
|
||||||
# if maker, rates taker
|
# if maker, rates taker
|
||||||
if order.maker == user:
|
if order.maker == user:
|
||||||
order.taker.profile.total_ratings = order.taker.profile.total_ratings + 1
|
cls.add_profile_rating(order.taker.profile, rating)
|
||||||
last_ratings = list(order.taker.profile.last_ratings).append(rating)
|
|
||||||
order.taker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
|
|
||||||
|
|
||||||
# if taker, rates maker
|
# if taker, rates maker
|
||||||
if order.taker == user:
|
if order.taker == user:
|
||||||
order.maker.profile.total_ratings = order.maker.profile.total_ratings + 1
|
cls.add_profile_rating(order.maker.profile, rating)
|
||||||
last_ratings = list(order.maker.profile.last_ratings).append(rating)
|
|
||||||
order.maker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
|
|
||||||
else:
|
else:
|
||||||
return False, {'bad_request':'You cannot rate your counterparty yet.'}
|
return False, {'bad_request':'You cannot rate your counterparty yet.'}
|
||||||
|
|
||||||
order.save()
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def is_penalized(user):
|
def is_penalized(user):
|
||||||
@ -204,7 +212,7 @@ class Logics():
|
|||||||
order.maker = None
|
order.maker = None
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, {}
|
||||||
|
|
||||||
# 2) When maker cancels after bond
|
# 2) When maker cancels after bond
|
||||||
'''The order dissapears from book and goes to cancelled.
|
'''The order dissapears from book and goes to cancelled.
|
||||||
@ -213,12 +221,14 @@ class Logics():
|
|||||||
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:
|
elif order.status == Order.Status.PUB and order.maker == user:
|
||||||
#Settle the maker bond (Maker loses the bond for a public order)
|
#Settle the maker bond (Maker loses the bond for a public order)
|
||||||
valid = cls.settle_maker_bond(order)
|
if cls.settle_maker_bond(order):
|
||||||
if valid:
|
order.maker_bond.status = LNPayment.Status.SETLED
|
||||||
|
order.maker_bond.save()
|
||||||
|
|
||||||
order.maker = None
|
order.maker = None
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, {}
|
||||||
|
|
||||||
# 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.
|
||||||
@ -226,13 +236,13 @@ class Logics():
|
|||||||
elif order.status == Order.Status.TAK and order.taker == user:
|
elif order.status == Order.Status.TAK and order.taker == user:
|
||||||
# adds a timeout penalty
|
# adds a timeout penalty
|
||||||
user.profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT)
|
user.profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT)
|
||||||
user.save()
|
user.profile.save()
|
||||||
|
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.status = Order.Status.PUB
|
order.status = Order.Status.PUB
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
return True, None
|
return True, {}
|
||||||
|
|
||||||
# 4) When taker or maker cancel after bond (before escrow)
|
# 4) When taker or maker cancel after bond (before escrow)
|
||||||
'''The order goes into cancelled status if maker cancels.
|
'''The order goes into cancelled status if maker cancels.
|
||||||
@ -248,19 +258,19 @@ class Logics():
|
|||||||
order.maker = None
|
order.maker = None
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, {}
|
||||||
|
|
||||||
# 4.b) When taker cancel after bond (before escrow)
|
# 4.b) When taker cancel after bond (before escrow)
|
||||||
'''The order into cancelled status if maker cancels.'''
|
'''The order into cancelled status if maker cancels.'''
|
||||||
elif order.status > Order.Status.TAK and order.status < Order.Status.CHA and order.taker == user:
|
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)
|
# Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||||
valid = cls.settle_taker_bond(order)
|
valid = cls.settle_taker_bond(order)
|
||||||
if valid:
|
if valid:
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.status = Order.Status.PUB
|
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.taker_bond = None # TODO fix this, it overrides the information about the settled taker bond. Might make admin tasks hard.
|
||||||
order.save()
|
order.save()
|
||||||
return True, None
|
return True, {}
|
||||||
|
|
||||||
# 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.
|
||||||
@ -281,6 +291,7 @@ class Logics():
|
|||||||
|
|
||||||
# Return the previous invoice if there was one and is still unpaid
|
# Return the previous invoice if there was one and is still unpaid
|
||||||
if order.maker_bond:
|
if order.maker_bond:
|
||||||
|
cls.check_maker_bond_locked(order)
|
||||||
if order.maker_bond.status == LNPayment.Status.INVGEN:
|
if order.maker_bond.status == LNPayment.Status.INVGEN:
|
||||||
return True, {'bond_invoice':order.maker_bond.invoice,'bond_satoshis':order.maker_bond.num_satoshis}
|
return True, {'bond_invoice':order.maker_bond.invoice,'bond_satoshis':order.maker_bond.num_satoshis}
|
||||||
else:
|
else:
|
||||||
@ -289,7 +300,7 @@ 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 is a maker bond. It will automatically return if you do not cancel or cheat"
|
description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond, it will freeze in your wallet. It automatically returns. It will be charged if you cheat or cancel."
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||||
@ -311,6 +322,29 @@ class Logics():
|
|||||||
order.save()
|
order.save()
|
||||||
return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis}
|
return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_until_maker_bond_locked(cls, order):
|
||||||
|
expiration = order.maker_bond.created_at + timedelta(seconds=EXP_MAKER_BOND_INVOICE)
|
||||||
|
is_locked = LNNode.check_until_invoice_locked(order.payment_hash, expiration)
|
||||||
|
|
||||||
|
if is_locked:
|
||||||
|
order.maker_bond.status = LNPayment.Status.LOCKED
|
||||||
|
order.maker_bond.save()
|
||||||
|
order.status = Order.Status.PUB
|
||||||
|
|
||||||
|
order.save()
|
||||||
|
return is_locked
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_maker_bond_locked(cls, order):
|
||||||
|
if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash):
|
||||||
|
order.maker_bond.status = LNPayment.Status.LOCKED
|
||||||
|
order.maker_bond.save()
|
||||||
|
order.status = Order.Status.PUB
|
||||||
|
order.save()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_taker_hold_invoice(cls, order, user):
|
def gen_taker_hold_invoice(cls, order, user):
|
||||||
|
|
||||||
@ -330,7 +364,7 @@ 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 is a taker bond. It will automatically return if you do not cancel or cheat"
|
description = f"RoboSats - Taking '{str(order)}' - This is a taker bond, it will freeze in your wallet. It automatically returns. It will be charged if you cheat or cancel."
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||||
@ -407,12 +441,10 @@ class Logics():
|
|||||||
def settle_maker_bond(order):
|
def settle_maker_bond(order):
|
||||||
''' Settles the maker bond hold invoice'''
|
''' Settles the maker bond hold invoice'''
|
||||||
# TODO ERROR HANDLING
|
# TODO ERROR HANDLING
|
||||||
valid = LNNode.settle_hold_invoice(order.maker_bond.preimage)
|
if LNNode.settle_hold_invoice(order.maker_bond.preimage):
|
||||||
if valid:
|
|
||||||
order.maker_bond.status = LNPayment.Status.SETLED
|
order.maker_bond.status = LNPayment.Status.SETLED
|
||||||
order.save()
|
order.save()
|
||||||
|
return True
|
||||||
return valid
|
|
||||||
|
|
||||||
def settle_taker_bond(order):
|
def settle_taker_bond(order):
|
||||||
''' Settles the taker bond hold invoice'''
|
''' Settles the taker bond hold invoice'''
|
||||||
|
@ -34,8 +34,8 @@ class LNPayment(models.Model):
|
|||||||
RETNED = 3, 'Returned'
|
RETNED = 3, 'Returned'
|
||||||
MISSNG = 4, 'Missing'
|
MISSNG = 4, 'Missing'
|
||||||
VALIDI = 5, 'Valid'
|
VALIDI = 5, 'Valid'
|
||||||
PAYING = 6, 'Paying ongoing'
|
FLIGHT = 6, 'On flight'
|
||||||
FAILRO = 7, 'Failed routing'
|
FAILRO = 7, 'Routing failed'
|
||||||
|
|
||||||
# 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)
|
||||||
|
34
api/views.py
34
api/views.py
@ -60,7 +60,7 @@ class MakerView(CreateAPIView):
|
|||||||
premium=premium,
|
premium=premium,
|
||||||
satoshis=satoshis,
|
satoshis=satoshis,
|
||||||
is_explicit=is_explicit,
|
is_explicit=is_explicit,
|
||||||
expires_at=timezone.now()+timedelta(minutes=EXP_MAKER_BOND_INVOICE), # TODO Move to class method
|
expires_at=timezone.now()+timedelta(seconds=EXP_MAKER_BOND_INVOICE), # TODO Move to class method
|
||||||
maker=request.user)
|
maker=request.user)
|
||||||
|
|
||||||
# TODO move to Order class method when new instance is created!
|
# TODO move to Order class method when new instance is created!
|
||||||
@ -95,11 +95,11 @@ class OrderView(viewsets.ViewSet):
|
|||||||
# This is our order.
|
# This is our order.
|
||||||
order = order[0]
|
order = order[0]
|
||||||
|
|
||||||
# 1) If order expired
|
# 1) If order has expired
|
||||||
if order.status == Order.Status.EXP:
|
if order.status == Order.Status.EXP:
|
||||||
return Response({'bad_request':'This order has expired'},status.HTTP_400_BAD_REQUEST)
|
return Response({'bad_request':'This order has expired'},status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# 2) If order cancelled
|
# 2) If order has been cancelled
|
||||||
if order.status == Order.Status.UCA:
|
if order.status == Order.Status.UCA:
|
||||||
return Response({'bad_request':'This order has been cancelled by the maker'},status.HTTP_400_BAD_REQUEST)
|
return Response({'bad_request':'This order has been cancelled by the maker'},status.HTTP_400_BAD_REQUEST)
|
||||||
if order.status == Order.Status.CCA:
|
if order.status == Order.Status.CCA:
|
||||||
@ -107,7 +107,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
data = ListOrderSerializer(order).data
|
data = ListOrderSerializer(order).data
|
||||||
|
|
||||||
# if user is under a limit (penalty), inform him
|
# if user is under a limit (penalty), inform him.
|
||||||
is_penalized, time_out = Logics.is_penalized(request.user)
|
is_penalized, time_out = Logics.is_penalized(request.user)
|
||||||
if is_penalized:
|
if is_penalized:
|
||||||
data['penalty'] = time_out
|
data['penalty'] = time_out
|
||||||
@ -125,7 +125,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
elif not data['is_participant'] and order.status != Order.Status.PUB:
|
elif not data['is_participant'] and order.status != Order.Status.PUB:
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# For participants add position side, nicks and status as message
|
# For participants add positions, nicks and status as a message
|
||||||
data['is_buyer'] = Logics.is_buyer(order,request.user)
|
data['is_buyer'] = Logics.is_buyer(order,request.user)
|
||||||
data['is_seller'] = Logics.is_seller(order,request.user)
|
data['is_seller'] = Logics.is_seller(order,request.user)
|
||||||
data['maker_nick'] = str(order.maker)
|
data['maker_nick'] = str(order.maker)
|
||||||
@ -134,7 +134,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
data['is_fiat_sent'] = order.is_fiat_sent
|
data['is_fiat_sent'] = order.is_fiat_sent
|
||||||
data['is_disputed'] = order.is_disputed
|
data['is_disputed'] = order.is_disputed
|
||||||
|
|
||||||
# If both bonds are locked, participants can see the trade in sats is also final.
|
# If both bonds are locked, participants can see the final trade amount in sats.
|
||||||
if order.taker_bond:
|
if order.taker_bond:
|
||||||
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
|
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
|
||||||
# Seller sees the amount he pays
|
# Seller sees the amount he pays
|
||||||
@ -182,8 +182,10 @@ class OrderView(viewsets.ViewSet):
|
|||||||
else:
|
else:
|
||||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# 8) If status is 'CHA'or '' or '' and all HTLCS are in LOCKED
|
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
|
||||||
elif order.status == Order.Status.CHA: # TODO Add the other status
|
elif order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Add the other status
|
||||||
|
|
||||||
|
# If all bonds are locked.
|
||||||
if order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED:
|
if order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||||
# add whether a collaborative cancel is pending
|
# add whether a collaborative cancel is pending
|
||||||
data['pending_cancel'] = order.is_pending_cancel
|
data['pending_cancel'] = order.is_pending_cancel
|
||||||
@ -193,7 +195,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
def take_update_confirm_dispute_cancel(self, request, format=None):
|
def take_update_confirm_dispute_cancel(self, request, format=None):
|
||||||
'''
|
'''
|
||||||
Here take place all of the user updates to the order object.
|
Here takes place all of updatesto the order object.
|
||||||
That is: take, confim, cancel, dispute, update_invoice or rate.
|
That is: take, confim, cancel, dispute, update_invoice or rate.
|
||||||
'''
|
'''
|
||||||
order_id = request.GET.get(self.lookup_url_kwarg)
|
order_id = request.GET.get(self.lookup_url_kwarg)
|
||||||
@ -208,7 +210,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
invoice = serializer.data.get('invoice')
|
invoice = serializer.data.get('invoice')
|
||||||
rating = serializer.data.get('rating')
|
rating = serializer.data.get('rating')
|
||||||
|
|
||||||
# 1) If action is take, it is be taker request!
|
# 1) If action is take, it is a taker request!
|
||||||
if action == 'take':
|
if action == 'take':
|
||||||
if order.status == Order.Status.PUB:
|
if order.status == Order.Status.PUB:
|
||||||
valid, context = Logics.validate_already_maker_or_taker(request.user)
|
valid, context = Logics.validate_already_maker_or_taker(request.user)
|
||||||
@ -253,7 +255,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
return Response(
|
return Response(
|
||||||
{'bad_request':
|
{'bad_request':
|
||||||
'The Robotic Satoshis working in the warehouse did not understand you. ' +
|
'The Robotic Satoshis working in the warehouse did not understand you. ' +
|
||||||
'Please, fill a Bug Issue in Github https://github.com/Reckless-Satoshi/robosats/issues'},
|
'Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues'},
|
||||||
status.HTTP_501_NOT_IMPLEMENTED)
|
status.HTTP_501_NOT_IMPLEMENTED)
|
||||||
|
|
||||||
return self.get(request)
|
return self.get(request)
|
||||||
@ -277,6 +279,16 @@ class UserView(APIView):
|
|||||||
- Creates login credentials (new User object)
|
- Creates login credentials (new User object)
|
||||||
Response with Avatar and Nickname.
|
Response with Avatar and Nickname.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# if request.user.id:
|
||||||
|
# context = {}
|
||||||
|
# context['nickname'] = request.user.username
|
||||||
|
# participant = not Logics.validate_already_maker_or_taker(request.user)
|
||||||
|
# context['bad_request'] = f'You are already logged in as {request.user}'
|
||||||
|
# if participant:
|
||||||
|
# context['bad_request'] = f'You are already logged in as as {request.user} and have an active order'
|
||||||
|
# return Response(context,status.HTTP_200_OK)
|
||||||
|
|
||||||
token = request.GET.get(self.lookup_url_kwarg)
|
token = request.GET.get(self.lookup_url_kwarg)
|
||||||
|
|
||||||
# Compute token entropy
|
# Compute token entropy
|
||||||
|
@ -67,7 +67,7 @@ export default class OrderPage extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isExplicit: false,
|
isExplicit: false,
|
||||||
delay: 10000, // Refresh every 10 seconds
|
delay: 2000, // Refresh every 2 seconds by default
|
||||||
currencies_dict: {"1":"USD"}
|
currencies_dict: {"1":"USD"}
|
||||||
};
|
};
|
||||||
this.orderId = this.props.match.params.orderId;
|
this.orderId = this.props.match.params.orderId;
|
||||||
@ -109,7 +109,7 @@ export default class OrderPage extends Component {
|
|||||||
escrowInvoice: data.escrow_invoice,
|
escrowInvoice: data.escrow_invoice,
|
||||||
escrowSatoshis: data.escrow_satoshis,
|
escrowSatoshis: data.escrow_satoshis,
|
||||||
invoiceAmount: data.invoice_amount,
|
invoiceAmount: data.invoice_amount,
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,9 +129,6 @@ export default class OrderPage extends Component {
|
|||||||
tick = () => {
|
tick = () => {
|
||||||
this.getOrderDetails();
|
this.getOrderDetails();
|
||||||
}
|
}
|
||||||
handleDelayChange = (e) => {
|
|
||||||
this.setState({ delay: Number(e.target.value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix to use proper react props
|
// Fix to use proper react props
|
||||||
handleClickBackButton=()=>{
|
handleClickBackButton=()=>{
|
||||||
@ -149,7 +146,9 @@ export default class OrderPage extends Component {
|
|||||||
};
|
};
|
||||||
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => (console.log(data) & this.getOrderDetails(data.id)));
|
.then((data) => (this.setState({badRequest:data.bad_request})
|
||||||
|
& console.log(data)
|
||||||
|
& this.getOrderDetails(data.id)));
|
||||||
}
|
}
|
||||||
getCurrencyDict() {
|
getCurrencyDict() {
|
||||||
fetch('/static/assets/currencies.json')
|
fetch('/static/assets/currencies.json')
|
||||||
@ -278,8 +277,9 @@ export default class OrderPage extends Component {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* Makers can cancel before commiting the bond (status 0)*/}
|
{/* Makers can cancel before trade escrow deposited (status <9)*/}
|
||||||
{this.state.isMaker & this.state.statusCode == 0 ?
|
{/* Only free cancel before bond locked (status 0)*/}
|
||||||
|
{this.state.isMaker & this.state.statusCode < 9 ?
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Button variant='contained' color='secondary' onClick={this.handleClickCancelOrderButton}>Cancel</Button>
|
<Button variant='contained' color='secondary' onClick={this.handleClickCancelOrderButton}>Cancel</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Paper, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider} from "@mui/material"
|
import { Link, Paper, Rating, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider} from "@mui/material"
|
||||||
import QRCode from "react-qr-code";
|
import QRCode from "react-qr-code";
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
@ -294,6 +294,19 @@ handleClickOpenDisputeButton=()=>{
|
|||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => (this.props.data = data));
|
.then((data) => (this.props.data = data));
|
||||||
}
|
}
|
||||||
|
handleRatingChange=(e)=>{
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'action': "rate",
|
||||||
|
'rating': e.target.value,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => (this.props.data = data));
|
||||||
|
}
|
||||||
|
|
||||||
showFiatSentButton(){
|
showFiatSentButton(){
|
||||||
return(
|
return(
|
||||||
@ -359,6 +372,7 @@ handleClickOpenDisputeButton=()=>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// showFiatReceivedButton(){
|
// showFiatReceivedButton(){
|
||||||
|
|
||||||
// }
|
// }
|
||||||
@ -367,9 +381,28 @@ handleClickOpenDisputeButton=()=>{
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// showRateSelect(){
|
showRateSelect(){
|
||||||
|
return(
|
||||||
// }
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Typography component="h6" variant="h6">
|
||||||
|
🎉Trade finished!🥳
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Typography component="body2" variant="body2" align="center">
|
||||||
|
What do you think of <b>{this.props.data.isMaker ? this.props.data.takerNick : this.props.data.makerNick}</b>?
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Rating name="size-large" defaultValue={2} size="large" onChange={this.handleRatingChange} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Button color='primary' to='/' component={Link}>Start Again</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -393,14 +426,25 @@ handleClickOpenDisputeButton=()=>{
|
|||||||
{this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""}
|
{this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""}
|
||||||
{this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""}
|
{this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""}
|
||||||
|
|
||||||
{/* In Chatroom - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
{/* In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||||
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat(true,false,true) : ""}
|
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat(true,false,true) : ""}
|
||||||
{this.props.data.isSeller & this.props.data.statusCode == 9 ? this.showChat(false,false,true) : ""}
|
{this.props.data.isSeller & this.props.data.statusCode == 9 ? this.showChat(false,false,true) : ""}
|
||||||
|
|
||||||
|
{/* In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||||
{this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat(false,false,true) : ""}
|
{this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat(false,false,true) : ""}
|
||||||
{this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""}
|
{this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""}
|
||||||
|
|
||||||
{/* Trade Finished */}
|
{/* Trade Finished */}
|
||||||
{this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""}
|
{this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""}
|
||||||
|
{this.props.data.isBuyer & this.props.data.statusCode == 14 ? this.showRateSelect() : ""}
|
||||||
|
|
||||||
|
{/* Trade Finished - Payment Routing Failed */}
|
||||||
|
{this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""}
|
||||||
|
|
||||||
|
{/* Trade Finished - Payment Routing Failed - TODO Needs more planning */}
|
||||||
|
{this.props.data.statusCode == 11 ? this.showInDispute() : ""}
|
||||||
|
|
||||||
|
|
||||||
{/* TODO */}
|
{/* TODO */}
|
||||||
{/* */}
|
{/* */}
|
||||||
{/* */}
|
{/* */}
|
||||||
|
12
setup.md
12
setup.md
@ -58,15 +58,17 @@ git clone https://github.com/googleapis/googleapis.git
|
|||||||
curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
|
curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
|
||||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto
|
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto
|
||||||
```
|
```
|
||||||
We also use the *Invoices* subservice for invoice validation.
|
We also use the *Invoices* and *Router* subservices for invoice validation and payment routing.
|
||||||
```
|
```
|
||||||
curl -o invoices.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto
|
curl -o invoices.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto
|
||||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. invoices.proto
|
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. invoices.proto
|
||||||
|
curl -o router.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto
|
||||||
|
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. router.proto
|
||||||
```
|
```
|
||||||
Relative imports are not working at the moment, so some editing is needed in
|
Relative imports are not working at the moment, so some editing is needed in
|
||||||
`api/lightning` files `lightning_pb2_grpc.py`, `invoices_pb2_grpc.py` and `invoices_pb2.py`.
|
`api/lightning` files `lightning_pb2_grpc.py`, `invoices_pb2_grpc.py`, `invoices_pb2.py`, `router_pb2_grpc.py` and `router_pb2.py`.
|
||||||
|
|
||||||
Example, change line :
|
For example in `lightning_pb2_grpc.py` , add "from . " :
|
||||||
|
|
||||||
`import lightning_pb2 as lightning__pb2`
|
`import lightning_pb2 as lightning__pb2`
|
||||||
|
|
||||||
@ -74,6 +76,8 @@ to
|
|||||||
|
|
||||||
`from . import lightning_pb2 as lightning__pb2`
|
`from . import lightning_pb2 as lightning__pb2`
|
||||||
|
|
||||||
|
Same for every other file
|
||||||
|
|
||||||
## React development environment
|
## React development environment
|
||||||
### Install npm
|
### Install npm
|
||||||
`sudo apt install npm`
|
`sudo apt install npm`
|
||||||
@ -96,7 +100,7 @@ npm install react-native-svg
|
|||||||
npm install react-qr-code
|
npm install react-qr-code
|
||||||
npm install @mui/material
|
npm install @mui/material
|
||||||
```
|
```
|
||||||
Note we are using mostly MaterialUI V5, but Image loading from V4 extentions (so both V4 and V5 are needed)
|
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
||||||
|
|
||||||
### Launch the React render
|
### Launch the React render
|
||||||
from frontend/ directory
|
from frontend/ directory
|
||||||
|
Loading…
Reference in New Issue
Block a user