Bug fix invoice follow. Copy user token button.

This commit is contained in:
Reckless_Satoshi 2022-01-18 05:20:19 -08:00
parent a005b3509d
commit e31bc1adad
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
9 changed files with 111 additions and 64 deletions

View File

@ -9,9 +9,12 @@ REDIS_URL=''
# List of market price public APIs. If the currency is available in more than 1 API, will use median price. # List of market price public APIs. If the currency is available in more than 1 API, will use median price.
MARKET_PRICE_APIS = https://blockchain.info/ticker, https://api.yadio.io/exrates/BTC MARKET_PRICE_APIS = https://blockchain.info/ticker, https://api.yadio.io/exrates/BTC
# Host e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion # Host e.g. robosats.com
HOST_NAME = '' HOST_NAME = ''
# e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
ONION_LOCATION = ''
# Trade fee in percentage % # Trade fee in percentage %
FEE = 0.002 FEE = 0.002
# Bond size in percentage % # Bond size in percentage %

View File

@ -31,8 +31,8 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@admin.register(LNPayment) @admin.register(LNPayment)
class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link') list_display = ('id','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link','order_made','order_taken','order_escrow','order_paid')
list_display_links = ('id','concept') list_display_links = ('id','concept','order_made','order_taken','order_escrow','order_paid')
change_links = ('sender','receiver') change_links = ('sender','receiver')
list_filter = ('type','concept','status') list_filter = ('type','concept','status')

View File

@ -446,7 +446,9 @@ class Logics():
@classmethod @classmethod
def is_maker_bond_locked(cls, order): def is_maker_bond_locked(cls, order):
if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash): if order.maker_bond.status == LNPayment.Status.LOCKED:
return True
elif LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash):
order.maker_bond.status = LNPayment.Status.LOCKED order.maker_bond.status = LNPayment.Status.LOCKED
order.maker_bond.save() order.maker_bond.save()
cls.publish_order(order) cls.publish_order(order)
@ -524,7 +526,7 @@ class Logics():
def is_taker_bond_locked(cls, order): def is_taker_bond_locked(cls, order):
if order.taker_bond.status == LNPayment.Status.LOCKED: if order.taker_bond.status == LNPayment.Status.LOCKED:
return True return True
if LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash): elif LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash):
cls.finalize_contract(order) cls.finalize_contract(order)
return True return True
return False return False
@ -574,20 +576,25 @@ 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}
def trade_escrow_received(order):
''' Moves the order forward'''
# If status is 'Waiting for both' move to Waiting for invoice
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.WFE:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.CHA])
order.save()
@classmethod @classmethod
def is_trade_escrow_locked(cls, order): def is_trade_escrow_locked(cls, order):
if LNNode.validate_hold_invoice_locked(order.trade_escrow.payment_hash): if order.trade_escrow.status == LNPayment.Status.LOCKED:
return True
elif LNNode.validate_hold_invoice_locked(order.trade_escrow.payment_hash):
order.trade_escrow.status = LNPayment.Status.LOCKED order.trade_escrow.status = LNPayment.Status.LOCKED
order.trade_escrow.save() order.trade_escrow.save()
# If status is 'Waiting for both' move to Waiting for invoice cls.trade_escrow_received(order)
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.WFE:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.CHA])
order.save()
return True return True
return False return False
@ -607,7 +614,7 @@ class Logics():
elif order.trade_escrow.status == LNPayment.Status.INVGEN: elif order.trade_escrow.status == LNPayment.Status.INVGEN:
return True, {'escrow_invoice':order.trade_escrow.invoice, 'escrow_satoshis':order.trade_escrow.num_satoshis} return True, {'escrow_invoice':order.trade_escrow.invoice, 'escrow_satoshis':order.trade_escrow.num_satoshis}
# If there was no taker_bond object yet, generates one # If there was no taker_bond object yet, generate one
escrow_satoshis = order.last_satoshis # Amount was fixed when taker bond was locked escrow_satoshis = order.last_satoshis # Amount was fixed when taker bond was locked
description = f"RoboSats - Escrow amount for '{str(order)}' - The escrow will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment." description = f"RoboSats - Escrow amount for '{str(order)}' - The escrow will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."

View File

@ -22,6 +22,7 @@ class Command(BaseCommand):
''' '''
help = 'Follows all active hold invoices' help = 'Follows all active hold invoices'
rest = 5 # seconds between consecutive checks for invoice updates
# def add_arguments(self, parser): # def add_arguments(self, parser):
# parser.add_argument('debug', nargs='+', type=boolean) # parser.add_argument('debug', nargs='+', type=boolean)
@ -40,7 +41,7 @@ class Command(BaseCommand):
stub = LNNode.invoicesstub stub = LNNode.invoicesstub
while True: while True:
time.sleep(5) time.sleep(self.rest)
# time it for debugging # time it for debugging
t0 = time.time() t0 = time.time()
@ -95,21 +96,24 @@ class Command(BaseCommand):
def update_order_status(self, lnpayment): def update_order_status(self, lnpayment):
''' Background process following LND hold invoices ''' Background process following LND hold invoices
might catch LNpayments changing status. If they do, can catch LNpayments changing status. If they do,
the order status might have to change status too.''' the order status might have to change status too.'''
# If the LNPayment goes to LOCKED (ACCEPTED) # If the LNPayment goes to LOCKED (ACCEPTED)
if lnpayment.status == LNPayment.Status.LOCKED: if lnpayment.status == LNPayment.Status.LOCKED:
# It is a maker bond => Publish order. # It is a maker bond => Publish order.
order = lnpayment.order_made if not lnpayment.order_made == None:
if not order == None: Logics.publish_order(lnpayment.order_made)
Logics.publish_order(order)
return return
# It is a taker bond => close contract. # It is a taker bond => close contract.
order = lnpayment.order_taken elif not lnpayment.order_taken == None:
if not order == None: if lnpayment.order_taken.status == Order.Status.TAK:
if order.status == Order.Status.TAK: Logics.finalize_contract(lnpayment.order_taken)
Logics.finalize_contract(order) return
return
# It is a trade escrow => move foward order status.
elif not lnpayment.order_escrow == None:
Logics.trade_escrow_received(lnpayment.order_escrow)
return

View File

@ -151,7 +151,7 @@ class Order(models.Model):
trade_escrow = models.OneToOneField(LNPayment, related_name='order_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True) trade_escrow = models.OneToOneField(LNPayment, related_name='order_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True)
# buyer payment LN invoice # buyer payment LN invoice
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True) buyer_invoice = models.OneToOneField(LNPayment, related_name='order_paid', on_delete=models.SET_NULL, null=True, default=None, blank=True)
# ratings # ratings
maker_rated = models.BooleanField(default=False, null=False) maker_rated = models.BooleanField(default=False, null=False)
@ -180,9 +180,8 @@ class Order(models.Model):
} }
def __str__(self): def __str__(self):
# Make relational back to ORDER
return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}') return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}')
@receiver(pre_delete, sender=Order) @receiver(pre_delete, sender=Order)
def delete_lnpayment_at_order_deletion(sender, instance, **kwargs): def delete_lnpayment_at_order_deletion(sender, instance, **kwargs):

View File

@ -40,7 +40,7 @@ def follow_send_payment(lnpayment):
from base64 import b64decode from base64 import b64decode
from api.lightning.node import LNNode from api.lightning.node import LNNode
from api.models import LNPayment from api.models import LNPayment, Order
MACAROON = b64decode(config('LND_MACAROON_BASE64')) MACAROON = b64decode(config('LND_MACAROON_BASE64'))
@ -48,25 +48,37 @@ def follow_send_payment(lnpayment):
request = LNNode.routerrpc.SendPaymentRequest( request = LNNode.routerrpc.SendPaymentRequest(
payment_request=lnpayment.invoice, payment_request=lnpayment.invoice,
fee_limit_sat=fee_limit_sat, fee_limit_sat=fee_limit_sat,
timeout_seconds=60) timeout_seconds=60) # time out payment in 60 seconds
order = lnpayment.order_paid
for response in LNNode.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]): for response in LNNode.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]):
if response.status == 0 : # Status 0 'UNKNOWN' if response.status == 0 : # Status 0 'UNKNOWN'
# Not sure when this status happens
pass pass
if response.status == 1 : # Status 1 'IN_FLIGHT' if response.status == 1 : # Status 1 'IN_FLIGHT'
print('IN_FLIGHT')
lnpayment.status = LNPayment.Status.FLIGHT lnpayment.status = LNPayment.Status.FLIGHT
lnpayment.save() lnpayment.save()
order.status = Order.Status.PAY
order.save()
if response.status == 3 : # Status 3 'FAILED' if response.status == 3 : # Status 3 'FAILED'
print('FAILED')
lnpayment.status = LNPayment.Status.FAILRO lnpayment.status = LNPayment.Status.FAILRO
lnpayment.save() lnpayment.save()
order.status = Order.Status.FAI
order.save()
context = LNNode.payment_failure_context[response.failure_reason] context = LNNode.payment_failure_context[response.failure_reason]
# Call for a retry here
return False, context return False, context
if response.status == 2 : # Status 2 'SUCCEEDED' if response.status == 2 : # Status 2 'SUCCEEDED'
print('SUCCEEDED')
lnpayment.status = LNPayment.Status.SUCCED lnpayment.status = LNPayment.Status.SUCCED
lnpayment.save() lnpayment.save()
order.status = Order.Status.SUC
order.save()
return True, None return True, None
@shared_task(name="cache_external_market_prices", ignore_result=True) @shared_task(name="cache_external_market_prices", ignore_result=True)

View File

@ -5,7 +5,7 @@ import numpy as np
market_cache = {} market_cache = {}
@ring.dict(market_cache, expire=5) #keeps in cache for 5 seconds @ring.dict(market_cache, expire=3) #keeps in cache for 3 seconds
def get_exchange_rates(currencies): def get_exchange_rates(currencies):
''' '''
Params: list of currency codes. Params: list of currency codes.

View File

@ -63,7 +63,7 @@ export default class OrderPage extends Component {
"10": 15000, //'Fiat sent - In chatroom' "10": 15000, //'Fiat sent - In chatroom'
"11": 60000, //'In dispute' "11": 60000, //'In dispute'
"12": 9999999,//'Collaboratively cancelled' "12": 9999999,//'Collaboratively cancelled'
"13": 120000, //'Sending satoshis to buyer' "13": 3000, //'Sending satoshis to buyer'
"14": 9999999,//'Sucessful trade' "14": 9999999,//'Sucessful trade'
"15": 10000, //'Failed lightning network routing' "15": 10000, //'Failed lightning network routing'
"16": 9999999,//'Maker lost dispute' "16": 9999999,//'Maker lost dispute'

View File

@ -1,8 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Button , Dialog, Grid, Typography, TextField, ButtonGroup} from "@mui/material" import { Button , Dialog, Grid, Typography, TextField, ButtonGroup, CircularProgress, IconButton} from "@mui/material"
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import Image from 'material-ui-image' import Image from 'material-ui-image'
import InfoDialog from './InfoDialog' import InfoDialog from './InfoDialog'
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import ContentCopy from "@mui/icons-material/ContentCopy";
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;
@ -27,6 +29,7 @@ export default class UserGenPage extends Component {
this.state = { this.state = {
token: this.genBase62Token(34), token: this.genBase62Token(34),
openInfo: false, openInfo: false,
showRobosat: true,
}; };
this.getGeneratedUser(this.state.token); this.getGeneratedUser(this.state.token);
} }
@ -53,6 +56,7 @@ export default class UserGenPage extends Component {
shannon_entropy: data.token_shannon_entropy, shannon_entropy: data.token_shannon_entropy,
bad_request: data.bad_request, bad_request: data.bad_request,
found: data.found, found: data.found,
showRobosat:true,
}); });
}); });
} }
@ -69,10 +73,12 @@ export default class UserGenPage extends Component {
handleAnotherButtonPressed=(e)=>{ handleAnotherButtonPressed=(e)=>{
this.delGeneratedUser() this.delGeneratedUser()
this.setState({ // this.setState({
token: this.genBase62Token(34), // showRobosat: false,
}); // token: this.genBase62Token(34),
this.getGeneratedUser(this.state.token); // });
// this.getGeneratedUser(this.state.token);
window.location.reload();
} }
handleChangeToken=(e)=>{ handleChangeToken=(e)=>{
@ -81,6 +87,7 @@ export default class UserGenPage extends Component {
token: e.target.value, token: e.target.value,
}) })
this.getGeneratedUser(e.target.value); this.getGeneratedUser(e.target.value);
this.setState({showRobosat: false})
} }
handleClickOpenInfo = () => { handleClickOpenInfo = () => {
@ -109,20 +116,26 @@ export default class UserGenPage extends Component {
render() { render() {
return ( return (
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography component="h5" variant="h5"> {this.state.showRobosat ?
<b>{this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""}</b> <div>
</Typography> <Grid item xs={12} align="center">
</Grid> <Typography component="h5" variant="h5">
<Grid item xs={12} align="center"> <b>{this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""}</b>
<div style={{ maxWidth: 200, maxHeight: 200 }}> </Typography>
<Image className='newAvatar' </Grid>
disableError='true' <Grid item xs={12} align="center">
cover='true' <div style={{ maxWidth: 200, maxHeight: 200 }}>
color='null' <Image className='newAvatar'
src={this.state.avatar_url} disableError='true'
/> cover='true'
</div><br/> color='null'
src={this.state.avatar_url}
/>
</div><br/>
</Grid>
</div>
: <CircularProgress />}
</Grid> </Grid>
{ {
this.state.found ? this.state.found ?
@ -134,21 +147,30 @@ export default class UserGenPage extends Component {
: :
"" ""
} }
<Grid item xs={12} align="center"> <Grid container align="center">
<TextField <Grid item xs={12} align="center">
error={this.state.bad_request} <IconButton onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
label='Token - Store safely' <ContentCopy color='secondary'/>
required='true' </IconButton>
value={this.state.token} <TextField
variant='standard' //sx={{ input: { color: 'purple' } }}
helperText={this.state.bad_request} InputLabelProps={{
size='small' style: { color: 'purple' },
// multiline = {true} }}
onChange={this.handleChangeToken} error={this.state.bad_request}
/> label='Token - Store safely'
required='true'
value={this.state.token}
variant='standard'
helperText={this.state.bad_request}
size='small'
// multiline = {true}
onChange={this.handleChangeToken}
/>
</Grid>
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button onClick={this.handleAnotherButtonPressed}>Generate Another Robosat</Button> <Button size='small' onClick={this.handleAnotherButtonPressed}>Generate Another Robosat</Button>
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<ButtonGroup variant="contained" aria-label="outlined primary button group"> <ButtonGroup variant="contained" aria-label="outlined primary button group">