mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 12:11:35 +00:00
Bug fix invoice follow. Copy user token button.
This commit is contained in:
parent
a005b3509d
commit
e31bc1adad
@ -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 %
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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."
|
||||||
|
|
||||||
|
@ -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
|
@ -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):
|
||||||
|
16
api/tasks.py
16
api/tasks.py
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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'
|
||||||
|
@ -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">
|
||||||
|
Loading…
Reference in New Issue
Block a user