mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Improve logics around locked bonds. Add frontend confirm cancel dialog.
This commit is contained in:
parent
e31bc1adad
commit
c58070f437
@ -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,12 +430,10 @@ 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
|
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
||||||
# With the bond confirmation the order is extended 'public_order_duration' hours
|
order.save()
|
||||||
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
return
|
||||||
order.save()
|
|
||||||
return
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_maker_bond_locked(cls, order):
|
def is_maker_bond_locked(cls, order):
|
||||||
|
16
api/views.py
16
api/views.py
@ -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']:
|
||||||
|
@ -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>
|
||||||
|
@ -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,27 +420,7 @@ export default class OrderPage extends Component {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* Makers can cancel before trade escrow deposited (status <9)*/}
|
</Grid>
|
||||||
{/* 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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}}>
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user