mirror of
https://github.com/RoboSats/robosats.git
synced 2025-02-07 13:59:06 +00:00
Implement collaborative cancellation
This commit is contained in:
parent
6377b052ce
commit
b934534e1e
@ -421,14 +421,46 @@ class Logics():
|
|||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
# 5) When trade collateral has been posted (after escrow)
|
# 5) When trade collateral has been posted (after escrow)
|
||||||
'''Always goes to cancelled status. Collaboration is needed.
|
'''Always goes to CCA status. Collaboration is needed.
|
||||||
When a user asks for cancel, 'order.is_pending_cancel' goes True.
|
When a user asks for cancel, 'order.m/t/aker_asked_cancel' goes True.
|
||||||
When the second user asks for cancel. Order is totally cancelled.
|
When the second user asks for cancel. Order is totally cancelled.
|
||||||
Has a small cost for both parties to prevent node DDOS.'''
|
Must have a small cost for both parties to prevent node DDOS.'''
|
||||||
|
elif order.status in [Order.Status.WFI, Order.Status.CHA, Order.Status.FSE]:
|
||||||
|
|
||||||
|
# if the maker had asked, and now the taker does: cancel order, return everything
|
||||||
|
if order.maker_asked_cancel and user == order.taker:
|
||||||
|
cls.collaborative_cancel(order)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
# if the taker had asked, and now the maker does: cancel order, return everything
|
||||||
|
elif order.taker_asked_cancel and user == order.maker:
|
||||||
|
cls.collaborative_cancel(order)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
# Otherwise just make true the asked for cancel flags
|
||||||
|
elif user == order.taker:
|
||||||
|
order.taker_asked_cancel = True
|
||||||
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
elif user == order.maker:
|
||||||
|
order.maker_asked_cancel = True
|
||||||
|
order.save()
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return False, {'bad_request':'You cannot cancel this order'}
|
return False, {'bad_request':'You cannot cancel this order'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def collaborative_cancel(cls, order):
|
||||||
|
cls.return_bond(order.maker_bond)
|
||||||
|
cls.return_bond(order.taker_bond)
|
||||||
|
cls.return_escrow(order)
|
||||||
|
order.status = Order.Status.CCA
|
||||||
|
order.save()
|
||||||
|
return
|
||||||
|
|
||||||
def publish_order(order):
|
def publish_order(order):
|
||||||
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])
|
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
||||||
|
@ -142,7 +142,8 @@ class Order(models.Model):
|
|||||||
# order participants
|
# order participants
|
||||||
maker = models.ForeignKey(User, related_name='maker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a maker can only make one order
|
maker = models.ForeignKey(User, related_name='maker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a maker can only make one order
|
||||||
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None, blank=True) # unique = True, a taker can only take one order
|
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None, blank=True) # unique = True, a taker can only take one order
|
||||||
is_pending_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled.
|
maker_asked_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled.
|
||||||
|
taker_asked_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled.
|
||||||
is_fiat_sent = models.BooleanField(default=False, null=False)
|
is_fiat_sent = models.BooleanField(default=False, null=False)
|
||||||
|
|
||||||
# in dispute
|
# in dispute
|
||||||
|
19
api/views.py
19
api/views.py
@ -189,7 +189,6 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
# 7 a. ) If seller and status is 'WF2' or 'WFE'
|
# 7 a. ) If seller and status is 'WF2' or 'WFE'
|
||||||
elif data['is_seller'] and (order.status == Order.Status.WF2 or order.status == Order.Status.WFE):
|
elif data['is_seller'] and (order.status == Order.Status.WF2 or order.status == Order.Status.WFE):
|
||||||
|
|
||||||
# If the two bonds are locked, reply with an ESCROW hold invoice.
|
# If the two bonds are locked, reply with an ESCROW hold invoice.
|
||||||
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
|
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
|
||||||
valid, context = Logics.gen_escrow_hold_invoice(order, request.user)
|
valid, context = Logics.gen_escrow_hold_invoice(order, request.user)
|
||||||
@ -210,15 +209,23 @@ class OrderView(viewsets.ViewSet):
|
|||||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
|
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
|
||||||
elif order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Add the other status
|
elif order.status in [Order.Status.WFI, Order.Status.CHA, Order.Status.FSE]:
|
||||||
|
print('CCCAAABBAAAAAAAAAAAAAAAA')
|
||||||
# If all bonds are locked.
|
# If all bonds are locked.
|
||||||
if order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED:
|
if order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||||
# add whether a collaborative cancel is pending
|
print('AAABBAAAAAAAAAAAAAAAA')
|
||||||
data['pending_cancel'] = order.is_pending_cancel
|
# add whether a collaborative cancel is pending or has been asked
|
||||||
|
if (data['is_maker'] and order.taker_asked_cancel) or (data['is_taker'] and order.maker_asked_cancel):
|
||||||
|
print('PENDING')
|
||||||
|
data['pending_cancel'] = True
|
||||||
|
elif (data['is_maker'] and order.maker_asked_cancel) or (data['is_taker'] and order.taker_asked_cancel):
|
||||||
|
print('ASKED')
|
||||||
|
data['asked_for_cancel'] = True
|
||||||
|
else:
|
||||||
|
data['asked_for_cancel'] = False
|
||||||
|
|
||||||
# 9) If status is 'DIS' and all HTLCS are in LOCKED
|
# 9) If status is 'DIS' and all HTLCS are in LOCKED
|
||||||
elif order.status == Order.Status.DIS:# TODO Add the other status
|
elif order.status == Order.Status.DIS:
|
||||||
|
|
||||||
# add whether the dispute statement has been received
|
# add whether the dispute statement has been received
|
||||||
if data['is_maker']:
|
if data['is_maker']:
|
||||||
|
@ -9,9 +9,7 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
|||||||
import NumbersIcon from '@mui/icons-material/Numbers';
|
import NumbersIcon from '@mui/icons-material/Numbers';
|
||||||
import PriceChangeIcon from '@mui/icons-material/PriceChange';
|
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 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;
|
||||||
@ -45,12 +43,13 @@ export default class OrderPage extends Component {
|
|||||||
total_secs_exp: 300,
|
total_secs_exp: 300,
|
||||||
loading: true,
|
loading: true,
|
||||||
openCancel: false,
|
openCancel: false,
|
||||||
|
openCollaborativeCancel: false,
|
||||||
};
|
};
|
||||||
this.orderId = this.props.match.params.orderId;
|
this.orderId = this.props.match.params.orderId;
|
||||||
this.getCurrencyDict();
|
this.getCurrencyDict();
|
||||||
this.getOrderDetails();
|
this.getOrderDetails();
|
||||||
|
|
||||||
// Refresh delais according to Order status
|
// Refresh delays according to Order status
|
||||||
this.statusToDelay = {
|
this.statusToDelay = {
|
||||||
"0": 3000, //'Waiting for maker bond'
|
"0": 3000, //'Waiting for maker bond'
|
||||||
"1": 30000, //'Public'
|
"1": 30000, //'Public'
|
||||||
@ -64,69 +63,25 @@ export default class OrderPage extends Component {
|
|||||||
"9": 10000, //'Sending fiat - In chatroom'
|
"9": 10000, //'Sending fiat - In chatroom'
|
||||||
"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": 3000, //'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'
|
||||||
"17": 9999999,//'Taker lost dispute'
|
"17": 9999999, //'Taker lost dispute'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded for the most part. Let's keep variable names as they come from the API
|
|
||||||
// Will need some renaming everywhere, but will decrease the mess.
|
|
||||||
completeSetState=(newStateVars)=>{
|
completeSetState=(newStateVars)=>{
|
||||||
console.log(newStateVars)
|
|
||||||
var otherStateVars = {
|
var otherStateVars = {
|
||||||
loading: false,
|
loading: false,
|
||||||
delay: this.setDelay(newStateVars.status),
|
delay: this.setDelay(newStateVars.status),
|
||||||
currencyCode: this.getCurrencyCode(newStateVars.currency),
|
currencyCode: this.getCurrencyCode(newStateVars.currency),
|
||||||
};
|
};
|
||||||
console.log(otherStateVars)
|
|
||||||
var completeStateVars = Object.assign({}, newStateVars, otherStateVars);
|
var completeStateVars = Object.assign({}, newStateVars, otherStateVars);
|
||||||
console.log(completeStateVars)
|
|
||||||
this.setState(completeStateVars);
|
this.setState(completeStateVars);
|
||||||
// {
|
|
||||||
// loading: false,
|
|
||||||
// delay: this.setDelay(data.status),
|
|
||||||
// id: data.id,
|
|
||||||
// status: data.status,
|
|
||||||
// status_message: data.status_message,
|
|
||||||
// type: data.type,
|
|
||||||
// currency: data.currency,
|
|
||||||
// currencyCode: this.getCurrencyCode(data.currency),
|
|
||||||
// amount: data.amount,
|
|
||||||
// payment_method: data.payment_method,
|
|
||||||
// isExplicit: data.is_explicit,
|
|
||||||
// premium: data.premium,
|
|
||||||
// satoshis: data.satoshis,
|
|
||||||
// makerId: data.maker,
|
|
||||||
// is_participant: data.is_participant,
|
|
||||||
// urNick: data.ur_nick,
|
|
||||||
// maker_nick: data.maker_nick,
|
|
||||||
// takerId: data.taker,
|
|
||||||
// taker_nick: data.taker_nick,
|
|
||||||
// is_maker: data.is_maker,
|
|
||||||
// is_taker: data.is_taker,
|
|
||||||
// is_buyer: data.is_buyer,
|
|
||||||
// is_seller: data.is_seller,
|
|
||||||
// penalty: data.penalty,
|
|
||||||
// expires_at: data.expires_at,
|
|
||||||
// bad_request: data.bad_request,
|
|
||||||
// bond_invoice: data.bond_invoice,
|
|
||||||
// bondSatoshis: data.bond_satoshis,
|
|
||||||
// escrow_invoice: data.escrow_invoice,
|
|
||||||
// escrowSatoshis: data.escrow_satoshis,
|
|
||||||
// invoice_amount: data.invoice_amount,
|
|
||||||
// total_secs_exp: data.total_secs_exp,
|
|
||||||
// num_similar_orders: data.num_similar_orders,
|
|
||||||
// price_now: data.price_now,
|
|
||||||
// premium_now: data.premium_now,
|
|
||||||
// probots_in_book: data.robots_in_book,
|
|
||||||
// premium_percentile: data.premium_percentile,
|
|
||||||
// num_similar_orders: data.num_similar_orders
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderDetails() {
|
getOrderDetails() {
|
||||||
this.setState(null)
|
this.setState(null)
|
||||||
fetch('/api/order' + '?order_id=' + this.orderId)
|
fetch('/api/order' + '?order_id=' + this.orderId)
|
||||||
@ -277,6 +232,53 @@ export default class OrderPage extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickConfirmCollaborativeCancelButton=()=>{
|
||||||
|
console.log(this.state)
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'action':'cancel',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => (console.log(data) & this.getOrderDetails(data.id)));
|
||||||
|
this.handleClickCloseCollaborativeCancelDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickOpenCollaborativeCancelDialog = () => {
|
||||||
|
this.setState({openCollaborativeCancel: true});
|
||||||
|
};
|
||||||
|
handleClickCloseCollaborativeCancelDialog = () => {
|
||||||
|
this.setState({openCollaborativeCancel: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
CollaborativeCancelDialog =() =>{
|
||||||
|
return(
|
||||||
|
<Dialog
|
||||||
|
open={this.state.openCollaborativeCancel}
|
||||||
|
onClose={this.handleClickCloseCollaborativeCancelDialog}
|
||||||
|
aria-labelledby="collaborative-cancel-dialog-title"
|
||||||
|
aria-describedby="collaborative-cancel-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="cancel-dialog-title">
|
||||||
|
{"Collaborative cancel the order?"}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="cancel-dialog-description">
|
||||||
|
The trade escrow has been posted. The order can be cancelled only if both, maker and
|
||||||
|
taker, agree to cancel.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={this.handleClickCloseCollaborativeCancelDialog} autoFocus>Go back</Button>
|
||||||
|
<Button onClick={this.handleClickConfirmCollaborativeCancelButton}> Collaborative Cancel </Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
CancelButton = () => {
|
CancelButton = () => {
|
||||||
|
|
||||||
// If maker and Waiting for Bond. Or if taker and Waiting for bond.
|
// If maker and Waiting for Bond. Or if taker and Waiting for bond.
|
||||||
@ -289,15 +291,25 @@ export default class OrderPage extends Component {
|
|||||||
)}
|
)}
|
||||||
// If the order does not yet have an escrow deposited. Show dialog
|
// If the order does not yet have an escrow deposited. Show dialog
|
||||||
// to confirm forfeiting the bond
|
// to confirm forfeiting the bond
|
||||||
if (this.state.status in [0,1,3,6,7]){
|
if ([1,3,6,7].includes(this.state.status)){
|
||||||
return(
|
return(
|
||||||
|
<div id="openDialogCancelButton">
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<this.CancelDialog/>
|
<this.CancelDialog/>
|
||||||
<Button variant='contained' color='secondary' onClick={this.handleClickOpenConfirmCancelDialog}>Cancel</Button>
|
<Button variant='contained' color='secondary' onClick={this.handleClickOpenConfirmCancelDialog}>Cancel</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
// TODO If the escrow is Locked, show the collaborative cancel button.
|
// If the escrow is Locked, show the collaborative cancel button.
|
||||||
|
|
||||||
|
if ([8,9].includes(this.state.status)){
|
||||||
|
return(
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<this.CollaborativeCancelDialog/>
|
||||||
|
<Button variant='contained' color='secondary' onClick={this.handleClickOpenCollaborativeCancelDialog}>Collaborative Cancel</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
// If none of the above do not return a cancel button.
|
// If none of the above do not return a cancel button.
|
||||||
return(null)
|
return(null)
|
||||||
@ -414,23 +426,48 @@ export default class OrderPage extends Component {
|
|||||||
</>
|
</>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
{/* If the counterparty asked for collaborative cancel */}
|
||||||
|
{this.state.pending_cancel ?
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Alert severity="warning" sx={{maxWidth:360}}>
|
||||||
|
{this.state.is_maker ? this.state.taker_nick : this.state.maker_nick} is asking for a collaborative cancel
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{/* If the user has asked for a collaborative cancel */}
|
||||||
|
{this.state.asked_for_cancel ?
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
|
<Alert severity="warning" sx={{maxWidth:360}}>
|
||||||
|
You asked for a collaborative cancellation
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} align="center">
|
||||||
{/* Participants can see the "Cancel" Button, but 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.is_participant ?
|
{this.state.is_participant ?
|
||||||
<this.CancelButton/>
|
<this.CancelButton/>
|
||||||
:
|
:
|
||||||
<>
|
<Grid container spacing={1}>
|
||||||
<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>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
<Button variant='contained' color='secondary' onClick={this.handleClickBackButton}>Back</Button>
|
<Button variant='contained' color='secondary' onClick={this.handleClickBackButton}>Back</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</Grid>
|
||||||
}
|
}
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -89,15 +89,15 @@ export default class TradeBox extends Component {
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
The RoboSats staff will examine the statements and evidence provided by the participants.
|
The RoboSats staff will examine the statements and evidence provided. You need to build
|
||||||
It is best if you provide a burner contact method on your statement for the staff to contact you.
|
a complete case, as the staff cannot read the chat. It is best to provide a burner contact
|
||||||
The satoshis in the trade escrow will be sent to the dispute winner, while the dispute
|
method with your statement. The satoshis in the trade escrow will be sent to the dispute winner,
|
||||||
loser will lose the bond.
|
while the dispute loser will lose the bond.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={this.handleClickCloseConfirmDispute} autoFocus>Disagree</Button>
|
<Button onClick={this.handleClickCloseConfirmDispute} autoFocus>Disagree</Button>
|
||||||
<Button onClick={this.handleClickAgreeDisputeButton}> Agree </Button>
|
<Button onClick={this.handleClickAgreeDisputeButton}> Agree and open dispute </Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user