Improve logics around locked bonds. Add frontend confirm cancel dialog.

This commit is contained in:
Reckless_Satoshi 2022-01-18 07:23:57 -08:00
parent e31bc1adad
commit c58070f437
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 95 additions and 63 deletions

View File

@ -125,7 +125,7 @@ class Logics():
elif order.status == Order.Status.WFB: elif order.status == Order.Status.WFB:
order.status = Order.Status.EXP order.status = Order.Status.EXP
cls.cancel_bond(order.maker_bond) cls.cancel_bond(order.maker_bond)
order.maker = None order.maker = None # TODO with the new validate_already_maker_taker there is no need to kick out participants on expired orders.
order.taker = None order.taker = None
order.save() order.save()
return True return True
@ -175,12 +175,10 @@ class Logics():
else: else:
cls.settle_bond(order.taker_bond) cls.settle_bond(order.taker_bond)
cls.cancel_escrow(order) cls.cancel_escrow(order)
order.status = Order.Status.PUB
order.taker = None order.taker = None
order.taker_bond = None order.taker_bond = None
order.trade_escrow = None order.trade_escrow = None
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) cls.publish_order(order)
order.save()
return True return True
elif order.status == Order.Status.WFI: elif order.status == Order.Status.WFI:
@ -203,12 +201,10 @@ class Logics():
else: else:
cls.settle_bond(order.taker_bond) cls.settle_bond(order.taker_bond)
cls.return_escrow(order) cls.return_escrow(order)
order.status = Order.Status.PUB
order.taker = None order.taker = None
order.taker_bond = None order.taker_bond = None
order.trade_escrow = None order.trade_escrow = None
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) cls.publish_order(order)
order.save()
return True return True
elif order.status == Order.Status.CHA: elif order.status == Order.Status.CHA:
@ -218,7 +214,8 @@ class Logics():
cls.open_dispute(order) cls.open_dispute(order)
return True return True
def kick_taker(order): @classmethod
def kick_taker(cls, order):
''' The taker did not lock the taker_bond. Now he has to go''' ''' The taker did not lock the taker_bond. Now he has to go'''
# Add a time out to the taker # Add a time out to the taker
profile = order.taker.profile profile = order.taker.profile
@ -226,11 +223,9 @@ class Logics():
profile.save() profile.save()
# Make order public again # Make order public again
order.status = Order.Status.PUB
order.taker = None order.taker = None
order.taker_bond = None order.taker_bond = None
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) cls.publish_order(order)
order.save()
return True return True
@classmethod @classmethod
@ -417,14 +412,12 @@ class Logics():
# 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 in [Order.Status.WF2, Order.Status.WFE] 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_bond(order.taker_bond) valid = cls.settle_bond(order.taker_bond)
if valid: if valid:
order.taker = None order.taker = None
order.status = Order.Status.PUB cls.publish_order(order)
# order.taker_bond = None # TODO fix this, it overrides the information about the settled taker bond. Might make admin tasks hard.
order.save()
return True, None return True, None
# 5) When trade collateral has been posted (after escrow) # 5) When trade collateral has been posted (after escrow)
@ -437,9 +430,7 @@ class Logics():
return False, {'bad_request':'You cannot cancel this order'} return False, {'bad_request':'You cannot cancel this order'}
def publish_order(order): def publish_order(order):
if order.status == Order.Status.WFB:
order.status = Order.Status.PUB order.status = Order.Status.PUB
# With the bond confirmation the order is extended 'public_order_duration' hours
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
order.save() order.save()
return return

View File

@ -166,14 +166,14 @@ class OrderView(viewsets.ViewSet):
data['escrow_locked'] = False data['escrow_locked'] = False
# If both bonds are locked, participants can see the final trade amount in sats. # 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 sends # # Seller sees the amount he sends
if data['is_seller']: # if data['is_seller']:
data['trade_satoshis'] = order.last_satoshis # data['trade_satoshis'] = order.last_satoshis
# Buyer sees the amount he receives # # Buyer sees the amount he receives
elif data['is_buyer']: # elif data['is_buyer']:
data['trade_satoshis'] = Logics.buyer_invoice_amount(order, request.user)[1]['invoice_amount'] # data['trade_satoshis'] = Logics.buyer_invoice_amount(order, request.user)[1]['invoice_amount']
# 5) If status is 'waiting for maker bond' and user is MAKER, reply with a MAKER hold invoice. # 5) If status is 'waiting for maker bond' and user is MAKER, reply with a MAKER hold invoice.
if order.status == Order.Status.WFB and data['is_maker']: if order.status == Order.Status.WFB and data['is_maker']:

View File

@ -166,10 +166,10 @@ export default class BookPage extends Component {
style: {textAlign:"center"} style: {textAlign:"center"}
}} }}
onChange={this.handleCurrencyChange} onChange={this.handleCurrencyChange}
> <MenuItem value={0}>ANY</MenuItem> > <MenuItem value={0}>🌍 ANY</MenuItem>
{ {
Object.entries(this.state.currencies_dict) Object.entries(this.state.currencies_dict)
.map( ([key, value]) => <MenuItem value={parseInt(key)}>{value}</MenuItem> ) .map( ([key, value]) => <MenuItem value={parseInt(key)}>{getFlags(value) + " " + value}</MenuItem> )
} }
</Select> </Select>
</FormControl> </FormControl>

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress} from "@mui/material" import { Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown'; import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown';
import TradeBox from "./TradeBox"; import TradeBox from "./TradeBox";
import getFlags from './getFlags' import getFlags from './getFlags'
@ -11,6 +11,7 @@ import PriceChangeIcon from '@mui/icons-material/PriceChange';
import PaymentsIcon from '@mui/icons-material/Payments'; import PaymentsIcon from '@mui/icons-material/Payments';
import MoneyIcon from '@mui/icons-material/Money'; import MoneyIcon from '@mui/icons-material/Money';
import ArticleIcon from '@mui/icons-material/Article'; import ArticleIcon from '@mui/icons-material/Article';
import ContentCopy from "@mui/icons-material/ContentCopy";
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;
@ -43,6 +44,7 @@ export default class OrderPage extends Component {
currencies_dict: {"1":"USD"}, currencies_dict: {"1":"USD"},
total_secs_expiry: 300, total_secs_expiry: 300,
loading: true, loading: true,
openCancel: false,
}; };
this.orderId = this.props.match.params.orderId; this.orderId = this.props.match.params.orderId;
this.getCurrencyDict(); this.getCurrencyDict();
@ -144,8 +146,8 @@ export default class OrderPage extends Component {
countdownRenderer = ({ total, hours, minutes, seconds, completed }) => { countdownRenderer = ({ total, hours, minutes, seconds, completed }) => {
if (completed) { if (completed) {
// Render a completed state // Render a completed state
this.getOrderDetails(); return (<span> The order has expired</span>);
return null;
} else { } else {
var col = 'black' var col = 'black'
var fraction_left = (total/1000) / this.state.total_secs_expiry var fraction_left = (total/1000) / this.state.total_secs_expiry
@ -218,7 +220,7 @@ export default class OrderPage extends Component {
return code return code
} }
handleClickCancelOrderButton=()=>{ handleClickConfirmCancelButton=()=>{
console.log(this.state) console.log(this.state)
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
@ -230,6 +232,64 @@ 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) => (console.log(data) & this.getOrderDetails(data.id)));
this.handleClickCloseConfirmCancelDialog();
}
handleClickOpenConfirmCancelDialog = () => {
this.setState({openCancel: true});
};
handleClickCloseConfirmCancelDialog = () => {
this.setState({openCancel: false});
};
CancelDialog =() =>{
return(
<Dialog
open={this.state.openCancel}
onClose={this.handleClickCloseConfirmCancelDialog}
aria-labelledby="cancel-dialog-title"
aria-describedby="cancel-dialog-description"
>
<DialogTitle id="cancel-dialog-title">
{"Cancel the order?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="cancel-dialog-description">
If the order is cancelled now you will lose your bond.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseConfirmCancelDialog} autoFocus>Go back</Button>
<Button onClick={this.handleClickConfirmCancelButton}> Confirm Cancel </Button>
</DialogActions>
</Dialog>
)
}
CancelButton = () => {
// If maker and Waiting for Bond. Or if taker and Waiting for bond.
// Simply allow to cancel without showing the cancel dialog.
if ((this.state.isMaker & this.state.statusCode == 0) || this.state.isTaker & this.state.statusCode == 3){
return(
<Grid item xs={12} align="center">
<Button variant='contained' color='secondary' onClick={this.handleClickConfirmCancelButton}>Cancel</Button>
</Grid>
)}
// If the order does not yet have an escrow deposited. Show dialog
// to confirm forfeiting the bond
if (this.state.statusCode < 8){
return(
<Grid item xs={12} align="center">
<this.CancelDialog/>
<Button variant='contained' color='secondary' onClick={this.handleClickOpenConfirmCancelDialog}>Cancel</Button>
</Grid>
)}
// TODO If the escrow is Locked, show the collaborative cancel button.
// If none of the above do not return a cancel button.
return(null)
} }
orderBox=()=>{ orderBox=()=>{
@ -346,8 +406,10 @@ export default class OrderPage extends Component {
</Paper> </Paper>
</Grid> </Grid>
{/* Participants cannot see the Back or Take Order buttons */} {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */}
{this.state.isParticipant ? "" : {this.state.isParticipant ?
<this.CancelButton/>
:
<> <>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button variant='contained' color='primary' onClick={this.handleClickTakeOrderButton}>Take Order</Button> <Button variant='contained' color='primary' onClick={this.handleClickTakeOrderButton}>Take Order</Button>
@ -358,26 +420,6 @@ export default class OrderPage extends Component {
</> </>
} }
{/* Makers can cancel before trade escrow deposited (status <9)*/}
{/* Only free cancel before bond locked (status 0)*/}
{this.state.isMaker & this.state.statusCode < 9 ?
<Grid item xs={12} align="center">
<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">
<Button variant='contained' color='secondary' onClick={this.handleClickCancelOrderButton}>Cancel</Button>
</Grid>
:""}
</Grid> </Grid>
) )
} }

View File

@ -183,7 +183,7 @@ export default class TradeBox extends Component {
return ( return (
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography color="primary" component="subtitle1" variant="subtitle1"> <Typography color="green" component="subtitle1" variant="subtitle1">
<b>Deposit {pn(this.props.data.escrowSatoshis)} Sats as trade collateral </b> <b>Deposit {pn(this.props.data.escrowSatoshis)} Sats as trade collateral </b>
</Typography> </Typography>
</Grid> </Grid>
@ -569,7 +569,6 @@ handleRatingChange=(e)=>{
) )
} }
render() { render() {
return ( return (
<Grid container spacing={1} style={{ width:330}}> <Grid container spacing={1} style={{ width:330}}>

View File

@ -150,15 +150,15 @@ export default class UserGenPage extends Component {
<Grid container align="center"> <Grid container align="center">
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<IconButton onClick= {()=>navigator.clipboard.writeText(this.state.token)}> <IconButton onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
<ContentCopy color='secondary'/> <ContentCopy/>
</IconButton> </IconButton>
<TextField <TextField
//sx={{ input: { color: 'purple' } }} //sx={{ input: { color: 'purple' } }}
InputLabelProps={{ InputLabelProps={{
style: { color: 'purple' }, style: { color: 'green' },
}} }}
error={this.state.bad_request} error={this.state.bad_request}
label='Token - Store safely' label='Store your token safely'
required='true' required='true'
value={this.state.token} value={this.state.token}
variant='standard' variant='standard'