From 2523e5ef0990c7c39516e2c8df42ebbbf7642e50 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 12 Jan 2022 04:57:03 -0800 Subject: [PATCH] Add needed LNNode routines and LNpayments status. First full trade demonstrated --- api/admin.py | 2 +- api/lightning/node.py | 20 +++++++++----- api/logics.py | 33 ++++++++++++++--------- api/models.py | 10 ++++--- api/serializers.py | 2 +- api/views.py | 3 ++- frontend/src/components/OrderPage.js | 7 ++++- frontend/src/components/TradeBox.js | 40 ++++++++++++++-------------- 8 files changed, 71 insertions(+), 46 deletions(-) diff --git a/api/admin.py b/api/admin.py index a5b32506..9d8ab64f 100644 --- a/api/admin.py +++ b/api/admin.py @@ -26,7 +26,7 @@ class EUserAdmin(UserAdmin): class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): list_display = ('id','type','maker_link','taker_link','status','amount','currency','t0_satoshis','is_disputed','is_fiat_sent','created_at','expires_at', 'buyer_invoice_link','maker_bond_link','taker_bond_link','trade_escrow_link') list_display_links = ('id','type') - change_links = ('maker','taker','buyer_invoice','maker_bond','taker_invoice','taker_bond','trade_escrow') + change_links = ('maker','taker','buyer_invoice','maker_bond','taker_bond','trade_escrow') list_filter = ('is_disputed','is_fiat_sent','type','currency','status') @admin.register(LNPayment) diff --git a/api/lightning/node.py b/api/lightning/node.py index fd2fdb49..6da8f0eb 100644 --- a/api/lightning/node.py +++ b/api/lightning/node.py @@ -83,6 +83,8 @@ class LNNode(): '''Checks if hold invoice is locked''' request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash)) response = cls.invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())]) + print('status here') + print(response.state) return response.state == 3 # True if hold invoice is accepted. @classmethod @@ -142,32 +144,38 @@ class LNNode(): buyer_invoice['description'] = payreq_decoded.description buyer_invoice['payment_hash'] = payreq_decoded.payment_hash + return buyer_invoice @classmethod def pay_invoice(cls, invoice, num_satoshis): '''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 - request = routerrpc.SendPaymentRequest( payment_request=invoice, - amt_msat=num_satoshis, fee_limit_sat=fee_limit_sat, timeout_seconds=60) for response in cls.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]): - if response.status == True: + print(response) + print(response.status) + print(response.grpc_status) + if response.status == 1 : # Status 1 'IN_FLIGHT' + pass # LIVE UPDATE THE order.lnpayment.status + if response.status == 'FAILED': + pass # LIVE UPDATE THE order.lnpayment.status + if response.status == 2 : # STATUS 'SUCCEEDED' return True + # How to catch the errors like:"grpc_message":"invoice is already paid","grpc_status":6} + # These are not in the response only printed to commandline return False @classmethod def double_check_htlc_is_settled(cls, payment_hash): ''' Just as it sounds. Better safe than sorry!''' - request = invoicesrpc.LookupInvoiceMsg(payment_hash=payment_hash) + request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash)) response = cls.invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())]) return response.state == 1 # LND states: 0 OPEN, 1 SETTLED, 3 ACCEPTED, GRPC_ERROR status 5 when cancelled/returned diff --git a/api/logics.py b/api/logics.py index ed88b2b5..66f6cc6e 100644 --- a/api/logics.py +++ b/api/logics.py @@ -166,13 +166,16 @@ class Logics(): ) # If the order status is 'Waiting for invoice'. Move forward to 'chat' - if order.status == Order.Status.WFI: order.status = Order.Status.CHA + if order.status == Order.Status.WFI: + order.status = Order.Status.CHA + order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION) # If the order status is 'Waiting for both'. Move forward to 'waiting for escrow' if order.status == Order.Status.WF2: - if order.trade_escrow: - if order.trade_escrow.status == LNPayment.Status.LOCKED: - order.status = Order.Status.CHA + # If the escrow is lock move to Chat. + if order.trade_escrow.status == LNPayment.Status.LOCKED: + order.status = Order.Status.CHA + order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION) else: order.status = Order.Status.WFE @@ -312,7 +315,7 @@ class Logics(): order.last_satoshis = cls.satoshis_now(order) bond_satoshis = int(order.last_satoshis * BOND_SIZE) - 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." + description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel." # Gen hold Invoice hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600) @@ -368,7 +371,7 @@ class Logics(): # If there was no taker_bond object yet, generates one order.last_satoshis = cls.satoshis_now(order) bond_satoshis = int(order.last_satoshis * BOND_SIZE) - 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." + description = f"RoboSats - Taking '{str(order)}' - This is a taker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel." # Gen hold Invoice hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600) @@ -400,7 +403,7 @@ class Logics(): if order.status == Order.Status.WF2: order.status = Order.Status.WFI # If status is 'Waiting for invoice' move to Chat - elif order.status == Order.Status.WFI: + elif order.status == Order.Status.WFE: order.status = Order.Status.CHA order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION) order.save() @@ -470,11 +473,17 @@ class Logics(): order.taker_bond.status = LNPayment.Status.SETLED order.taker_bond.save() return True + + def return_bond(bond): + '''returns a bond''' + if LNNode.cancel_return_hold_invoice(bond.payment_hash): + bond.status = LNPayment.Status.RETNED + return True def pay_buyer_invoice(order): ''' Pay buyer invoice''' # TODO ERROR HANDLING - if LNNode.pay_invoice(order.buyer_invoice.invoice): + if LNNode.pay_invoice(order.buyer_invoice.invoice, order.buyer_invoice.num_satoshis): return True @classmethod @@ -498,18 +507,18 @@ class Logics(): return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'} # Make sure the trade escrow is at least as big as the buyer invoice - if order.trade_escrow.num_satoshis > order.buyer_invoice.num_satoshis: + if order.trade_escrow.num_satoshis <= order.buyer_invoice.num_satoshis: return False, {'bad_request':'Woah, something broke badly. Report in the public channels, or open a Github Issue.'} # Double check the escrow is settled. if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash): if cls.pay_buyer_invoice(order): ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!! order.status = Order.Status.PAY - order.buyer_invoice.status = LNPayment.Status.PAYING + order.buyer_invoice.status = LNPayment.Status.SETLED # RETURN THE BONDS - LNNode.cancel_return_hold_invoice(order.taker_bond.payment_hash) - LNNode.cancel_return_hold_invoice(order.maker_bond.payment_hash) + cls.return_bond(order.taker_bond) + cls.return_bond(order.maker_bond) else: return False, {'bad_request':'You cannot confirm the fiat payment at this stage'} diff --git a/api/models.py b/api/models.py index fcd0190d..ace4cdf4 100644 --- a/api/models.py +++ b/api/models.py @@ -34,8 +34,10 @@ class LNPayment(models.Model): RETNED = 3, 'Returned' EXPIRE = 4, 'Expired' VALIDI = 5, 'Valid' - FLIGHT = 6, 'On flight' - FAILRO = 7, 'Routing failed' + FLIGHT = 6, 'In flight' + SUCCED = 7, 'Succeeded' + FAILRO = 8, 'Routing failed' + # payment use details type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HOLD) @@ -44,10 +46,10 @@ class LNPayment(models.Model): routing_retries = models.PositiveSmallIntegerField(null=False, default=0) # payment info - invoice = models.CharField(max_length=1000, unique=True, null=True, default=None, blank=True) + invoice = models.CharField(max_length=1200, unique=True, null=True, default=None, blank=True) # Some invoices with lots of routing hints might be long payment_hash = models.CharField(max_length=100, unique=True, null=True, default=None, blank=True) preimage = models.CharField(max_length=64, unique=True, null=True, default=None, blank=True) - description = models.CharField(max_length=200, unique=False, null=True, default=None, blank=True) + description = models.CharField(max_length=500, unique=False, null=True, default=None, blank=True) num_satoshis = models.PositiveBigIntegerField(validators=[MinValueValidator(MIN_TRADE*BOND_SIZE), MaxValueValidator(MAX_TRADE*(1+BOND_SIZE+FEE))]) created_at = models.DateTimeField() expires_at = models.DateTimeField() diff --git a/api/serializers.py b/api/serializers.py index a819db91..6beff335 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -12,6 +12,6 @@ class MakeOrderSerializer(serializers.ModelSerializer): fields = ('type','currency','amount','payment_method','is_explicit','premium','satoshis') class UpdateOrderSerializer(serializers.Serializer): - invoice = serializers.CharField(max_length=300, allow_null=True, allow_blank=True, default=None) + invoice = serializers.CharField(max_length=2000, allow_null=True, allow_blank=True, default=None) action = serializers.ChoiceField(choices=('take','update_invoice','dispute','cancel','confirm','rate'), allow_null=False) rating = serializers.ChoiceField(choices=('1','2','3','4','5'), allow_null=True, allow_blank=True, default=None) \ No newline at end of file diff --git a/api/views.py b/api/views.py index b4d3ec70..755a654e 100644 --- a/api/views.py +++ b/api/views.py @@ -137,7 +137,7 @@ class OrderView(viewsets.ViewSet): # If both bonds are locked, participants can see the final trade amount in sats. if order.taker_bond: if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED: - # Seller sees the amount he pays + # Seller sees the amount he sends if data['is_seller']: data['trade_satoshis'] = order.last_satoshis # Buyer sees the amount he receives @@ -227,6 +227,7 @@ class OrderView(viewsets.ViewSet): # 2) If action is 'update invoice' if action == 'update_invoice' and invoice: + print('AAAAAAAAAAAAAAAAAAAAAAAAAAAA') valid, context = Logics.update_invoice(order,request.user,invoice) if not valid: return Response(context, status.HTTP_400_BAD_REQUEST) diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index 5fb3c490..14b04cbf 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -284,7 +284,12 @@ export default class OrderPage extends Component { :""} - + {this.state.isMaker & this.state.statusCode > 0 & this.state.statusCode < 9 ? + + Cancelling now forfeits the maker bond + + :""} + {/* Takers can cancel before commiting the bond (status 3)*/} {this.state.isTaker & this.state.statusCode == 3 ? diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index f27ef3e3..5a9ceb2b 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -70,6 +70,16 @@ export default class TradeBox extends Component { ); } + showBondIsLocked=()=>{ + return ( + + + 🔒 Your {this.props.data.isMaker ? 'maker' : 'taker'} bond is safely locked + + + ); + } + showEscrowQRInvoice=()=>{ return ( @@ -92,6 +102,7 @@ export default class TradeBox extends Component { color = "secondary" /> + {this.showBondIsLocked()} ); } @@ -113,6 +124,7 @@ export default class TradeBox extends Component { Please wait for the taker to confirm his commitment by locking a bond. + {this.showBondIsLocked()} ); } @@ -138,12 +150,6 @@ export default class TradeBox extends Component { return to you (no action needed).

- - - - 🔒 Your maker bond is safely locked - - {/* TODO API sends data for a more confortable wait */} @@ -159,8 +165,11 @@ export default class TradeBox extends Component { + + + {this.showBondIsLocked()} ) } @@ -225,6 +234,7 @@ export default class TradeBox extends Component { + {this.showBondIsLocked()} ) } @@ -245,6 +255,7 @@ export default class TradeBox extends Component { you will get your bond back automatically.

+ {this.showBondIsLocked()} ) } @@ -266,6 +277,7 @@ export default class TradeBox extends Component { you will get back the trade collateral and your bond automatically.

+ {this.showBondIsLocked()} ) } @@ -322,22 +334,18 @@ handleRatingChange=(e)=>{ // TODO, show alert and ask for double confirmation (Have you check you received the fiat? Confirming fiat received settles the trade.) // Ask for double confirmation. return( - - ) } showOpenDisputeButton(){ // TODO, show alert about how opening a dispute might involve giving away personal data and might mean losing the bond. Ask for double confirmation. return( - - + - ) } @@ -368,19 +376,11 @@ handleRatingChange=(e)=>{ {receivedFiatButton ? this.showFiatReceivedButton() : ""} {openDisputeButton ? this.showOpenDisputeButton() : ""} + {this.showBondIsLocked()} ) } - - // showFiatReceivedButton(){ - - // } - - // showOpenDisputeButton(){ - - // } - showRateSelect(){ return(