mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-29 15:23:34 +03:00
Work frontend trade pipeline
This commit is contained in:
parent
fb846c91d8
commit
8e5233267f
@ -1,3 +1,6 @@
|
||||
# import codecs, grpc, os
|
||||
# import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
|
||||
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
@ -13,8 +16,15 @@ class LNNode():
|
||||
Place holder functions to interact with Lightning Node
|
||||
'''
|
||||
|
||||
def gen_hodl_invoice(num_satoshis, description, expiry):
|
||||
'''Generates hodl invoice to publish an order'''
|
||||
# macaroon = codecs.encode(open('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon', 'rb').read(), 'hex')
|
||||
# os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
|
||||
# cert = open('LND_DIR/tls.cert', 'rb').read()
|
||||
# ssl_creds = grpc.ssl_channel_credentials(cert)
|
||||
# channel = grpc.secure_channel('localhost:10009', ssl_creds)
|
||||
# stub = lightningstub.LightningStub(channel)
|
||||
|
||||
def gen_hold_invoice(num_satoshis, description, expiry):
|
||||
'''Generates hold invoice to publish an order'''
|
||||
# TODO
|
||||
invoice = ''.join(random.choices(string.ascii_uppercase + string.digits, k=80)) #FIX
|
||||
payment_hash = ''.join(random.choices(string.ascii_uppercase + string.digits, k=40)) #FIX
|
||||
@ -22,12 +32,46 @@ class LNNode():
|
||||
|
||||
return invoice, payment_hash, expires_at
|
||||
|
||||
def validate_hodl_invoice_locked(payment_hash):
|
||||
'''Generates hodl invoice to publish an order'''
|
||||
def validate_hold_invoice_locked(payment_hash):
|
||||
'''Checks if hodl invoice is locked'''
|
||||
|
||||
# request = ln.InvoiceSubscription()
|
||||
# When invoice is settled, return true. If time expires, return False.
|
||||
# for invoice in stub.SubscribeInvoices(request):
|
||||
# print(invoice)
|
||||
|
||||
return True
|
||||
|
||||
def validate_ln_invoice(invoice, num_satoshis): # num_satoshis
|
||||
def validate_ln_invoice(invoice, num_satoshis):
|
||||
'''Checks if the submited LN invoice is as expected'''
|
||||
|
||||
# request = lnrpc.PayReqString(pay_req=invoice)
|
||||
# response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)])
|
||||
|
||||
# # {
|
||||
# # "destination": <string>,
|
||||
# # "payment_hash": <string>,
|
||||
# # "num_satoshis": <int64>,
|
||||
# # "timestamp": <int64>,
|
||||
# # "expiry": <int64>,
|
||||
# # "description": <string>,
|
||||
# # "description_hash": <string>,
|
||||
# # "fallback_addr": <string>,
|
||||
# # "cltv_expiry": <int64>,
|
||||
# # "route_hints": <array RouteHint>,
|
||||
# # "payment_addr": <bytes>,
|
||||
# # "num_msat": <int64>,
|
||||
# # "features": <array FeaturesEntry>,
|
||||
# # }
|
||||
|
||||
# if not response['num_satoshis'] == num_satoshis:
|
||||
# return False, {'bad_invoice':f'The invoice provided is not for {num_satoshis}. '}, None, None, None
|
||||
# description = response['description']
|
||||
# payment_hash = response['payment_hash']
|
||||
# expires_at = timezone(response['expiry'])
|
||||
# if payment_hash and expires_at > timezone.now():
|
||||
# return True, None, description, payment_hash, expires_at
|
||||
|
||||
valid = True
|
||||
context = None
|
||||
description = 'Placeholder desc' # TODO decrypt from LN invoice
|
||||
@ -40,11 +84,11 @@ class LNNode():
|
||||
'''Sends sats to buyer, or cancelinvoices'''
|
||||
return True
|
||||
|
||||
def settle_hodl_htlcs(payment_hash):
|
||||
'''Charges a LN hodl invoice'''
|
||||
def settle_hold_htlcs(payment_hash):
|
||||
'''Charges a LN hold invoice'''
|
||||
return True
|
||||
|
||||
def return_hodl_htlcs(payment_hash):
|
||||
def return_hold_htlcs(payment_hash):
|
||||
'''Returns sats'''
|
||||
return True
|
||||
|
||||
|
@ -188,7 +188,7 @@ class Logics():
|
||||
return False, {'bad_request':'You cannot cancel this order'}
|
||||
|
||||
@classmethod
|
||||
def gen_maker_hodl_invoice(cls, order, user):
|
||||
def gen_maker_hold_invoice(cls, order, user):
|
||||
|
||||
# Do not gen and cancel if order is more than 5 minutes old
|
||||
if order.expires_at < timezone.now():
|
||||
@ -206,12 +206,12 @@ class Logics():
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat.'
|
||||
|
||||
# Gen HODL Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
# Gen hold Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
|
||||
order.maker_bond = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.MAKEBOND,
|
||||
type = LNPayment.Types.HODL,
|
||||
type = LNPayment.Types.hold,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
@ -225,7 +225,7 @@ class Logics():
|
||||
return True, {'bond_invoice':invoice,'bond_satoshis':bond_satoshis}
|
||||
|
||||
@classmethod
|
||||
def gen_taker_hodl_invoice(cls, order, user):
|
||||
def gen_taker_hold_invoice(cls, order, user):
|
||||
|
||||
# Do not gen and cancel if a taker invoice is there and older than X minutes and unpaid still
|
||||
if order.taker_bond:
|
||||
@ -245,12 +245,12 @@ class Logics():
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
description = f'RoboSats - Taking {str(order)} - This bond will return to you if you do not cheat.'
|
||||
|
||||
# Gen HODL Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
# Gen hold Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
|
||||
order.taker_bond = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.TAKEBOND,
|
||||
type = LNPayment.Types.HODL,
|
||||
type = LNPayment.Types.hold,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
@ -267,7 +267,7 @@ class Logics():
|
||||
return True, {'bond_invoice':invoice,'bond_satoshis': bond_satoshis}
|
||||
|
||||
@classmethod
|
||||
def gen_escrow_hodl_invoice(cls, order, user):
|
||||
def gen_escrow_hold_invoice(cls, order, user):
|
||||
# Do not generate and cancel if an invoice is there and older than X minutes and unpaid still
|
||||
if order.trade_escrow:
|
||||
# Check if status is INVGEN and still not expired
|
||||
@ -285,12 +285,12 @@ class Logics():
|
||||
escrow_satoshis = order.last_satoshis # Trade sats amount was fixed at the time of taker bond generation (order.last_satoshis)
|
||||
description = f'RoboSats - Escrow amount for {str(order)} - This escrow will be released to the buyer once you confirm you received the fiat.'
|
||||
|
||||
# Gen HODL Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
||||
# Gen hold Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
||||
|
||||
order.trade_escrow = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.TRESCROW,
|
||||
type = LNPayment.Types.HODL,
|
||||
type = LNPayment.Types.hold,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
@ -307,7 +307,7 @@ class Logics():
|
||||
''' Settles the trade escrow HTLC'''
|
||||
# TODO ERROR HANDLING
|
||||
|
||||
valid = LNNode.settle_hodl_htlcs(order.trade_escrow.payment_hash)
|
||||
valid = LNNode.settle_hold_htlcs(order.trade_escrow.payment_hash)
|
||||
return valid
|
||||
|
||||
def pay_buyer_invoice(order):
|
||||
@ -338,7 +338,7 @@ class Logics():
|
||||
return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'}
|
||||
|
||||
# Make sure the trade escrow is at least as big as the buyer invoice
|
||||
if order.trade_escrow.num_satoshis <= order.buyer_invoice.num_satoshis:
|
||||
if order.trade_escrow.num_satoshis > order.buyer_invoice.num_satoshis:
|
||||
return False, {'bad_request':'Woah, something broke badly. Report in the public channels, or open a Github Issue.'}
|
||||
|
||||
# Double check the escrow is settled.
|
||||
|
@ -18,8 +18,8 @@ BOND_SIZE = float(config('BOND_SIZE'))
|
||||
class LNPayment(models.Model):
|
||||
|
||||
class Types(models.IntegerChoices):
|
||||
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hodl)
|
||||
HODL = 1, 'Hodl invoice'
|
||||
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hold)
|
||||
hold = 1, 'hold invoice'
|
||||
|
||||
class Concepts(models.IntegerChoices):
|
||||
MAKEBOND = 0, 'Maker bond'
|
||||
@ -38,7 +38,7 @@ class LNPayment(models.Model):
|
||||
FAILRO = 7, 'Failed routing'
|
||||
|
||||
# payment use details
|
||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HODL)
|
||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.hold)
|
||||
concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
|
||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
|
||||
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
|
||||
@ -133,7 +133,7 @@ class Order(models.Model):
|
||||
return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency_dict[str(self.currency)]}')
|
||||
|
||||
@receiver(pre_delete, sender=Order)
|
||||
def delelete_HTLCs_at_order_deletion(sender, instance, **kwargs):
|
||||
def delete_HTLCs_at_order_deletion(sender, instance, **kwargs):
|
||||
to_delete = (instance.maker_bond, instance.buyer_invoice, instance.taker_bond, instance.trade_escrow)
|
||||
|
||||
for htlc in to_delete:
|
||||
@ -193,7 +193,7 @@ class MarketTick(models.Model):
|
||||
It is checked against current CEX price for useful
|
||||
insight on the historical premium of Non-KYC BTC
|
||||
|
||||
Price is set when both taker bond is locked. Both
|
||||
Price is set when taker bond is locked. Both
|
||||
maker and taker are commited with bonds (contract
|
||||
is finished and cancellation has a cost)
|
||||
'''
|
||||
|
15
api/views.py
15
api/views.py
@ -136,17 +136,17 @@ class OrderView(viewsets.ViewSet):
|
||||
elif data['is_buyer']:
|
||||
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 HODL 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']:
|
||||
valid, context = Logics.gen_maker_hodl_invoice(order, request.user)
|
||||
valid, context = Logics.gen_maker_hold_invoice(order, request.user)
|
||||
if valid:
|
||||
data = {**data, **context}
|
||||
else:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 6) If status is 'waiting for taker bond' and user is TAKER, reply with a TAKER HODL invoice.
|
||||
# 6) If status is 'waiting for taker bond' and user is TAKER, reply with a TAKER hold invoice.
|
||||
elif order.status == Order.Status.TAK and data['is_taker']:
|
||||
valid, context = Logics.gen_taker_hodl_invoice(order, request.user)
|
||||
valid, context = Logics.gen_taker_hold_invoice(order, request.user)
|
||||
if valid:
|
||||
data = {**data, **context}
|
||||
else:
|
||||
@ -155,9 +155,9 @@ 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 HODL 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:
|
||||
valid, context = Logics.gen_escrow_hodl_invoice(order, request.user)
|
||||
valid, context = Logics.gen_escrow_hold_invoice(order, request.user)
|
||||
if valid:
|
||||
data = {**data, **context}
|
||||
else:
|
||||
@ -180,9 +180,6 @@ class OrderView(viewsets.ViewSet):
|
||||
# add whether a collaborative cancel is pending
|
||||
data['pending_cancel'] = order.is_pending_cancel
|
||||
|
||||
# 9) if buyer confirmed FIAT SENT
|
||||
elif order.status == Order.Status.FSE:
|
||||
data['buyer_confirmed']
|
||||
|
||||
return Response(data, status.HTTP_200_OK)
|
||||
|
||||
|
@ -16,7 +16,6 @@ export default class BookPage extends Component {
|
||||
this.state.currencyCode = this.getCurrencyCode(this.state.currency)
|
||||
}
|
||||
|
||||
// Show message to be the first one to make an order
|
||||
getOrderDetails(type,currency) {
|
||||
fetch('/api/book' + '?currency=' + currency + "&type=" + type)
|
||||
.then((response) => response.json())
|
||||
|
@ -283,8 +283,7 @@ export default class OrderPage extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
render (){
|
||||
orderDetailsPage (){
|
||||
return(
|
||||
this.state.badRequest ?
|
||||
<div align='center'>
|
||||
@ -307,6 +306,13 @@ export default class OrderPage extends Component {
|
||||
<Grid item xs={12} align="center">
|
||||
{this.orderBox()}
|
||||
</Grid>)
|
||||
)
|
||||
}
|
||||
|
||||
render (){
|
||||
return (
|
||||
// Only so nothing shows while requesting the first batch of data
|
||||
(this.state.statusCode == null & this.state.badRequest == null) ? "" : this.orderDetailsPage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export default class TradeBox extends Component {
|
||||
size="small"
|
||||
defaultValue={this.props.data.bondInvoice}
|
||||
disabled="true"
|
||||
helperText="This is a HODL LN invoice. It will not be charged if the order succeeds or expires.
|
||||
helperText="This is a hold invoice. It will not be charged if the order succeeds or expires.
|
||||
It will be charged if the order is cancelled or you lose a dispute."
|
||||
color = "secondary"
|
||||
/>
|
||||
@ -66,6 +66,7 @@ export default class TradeBox extends Component {
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
showEscrowQRInvoice=()=>{
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
@ -84,7 +85,7 @@ export default class TradeBox extends Component {
|
||||
size="small"
|
||||
defaultValue={this.props.data.escrowInvoice}
|
||||
disabled="true"
|
||||
helperText="This is a HODL LN invoice. It will be charged once the buyer confirms he sent the fiat."
|
||||
helperText="This is a hold LN invoice. It will be charged once the buyer confirms he sent the fiat."
|
||||
color = "secondary"
|
||||
/>
|
||||
</Grid>
|
||||
@ -162,7 +163,7 @@ export default class TradeBox extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
// Fix this, clunky because it takes time. this.props.data does not refresh until next refresh of OrderPage.
|
||||
// Fix this. It's clunky because it takes time. this.props.data does not refresh until next refresh of OrderPage.
|
||||
|
||||
handleClickSubmitInvoiceButton=()=>{
|
||||
const requestOptions = {
|
||||
@ -215,7 +216,6 @@ export default class TradeBox extends Component {
|
||||
}
|
||||
|
||||
showWaitingForEscrow(){
|
||||
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
@ -236,7 +236,6 @@ export default class TradeBox extends Component {
|
||||
}
|
||||
|
||||
showWaitingForBuyerInvoice(){
|
||||
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
@ -257,13 +256,24 @@ export default class TradeBox extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
handleClickFiatConfirmButton=()=>{
|
||||
handleClickConfirmButton=()=>{
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
body: JSON.stringify({
|
||||
'action':'confirm',
|
||||
'invoice': this.state.invoice,
|
||||
'action': "confirm",
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.props.data = data));
|
||||
}
|
||||
handleClickOpenDisputeButton=()=>{
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
body: JSON.stringify({
|
||||
'action': "dispute",
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
@ -271,12 +281,65 @@ export default class TradeBox extends Component {
|
||||
.then((data) => (this.props.data = data));
|
||||
}
|
||||
|
||||
|
||||
showFiatSentButton(){
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button variant='contained' color='primary' onClick={this.handleClickFiatConfirmButton}>Confirm {this.props.data.currencyCode} was sent. </Button>
|
||||
<Button defaultValue="confirm" variant='contained' color='primary' onClick={this.handleClickConfirmButton}>Confirm {this.props.data.currencyCode} sent</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
showFiatReceivedButton(){
|
||||
// TODO, show alert and ask for double confirmation (Have you check you received the fiat? Confirming fiat received settles the trade.)
|
||||
// Ask for double confirmation.
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button defaultValue="confirm" variant='contained' color='primary' onClick={this.handleClickConfirmButton}>Confirm {this.props.data.currencyCode} received</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
showOpenDisputeButton(){
|
||||
// TODO, show alert about how opening a dispute might involve giving away personal data and might mean losing the bond. Ask for double confirmation.
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button defaultValue="dispute" variant='contained' onClick={this.handleClickOpenDisputeButton}>Open Dispute</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
showChat(sendFiatButton, receivedFiatButton, openDisputeButton){
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<b>Chatting with {this.props.data.isMaker ? this.props.data.takerNick : this.props.data.makerNick}</b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="left">
|
||||
{this.props.data.isSeller ?
|
||||
<Typography component="body2" variant="body2">
|
||||
Say hi to your peer robot! Be helpful and concise. Let him know how to send you {this.props.data.currencyCode}.
|
||||
</Typography>
|
||||
:
|
||||
<Typography component="body2" variant="body2">
|
||||
Say hi to your peer robot! Ask for payment details and click 'Confirm {this.props.data.currencyCode} sent' as soon as you send the payment.
|
||||
</Typography>
|
||||
}
|
||||
</Grid>
|
||||
<Grid item xs={12} style={{ width:330, height:360}}>
|
||||
CHAT PLACEHOLDER
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
{sendFiatButton ? this.showFiatSentButton() : ""}
|
||||
{receivedFiatButton ? this.showFiatReceivedButton() : ""}
|
||||
{openDisputeButton ? this.showOpenDisputeButton() : ""}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
@ -316,11 +379,11 @@ export default class TradeBox extends Component {
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""}
|
||||
|
||||
{/* In Chatroom */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat() & this.showFiatSentButton() : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode ==9 ? this.showChat() : ""}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat() & this.showOpenDisputeButton() : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat() & this.showFiatReceivedButton() & this.showOpenDisputeButton(): ""}
|
||||
{/* In Chatroom - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat(true,false,true) : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 9 ? this.showChat(false,false,true) : ""}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat(false,false,true) : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""}
|
||||
|
||||
{/* Trade Finished */}
|
||||
{this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""}
|
||||
|
Loading…
Reference in New Issue
Block a user