mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-28 23:03:31 +03:00
Add needed LNNode routines and LNpayments status. First full trade demonstrated
This commit is contained in:
parent
a3375df6e5
commit
2523e5ef09
@ -26,7 +26,7 @@ class EUserAdmin(UserAdmin):
|
||||
class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
|
||||
list_display = ('id','type','maker_link','taker_link','status','amount','currency','t0_satoshis','is_disputed','is_fiat_sent','created_at','expires_at', 'buyer_invoice_link','maker_bond_link','taker_bond_link','trade_escrow_link')
|
||||
list_display_links = ('id','type')
|
||||
change_links = ('maker','taker','buyer_invoice','maker_bond','taker_invoice','taker_bond','trade_escrow')
|
||||
change_links = ('maker','taker','buyer_invoice','maker_bond','taker_bond','trade_escrow')
|
||||
list_filter = ('is_disputed','is_fiat_sent','type','currency','status')
|
||||
|
||||
@admin.register(LNPayment)
|
||||
|
@ -83,6 +83,8 @@ class LNNode():
|
||||
'''Checks if hold invoice is locked'''
|
||||
request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
|
||||
response = cls.invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
print('status here')
|
||||
print(response.state)
|
||||
return response.state == 3 # True if hold invoice is accepted.
|
||||
|
||||
@classmethod
|
||||
@ -142,32 +144,38 @@ class LNNode():
|
||||
buyer_invoice['description'] = payreq_decoded.description
|
||||
buyer_invoice['payment_hash'] = payreq_decoded.payment_hash
|
||||
|
||||
|
||||
return buyer_invoice
|
||||
|
||||
@classmethod
|
||||
def pay_invoice(cls, invoice, num_satoshis):
|
||||
'''Sends sats to buyer'''
|
||||
# Needs router subservice
|
||||
# Maybe best to pass order and change status live.
|
||||
|
||||
fee_limit_sat = max(num_satoshis * 0.0002, 10) # 200 ppm or 10 sats
|
||||
|
||||
request = routerrpc.SendPaymentRequest(
|
||||
payment_request=invoice,
|
||||
amt_msat=num_satoshis,
|
||||
fee_limit_sat=fee_limit_sat,
|
||||
timeout_seconds=60)
|
||||
|
||||
for response in cls.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]):
|
||||
if response.status == True:
|
||||
print(response)
|
||||
print(response.status)
|
||||
print(response.grpc_status)
|
||||
if response.status == 1 : # Status 1 'IN_FLIGHT'
|
||||
pass # LIVE UPDATE THE order.lnpayment.status
|
||||
if response.status == 'FAILED':
|
||||
pass # LIVE UPDATE THE order.lnpayment.status
|
||||
if response.status == 2 : # STATUS 'SUCCEEDED'
|
||||
return True
|
||||
# How to catch the errors like:"grpc_message":"invoice is already paid","grpc_status":6}
|
||||
# These are not in the response only printed to commandline
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def double_check_htlc_is_settled(cls, payment_hash):
|
||||
''' Just as it sounds. Better safe than sorry!'''
|
||||
request = invoicesrpc.LookupInvoiceMsg(payment_hash=payment_hash)
|
||||
request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
|
||||
response = cls.invoicesstub.LookupInvoiceV2(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
|
||||
return response.state == 1 # LND states: 0 OPEN, 1 SETTLED, 3 ACCEPTED, GRPC_ERROR status 5 when cancelled/returned
|
||||
|
@ -166,13 +166,16 @@ class Logics():
|
||||
)
|
||||
|
||||
# If the order status is 'Waiting for invoice'. Move forward to 'chat'
|
||||
if order.status == Order.Status.WFI: order.status = Order.Status.CHA
|
||||
if order.status == Order.Status.WFI:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION)
|
||||
|
||||
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
||||
if order.status == Order.Status.WF2:
|
||||
if order.trade_escrow:
|
||||
if order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||
order.status = Order.Status.CHA
|
||||
# If the escrow is lock move to Chat.
|
||||
if order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION)
|
||||
else:
|
||||
order.status = Order.Status.WFE
|
||||
|
||||
@ -312,7 +315,7 @@ class Logics():
|
||||
order.last_satoshis = cls.satoshis_now(order)
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
|
||||
description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond, it will freeze in your wallet. It automatically returns. It will be charged if you cheat or cancel."
|
||||
description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel."
|
||||
|
||||
# Gen hold Invoice
|
||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
@ -368,7 +371,7 @@ class Logics():
|
||||
# If there was no taker_bond object yet, generates one
|
||||
order.last_satoshis = cls.satoshis_now(order)
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
description = f"RoboSats - Taking '{str(order)}' - This is a taker bond, it will freeze in your wallet. It automatically returns. It will be charged if you cheat or cancel."
|
||||
description = f"RoboSats - Taking '{str(order)}' - This is a taker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel."
|
||||
|
||||
# Gen hold Invoice
|
||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
@ -400,7 +403,7 @@ class Logics():
|
||||
if order.status == Order.Status.WF2:
|
||||
order.status = Order.Status.WFI
|
||||
# If status is 'Waiting for invoice' move to Chat
|
||||
elif order.status == Order.Status.WFI:
|
||||
elif order.status == Order.Status.WFE:
|
||||
order.status = Order.Status.CHA
|
||||
order.expires_at = timezone.now() + timedelta(hours=FIAT_EXCHANGE_DURATION)
|
||||
order.save()
|
||||
@ -470,11 +473,17 @@ class Logics():
|
||||
order.taker_bond.status = LNPayment.Status.SETLED
|
||||
order.taker_bond.save()
|
||||
return True
|
||||
|
||||
def return_bond(bond):
|
||||
'''returns a bond'''
|
||||
if LNNode.cancel_return_hold_invoice(bond.payment_hash):
|
||||
bond.status = LNPayment.Status.RETNED
|
||||
return True
|
||||
|
||||
def pay_buyer_invoice(order):
|
||||
''' Pay buyer invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.pay_invoice(order.buyer_invoice.invoice):
|
||||
if LNNode.pay_invoice(order.buyer_invoice.invoice, order.buyer_invoice.num_satoshis):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
@ -498,18 +507,18 @@ 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.
|
||||
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
|
||||
if cls.pay_buyer_invoice(order): ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
|
||||
order.status = Order.Status.PAY
|
||||
order.buyer_invoice.status = LNPayment.Status.PAYING
|
||||
order.buyer_invoice.status = LNPayment.Status.SETLED
|
||||
|
||||
# RETURN THE BONDS
|
||||
LNNode.cancel_return_hold_invoice(order.taker_bond.payment_hash)
|
||||
LNNode.cancel_return_hold_invoice(order.maker_bond.payment_hash)
|
||||
cls.return_bond(order.taker_bond)
|
||||
cls.return_bond(order.maker_bond)
|
||||
else:
|
||||
return False, {'bad_request':'You cannot confirm the fiat payment at this stage'}
|
||||
|
||||
|
@ -34,8 +34,10 @@ class LNPayment(models.Model):
|
||||
RETNED = 3, 'Returned'
|
||||
EXPIRE = 4, 'Expired'
|
||||
VALIDI = 5, 'Valid'
|
||||
FLIGHT = 6, 'On flight'
|
||||
FAILRO = 7, 'Routing failed'
|
||||
FLIGHT = 6, 'In flight'
|
||||
SUCCED = 7, 'Succeeded'
|
||||
FAILRO = 8, 'Routing failed'
|
||||
|
||||
|
||||
# payment use details
|
||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HOLD)
|
||||
@ -44,10 +46,10 @@ class LNPayment(models.Model):
|
||||
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
|
||||
|
||||
# payment info
|
||||
invoice = models.CharField(max_length=1000, unique=True, null=True, default=None, blank=True)
|
||||
invoice = models.CharField(max_length=1200, unique=True, null=True, default=None, blank=True) # Some invoices with lots of routing hints might be long
|
||||
payment_hash = models.CharField(max_length=100, unique=True, null=True, default=None, blank=True)
|
||||
preimage = models.CharField(max_length=64, unique=True, null=True, default=None, blank=True)
|
||||
description = models.CharField(max_length=200, unique=False, null=True, default=None, blank=True)
|
||||
description = models.CharField(max_length=500, unique=False, null=True, default=None, blank=True)
|
||||
num_satoshis = models.PositiveBigIntegerField(validators=[MinValueValidator(MIN_TRADE*BOND_SIZE), MaxValueValidator(MAX_TRADE*(1+BOND_SIZE+FEE))])
|
||||
created_at = models.DateTimeField()
|
||||
expires_at = models.DateTimeField()
|
||||
|
@ -12,6 +12,6 @@ class MakeOrderSerializer(serializers.ModelSerializer):
|
||||
fields = ('type','currency','amount','payment_method','is_explicit','premium','satoshis')
|
||||
|
||||
class UpdateOrderSerializer(serializers.Serializer):
|
||||
invoice = serializers.CharField(max_length=300, allow_null=True, allow_blank=True, default=None)
|
||||
invoice = serializers.CharField(max_length=2000, allow_null=True, allow_blank=True, default=None)
|
||||
action = serializers.ChoiceField(choices=('take','update_invoice','dispute','cancel','confirm','rate'), allow_null=False)
|
||||
rating = serializers.ChoiceField(choices=('1','2','3','4','5'), allow_null=True, allow_blank=True, default=None)
|
@ -137,7 +137,7 @@ class OrderView(viewsets.ViewSet):
|
||||
# If both bonds are locked, participants can see the final trade amount in sats.
|
||||
if order.taker_bond:
|
||||
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
|
||||
# Seller sees the amount he pays
|
||||
# Seller sees the amount he sends
|
||||
if data['is_seller']:
|
||||
data['trade_satoshis'] = order.last_satoshis
|
||||
# Buyer sees the amount he receives
|
||||
@ -227,6 +227,7 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
# 2) If action is 'update invoice'
|
||||
if action == 'update_invoice' and invoice:
|
||||
print('AAAAAAAAAAAAAAAAAAAAAAAAAAAA')
|
||||
valid, context = Logics.update_invoice(order,request.user,invoice)
|
||||
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
@ -284,7 +284,12 @@ export default class OrderPage extends Component {
|
||||
<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">
|
||||
|
@ -70,6 +70,16 @@ export default class TradeBox extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
showBondIsLocked=()=>{
|
||||
return (
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1" align="center">
|
||||
🔒 Your {this.props.data.isMaker ? 'maker' : 'taker'} bond is safely locked
|
||||
</Typography>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
showEscrowQRInvoice=()=>{
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
@ -92,6 +102,7 @@ export default class TradeBox extends Component {
|
||||
color = "secondary"
|
||||
/>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -113,6 +124,7 @@ export default class TradeBox extends Component {
|
||||
Please wait for the taker to confirm his commitment by locking a bond.
|
||||
</Typography>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -138,12 +150,6 @@ export default class TradeBox extends Component {
|
||||
return to you (no action needed).</p>
|
||||
</Typography>
|
||||
</ListItem>
|
||||
<Divider/>
|
||||
<ListItem color="info" align="center">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1" align="center">
|
||||
🔒 Your maker bond is safely locked
|
||||
</Typography>
|
||||
</ListItem>
|
||||
{/* TODO API sends data for a more confortable wait */}
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
@ -159,8 +165,11 @@ export default class TradeBox extends Component {
|
||||
<ListItem>
|
||||
<ListItemText primary="33%" secondary="Premium percentile" />
|
||||
</ListItem>
|
||||
<Divider/>
|
||||
|
||||
</List>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -225,6 +234,7 @@ export default class TradeBox extends Component {
|
||||
<Grid item xs={12} align="center">
|
||||
<Button variant='contained' color='primary' onClick={this.handleClickSubmitInvoiceButton}>Submit</Button>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -245,6 +255,7 @@ export default class TradeBox extends Component {
|
||||
you will get your bond back automatically.</p>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -266,6 +277,7 @@ export default class TradeBox extends Component {
|
||||
you will get back the trade collateral and your bond automatically.</p>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -322,22 +334,18 @@ handleRatingChange=(e)=>{
|
||||
// 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>
|
||||
<Button color="inherit" onClick={this.handleClickOpenDisputeButton}>Open Dispute</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
@ -368,19 +376,11 @@ handleRatingChange=(e)=>{
|
||||
{receivedFiatButton ? this.showFiatReceivedButton() : ""}
|
||||
{openDisputeButton ? this.showOpenDisputeButton() : ""}
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// showFiatReceivedButton(){
|
||||
|
||||
// }
|
||||
|
||||
// showOpenDisputeButton(){
|
||||
|
||||
// }
|
||||
|
||||
showRateSelect(){
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
|
Loading…
Reference in New Issue
Block a user