diff --git a/api/logics.py b/api/logics.py index 0dd2f499..8374f17b 100644 --- a/api/logics.py +++ b/api/logics.py @@ -36,9 +36,9 @@ class Logics(): def validate_order_size(order): '''Checks if order is withing limits at t0''' if order.t0_satoshis > MAX_TRADE: - return False, {'bad_request': f'Your order is too big. It is worth {order.t0_satoshis} now. But maximum is {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'} if order.t0_satoshis < MIN_TRADE: - return False, {'bad_request': f'Your order is too small. It is worth {order.t0_satoshis} now. But minimum is {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 True, None def take(order, user): @@ -66,7 +66,22 @@ class Logics(): satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000 return int(satoshis_now) - + + def price_and_premium_now(order): + ''' computes order premium live ''' + exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)]) + if not order.is_explicit: + premium = order.premium + price = exchange_rate + else: + exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)]) + order_rate = float(order.amount) / (float(order.satoshis) / 100000000) + premium = order_rate / exchange_rate - 1 + price = order_rate + + premium = int(premium*100) # 2 decimals left + return price, premium + def order_expires(order): order.status = Order.Status.EXP order.maker = None diff --git a/api/utils.py b/api/utils.py index 0b1b934b..85333355 100644 --- a/api/utils.py +++ b/api/utils.py @@ -1,7 +1,11 @@ from decouple import config import requests +import ring +storage = {} + +@ring.dict(storage, expire=60) #keeps in cache for 60 secs def get_exchange_rate(currency): # TODO Add fallback Public APIs and error handling # Think about polling price data in a different way (e.g. store locally every t seconds) diff --git a/api/views.py b/api/views.py index 8a20c77c..a4f474d4 100644 --- a/api/views.py +++ b/api/views.py @@ -369,6 +369,9 @@ class BookView(ListAPIView): data = ListOrderSerializer(order).data data['maker_nick'] = str(order.maker) + # Compute current premium for those orders that are explicitly priced. + data['price'], data['premium'] = Logics.price_and_premium_now(order) + for key in ('status','taker'): # Non participants should not see the status or who is the taker del data[key] diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index 03d5b9d7..f232c0ae 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -1,5 +1,5 @@ import React, { Component } from "react"; -import { Button , Divider, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, RouterLink, ListItemAvatar} from "@mui/material"; +import { Box, Button , Divider, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, List, ListItem, ListItemText, Avatar, RouterLink, ListItemAvatar} from "@mui/material"; import { Link } from 'react-router-dom' export default class BookPage extends Component { @@ -65,14 +65,51 @@ export default class BookPage extends Component { bookListItems=()=>{ return (this.state.orders.map((order) => - - - - - + <> + this.handleCardClick(order.id)}> + + + + + + + + {order.maker_nick+" "} + + + + + + {order.type ? " Sells ": " Buys "} BTC for {parseFloat( + parseFloat(order.amount).toFixed(4))+" "+ this.getCurrencyCode(order.currency)+" "} + + + + + + via {order.payment_method} + + + + + + at {this.pn(order.price) + " " + this.getCurrencyCode(order.currency)}/BTC + + + + + + {order.premium > 1 ? "🔴" : "🔵" } {parseFloat(parseFloat(order.premium).toFixed(4))}% + + + + + + + > )); } @@ -133,7 +170,7 @@ export default class BookPage extends Component { return ( - + Order Book @@ -202,11 +239,9 @@ export default class BookPage extends Component { ) : - - - {this.bookListItems()} + + {this.bookListItems()} - } diff --git a/frontend/src/components/MakerPage.js b/frontend/src/components/MakerPage.js index e868a826..f76df118 100644 --- a/frontend/src/components/MakerPage.js +++ b/frontend/src/components/MakerPage.js @@ -23,8 +23,10 @@ export default class MakerPage extends Component { defaultCurrency = 1; defaultCurrencyCode = 'USD'; defaultAmount = 0 ; - defaultPaymentMethod = "Not specified"; + defaultPaymentMethod = "not specified"; defaultPremium = 0; + minTradeSats = 10000; + maxTradeSats = 500000; constructor(props) { super(props); @@ -69,9 +71,16 @@ export default class MakerPage extends Component { }); } handleSatoshisChange=(e)=>{ + var bad_sats = e.target.value > this.maxTradeSats ? + ("Must be less than "+this.maxTradeSats): + (e.target.value < this.minTradeSats ? + ("Must be more than "+this.minTradeSats): null) + this.setState({ - satoshis: e.target.value, - }); + satoshis: e.target.value, + badSatoshis: bad_sats, + }) + ; } handleClickRelative=(e)=>{ this.setState({ @@ -125,21 +134,19 @@ export default class MakerPage extends Component { render() { return ( - - + - - Make an Order + + Order Maker - - + + + - - - Choose Buy or Sell Bitcoin - - + + Buy or Sell Bitcoin? + - - - - - { - Object.entries(this.state.currencies_dict) - .map( ([key, value]) => {value} ) - } - - - + + + + { + Object.entries(this.state.currencies_dict) + .map( ([key, value]) => {value} ) + } + + + + + + + Choose a Pricing Method + + - - - Choose a Pricing Method - - {/* conditional shows either Premium % field or Satoshis field based on pricing method */} { this.state.isExplicit ? } - + Create Order diff --git a/frontend/src/components/UserGenPage.js b/frontend/src/components/UserGenPage.js index cf53e63f..cee57cf9 100644 --- a/frontend/src/components/UserGenPage.js +++ b/frontend/src/components/UserGenPage.js @@ -24,7 +24,7 @@ export default class UserGenPage extends Component { constructor(props) { super(props); this.state = { - token: this.genBase62Token(32), + token: this.genBase62Token(34), }; this.getGeneratedUser(this.state.token); } @@ -72,7 +72,7 @@ export default class UserGenPage extends Component { handleAnotherButtonPressed=(e)=>{ this.delGeneratedUser() this.setState({ - token: this.genBase62Token(32), + token: this.genBase62Token(34), }) this.reload_for_csrf_to_work(); } diff --git a/setup.md b/setup.md index 67887df9..a96618fe 100644 --- a/setup.md +++ b/setup.md @@ -7,6 +7,7 @@ ``` pip install virtualenvwrapper pip install python-decouple +pip install ring ``` ### Add to .bashrc