From b934534e1e4991b7e3b7329c96064cf264101f3e Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sun, 23 Jan 2022 11:02:25 -0800 Subject: [PATCH] Implement collaborative cancellation --- api/logics.py | 40 ++++- api/models.py | 3 +- api/views.py | 19 ++- frontend/src/components/OrderPage.js | 211 ++++++++++++++++----------- frontend/src/components/TradeBox.js | 10 +- 5 files changed, 180 insertions(+), 103 deletions(-) diff --git a/api/logics.py b/api/logics.py index 2cb803e0..c553ab4e 100644 --- a/api/logics.py +++ b/api/logics.py @@ -421,14 +421,46 @@ class Logics(): return True, None # 5) When trade collateral has been posted (after escrow) - '''Always goes to cancelled status. Collaboration is needed. - When a user asks for cancel, 'order.is_pending_cancel' goes True. + '''Always goes to CCA status. Collaboration is needed. + 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. - 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: 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): order.status = Order.Status.PUB order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) diff --git a/api/models.py b/api/models.py index 942d811a..e82f0226 100644 --- a/api/models.py +++ b/api/models.py @@ -142,7 +142,8 @@ class Order(models.Model): # 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 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) # in dispute diff --git a/api/views.py b/api/views.py index 6141c20d..3ad3449e 100644 --- a/api/views.py +++ b/api/views.py @@ -189,7 +189,6 @@ class OrderView(viewsets.ViewSet): # 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): - # If the two bonds are locked, reply with an ESCROW hold invoice. if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED: 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) # 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 order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED: - # add whether a collaborative cancel is pending - data['pending_cancel'] = order.is_pending_cancel + print('AAABBAAAAAAAAAAAAAAAA') + # 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 - 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 if data['is_maker']: diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index d03553d6..fd198892 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -9,9 +9,7 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime'; import NumbersIcon from '@mui/icons-material/Numbers'; import PriceChangeIcon from '@mui/icons-material/PriceChange'; import PaymentsIcon from '@mui/icons-material/Payments'; -import MoneyIcon from '@mui/icons-material/Money'; import ArticleIcon from '@mui/icons-material/Article'; -import ContentCopy from "@mui/icons-material/ContentCopy"; function getCookie(name) { let cookieValue = null; @@ -45,88 +43,45 @@ export default class OrderPage extends Component { total_secs_exp: 300, loading: true, openCancel: false, + openCollaborativeCancel: false, }; this.orderId = this.props.match.params.orderId; this.getCurrencyDict(); this.getOrderDetails(); - // Refresh delais according to Order status + // Refresh delays according to Order status this.statusToDelay = { - "0": 3000, //'Waiting for maker bond' - "1": 30000, //'Public' - "2": 9999999, //'Deleted' - "3": 3000, //'Waiting for taker bond' - "4": 9999999, //'Cancelled' - "5": 999999, //'Expired' - "6": 3000, //'Waiting for trade collateral and buyer invoice' - "7": 3000, //'Waiting only for seller trade collateral' - "8": 10000, //'Waiting only for buyer invoice' - "9": 10000, //'Sending fiat - In chatroom' - "10": 15000, //'Fiat sent - In chatroom' - "11": 60000, //'In dispute' - "12": 9999999,//'Collaboratively cancelled' - "13": 3000, //'Sending satoshis to buyer' - "14": 9999999,//'Sucessful trade' - "15": 10000, //'Failed lightning network routing' - "16": 9999999,//'Maker lost dispute' - "17": 9999999,//'Taker lost dispute' + "0": 3000, //'Waiting for maker bond' + "1": 30000, //'Public' + "2": 9999999, //'Deleted' + "3": 3000, //'Waiting for taker bond' + "4": 9999999, //'Cancelled' + "5": 999999, //'Expired' + "6": 3000, //'Waiting for trade collateral and buyer invoice' + "7": 3000, //'Waiting only for seller trade collateral' + "8": 10000, //'Waiting only for buyer invoice' + "9": 10000, //'Sending fiat - In chatroom' + "10": 15000, //'Fiat sent - In chatroom' + "11": 60000, //'In dispute' + "12": 9999999, //'Collaboratively cancelled' + "13": 3000, //'Sending satoshis to buyer' + "14": 9999999, //'Sucessful trade' + "15": 10000, //'Failed lightning network routing' + "16": 9999999, //'Maker 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)=>{ - console.log(newStateVars) var otherStateVars = { loading: false, delay: this.setDelay(newStateVars.status), currencyCode: this.getCurrencyCode(newStateVars.currency), }; - console.log(otherStateVars) var completeStateVars = Object.assign({}, newStateVars, otherStateVars); - console.log(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() { this.setState(null) 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( + + + {"Collaborative cancel the order?"} + + + + The trade escrow has been posted. The order can be cancelled only if both, maker and + taker, agree to cancel. + + + + + + + + ) + } + CancelButton = () => { // If maker and Waiting for Bond. Or if taker and Waiting for bond. @@ -289,16 +291,26 @@ export default class OrderPage extends Component { )} // If the order does not yet have an escrow deposited. Show dialog // 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( - - - - +
+ + + + +
)} - // 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( + + + + + )} + // If none of the above do not return a cancel button. return(null) } @@ -413,24 +425,49 @@ export default class OrderPage extends Component { : null} + + {/* If the counterparty asked for collaborative cancel */} + {this.state.pending_cancel ? + <> + + + + {this.state.is_maker ? this.state.taker_nick : this.state.maker_nick} is asking for a collaborative cancel + + + + : null} + + {/* If the user has asked for a collaborative cancel */} + {this.state.asked_for_cancel ? + <> + + + + You asked for a collaborative cancellation + + + + : null} - - {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */} - {this.state.is_participant ? - - : - <> - - + + + {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */} + {this.state.is_participant ? + + : + + + + + + + - - - - - } - + } + ) } diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index 1d7b90ac..b4b37f39 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -89,15 +89,15 @@ export default class TradeBox extends Component { - The RoboSats staff will examine the statements and evidence provided by the participants. - It is best if you provide a burner contact method on your statement for the staff to contact you. - The satoshis in the trade escrow will be sent to the dispute winner, while the dispute - loser will lose the bond. + The RoboSats staff will examine the statements and evidence provided. You need to build + a complete case, as the staff cannot read the chat. It is best to provide a burner contact + method with your statement. The satoshis in the trade escrow will be sent to the dispute winner, + while the dispute loser will lose the bond. - + )