diff --git a/.env-sample b/.env-sample index f5612fb4..3eb14fbb 100644 --- a/.env-sample +++ b/.env-sample @@ -9,9 +9,12 @@ REDIS_URL='' # List of market price public APIs. If the currency is available in more than 1 API, will use median price. MARKET_PRICE_APIS = https://blockchain.info/ticker, https://api.yadio.io/exrates/BTC -# Host e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion +# Host e.g. robosats.com HOST_NAME = '' +# e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion +ONION_LOCATION = '' + # Trade fee in percentage % FEE = 0.002 # Bond size in percentage % diff --git a/api/admin.py b/api/admin.py index 8507a508..db50098f 100644 --- a/api/admin.py +++ b/api/admin.py @@ -31,8 +31,8 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): @admin.register(LNPayment) class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): - list_display = ('id','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link') - list_display_links = ('id','concept') + list_display = ('id','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link','order_made','order_taken','order_escrow','order_paid') + list_display_links = ('id','concept','order_made','order_taken','order_escrow','order_paid') change_links = ('sender','receiver') list_filter = ('type','concept','status') diff --git a/api/logics.py b/api/logics.py index a47ee882..85ff5de5 100644 --- a/api/logics.py +++ b/api/logics.py @@ -446,7 +446,9 @@ class Logics(): @classmethod def is_maker_bond_locked(cls, order): - if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash): + if order.maker_bond.status == LNPayment.Status.LOCKED: + return True + elif LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash): order.maker_bond.status = LNPayment.Status.LOCKED order.maker_bond.save() cls.publish_order(order) @@ -524,7 +526,7 @@ class Logics(): def is_taker_bond_locked(cls, order): if order.taker_bond.status == LNPayment.Status.LOCKED: return True - if LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash): + elif LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash): cls.finalize_contract(order) return True return False @@ -574,20 +576,25 @@ class Logics(): order.save() return True, {'bond_invoice': hold_payment['invoice'], 'bond_satoshis': bond_satoshis} + def trade_escrow_received(order): + ''' Moves the order forward''' + # If status is 'Waiting for both' move to Waiting for invoice + 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.WFE: + order.status = Order.Status.CHA + order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.CHA]) + order.save() @classmethod def is_trade_escrow_locked(cls, order): - if LNNode.validate_hold_invoice_locked(order.trade_escrow.payment_hash): + if order.trade_escrow.status == LNPayment.Status.LOCKED: + return True + elif LNNode.validate_hold_invoice_locked(order.trade_escrow.payment_hash): order.trade_escrow.status = LNPayment.Status.LOCKED order.trade_escrow.save() - # If status is 'Waiting for both' move to Waiting for invoice - 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.WFE: - order.status = Order.Status.CHA - order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.CHA]) - order.save() + cls.trade_escrow_received(order) return True return False @@ -607,7 +614,7 @@ class Logics(): elif order.trade_escrow.status == LNPayment.Status.INVGEN: return True, {'escrow_invoice':order.trade_escrow.invoice, 'escrow_satoshis':order.trade_escrow.num_satoshis} - # If there was no taker_bond object yet, generates one + # If there was no taker_bond object yet, generate one escrow_satoshis = order.last_satoshis # Amount was fixed when taker bond was locked description = f"RoboSats - Escrow amount for '{str(order)}' - The escrow will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment." diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py index e1edb6ec..6a590cc7 100644 --- a/api/management/commands/follow_invoices.py +++ b/api/management/commands/follow_invoices.py @@ -22,6 +22,7 @@ class Command(BaseCommand): ''' help = 'Follows all active hold invoices' + rest = 5 # seconds between consecutive checks for invoice updates # def add_arguments(self, parser): # parser.add_argument('debug', nargs='+', type=boolean) @@ -40,7 +41,7 @@ class Command(BaseCommand): stub = LNNode.invoicesstub while True: - time.sleep(5) + time.sleep(self.rest) # time it for debugging t0 = time.time() @@ -95,21 +96,24 @@ class Command(BaseCommand): def update_order_status(self, lnpayment): ''' Background process following LND hold invoices - might catch LNpayments changing status. If they do, + can catch LNpayments changing status. If they do, the order status might have to change status too.''' # If the LNPayment goes to LOCKED (ACCEPTED) if lnpayment.status == LNPayment.Status.LOCKED: # It is a maker bond => Publish order. - order = lnpayment.order_made - if not order == None: - Logics.publish_order(order) + if not lnpayment.order_made == None: + Logics.publish_order(lnpayment.order_made) return # It is a taker bond => close contract. - order = lnpayment.order_taken - if not order == None: - if order.status == Order.Status.TAK: - Logics.finalize_contract(order) - return \ No newline at end of file + elif not lnpayment.order_taken == None: + if lnpayment.order_taken.status == Order.Status.TAK: + Logics.finalize_contract(lnpayment.order_taken) + return + + # It is a trade escrow => move foward order status. + elif not lnpayment.order_escrow == None: + Logics.trade_escrow_received(lnpayment.order_escrow) + return \ No newline at end of file diff --git a/api/models.py b/api/models.py index a65515e7..2285a928 100644 --- a/api/models.py +++ b/api/models.py @@ -151,7 +151,7 @@ class Order(models.Model): trade_escrow = models.OneToOneField(LNPayment, related_name='order_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True) # buyer payment LN invoice - buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True) + buyer_invoice = models.OneToOneField(LNPayment, related_name='order_paid', on_delete=models.SET_NULL, null=True, default=None, blank=True) # ratings maker_rated = models.BooleanField(default=False, null=False) @@ -180,9 +180,8 @@ class Order(models.Model): } def __str__(self): - # Make relational back to ORDER return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}') - + @receiver(pre_delete, sender=Order) def delete_lnpayment_at_order_deletion(sender, instance, **kwargs): diff --git a/api/tasks.py b/api/tasks.py index 84744f5f..9f1aaaa8 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -40,7 +40,7 @@ def follow_send_payment(lnpayment): from base64 import b64decode from api.lightning.node import LNNode - from api.models import LNPayment + from api.models import LNPayment, Order MACAROON = b64decode(config('LND_MACAROON_BASE64')) @@ -48,25 +48,37 @@ def follow_send_payment(lnpayment): request = LNNode.routerrpc.SendPaymentRequest( payment_request=lnpayment.invoice, fee_limit_sat=fee_limit_sat, - timeout_seconds=60) + timeout_seconds=60) # time out payment in 60 seconds + order = lnpayment.order_paid for response in LNNode.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]): if response.status == 0 : # Status 0 'UNKNOWN' + # Not sure when this status happens pass if response.status == 1 : # Status 1 'IN_FLIGHT' + print('IN_FLIGHT') lnpayment.status = LNPayment.Status.FLIGHT lnpayment.save() + order.status = Order.Status.PAY + order.save() if response.status == 3 : # Status 3 'FAILED' + print('FAILED') lnpayment.status = LNPayment.Status.FAILRO lnpayment.save() + order.status = Order.Status.FAI + order.save() context = LNNode.payment_failure_context[response.failure_reason] + # Call for a retry here return False, context if response.status == 2 : # Status 2 'SUCCEEDED' + print('SUCCEEDED') lnpayment.status = LNPayment.Status.SUCCED lnpayment.save() + order.status = Order.Status.SUC + order.save() return True, None @shared_task(name="cache_external_market_prices", ignore_result=True) diff --git a/api/utils.py b/api/utils.py index 1d240b4f..4f747657 100644 --- a/api/utils.py +++ b/api/utils.py @@ -5,7 +5,7 @@ import numpy as np market_cache = {} -@ring.dict(market_cache, expire=5) #keeps in cache for 5 seconds +@ring.dict(market_cache, expire=3) #keeps in cache for 3 seconds def get_exchange_rates(currencies): ''' Params: list of currency codes. diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index 2b05a63b..7ca10bda 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -63,7 +63,7 @@ export default class OrderPage extends Component { "10": 15000, //'Fiat sent - In chatroom' "11": 60000, //'In dispute' "12": 9999999,//'Collaboratively cancelled' - "13": 120000, //'Sending satoshis to buyer' + "13": 3000, //'Sending satoshis to buyer' "14": 9999999,//'Sucessful trade' "15": 10000, //'Failed lightning network routing' "16": 9999999,//'Maker lost dispute' diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index 796e756e..45eb625c 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -1,8 +1,10 @@ import React, { Component } from "react"; -import { Button , Dialog, Grid, Typography, TextField, ButtonGroup} from "@mui/material" +import { Button , Dialog, Grid, Typography, TextField, ButtonGroup, CircularProgress, IconButton} from "@mui/material" import { Link } from 'react-router-dom' import Image from 'material-ui-image' import InfoDialog from './InfoDialog' +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import ContentCopy from "@mui/icons-material/ContentCopy"; function getCookie(name) { let cookieValue = null; @@ -27,6 +29,7 @@ export default class UserGenPage extends Component { this.state = { token: this.genBase62Token(34), openInfo: false, + showRobosat: true, }; this.getGeneratedUser(this.state.token); } @@ -53,6 +56,7 @@ export default class UserGenPage extends Component { shannon_entropy: data.token_shannon_entropy, bad_request: data.bad_request, found: data.found, + showRobosat:true, }); }); } @@ -69,10 +73,12 @@ export default class UserGenPage extends Component { handleAnotherButtonPressed=(e)=>{ this.delGeneratedUser() - this.setState({ - token: this.genBase62Token(34), - }); - this.getGeneratedUser(this.state.token); + // this.setState({ + // showRobosat: false, + // token: this.genBase62Token(34), + // }); + // this.getGeneratedUser(this.state.token); + window.location.reload(); } handleChangeToken=(e)=>{ @@ -81,6 +87,7 @@ export default class UserGenPage extends Component { token: e.target.value, }) this.getGeneratedUser(e.target.value); + this.setState({showRobosat: false}) } handleClickOpenInfo = () => { @@ -109,20 +116,26 @@ export default class UserGenPage extends Component { render() { return ( - - - {this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""} - - - -
- -

+ + {this.state.showRobosat ? +
+ + + {this.state.nickname ? "⚡"+this.state.nickname+"⚡" : ""} + + + +
+ +

+
+
+ : }
{ this.state.found ? @@ -134,21 +147,30 @@ export default class UserGenPage extends Component { : "" } - - + + + navigator.clipboard.writeText(this.state.token)}> + + + + - +