diff --git a/api/lightning/node.py b/api/lightning/node.py index e95b9668..092db860 100644 --- a/api/lightning/node.py +++ b/api/lightning/node.py @@ -122,22 +122,22 @@ class LNNode(): lnpayment.save() return True - @classmethod - def check_until_invoice_locked(cls, payment_hash, expiration): - '''Checks until hold invoice is locked. - When invoice is locked, returns true. - If time expires, return False.''' - # Experimental, might need asyncio. Best if subscribing all invoices and running a background task - # Maybe best to pass LNpayment object and change status live. + # @classmethod + # def check_until_invoice_locked(cls, payment_hash, expiration): + # '''Checks until hold invoice is locked. + # When invoice is locked, returns true. + # If time expires, return False.''' + # # Experimental, might need asyncio. Best if subscribing all invoices and running a background task + # # Maybe best to pass LNpayment object and change status live. - request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash) - for invoice in cls.invoicesstub.SubscribeSingleInvoice(request): - print(invoice) - if timezone.now > expiration: - break - if invoice.state == 3: # True if hold invoice is accepted. - return True - return False + # request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash) + # for invoice in cls.invoicesstub.SubscribeSingleInvoice(request): + # print(invoice) + # if timezone.now > expiration: + # break + # if invoice.state == 3: # True if hold invoice is accepted. + # return True + # return False @classmethod diff --git a/api/logics.py b/api/logics.py index 291cb572..85f5acda 100644 --- a/api/logics.py +++ b/api/logics.py @@ -50,9 +50,9 @@ class Logics(): def validate_order_size(order): '''Validates if order is withing limits in satoshis at t0''' if order.t0_satoshis > MAX_TRADE: - return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'} + return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'} if order.t0_satoshis < MIN_TRADE: - return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'} + return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'} return True, None @classmethod @@ -386,11 +386,12 @@ class Logics(): return True, None # 2) When maker cancels after bond - '''The order dissapears from book and goes to cancelled. Maker is charged the bond to prevent DDOS - on the LN node and order book. TODO Only charge a small part of the bond (requires maker submitting an invoice)''' + '''The order dissapears from book and goes to cancelled. If strict, maker is charged the bond + to prevent DDOS on the LN node and order book. If not strict, maker is returned + the bond (more user friendly).''' elif order.status == Order.Status.PUB and order.maker == user: #Settle the maker bond (Maker loses the bond for cancelling public order) - if cls.settle_bond(order.maker_bond): + if cls.return_bond(order.maker_bond): # strict: cls.settle_bond(order.maker_bond): order.status = Order.Status.UCA order.save() return True, None diff --git a/api/views.py b/api/views.py index 21b7b9b8..b4168400 100644 --- a/api/views.py +++ b/api/views.py @@ -39,6 +39,9 @@ class MakerView(CreateAPIView): def post(self,request): serializer = self.serializer_class(data=request.data) + if not request.user.is_authenticated: + return Response({'bad_request':'Woops! It seems you do not have a robot avatar'}, status.HTTP_400_BAD_REQUEST) + if not serializer.is_valid(): return Response(status=status.HTTP_400_BAD_REQUEST) type = serializer.data.get('type') @@ -413,15 +416,16 @@ class UserView(APIView): def delete(self,request): ''' Pressing "give me another" deletes the logged in user ''' user = request.user - if not user: + if not user.is_authenticated: return Response(status.HTTP_403_FORBIDDEN) - # Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake + # Only delete if user life is shorter than 30 minutes. Helps to avoid deleting users by mistake if user.date_joined < (timezone.now() - timedelta(minutes=30)): return Response(status.HTTP_400_BAD_REQUEST) # Check if it is not a maker or taker! - if not Logics.validate_already_maker_or_taker(user): + not_participant, _, _ = Logics.validate_already_maker_or_taker(user) + if not not_participant: return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST) logout(request) diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index 37b5b903..6e1100c6 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -93,7 +93,7 @@ export default class BookPage extends Component { renderCell: (params) => {return ( - + @@ -147,7 +147,7 @@ export default class BookPage extends Component { { field: 'robosat', headerName: 'Robot', width: 80, renderCell: (params) => {return ( - + ); } }, diff --git a/frontend/src/components/BottomBar.js b/frontend/src/components/BottomBar.js index 1cf718b5..b5c4cd91 100644 --- a/frontend/src/components/BottomBar.js +++ b/frontend/src/components/BottomBar.js @@ -17,6 +17,7 @@ import SendIcon from '@mui/icons-material/Send'; import PublicIcon from '@mui/icons-material/Public'; import NumbersIcon from '@mui/icons-material/Numbers'; import PasswordIcon from '@mui/icons-material/Password'; +import ContentCopy from "@mui/icons-material/ContentCopy"; // pretty numbers function pn(x) { @@ -198,7 +199,7 @@ export default class BottomBar extends Component { - - + {this.props.token ? + size='small' + InputProps={{ + endAdornment: + navigator.clipboard.writeText(this.props.token)}> + + , + }} + /> : 'Cannot remember'} @@ -258,7 +266,7 @@ bottomBarDesktop =()=>{ 0 & !this.state.profileShown) ? "": null} color="primary"> - @@ -462,7 +470,7 @@ bottomBarPhone =()=>{ 0 & !this.state.profileShown) ? "1": null} color="primary"> - diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index e8f20b5b..8c712aee 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -309,7 +309,7 @@ export default class OrderPage extends Component { // If maker and Waiting for Bond. Or if taker and Waiting for bond. // Simply allow to cancel without showing the cancel dialog. - if ((this.state.is_maker & this.state.status == 0) || this.state.is_taker & this.state.status == 3){ + if ((this.state.is_maker & [0,1].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){ return( @@ -317,7 +317,7 @@ export default class OrderPage extends Component { )} // If the order does not yet have an escrow deposited. Show dialog // to confirm forfeiting the bond - if ([1,3,6,7].includes(this.state.status)){ + if ([3,6,7].includes(this.state.status)){ return(
@@ -354,7 +354,7 @@ export default class OrderPage extends Component { - @@ -370,7 +370,7 @@ export default class OrderPage extends Component { - diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index 5a82141a..8873ebd2 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -3,7 +3,7 @@ import { Button , Dialog, Grid, Typography, TextField, ButtonGroup, CircularProg import { Link } from 'react-router-dom' import Image from 'material-ui-image' import InfoDialog from './InfoDialog' -import PublishIcon from '@mui/icons-material/Publish'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; import CasinoIcon from '@mui/icons-material/Casino'; import ContentCopy from "@mui/icons-material/ContentCopy"; @@ -161,14 +161,11 @@ export default class UserGenPage extends Component { } - navigator.clipboard.writeText(this.state.token)}> - - - navigator.clipboard.writeText(this.state.token)}> + + , + endAdornment: + , + }} /> - - - diff --git a/frontend/static/css/index.css b/frontend/static/css/index.css index 81e52a49..53be5963 100644 --- a/frontend/static/css/index.css +++ b/frontend/static/css/index.css @@ -39,7 +39,7 @@ body { .profileNickname { margin: 0; - left: -22px; + left: -16px; } .newAvatar { @@ -51,14 +51,19 @@ body { width: 200px; } -.avatar { +.profileAvatar { border: 0.5px solid #555; filter: drop-shadow(0.5px 0.5px 0.5px #000000); left: 35px; } -.rotatedAvatar { - transform: scaleX(-1); +.smallAvatar { border: 0.5px solid #555; filter: drop-shadow(0.5px 0.5px 0.5px #000000); +} + +.flippedSmallAvatar { + transform: scaleX(-1); + border: 0.3px solid #555; + filter: drop-shadow(0.5px 0.5px 0.5px #000000); } \ No newline at end of file diff --git a/setup.md b/setup.md index 2983156a..8bdb232e 100644 --- a/setup.md +++ b/setup.md @@ -133,4 +133,79 @@ Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 ### Launch the React render from frontend/ directory -`npm run dev` \ No newline at end of file +`npm run dev` + +## Robosats background threads. + +There is 3 processes that run asynchronously: two admin commands and a celery beat scheduler. +The celery worker will run the task of caching external API market prices and cleaning(deleting) the generated robots that were never used. +`celery -A robosats worker --beat -l debug -S django` + +The admin commands are used to keep an eye on the state of LND hold invoices and check whether orders have expired +``` +python3 manage.py follow_invoices +python3 manage.py clean_order +``` + +It might be best to set up system services to continuously run these background processes. + +### Follow invoices admin command as system service + +Create `/etc/systemd/system/follow_invoices.service` and edit with: + +``` +[Unit] +Description=RoboSats Follow LND Invoices +After=lnd.service +StartLimitIntervalSec=0 + +[Service] +WorkingDirectory=/home//robosats/ +StandardOutput=file:/home//robosats/follow_invoices.log +StandardError=file:/home//robosats/follow_invoices.log +Type=simple +Restart=always +RestartSec=1 +User= +ExecStart=python3 manage.py follow_invoices + +[Install] +WantedBy=multi-user.target +``` + +Then launch it with + +``` +systemctl start follow_invoices +systemctl enable follow_invoices +``` +### Clean orders admin command as system service + +Create `/etc/systemd/system/clean_orders.service` and edit with (replace for your username): + +``` +[Unit] +Description=RoboSats Clean Orders +After=lnd.service +StartLimitIntervalSec=0 + +[Service] +WorkingDirectory=/home//robosats/ +StandardOutput=file:/home//robosats/clean_orders.log +StandardError=file:/home//robosats/clean_orders.log +Type=simple +Restart=always +RestartSec=1 +User= +ExecStart=python3 manage.py clean_orders + +[Install] +WantedBy=multi-user.target +``` + +Then launch it with + +``` +systemctl start clean_orders +systemctl enable clean_orders +``` \ No newline at end of file