Work frontend trade pipeline

This commit is contained in:
Reckless_Satoshi 2022-01-09 12:05:19 -08:00
parent fb846c91d8
commit 8e5233267f
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
7 changed files with 166 additions and 57 deletions

View File

@ -1,3 +1,6 @@
# import codecs, grpc, os
# import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
from datetime import timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
@ -13,8 +16,15 @@ class LNNode():
Place holder functions to interact with Lightning Node Place holder functions to interact with Lightning Node
''' '''
def gen_hodl_invoice(num_satoshis, description, expiry): # macaroon = codecs.encode(open('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon', 'rb').read(), 'hex')
'''Generates hodl invoice to publish an order''' # 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 # TODO
invoice = ''.join(random.choices(string.ascii_uppercase + string.digits, k=80)) #FIX 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 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 return invoice, payment_hash, expires_at
def validate_hodl_invoice_locked(payment_hash): def validate_hold_invoice_locked(payment_hash):
'''Generates hodl invoice to publish an order''' '''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 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''' '''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 valid = True
context = None context = None
description = 'Placeholder desc' # TODO decrypt from LN invoice description = 'Placeholder desc' # TODO decrypt from LN invoice
@ -40,11 +84,11 @@ class LNNode():
'''Sends sats to buyer, or cancelinvoices''' '''Sends sats to buyer, or cancelinvoices'''
return True return True
def settle_hodl_htlcs(payment_hash): def settle_hold_htlcs(payment_hash):
'''Charges a LN hodl invoice''' '''Charges a LN hold invoice'''
return True return True
def return_hodl_htlcs(payment_hash): def return_hold_htlcs(payment_hash):
'''Returns sats''' '''Returns sats'''
return True return True

View File

@ -188,7 +188,7 @@ class Logics():
return False, {'bad_request':'You cannot cancel this order'} return False, {'bad_request':'You cannot cancel this order'}
@classmethod @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 # Do not gen and cancel if order is more than 5 minutes old
if order.expires_at < timezone.now(): if order.expires_at < timezone.now():
@ -206,12 +206,12 @@ class Logics():
bond_satoshis = int(order.last_satoshis * BOND_SIZE) 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.' description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat.'
# Gen HODL Invoice # Gen hold Invoice
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(bond_satoshis, description, BOND_EXPIRY*3600) invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
order.maker_bond = LNPayment.objects.create( order.maker_bond = LNPayment.objects.create(
concept = LNPayment.Concepts.MAKEBOND, concept = LNPayment.Concepts.MAKEBOND,
type = LNPayment.Types.HODL, type = LNPayment.Types.hold,
sender = user, sender = user,
receiver = User.objects.get(username=ESCROW_USERNAME), receiver = User.objects.get(username=ESCROW_USERNAME),
invoice = invoice, invoice = invoice,
@ -225,7 +225,7 @@ class Logics():
return True, {'bond_invoice':invoice,'bond_satoshis':bond_satoshis} return True, {'bond_invoice':invoice,'bond_satoshis':bond_satoshis}
@classmethod @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 # Do not gen and cancel if a taker invoice is there and older than X minutes and unpaid still
if order.taker_bond: if order.taker_bond:
@ -245,12 +245,12 @@ class Logics():
bond_satoshis = int(order.last_satoshis * BOND_SIZE) 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.' description = f'RoboSats - Taking {str(order)} - This bond will return to you if you do not cheat.'
# Gen HODL Invoice # Gen hold Invoice
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(bond_satoshis, description, BOND_EXPIRY*3600) invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
order.taker_bond = LNPayment.objects.create( order.taker_bond = LNPayment.objects.create(
concept = LNPayment.Concepts.TAKEBOND, concept = LNPayment.Concepts.TAKEBOND,
type = LNPayment.Types.HODL, type = LNPayment.Types.hold,
sender = user, sender = user,
receiver = User.objects.get(username=ESCROW_USERNAME), receiver = User.objects.get(username=ESCROW_USERNAME),
invoice = invoice, invoice = invoice,
@ -267,7 +267,7 @@ class Logics():
return True, {'bond_invoice':invoice,'bond_satoshis': bond_satoshis} return True, {'bond_invoice':invoice,'bond_satoshis': bond_satoshis}
@classmethod @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 # Do not generate and cancel if an invoice is there and older than X minutes and unpaid still
if order.trade_escrow: if order.trade_escrow:
# Check if status is INVGEN and still not expired # 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) 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.' 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 # Gen hold Invoice
invoice, payment_hash, expires_at = LNNode.gen_hodl_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600) invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
order.trade_escrow = LNPayment.objects.create( order.trade_escrow = LNPayment.objects.create(
concept = LNPayment.Concepts.TRESCROW, concept = LNPayment.Concepts.TRESCROW,
type = LNPayment.Types.HODL, type = LNPayment.Types.hold,
sender = user, sender = user,
receiver = User.objects.get(username=ESCROW_USERNAME), receiver = User.objects.get(username=ESCROW_USERNAME),
invoice = invoice, invoice = invoice,
@ -307,7 +307,7 @@ class Logics():
''' Settles the trade escrow HTLC''' ''' Settles the trade escrow HTLC'''
# TODO ERROR HANDLING # 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 return valid
def pay_buyer_invoice(order): 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.'} 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 # 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.'} return False, {'bad_request':'Woah, something broke badly. Report in the public channels, or open a Github Issue.'}
# Double check the escrow is settled. # Double check the escrow is settled.

View File

@ -18,8 +18,8 @@ BOND_SIZE = float(config('BOND_SIZE'))
class LNPayment(models.Model): class LNPayment(models.Model):
class Types(models.IntegerChoices): class Types(models.IntegerChoices):
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hodl) NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hold)
HODL = 1, 'Hodl invoice' hold = 1, 'hold invoice'
class Concepts(models.IntegerChoices): class Concepts(models.IntegerChoices):
MAKEBOND = 0, 'Maker bond' MAKEBOND = 0, 'Maker bond'
@ -38,7 +38,7 @@ class LNPayment(models.Model):
FAILRO = 7, 'Failed routing' FAILRO = 7, 'Failed routing'
# payment use details # 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) concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN) status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
routing_retries = models.PositiveSmallIntegerField(null=False, default=0) 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)]}') 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) @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) to_delete = (instance.maker_bond, instance.buyer_invoice, instance.taker_bond, instance.trade_escrow)
for htlc in to_delete: for htlc in to_delete:
@ -193,7 +193,7 @@ class MarketTick(models.Model):
It is checked against current CEX price for useful It is checked against current CEX price for useful
insight on the historical premium of Non-KYC BTC 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 maker and taker are commited with bonds (contract
is finished and cancellation has a cost) is finished and cancellation has a cost)
''' '''

View File

@ -136,17 +136,17 @@ class OrderView(viewsets.ViewSet):
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 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']: 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: if valid:
data = {**data, **context} data = {**data, **context}
else: else:
return Response(context, status.HTTP_400_BAD_REQUEST) 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']: 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: if valid:
data = {**data, **context} data = {**data, **context}
else: else:
@ -155,9 +155,9 @@ 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 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: 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: if valid:
data = {**data, **context} data = {**data, **context}
else: else:
@ -180,9 +180,6 @@ class OrderView(viewsets.ViewSet):
# add whether a collaborative cancel is pending # add whether a collaborative cancel is pending
data['pending_cancel'] = order.is_pending_cancel 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) return Response(data, status.HTTP_200_OK)

View File

@ -16,7 +16,6 @@ export default class BookPage extends Component {
this.state.currencyCode = this.getCurrencyCode(this.state.currency) this.state.currencyCode = this.getCurrencyCode(this.state.currency)
} }
// Show message to be the first one to make an order
getOrderDetails(type,currency) { getOrderDetails(type,currency) {
fetch('/api/book' + '?currency=' + currency + "&type=" + type) fetch('/api/book' + '?currency=' + currency + "&type=" + type)
.then((response) => response.json()) .then((response) => response.json())

View File

@ -283,9 +283,8 @@ export default class OrderPage extends Component {
) )
} }
orderDetailsPage (){
render (){ return(
return (
this.state.badRequest ? this.state.badRequest ?
<div align='center'> <div align='center'>
<Typography component="subtitle2" variant="subtitle2" color="secondary" > <Typography component="subtitle2" variant="subtitle2" color="secondary" >
@ -307,6 +306,13 @@ export default class OrderPage extends Component {
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
{this.orderBox()} {this.orderBox()}
</Grid>) </Grid>)
)
}
render (){
return (
// Only so nothing shows while requesting the first batch of data
(this.state.statusCode == null & this.state.badRequest == null) ? "" : this.orderDetailsPage()
); );
} }
} }

View File

@ -58,7 +58,7 @@ export default class TradeBox extends Component {
size="small" size="small"
defaultValue={this.props.data.bondInvoice} defaultValue={this.props.data.bondInvoice}
disabled="true" 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." It will be charged if the order is cancelled or you lose a dispute."
color = "secondary" color = "secondary"
/> />
@ -66,6 +66,7 @@ export default class TradeBox extends Component {
</Grid> </Grid>
); );
} }
showEscrowQRInvoice=()=>{ showEscrowQRInvoice=()=>{
return ( return (
<Grid container spacing={1}> <Grid container spacing={1}>
@ -84,7 +85,7 @@ export default class TradeBox extends Component {
size="small" size="small"
defaultValue={this.props.data.escrowInvoice} defaultValue={this.props.data.escrowInvoice}
disabled="true" 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" color = "secondary"
/> />
</Grid> </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=()=>{ handleClickSubmitInvoiceButton=()=>{
const requestOptions = { const requestOptions = {
@ -215,7 +216,6 @@ export default class TradeBox extends Component {
} }
showWaitingForEscrow(){ showWaitingForEscrow(){
return( return(
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
@ -236,7 +236,6 @@ export default class TradeBox extends Component {
} }
showWaitingForBuyerInvoice(){ showWaitingForBuyerInvoice(){
return( return(
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
@ -257,13 +256,24 @@ export default class TradeBox extends Component {
) )
} }
handleClickFiatConfirmButton=()=>{ handleClickConfirmButton=()=>{
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({ body: JSON.stringify({
'action':'confirm', 'action': "confirm",
'invoice': this.state.invoice, }),
};
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) 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)); .then((data) => (this.props.data = data));
} }
showFiatSentButton(){ showFiatSentButton(){
return( return(
<Grid container spacing={1}> <Grid container spacing={1}>
<Grid item xs={12} align="center"> <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>
</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.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""}
{this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""} {this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""}
{/* In Chatroom */} {/* In Chatroom - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat() & this.showFiatSentButton() : ""} {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() : ""} {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() & this.showOpenDisputeButton() : ""} {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() & this.showFiatReceivedButton() & this.showOpenDisputeButton(): ""} {this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""}
{/* Trade Finished */} {/* Trade Finished */}
{this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""} {this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""}