merge with branch logics-second-iteration

This commit is contained in:
LowEntropyFace 2022-01-08 20:18:11 -05:00
commit ec823586c1
9 changed files with 286 additions and 88 deletions

17
.env-sample Normal file
View File

@ -0,0 +1,17 @@
# Market price public API
MARKET_PRICE_API = 'https://blockchain.info/ticker'
# Trade fee in percentage %
FEE = 0.002
# Bond size in percentage %
BOND_SIZE = 0.01
# Trade limits in satoshis
MIN_TRADE = 10000
MAX_TRADE = 500000
# Expiration time in minutes
EXPIRATION_MAKE = 5
# Username for HTLCs escrows
ESCROW_USERNAME = 'admin'

View File

@ -2,7 +2,7 @@ from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import UserAdmin
from .models import Order, LNPayment, Profile
from .models import Order, LNPayment, Profile, MarketTick
admin.site.unregister(Group)
admin.site.unregister(User)
@ -17,22 +17,24 @@ class ProfileInline(admin.StackedInline):
@admin.register(User)
class EUserAdmin(UserAdmin):
inlines = [ProfileInline]
list_display = ('avatar_tag',) + UserAdmin.list_display
list_display_links = ['username']
list_display = ('avatar_tag','id','username','last_login','date_joined','is_staff')
list_display_links = ('id','username')
def avatar_tag(self, obj):
return obj.profile.avatar_tag()
@admin.register(Order)
class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','type','maker_link','taker_link','status','amount','currency','t0_satoshis','created_at','expires_at', 'buyer_invoice_link','maker_bond_link','taker_bond_link','trade_escrow_link')
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')
list_filter = ('is_disputed','is_fiat_sent','type','currency','status')
@admin.register(LNPayment)
class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','concept','status','num_satoshis','type','invoice','expires_at','sender_link','receiver_link')
list_display_links = ('id','concept')
change_links = ('sender','receiver')
list_filter = ('type','concept','status')
@admin.register(Profile)
class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@ -40,3 +42,9 @@ class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display_links = ('avatar_tag','id')
change_links =['user']
readonly_fields = ['avatar_tag']
@admin.register(MarketTick)
class MarketTickAdmin(admin.ModelAdmin):
list_display = ('timestamp','price','volume','premium','currency','fee')
readonly_fields = ('timestamp','price','volume','premium','currency','fee')
list_filter = ['currency']

View File

@ -22,32 +22,36 @@ class LNNode():
return invoice, payment_hash, expires_at
def validate_hodl_invoice_locked():
def validate_hodl_invoice_locked(payment_hash):
'''Generates hodl invoice to publish an order'''
return True
def validate_ln_invoice(invoice): # num_satoshis
def validate_ln_invoice(invoice, num_satoshis): # num_satoshis
'''Checks if the submited LN invoice is as expected'''
valid = True
num_satoshis = 50000 # TODO decrypt and confirm sats are as expected
context = None
description = 'Placeholder desc' # TODO decrypt from LN invoice
payment_hash = '567126' # TODO decrypt
payment_hash = '567&*GIHU126' # TODO decrypt
expires_at = timezone.now() # TODO decrypt
return valid, num_satoshis, description, payment_hash, expires_at
return valid, context, description, payment_hash, expires_at
def pay_buyer_invoice(invoice):
'''Sends sats to buyer'''
def pay_invoice(invoice):
'''Sends sats to buyer, or cancelinvoices'''
return True
def charge_hodl_htlcs(invoice):
def settle_hodl_htlcs(payment_hash):
'''Charges a LN hodl invoice'''
return True
def free_hodl_htlcs(invoice):
def return_hodl_htlcs(payment_hash):
'''Returns sats'''
return True
def double_check_htlc_is_settled(payment_hash):
''' Just as it sounds. Better safe than sorry!'''
return True

View File

@ -1,10 +1,10 @@
from datetime import timedelta
from django.utils import timezone
import requests
from .lightning import LNNode
from .models import Order, LNPayment, User
from .models import Order, LNPayment, MarketTick, User
from decouple import config
from .utils import get_exchange_rate
FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE'))
@ -23,8 +23,6 @@ ESCROW_EXPIRY = int(config('ESCROW_EXPIRY'))
class Logics():
# escrow_user = User.objects.get(username=ESCROW_USERNAME)
def validate_already_maker_or_taker(user):
'''Checks if the user is already partipant of an order'''
queryset = Order.objects.filter(maker=user)
@ -63,11 +61,9 @@ class Logics():
if order.is_explicit:
satoshis_now = order.satoshis
else:
# TODO Add fallback Public APIs and error handling
# Think about polling price data in a different way (e.g. store locally every t seconds)
market_prices = requests.get(MARKET_PRICE_API).json()
exchange_rate = float(market_prices[Order.currency_dict[str(order.currency)]]['last'])
satoshis_now = ((float(order.amount) * 1+float(order.premium)) / exchange_rate) * 100*1000*1000
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
premium_rate = exchange_rate * (1+float(order.premium)/100)
satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000
return int(satoshis_now)
@ -89,10 +85,19 @@ class Logics():
@classmethod
def update_invoice(cls, order, user, invoice):
is_valid_invoice, num_satoshis, description, payment_hash, expires_at = LNNode.validate_ln_invoice(invoice)
# only user is the buyer and a valid LN invoice
if not (cls.is_buyer(order, user) or is_valid_invoice):
return False, {'bad_request':'Invalid Lightning Network Invoice. It starts by LNTB...'}
# only the buyer can post a buyer invoice
if not cls.is_buyer(order, user):
return False, {'bad_request':'Only the buyer of this order can provide a buyer invoice.'}
if not order.taker_bond:
return False, {'bad_request':'Wait for your order to be taken.'}
if not (order.taker_bond.status == order.maker_bond.status == LNPayment.Status.LOCKED):
return False, {'bad_request':'You cannot a invoice while bonds are not posted.'}
num_satoshis = cls.buyer_invoice_amount(order, user)[1]['invoice_amount']
valid, context, description, payment_hash, expires_at = LNNode.validate_ln_invoice(invoice, num_satoshis)
if not valid:
return False, context
order.buyer_invoice, _ = LNPayment.objects.update_or_create(
concept = LNPayment.Concepts.PAYBUYER,
@ -130,22 +135,29 @@ class Logics():
@classmethod
def rate_counterparty(cls, order, user, rating):
# if maker, rates taker
if order.maker == user:
order.taker.profile.total_ratings = order.taker.profile.total_ratings + 1
last_ratings = list(order.taker.profile.last_ratings).append(rating)
order.taker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
# if taker, rates maker
if order.taker == user:
order.maker.profile.total_ratings = order.maker.profile.total_ratings + 1
last_ratings = list(order.maker.profile.last_ratings).append(rating)
order.maker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
# If the trade is finished
if order.status > Order.Status.PAY:
# if maker, rates taker
if order.maker == user:
order.taker.profile.total_ratings = order.taker.profile.total_ratings + 1
last_ratings = list(order.taker.profile.last_ratings).append(rating)
order.taker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
# if taker, rates maker
if order.taker == user:
order.maker.profile.total_ratings = order.maker.profile.total_ratings + 1
last_ratings = list(order.maker.profile.last_ratings).append(rating)
order.maker.profile.total_ratings = sum(last_ratings) / len(last_ratings)
else:
return False, {'bad_request':'You cannot rate your counterparty yet.'}
order.save()
return True, None
@classmethod
def cancel_order(cls, order, user, state):
def cancel_order(cls, order, user, state=None):
# 1) When maker cancels before bond
'''The order never shows up on the book and order
@ -292,3 +304,54 @@ class Logics():
order.save()
return True, {'escrow_invoice':invoice,'escrow_satoshis': escrow_satoshis}
def settle_escrow(order):
''' Settles the trade escrow HTLC'''
# TODO ERROR HANDLING
valid = LNNode.settle_hodl_htlcs(order.trade_escrow.payment_hash)
return valid
def pay_buyer_invoice(order):
''' Settles the trade escrow HTLC'''
# TODO ERROR HANDLING
valid = LNNode.pay_invoice(order.buyer_invoice.payment_hash)
return valid
@classmethod
def confirm_fiat(cls, order, user):
''' If Order is in the CHAT states:
If user is buyer: mark FIAT SENT and settle escrow!
If User is the seller and FIAT is SENT: Pay buyer invoice!'''
if order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Alternatively, if all collateral is locked? test out
# If buyer, settle escrow and mark fiat sent
if cls.is_buyer(order, user):
if cls.settle_escrow(order): # KEY LINE - SETTLES THE TRADE ESCROW !!
order.trade_escrow.status = LNPayment.Status.SETLED
order.status = Order.Status.FSE
order.is_fiat_sent = True
# If seller and fiat sent, pay buyer invoice
elif cls.is_seller(order, user):
if not order.is_fiat_sent:
return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'}
# Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
# 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:
return False, {'bad_request':'Woah, something broke badly. Report in the public channels, or open a Github Issue.'}
# Double check the trade escrow is settled
elif cls.pay_buyer_invoice(order): # KEY LINE - PAYS THE BUYER !!
order.status = Order.Status.PAY
order.buyer_invoice.status = LNPayment.Status.PAYING
else:
return False, {'bad_request':'You cannot confirm the fiat payment at this stage'}
order.save()
return True, None

View File

@ -7,19 +7,14 @@ from django.utils.html import mark_safe
from decouple import config
from pathlib import Path
from .utils import get_exchange_rate
import json
#############################
# TODO
# Load hparams from .env file
MIN_TRADE = int(config('MIN_TRADE'))
MAX_TRADE = int(config('MAX_TRADE'))
FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE'))
class LNPayment(models.Model):
class Types(models.IntegerChoices):
@ -39,7 +34,8 @@ class LNPayment(models.Model):
RETNED = 3, 'Returned'
MISSNG = 4, 'Missing'
VALIDI = 5, 'Valid'
INFAIL = 6, 'Failed routing'
PAYING = 6, 'Paying ongoing'
FAILRO = 7, 'Failed routing'
# payment use details
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HODL)
@ -74,24 +70,24 @@ class Order(models.Model):
DEL = 2, 'Deleted'
TAK = 3, 'Waiting for taker bond'
UCA = 4, 'Cancelled'
WF2 = 5, 'Waiting for trade collateral and buyer invoice'
WFE = 6, 'Waiting only for seller trade collateral'
WFI = 7, 'Waiting only for buyer invoice'
CHA = 8, 'Sending fiat - In chatroom'
CCA = 9, 'Collaboratively cancelled'
EXP = 5, 'Expired'
WF2 = 6, 'Waiting for trade collateral and buyer invoice'
WFE = 7, 'Waiting only for seller trade collateral'
WFI = 8, 'Waiting only for buyer invoice'
CHA = 9, 'Sending fiat - In chatroom'
FSE = 10, 'Fiat sent - In chatroom'
FCO = 11, 'Fiat confirmed'
SUC = 12, 'Sucessfully settled'
FAI = 13, 'Failed lightning network routing'
UPI = 14, 'Updated invoice'
DIS = 15, 'In dispute'
DIS = 11, 'In dispute'
CCA = 12, 'Collaboratively cancelled'
PAY = 13, 'Sending satoshis to buyer'
SUC = 14, 'Sucessfully settled'
FAI = 15, 'Failed lightning network routing'
MLD = 16, 'Maker lost dispute'
TLD = 17, 'Taker lost dispute'
EXP = 18, 'Expired'
currency_dict = json.load(open('./api/currencies.json'))
currency_choices = [(int(val), label) for val, label in list(currency_dict.items())]
print(currency_choices)
# order info
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.WFB)
created_at = models.DateTimeField(auto_now_add=True)
@ -101,7 +97,7 @@ class Order(models.Model):
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
currency = models.PositiveSmallIntegerField(choices=currency_choices, null=False)
amount = models.DecimalField(max_digits=9, decimal_places=4, validators=[MinValueValidator(0.00001)])
payment_method = models.CharField(max_length=30, null=False, default="not specified", blank=True)
payment_method = models.CharField(max_length=50, null=False, default="not specified", blank=True)
# order pricing method. A explicit amount of sats, or a relative premium above/below market.
is_explicit = models.BooleanField(default=False, null=False)
@ -117,8 +113,11 @@ class Order(models.Model):
maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None, blank=True) # unique = True, a taker can only take one order
is_pending_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled.
is_disputed = models.BooleanField(default=False, null=False)
is_fiat_sent = models.BooleanField(default=False, null=False)
# order collateral
# HTLCs
# Order collateral
maker_bond = models.ForeignKey(LNPayment, related_name='maker_bond', on_delete=models.SET_NULL, null=True, default=None, blank=True)
taker_bond = models.ForeignKey(LNPayment, related_name='taker_bond', on_delete=models.SET_NULL, null=True, default=None, blank=True)
trade_escrow = models.ForeignKey(LNPayment, related_name='trade_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True)
@ -126,9 +125,13 @@ class Order(models.Model):
# buyer payment LN invoice
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True)
# cancel LN invoice // these are only needed to charge lower-than-bond amounts. E.g., a taken order has a small cost if cancelled, to avoid DDOSing.
maker_cancel = models.ForeignKey(LNPayment, related_name='maker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True)
taker_cancel = models.ForeignKey(LNPayment, related_name='taker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True)
def __str__(self):
# Make relational back to ORDER
return (f'Order {self.id}: {self.Types(self.type).label} BTC for {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)
def delelete_HTLCs_at_order_deletion(sender, instance, **kwargs):
@ -183,3 +186,48 @@ class Profile(models.Model):
def avatar_tag(self):
return mark_safe('<img src="%s" width="50" height="50" />' % self.get_avatar())
class MarketTick(models.Model):
'''
Records tick by tick Non-KYC Bitcoin price.
Data to be aggregated and offered via public API.
It is checked against current cex prices for nice
insight on the historical premium of Non-KYC BTC
Price is set when both taker bond is locked. Both
maker and taker are commited with bonds (contract
is finished and cancellation has a cost)
'''
price = models.DecimalField(max_digits=10, decimal_places=2, default=None, null=True, validators=[MinValueValidator(0)])
volume = models.DecimalField(max_digits=8, decimal_places=8, default=None, null=True, validators=[MinValueValidator(0)])
premium = models.DecimalField(max_digits=5, decimal_places=2, default=None, null=True, validators=[MinValueValidator(-100), MaxValueValidator(999)], blank=True)
currency = models.PositiveSmallIntegerField(choices=Order.currency_choices, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
# Relevant to keep record of the historical fee, so the insight on the premium can be better analyzed
fee = models.DecimalField(max_digits=4, decimal_places=4, default=FEE, validators=[MinValueValidator(0), MaxValueValidator(1)])
def log_a_tick(order):
'''
Adds a new tick
'''
if not order.taker_bond:
return None
elif order.taker_bond.status == LNPayment.Status.LOCKED:
volume = order.last_satoshis / 100000000
price = float(order.amount) / volume # Amount Fiat / Amount BTC
premium = 100 * (price / get_exchange_rate(Order.currency_dict[str(order.currency)]) - 1)
tick = MarketTick.objects.create(
price=price,
volume=volume,
premium=premium,
currency=order.currency)
tick.save()
def __str__(self):
return f'Tick: {self.id}'

View File

@ -1,10 +1,11 @@
from django.urls import path
from .views import OrderMakerView, OrderView, UserView, BookView, get_currencies_json
from .views import MakerView, OrderView, UserView, BookView, InfoView, get_currencies_json
urlpatterns = [
path('make/', OrderMakerView.as_view()),
path('make/', MakerView.as_view()),
path('order/', OrderView.as_view({'get':'get','post':'take_update_confirm_dispute_cancel'})),
path('usergen/', UserView.as_view()),
path('book/', BookView.as_view()),
path('info/', InfoView.as_view()),
path('currencies/', get_currencies_json),
]

12
api/utils.py Normal file
View File

@ -0,0 +1,12 @@
from decouple import config
import requests
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)
market_prices = requests.get(config('MARKET_PRICE_API')).json()
exchange_rate = float(market_prices[currency]['last'])
return exchange_rate

View File

@ -25,13 +25,14 @@ import json
from django.http import HttpResponse
EXP_MAKER_BOND_INVOICE = int(config('EXP_MAKER_BOND_INVOICE'))
FEE = float(config('FEE'))
avatar_path = Path('frontend/static/assets/avatars')
avatar_path.mkdir(parents=True, exist_ok=True)
# Create your views here.
class OrderMakerView(CreateAPIView):
class MakerView(CreateAPIView):
serializer_class = MakeOrderSerializer
def post(self,request):
@ -125,6 +126,18 @@ class OrderView(viewsets.ViewSet):
data['maker_nick'] = str(order.maker)
data['taker_nick'] = str(order.taker)
data['status_message'] = Order.Status(order.status).label
data['is_fiat_sent'] = order.is_fiat_sent
data['is_disputed'] = order.is_disputed
# If both bonds are locked, participants can see the trade in sats is also final.
if order.taker_bond:
if order.maker_bond.status == order.taker_bond.status == LNPayment.Status.LOCKED:
# Seller sees the amount he pays
if data['is_seller']:
data['trade_satoshis'] = order.last_satoshis
# Buyer sees the amount he receives
elif data['is_buyer']:
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.
if order.status == Order.Status.WFB and data['is_maker']:
@ -170,6 +183,10 @@ class OrderView(viewsets.ViewSet):
# add whether a collaborative cancel is pending
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)
def take_update_confirm_dispute_cancel(self, request, format=None):
@ -198,32 +215,42 @@ class OrderView(viewsets.ViewSet):
Logics.take(order, request.user)
else: Response({'bad_request':'This order is not public anymore.'}, status.HTTP_400_BAD_REQUEST)
# 2) If action is update (invoice)
elif action == 'update_invoice' and invoice:
# Any other action is only allowed if the user is a participant
if not (order.maker == request.user or order.taker == request.user):
return Response({'bad_request':'You are not a participant in this order'}, status.HTTP_403_FORBIDDEN)
# 2) If action is 'update invoice'
if action == 'update_invoice' and invoice:
valid, context = Logics.update_invoice(order,request.user,invoice)
if not valid: return Response(context,status.HTTP_400_BAD_REQUEST)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# 3) If action is cancel
elif action == 'cancel':
valid, context = Logics.cancel_order(order,request.user)
if not valid: return Response(context,status.HTTP_400_BAD_REQUEST)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# 4) If action is confirm
elif action == 'confirm':
pass
valid, context = Logics.confirm_fiat(order,request.user)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# 5) If action is dispute
elif action == 'dispute':
pass
valid, context = Logics.open_dispute(order,request.user, rating)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# 6) If action is dispute
# 6) If action is rate
elif action == 'rate' and rating:
valid, context = Logics.rate_counterparty(order,request.user, rating)
if not valid: return Response(context,status.HTTP_400_BAD_REQUEST)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# If nothing... something else is going on. Probably not allowed!
# If nothing of the above... something else is going on. Probably not allowed!
else:
return Response({'bad_request':'The Robotic Satoshis working in the warehouse did not understand you'})
return Response(
{'bad_request':
'The Robotic Satoshis working in the warehouse did not understand you. ' +
'Please, fill a Bug Issue in Github https://github.com/Reckless-Satoshi/robosats/issues'},
status.HTTP_501_NOT_IMPLEMENTED)
return self.get(request)
@ -298,23 +325,27 @@ class UserView(APIView):
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
context['found'] = 'Bad luck, this nickname is taken'
context['bad_request'] = 'Enter a different token'
return Response(context, status=status.HTTP_403_FORBIDDEN)
return Response(context, status.HTTP_403_FORBIDDEN)
def delete(self,request):
user = User.objects.get(id = request.user.id)
''' Pressing "give me another" deletes the logged in user '''
user = request.user
if not user:
return Response(status.HTTP_403_FORBIDDEN)
# TO DO. Pressing "give me another" deletes the logged in user
# However it might be a long time recovered user
# Only delete if user live is < 5 minutes
# Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake
if user.date_joined < (timezone.now() - timedelta(minutes=30)):
return Response(status.HTTP_400_BAD_REQUEST)
# TODO check if user exists AND it is not a maker or taker!
if user is not None:
logout(request)
user.delete()
# Check if it is not a maker or taker!
if not Logics.validate_already_maker_or_taker(user):
return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST)
logout(request)
user.delete()
return Response({'user_deleted':'User deleted permanently'}, status.HTTP_301_MOVED_PERMANENTLY)
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_302_FOUND)
return Response(status=status.HTTP_403_FORBIDDEN)
class BookView(ListAPIView):
serializer_class = ListOrderSerializer
@ -342,6 +373,19 @@ class BookView(ListAPIView):
return Response(book_data, status=status.HTTP_200_OK)
class InfoView(ListAPIView):
def get(self):
context = {}
context['num_public_buy_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB))
context['num_public_sell_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB))
context['last_day_avg_btc_premium'] = None # Todo
context['num_active_robots'] = None
context['total_volume'] = None
return Response(context, status.HTTP_200_OK)
def get_currencies_json(request):
currency_dict = json.load(open('./api/currencies.json'))
return HttpResponse(json.dumps(currency_dict),content_type="application/json")

View File

@ -193,7 +193,8 @@ export default class MakerPage extends Component {
type="text"
require={true}
inputProps={{
style: {textAlign:"center"}
style: {textAlign:"center"},
maxLength: 50
}}
onChange={this.handlePaymentMethodChange}
/>