Work on LN bonds. Maker bond works. Yet, this is not the best way probably.

This commit is contained in:
Reckless_Satoshi 2022-01-11 12:49:53 -08:00
parent 17df987630
commit 8bc8f539d0
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
8 changed files with 191 additions and 74 deletions

1
.gitignore vendored
View File

@ -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*

View File

@ -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):

View File

@ -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'''

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -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 */}
{/* */} {/* */}
{/* */} {/* */}

View File

@ -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