Add needed LNNode routines and LNpayments status. First full trade demonstrated

This commit is contained in:
Reckless_Satoshi 2022-01-12 04:57:03 -08:00
parent a3375df6e5
commit 2523e5ef09
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
8 changed files with 71 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -284,7 +284,12 @@ export default class OrderPage extends Component {
<Button variant='contained' color='secondary' onClick={this.handleClickCancelOrderButton}>Cancel</Button>
</Grid>
:""}
{this.state.isMaker & this.state.statusCode > 0 & this.state.statusCode < 9 ?
<Grid item xs={12} align="center">
<Typography color="secondary" variant="subtitle2" component="subtitle2">Cancelling now forfeits the maker bond</Typography>
</Grid>
:""}
{/* Takers can cancel before commiting the bond (status 3)*/}
{this.state.isTaker & this.state.statusCode == 3 ?
<Grid item xs={12} align="center">

View File

@ -70,6 +70,16 @@ export default class TradeBox extends Component {
);
}
showBondIsLocked=()=>{
return (
<Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1" align="center">
🔒 Your {this.props.data.isMaker ? 'maker' : 'taker'} bond is safely locked
</Typography>
</Grid>
);
}
showEscrowQRInvoice=()=>{
return (
<Grid container spacing={1}>
@ -92,6 +102,7 @@ export default class TradeBox extends Component {
color = "secondary"
/>
</Grid>
{this.showBondIsLocked()}
</Grid>
);
}
@ -113,6 +124,7 @@ export default class TradeBox extends Component {
Please wait for the taker to confirm his commitment by locking a bond.
</Typography>
</Grid>
{this.showBondIsLocked()}
</Grid>
);
}
@ -138,12 +150,6 @@ export default class TradeBox extends Component {
return to you (no action needed).</p>
</Typography>
</ListItem>
<Divider/>
<ListItem color="info" align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1" align="center">
🔒 Your maker bond is safely locked
</Typography>
</ListItem>
{/* TODO API sends data for a more confortable wait */}
<Divider/>
<ListItem>
@ -159,8 +165,11 @@ export default class TradeBox extends Component {
<ListItem>
<ListItemText primary="33%" secondary="Premium percentile" />
</ListItem>
<Divider/>
</List>
</Grid>
{this.showBondIsLocked()}
</Grid>
)
}
@ -225,6 +234,7 @@ export default class TradeBox extends Component {
<Grid item xs={12} align="center">
<Button variant='contained' color='primary' onClick={this.handleClickSubmitInvoiceButton}>Submit</Button>
</Grid>
{this.showBondIsLocked()}
</Grid>
)
}
@ -245,6 +255,7 @@ export default class TradeBox extends Component {
you will get your bond back automatically.</p>
</Typography>
</Grid>
{this.showBondIsLocked()}
</Grid>
)
}
@ -266,6 +277,7 @@ export default class TradeBox extends Component {
you will get back the trade collateral and your bond automatically.</p>
</Typography>
</Grid>
{this.showBondIsLocked()}
</Grid>
)
}
@ -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(
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Button defaultValue="confirm" variant='contained' color='primary' onClick={this.handleClickConfirmButton}>Confirm {this.props.data.currencyCode} received</Button>
</Grid>
</Grid>
)
}
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(
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Button defaultValue="dispute" variant='contained' onClick={this.handleClickOpenDisputeButton}>Open Dispute</Button>
<Button color="inherit" onClick={this.handleClickOpenDisputeButton}>Open Dispute</Button>
</Grid>
</Grid>
)
}
@ -368,19 +376,11 @@ handleRatingChange=(e)=>{
{receivedFiatButton ? this.showFiatReceivedButton() : ""}
{openDisputeButton ? this.showOpenDisputeButton() : ""}
</Grid>
{this.showBondIsLocked()}
</Grid>
)
}
// showFiatReceivedButton(){
// }
// showOpenDisputeButton(){
// }
showRateSelect(){
return(
<Grid container spacing={1}>