mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
Merge pull request #26 from Reckless-Satoshi/lightning-integration
Lightning integration first cycle. Just enough so it works. Fixes #24
This commit is contained in:
commit
fac9a62bcd
25
.env-sample
25
.env-sample
@ -1,25 +1,40 @@
|
||||
# base64 ~/.lnd/tls.cert | tr -d '\n'
|
||||
LND_CERT_BASE64=''
|
||||
# base64 ~/.lnd/data/chain/bitcoin/testnet/admin.macaroon | tr -d '\n'
|
||||
LND_MACAROON_BASE64=''
|
||||
LND_GRPC_HOST='127.0.0.1:10009'
|
||||
|
||||
# Market price public API
|
||||
MARKET_PRICE_API = 'https://blockchain.info/ticker'
|
||||
|
||||
# Host e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
|
||||
HOST_NAME = ''
|
||||
|
||||
# Trade fee in percentage %
|
||||
FEE = 0.002
|
||||
# Bond size in percentage %
|
||||
BOND_SIZE = 0.01
|
||||
# Time out penalty for canceling takers in MINUTES
|
||||
PENALTY_TIMEOUT = 2
|
||||
# Time out penalty for canceling takers in SECONDS
|
||||
PENALTY_TIMEOUT = 60
|
||||
|
||||
# Trade limits in satoshis
|
||||
MIN_TRADE = 10000
|
||||
MAX_TRADE = 500000
|
||||
|
||||
# Expiration time for HODL invoices and returning collateral in HOURS
|
||||
BOND_EXPIRY = 8
|
||||
BOND_EXPIRY = 14
|
||||
ESCROW_EXPIRY = 8
|
||||
|
||||
# Expiration time for locking collateral in MINUTES
|
||||
# Expiration time for locking collateral in SECONDS
|
||||
EXP_MAKER_BOND_INVOICE = 300
|
||||
EXP_TAKER_BOND_INVOICE = 200
|
||||
EXP_TRADE_ESCR_INVOICE = 200
|
||||
|
||||
# Time a order is public in the book HOURS
|
||||
PUBLIC_ORDER_DURATION = 6
|
||||
# Time to provide a valid invoice and the trade escrow MINUTES
|
||||
INVOICE_AND_ESCROW_DURATION = 30
|
||||
# Time to confim chat and confirm fiat (time to Fiat Sent confirmation) HOURS
|
||||
FIAT_EXCHANGE_DURATION = 4
|
||||
|
||||
# Username for HTLCs escrows
|
||||
ESCROW_USERNAME = 'admin'
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -641,3 +641,7 @@ frontend/static/frontend/main*
|
||||
|
||||
# robosats
|
||||
frontend/static/assets/avatars*
|
||||
api/lightning/lightning*
|
||||
api/lightning/invoices*
|
||||
api/lightning/router*
|
||||
api/lightning/googleapis*
|
||||
|
@ -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)
|
||||
|
110
api/lightning.py
110
api/lightning.py
@ -1,110 +0,0 @@
|
||||
# import codecs, grpc, os
|
||||
# import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
|
||||
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
#######
|
||||
# Placeholder functions
|
||||
# Should work with LND (maybe c-lightning in the future)
|
||||
|
||||
class LNNode():
|
||||
'''
|
||||
Place holder functions to interact with Lightning Node
|
||||
'''
|
||||
|
||||
# macaroon = codecs.encode(open('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon', 'rb').read(), 'hex')
|
||||
# os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
|
||||
# cert = open('LND_DIR/tls.cert', 'rb').read()
|
||||
# ssl_creds = grpc.ssl_channel_credentials(cert)
|
||||
# channel = grpc.secure_channel('localhost:10009', ssl_creds)
|
||||
# stub = lightningstub.LightningStub(channel)
|
||||
|
||||
def gen_hold_invoice(num_satoshis, description, expiry):
|
||||
'''Generates hold invoice to publish an order'''
|
||||
# TODO
|
||||
invoice = ''.join(random.choices(string.ascii_uppercase + string.digits, k=80)) #FIX
|
||||
payment_hash = ''.join(random.choices(string.ascii_uppercase + string.digits, k=40)) #FIX
|
||||
expires_at = timezone.now() + timedelta(hours=8) ##FIX
|
||||
|
||||
return invoice, payment_hash, expires_at
|
||||
|
||||
def validate_hold_invoice_locked(payment_hash):
|
||||
'''Checks if hodl invoice is locked'''
|
||||
|
||||
# request = ln.InvoiceSubscription()
|
||||
# When invoice is settled, return true. If time expires, return False.
|
||||
# for invoice in stub.SubscribeInvoices(request):
|
||||
# print(invoice)
|
||||
|
||||
return True
|
||||
|
||||
def validate_ln_invoice(invoice, num_satoshis):
|
||||
'''Checks if the submited LN invoice is as expected'''
|
||||
|
||||
# request = lnrpc.PayReqString(pay_req=invoice)
|
||||
# response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)])
|
||||
|
||||
# # {
|
||||
# # "destination": <string>,
|
||||
# # "payment_hash": <string>,
|
||||
# # "num_satoshis": <int64>,
|
||||
# # "timestamp": <int64>,
|
||||
# # "expiry": <int64>,
|
||||
# # "description": <string>,
|
||||
# # "description_hash": <string>,
|
||||
# # "fallback_addr": <string>,
|
||||
# # "cltv_expiry": <int64>,
|
||||
# # "route_hints": <array RouteHint>,
|
||||
# # "payment_addr": <bytes>,
|
||||
# # "num_msat": <int64>,
|
||||
# # "features": <array FeaturesEntry>,
|
||||
# # }
|
||||
|
||||
# if not response['num_satoshis'] == num_satoshis:
|
||||
# return False, {'bad_invoice':f'The invoice provided is not for {num_satoshis}. '}, None, None, None
|
||||
# description = response['description']
|
||||
# payment_hash = response['payment_hash']
|
||||
# expires_at = timezone(response['expiry'])
|
||||
# if payment_hash and expires_at > timezone.now():
|
||||
# return True, None, description, payment_hash, expires_at
|
||||
|
||||
valid = True
|
||||
context = None
|
||||
description = 'Placeholder desc' # TODO decrypt from LN invoice
|
||||
payment_hash = '567&*GIHU126' # TODO decrypt
|
||||
expires_at = timezone.now() # TODO decrypt
|
||||
|
||||
return valid, context, description, payment_hash, expires_at
|
||||
|
||||
def pay_invoice(invoice):
|
||||
'''Sends sats to buyer, or cancelinvoices'''
|
||||
return True
|
||||
|
||||
def check_if_hold_invoice_is_locked(payment_hash):
|
||||
'''Every hodl invoice that is in state INVGEN
|
||||
Has to be checked for payment received until
|
||||
the window expires'''
|
||||
|
||||
return True
|
||||
|
||||
def settle_hold_htlcs(payment_hash):
|
||||
'''Charges a LN hold invoice'''
|
||||
return True
|
||||
|
||||
def return_hold_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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
220
api/lightning/node.py
Normal file
220
api/lightning/node.py
Normal file
@ -0,0 +1,220 @@
|
||||
import grpc, os, hashlib, secrets, json
|
||||
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
|
||||
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
|
||||
from . import router_pb2 as routerrpc, router_pb2_grpc as routerstub
|
||||
|
||||
from decouple import config
|
||||
from base64 import b64decode
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
from django.utils import timezone
|
||||
|
||||
#######
|
||||
# Should work with LND (c-lightning in the future if there are features that deserve the work)
|
||||
#######
|
||||
|
||||
CERT = b64decode(config('LND_CERT_BASE64'))
|
||||
MACAROON = b64decode(config('LND_MACAROON_BASE64'))
|
||||
LND_GRPC_HOST = config('LND_GRPC_HOST')
|
||||
|
||||
class LNNode():
|
||||
|
||||
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
|
||||
|
||||
creds = grpc.ssl_channel_credentials(CERT)
|
||||
channel = grpc.secure_channel(LND_GRPC_HOST, creds)
|
||||
|
||||
lightningstub = lightningstub.LightningStub(channel)
|
||||
invoicesstub = invoicesstub.InvoicesStub(channel)
|
||||
routerstub = routerstub.RouterStub(channel)
|
||||
|
||||
payment_failure_context = {
|
||||
0: "Payment isn't failed (yet)",
|
||||
1: "There are more routes to try, but the payment timeout was exceeded.",
|
||||
2: "All possible routes were tried and failed permanently. Or were no routes to the destination at all.",
|
||||
3: "A non-recoverable error has occured.",
|
||||
4: "Payment details incorrect (unknown hash, invalid amt or invalid final cltv delta)",
|
||||
5: "Insufficient local balance."}
|
||||
|
||||
@classmethod
|
||||
def decode_payreq(cls, invoice):
|
||||
'''Decodes a lightning payment request (invoice)'''
|
||||
request = lnrpc.PayReqString(pay_req=invoice)
|
||||
response = cls.lightningstub.DecodePayReq(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def cancel_return_hold_invoice(cls, payment_hash):
|
||||
'''Cancels or returns a hold invoice'''
|
||||
request = invoicesrpc.CancelInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
|
||||
response = cls.invoicesstub.CancelInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
# Fix this: tricky because canceling sucessfully an invoice has no response. TODO
|
||||
return str(response) == "" # True if no response, false otherwise.
|
||||
|
||||
@classmethod
|
||||
def settle_hold_invoice(cls, preimage):
|
||||
'''settles a hold invoice'''
|
||||
request = invoicesrpc.SettleInvoiceMsg(preimage=bytes.fromhex(preimage))
|
||||
response = cls.invoicesstub.SettleInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
# Fix this: tricky because settling sucessfully an invoice has None response. TODO
|
||||
return str(response)=="" # True if no response, false otherwise.
|
||||
|
||||
@classmethod
|
||||
def gen_hold_invoice(cls, num_satoshis, description, expiry):
|
||||
'''Generates hold invoice'''
|
||||
|
||||
hold_payment = {}
|
||||
# The preimage is a random hash of 256 bits entropy
|
||||
preimage = hashlib.sha256(secrets.token_bytes(nbytes=32)).digest()
|
||||
|
||||
# Its hash is used to generate the hold invoice
|
||||
r_hash = hashlib.sha256(preimage).digest()
|
||||
|
||||
request = invoicesrpc.AddHoldInvoiceRequest(
|
||||
memo=description,
|
||||
value=num_satoshis,
|
||||
hash=r_hash,
|
||||
expiry=expiry)
|
||||
response = cls.invoicesstub.AddHoldInvoice(request, metadata=[('macaroon', MACAROON.hex())])
|
||||
|
||||
hold_payment['invoice'] = response.payment_request
|
||||
payreq_decoded = cls.decode_payreq(hold_payment['invoice'])
|
||||
hold_payment['preimage'] = preimage.hex()
|
||||
hold_payment['payment_hash'] = payreq_decoded.payment_hash
|
||||
hold_payment['created_at'] = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
||||
hold_payment['expires_at'] = hold_payment['created_at'] + timedelta(seconds=payreq_decoded.expiry)
|
||||
|
||||
return hold_payment
|
||||
|
||||
@classmethod
|
||||
def validate_hold_invoice_locked(cls, payment_hash):
|
||||
'''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)
|
||||
|
||||
# TODO ERROR HANDLING
|
||||
if response.state == 0: # OPEN
|
||||
print('STATUS: OPEN')
|
||||
pass
|
||||
if response.state == 1: # SETTLED
|
||||
pass
|
||||
if response.state == 2: # CANCELLED
|
||||
pass
|
||||
if response.state == 3: # ACCEPTED (LOCKED)
|
||||
print('STATUS: ACCEPTED')
|
||||
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.
|
||||
|
||||
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
|
||||
def validate_ln_invoice(cls, invoice, num_satoshis):
|
||||
'''Checks if the submited LN invoice comforms to expectations'''
|
||||
|
||||
buyer_invoice = {
|
||||
'valid': False,
|
||||
'context': None,
|
||||
'description': None,
|
||||
'payment_hash': None,
|
||||
'created_at': None,
|
||||
'expires_at': None,
|
||||
}
|
||||
|
||||
try:
|
||||
payreq_decoded = cls.decode_payreq(invoice)
|
||||
print(payreq_decoded)
|
||||
except:
|
||||
buyer_invoice['context'] = {'bad_invoice':'Does not look like a valid lightning invoice'}
|
||||
return buyer_invoice
|
||||
|
||||
if payreq_decoded.num_satoshis == 0:
|
||||
buyer_invoice['context'] = {'bad_invoice':'The invoice provided has no explicit amount'}
|
||||
return buyer_invoice
|
||||
|
||||
if not payreq_decoded.num_satoshis == num_satoshis:
|
||||
buyer_invoice['context'] = {'bad_invoice':'The invoice provided is not for '+'{:,}'.format(num_satoshis)+ ' Sats'}
|
||||
return buyer_invoice
|
||||
|
||||
buyer_invoice['created_at'] = timezone.make_aware(datetime.fromtimestamp(payreq_decoded.timestamp))
|
||||
buyer_invoice['expires_at'] = buyer_invoice['created_at'] + timedelta(seconds=payreq_decoded.expiry)
|
||||
|
||||
if buyer_invoice['expires_at'] < timezone.now():
|
||||
buyer_invoice['context'] = {'bad_invoice':f'The invoice provided has already expired'}
|
||||
return buyer_invoice
|
||||
|
||||
buyer_invoice['valid'] = True
|
||||
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'''
|
||||
|
||||
fee_limit_sat = max(num_satoshis * 0.0002, 10) # 200 ppm or 10 sats
|
||||
request = routerrpc.SendPaymentRequest(
|
||||
payment_request=invoice,
|
||||
fee_limit_sat=fee_limit_sat,
|
||||
timeout_seconds=60)
|
||||
|
||||
for response in cls.routerstub.SendPaymentV2(request, metadata=[('macaroon', MACAROON.hex())]):
|
||||
print(response)
|
||||
print(response.status)
|
||||
|
||||
# TODO ERROR HANDLING
|
||||
if response.status == 0 : # Status 0 'UNKNOWN'
|
||||
pass
|
||||
if response.status == 1 : # Status 1 'IN_FLIGHT'
|
||||
return True, 'In flight'
|
||||
if response.status == 3 : # 4 'FAILED' ??
|
||||
'''0 Payment isn't failed (yet).
|
||||
1 There are more routes to try, but the payment timeout was exceeded.
|
||||
2 All possible routes were tried and failed permanently. Or were no routes to the destination at all.
|
||||
3 A non-recoverable error has occured.
|
||||
4 Payment details incorrect (unknown hash, invalid amt or invalid final cltv delta)
|
||||
5 Insufficient local balance.
|
||||
'''
|
||||
context = cls.payment_failure_context[response.failure_reason]
|
||||
return False, context
|
||||
if response.status == 2 : # STATUS 'SUCCEEDED'
|
||||
return True, None
|
||||
|
||||
|
||||
# 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=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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
360
api/logics.py
360
api/logics.py
@ -1,11 +1,13 @@
|
||||
from datetime import timedelta
|
||||
from datetime import time, timedelta
|
||||
from django.utils import timezone
|
||||
from .lightning import LNNode
|
||||
from .lightning.node import LNNode
|
||||
|
||||
from .models import Order, LNPayment, MarketTick, User
|
||||
from decouple import config
|
||||
from .utils import get_exchange_rate
|
||||
|
||||
import math
|
||||
|
||||
FEE = float(config('FEE'))
|
||||
BOND_SIZE = float(config('BOND_SIZE'))
|
||||
MARKET_PRICE_API = config('MARKET_PRICE_API')
|
||||
@ -17,11 +19,13 @@ MAX_TRADE = int(config('MAX_TRADE'))
|
||||
|
||||
EXP_MAKER_BOND_INVOICE = int(config('EXP_MAKER_BOND_INVOICE'))
|
||||
EXP_TAKER_BOND_INVOICE = int(config('EXP_TAKER_BOND_INVOICE'))
|
||||
EXP_TRADE_ESCR_INVOICE = int(config('EXP_TRADE_ESCR_INVOICE'))
|
||||
|
||||
BOND_EXPIRY = int(config('BOND_EXPIRY'))
|
||||
ESCROW_EXPIRY = int(config('ESCROW_EXPIRY'))
|
||||
|
||||
PUBLIC_ORDER_DURATION = int(config('PUBLIC_ORDER_DURATION'))
|
||||
INVOICE_AND_ESCROW_DURATION = int(config('INVOICE_AND_ESCROW_DURATION'))
|
||||
FIAT_EXCHANGE_DURATION = int(config('FIAT_EXCHANGE_DURATION'))
|
||||
|
||||
class Logics():
|
||||
|
||||
@ -51,6 +55,7 @@ class Logics():
|
||||
else:
|
||||
order.taker = user
|
||||
order.status = Order.Status.TAK
|
||||
order.expires_at = timezone.now() + timedelta(minutes=EXP_TAKER_BOND_INVOICE)
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
@ -80,22 +85,43 @@ class Logics():
|
||||
exchange_rate = get_exchange_rate(Order.currency_dict[str(order.currency)])
|
||||
if not order.is_explicit:
|
||||
premium = order.premium
|
||||
price = exchange_rate
|
||||
price = exchange_rate * (1+float(premium)/100)
|
||||
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
|
||||
premium = int(premium*100) # 2 decimals left
|
||||
price = order_rate
|
||||
|
||||
premium = int(premium*100) # 2 decimals left
|
||||
significant_digits = 6
|
||||
price = round(price, significant_digits - int(math.floor(math.log10(abs(price)))) - 1)
|
||||
|
||||
return price, premium
|
||||
|
||||
def order_expires(order):
|
||||
''' General case when time runs out. Only
|
||||
used when the maker does not lock a publishing bond'''
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
|
||||
def kick_taker(order):
|
||||
''' The taker did not lock the taker_bond. Now he has to go'''
|
||||
# Add a time out to the taker
|
||||
profile = order.taker.profile
|
||||
profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT)
|
||||
profile.save()
|
||||
|
||||
# Delete the taker_bond payment request, and make order public again
|
||||
if LNNode.cancel_return_hold_invoice(order.taker_bond.payment_hash):
|
||||
order.status = Order.Status.PUB
|
||||
order.taker = None
|
||||
order.taker_bond = None
|
||||
order.expires_at = timezone.now() + timedelta(hours=PUBLIC_ORDER_DURATION) ## TO FIX. Restore the remaining order durantion, not all of it!
|
||||
order.save()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def buyer_invoice_amount(cls, order, user):
|
||||
''' Computes buyer invoice amount. Uses order.last_satoshis,
|
||||
@ -118,9 +144,10 @@ class Logics():
|
||||
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
|
||||
buyer_invoice = LNNode.validate_ln_invoice(invoice, num_satoshis)
|
||||
|
||||
if not buyer_invoice['valid']:
|
||||
return False, buyer_invoice['context']
|
||||
|
||||
order.buyer_invoice, _ = LNPayment.objects.update_or_create(
|
||||
concept = LNPayment.Concepts.PAYBUYER,
|
||||
@ -132,48 +159,44 @@ class Logics():
|
||||
'invoice' : invoice,
|
||||
'status' : LNPayment.Status.VALIDI,
|
||||
'num_satoshis' : num_satoshis,
|
||||
'description' : description,
|
||||
'payment_hash' : payment_hash,
|
||||
'expires_at' : expires_at}
|
||||
'description' : buyer_invoice['description'],
|
||||
'payment_hash' : buyer_invoice['payment_hash'],
|
||||
'created_at' : buyer_invoice['created_at'],
|
||||
'expires_at' : buyer_invoice['expires_at']}
|
||||
)
|
||||
|
||||
# If the order status is 'Waiting for invoice'. Move forward to 'waiting for invoice'
|
||||
if order.status == Order.Status.WFE: order.status = Order.Status.CHA
|
||||
# If the order status is 'Waiting for invoice'. Move forward to 'chat'
|
||||
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' or to 'chat'
|
||||
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
|
||||
if order.status == Order.Status.WF2:
|
||||
print(order.trade_escrow)
|
||||
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
|
||||
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def rate_counterparty(cls, order, user, rating):
|
||||
def add_profile_rating(profile, rating):
|
||||
''' adds a new rating to a user profile'''
|
||||
|
||||
# If the trade is finished
|
||||
if order.status > Order.Status.PAY:
|
||||
profile.total_ratings = profile.total_ratings + 1
|
||||
latest_ratings = profile.latest_ratings
|
||||
if len(latest_ratings) <= 1:
|
||||
profile.latest_ratings = [rating]
|
||||
profile.avg_rating = 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)
|
||||
else:
|
||||
return False, {'bad_request':'You cannot rate your counterparty yet.'}
|
||||
latest_ratings = list(latest_ratings).append(rating)
|
||||
profile.latest_ratings = latest_ratings
|
||||
profile.avg_rating = sum(latest_ratings) / len(latest_ratings)
|
||||
|
||||
order.save()
|
||||
return True, None
|
||||
profile.save()
|
||||
|
||||
def is_penalized(user):
|
||||
''' Checks if a user that is not participant of orders
|
||||
@ -200,11 +223,15 @@ 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. 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)'''
|
||||
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_maker_bond(order):
|
||||
order.maker = None
|
||||
order.status = Order.Status.UCA
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
# 3) When taker cancels before bond
|
||||
''' The order goes back to the book as public.
|
||||
@ -212,7 +239,7 @@ class Logics():
|
||||
elif order.status == Order.Status.TAK and order.taker == user:
|
||||
# adds a timeout penalty
|
||||
user.profile.penalty_expiration = timezone.now() + timedelta(seconds=PENALTY_TIMEOUT)
|
||||
user.save()
|
||||
user.profile.save()
|
||||
|
||||
order.taker = None
|
||||
order.status = Order.Status.PUB
|
||||
@ -225,6 +252,29 @@ class Logics():
|
||||
The order goes into the public book if taker cancels.
|
||||
In both cases there is a small fee.'''
|
||||
|
||||
# 4.a) When maker cancel after bond (before escrow)
|
||||
'''The order into cancelled status if maker cancels.'''
|
||||
elif order.status > Order.Status.PUB and order.status < Order.Status.CHA and order.maker == user:
|
||||
#Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||
valid = cls.settle_maker_bond(order)
|
||||
if valid:
|
||||
order.maker = None
|
||||
order.status = Order.Status.UCA
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
# 4.b) When taker cancel after bond (before escrow)
|
||||
'''The order into cancelled status if maker cancels.'''
|
||||
elif order.status > Order.Status.TAK and order.status < Order.Status.CHA and order.taker == user:
|
||||
# Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||
valid = cls.settle_taker_bond(order)
|
||||
if valid:
|
||||
order.taker = None
|
||||
order.status = Order.Status.PUB
|
||||
# order.taker_bond = None # TODO fix this, it overrides the information about the settled taker bond. Might make admin tasks hard.
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
# 5) When trade collateral has been posted (after escrow)
|
||||
'''Always goes to cancelled status. Collaboration is needed.
|
||||
When a user asks for cancel, 'order.is_pending_cancel' goes True.
|
||||
@ -234,135 +284,211 @@ class Logics():
|
||||
else:
|
||||
return False, {'bad_request':'You cannot cancel this order'}
|
||||
|
||||
@classmethod
|
||||
def is_maker_bond_locked(cls, order):
|
||||
if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash):
|
||||
order.maker_bond.status = LNPayment.Status.LOCKED
|
||||
order.maker_bond.save()
|
||||
order.status = Order.Status.PUB
|
||||
# With the bond confirmation the order is extended 'public_order_duration' hours
|
||||
order.expires_at = timezone.now() + timedelta(hours=PUBLIC_ORDER_DURATION)
|
||||
order.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def gen_maker_hold_invoice(cls, order, user):
|
||||
|
||||
# Do not gen and cancel if order is more than 5 minutes old
|
||||
# Do not gen and cancel if order is older than expiry time
|
||||
if order.expires_at < timezone.now():
|
||||
cls.order_expires(order)
|
||||
return False, {'bad_request':'Invoice expired. You did not confirm publishing the order in time. Make a new order.'}
|
||||
|
||||
# Return the previous invoice if there was one and is still unpaid
|
||||
if order.maker_bond:
|
||||
if order.maker_bond.status == LNPayment.Status.INVGEN:
|
||||
return True, {'bond_invoice':order.maker_bond.invoice,'bond_satoshis':order.maker_bond.num_satoshis}
|
||||
else:
|
||||
if cls.is_maker_bond_locked(order):
|
||||
return False, None
|
||||
elif order.maker_bond.status == LNPayment.Status.INVGEN:
|
||||
return True, {'bond_invoice':order.maker_bond.invoice,'bond_satoshis':order.maker_bond.num_satoshis}
|
||||
|
||||
# If there was no maker_bond object yet, generates one
|
||||
order.last_satoshis = cls.satoshis_now(order)
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
description = f'RoboSats - Publishing {str(order)} - This bond will return to you if you do not cheat.'
|
||||
|
||||
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
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
|
||||
order.maker_bond = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.MAKEBOND,
|
||||
type = LNPayment.Types.hold,
|
||||
type = LNPayment.Types.HOLD,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
invoice = hold_payment['invoice'],
|
||||
preimage = hold_payment['preimage'],
|
||||
status = LNPayment.Status.INVGEN,
|
||||
num_satoshis = bond_satoshis,
|
||||
description = description,
|
||||
payment_hash = payment_hash,
|
||||
expires_at = expires_at)
|
||||
payment_hash = hold_payment['payment_hash'],
|
||||
created_at = hold_payment['created_at'],
|
||||
expires_at = hold_payment['expires_at'])
|
||||
|
||||
order.save()
|
||||
return True, {'bond_invoice':invoice,'bond_satoshis':bond_satoshis}
|
||||
return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis}
|
||||
|
||||
@classmethod
|
||||
def is_taker_bond_locked(cls, order):
|
||||
if LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash):
|
||||
# THE TRADE AMOUNT IS FINAL WITH THE CONFIRMATION OF THE TAKER BOND!
|
||||
# (This is the last update to "last_satoshis", it becomes the escrow amount next!)
|
||||
order.last_satoshis = cls.satoshis_now(order)
|
||||
order.taker_bond.status = LNPayment.Status.LOCKED
|
||||
order.taker_bond.save()
|
||||
|
||||
# Log a market tick
|
||||
MarketTick.log_a_tick(order)
|
||||
|
||||
# With the bond confirmation the order is extended 'public_order_duration' hours
|
||||
order.expires_at = timezone.now() + timedelta(minutes=INVOICE_AND_ESCROW_DURATION)
|
||||
order.status = Order.Status.WF2
|
||||
order.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def gen_taker_hold_invoice(cls, order, user):
|
||||
|
||||
# Do not gen and cancel if a taker invoice is there and older than X minutes and unpaid still
|
||||
# Do not gen and kick out the taker if order is older than expiry time
|
||||
if order.expires_at < timezone.now():
|
||||
cls.kick_taker(order)
|
||||
return False, {'bad_request':'Invoice expired. You did not confirm taking the order in time.'}
|
||||
|
||||
# Do not gen if a taker invoice exist. Do not return if it is already locked. Return the old one if still waiting.
|
||||
if order.taker_bond:
|
||||
# Check if status is INVGEN and still not expired
|
||||
if order.taker_bond.status == LNPayment.Status.INVGEN:
|
||||
if order.taker_bond.created_at > (timezone.now()+timedelta(minutes=EXP_TAKER_BOND_INVOICE)):
|
||||
cls.cancel_order(order, user, 3) # State 3, cancel order before taker bond
|
||||
return False, {'bad_request':'Invoice expired. You did not confirm taking the order in time.'}
|
||||
# Return the previous invoice there was with INVGEN status
|
||||
else:
|
||||
return True, {'bond_invoice':order.taker_bond.invoice,'bond_satoshis':order.taker_bond.num_satoshis}
|
||||
# Invoice exists, but was already locked or settled
|
||||
else:
|
||||
if cls.is_taker_bond_locked(order):
|
||||
return False, None
|
||||
elif order.taker_bond.status == LNPayment.Status.INVGEN:
|
||||
return True, {'bond_invoice':order.taker_bond.invoice,'bond_satoshis':order.taker_bond.num_satoshis}
|
||||
|
||||
order.last_satoshis = cls.satoshis_now(order) # LOCKS THE AMOUNT OF SATOSHIS FOR THE TRADE
|
||||
# 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 bond will return to you if you do not cheat.'
|
||||
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
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
hold_payment = LNNode.gen_hold_invoice(bond_satoshis, description, BOND_EXPIRY*3600)
|
||||
|
||||
order.taker_bond = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.TAKEBOND,
|
||||
type = LNPayment.Types.hold,
|
||||
type = LNPayment.Types.HOLD,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
invoice = hold_payment['invoice'],
|
||||
preimage = hold_payment['preimage'],
|
||||
status = LNPayment.Status.INVGEN,
|
||||
num_satoshis = bond_satoshis,
|
||||
description = description,
|
||||
payment_hash = payment_hash,
|
||||
expires_at = expires_at)
|
||||
payment_hash = hold_payment['payment_hash'],
|
||||
created_at = hold_payment['created_at'],
|
||||
expires_at = hold_payment['expires_at'])
|
||||
|
||||
# Extend expiry time to allow for escrow deposit
|
||||
## Not here, on func for confirming taker collar. order.expires_at = timezone.now() + timedelta(minutes=EXP_TRADE_ESCR_INVOICE)
|
||||
|
||||
order.save()
|
||||
return True, {'bond_invoice':invoice,'bond_satoshis': bond_satoshis}
|
||||
return True, {'bond_invoice': hold_payment['invoice'], 'bond_satoshis': bond_satoshis}
|
||||
|
||||
|
||||
@classmethod
|
||||
def is_trade_escrow_locked(cls, order):
|
||||
if 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(hours=FIAT_EXCHANGE_DURATION)
|
||||
order.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def gen_escrow_hold_invoice(cls, order, user):
|
||||
# Do not generate and cancel if an invoice is there and older than X minutes and unpaid still
|
||||
|
||||
# Do not generate if escrow deposit time has expired
|
||||
if order.expires_at < timezone.now():
|
||||
cls.cancel_order(order,user)
|
||||
return False, {'bad_request':'Invoice expired. You did not send the escrow in time.'}
|
||||
|
||||
# Do not gen if an escrow invoice exist. Do not return if it is already locked. Return the old one if still waiting.
|
||||
if order.trade_escrow:
|
||||
# Check if status is INVGEN and still not expired
|
||||
if order.trade_escrow.status == LNPayment.Status.INVGEN:
|
||||
if order.trade_escrow.created_at > (timezone.now()+timedelta(minutes=EXP_TRADE_ESCR_INVOICE)): # Expired
|
||||
cls.cancel_order(order, user, 4) # State 4, cancel order before trade escrow locked
|
||||
return False, {'bad_request':'Invoice expired. You did not lock the trade escrow in time.'}
|
||||
# Return the previous invoice there was with INVGEN status
|
||||
else:
|
||||
return True, {'escrow_invoice': order.trade_escrow.invoice, 'escrow_satoshis':order.trade_escrow.num_satoshis}
|
||||
# Invoice exists, but was already locked or settled
|
||||
else:
|
||||
return False, None # Does not return any context of a healthy locked escrow
|
||||
if cls.is_trade_escrow_locked(order):
|
||||
return False, None
|
||||
elif order.trade_escrow.status == LNPayment.Status.INVGEN:
|
||||
return True, {'escrow_invoice':order.trade_escrow.invoice, 'escrow_satoshis':order.trade_escrow.num_satoshis}
|
||||
|
||||
escrow_satoshis = order.last_satoshis # Trade sats amount was fixed at the time of taker bond generation (order.last_satoshis)
|
||||
description = f'RoboSats - Escrow amount for {str(order)} - This escrow will be released to the buyer once you confirm you received the fiat.'
|
||||
# If there was no taker_bond object yet, generates 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."
|
||||
|
||||
# Gen hold Invoice
|
||||
invoice, payment_hash, expires_at = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
||||
hold_payment = LNNode.gen_hold_invoice(escrow_satoshis, description, ESCROW_EXPIRY*3600)
|
||||
|
||||
order.trade_escrow = LNPayment.objects.create(
|
||||
concept = LNPayment.Concepts.TRESCROW,
|
||||
type = LNPayment.Types.hold,
|
||||
type = LNPayment.Types.HOLD,
|
||||
sender = user,
|
||||
receiver = User.objects.get(username=ESCROW_USERNAME),
|
||||
invoice = invoice,
|
||||
invoice = hold_payment['invoice'],
|
||||
preimage = hold_payment['preimage'],
|
||||
status = LNPayment.Status.INVGEN,
|
||||
num_satoshis = escrow_satoshis,
|
||||
description = description,
|
||||
payment_hash = payment_hash,
|
||||
expires_at = expires_at)
|
||||
payment_hash = hold_payment['payment_hash'],
|
||||
created_at = hold_payment['created_at'],
|
||||
expires_at = hold_payment['expires_at'])
|
||||
|
||||
order.save()
|
||||
return True, {'escrow_invoice':invoice,'escrow_satoshis': escrow_satoshis}
|
||||
return True, {'escrow_invoice':hold_payment['invoice'],'escrow_satoshis': escrow_satoshis}
|
||||
|
||||
def settle_escrow(order):
|
||||
''' Settles the trade escrow HTLC'''
|
||||
''' Settles the trade escrow hold invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.settle_hold_invoice(order.trade_escrow.preimage):
|
||||
order.trade_escrow.status = LNPayment.Status.SETLED
|
||||
order.trade_escrow.save()
|
||||
return True
|
||||
|
||||
valid = LNNode.settle_hold_htlcs(order.trade_escrow.payment_hash)
|
||||
return valid
|
||||
def settle_maker_bond(order):
|
||||
''' Settles the maker bond hold invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.settle_hold_invoice(order.maker_bond.preimage):
|
||||
order.maker_bond.status = LNPayment.Status.SETLED
|
||||
order.maker_bond.save()
|
||||
return True
|
||||
|
||||
def settle_taker_bond(order):
|
||||
''' Settles the taker bond hold invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.settle_hold_invoice(order.taker_bond.preimage):
|
||||
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):
|
||||
''' Settles the trade escrow HTLC'''
|
||||
''' Pay buyer invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
|
||||
valid = LNNode.pay_invoice(order.buyer_invoice.payment_hash)
|
||||
return valid
|
||||
if LNNode.pay_invoice(order.buyer_invoice.invoice, order.buyer_invoice.num_satoshis):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def confirm_fiat(cls, order, user):
|
||||
@ -385,16 +511,42 @@ 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
|
||||
is_payed, context = cls.pay_buyer_invoice(order) ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
|
||||
if is_payed:
|
||||
order.status = Order.Status.SUC
|
||||
order.buyer_invoice.status = LNPayment.Status.SUCCED
|
||||
order.expires_at = timezone.now() + timedelta(days=1) # One day to rate / see this order.
|
||||
order.save()
|
||||
|
||||
# RETURN THE BONDS
|
||||
cls.return_bond(order.taker_bond)
|
||||
cls.return_bond(order.maker_bond)
|
||||
else:
|
||||
# error handling here
|
||||
pass
|
||||
else:
|
||||
return False, {'bad_request':'You cannot confirm the fiat payment at this stage'}
|
||||
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def rate_counterparty(cls, order, user, rating):
|
||||
|
||||
# If the trade is finished
|
||||
if order.status > Order.Status.PAY:
|
||||
# if maker, rates taker
|
||||
if order.maker == user:
|
||||
cls.add_profile_rating(order.taker.profile, rating)
|
||||
# if taker, rates maker
|
||||
if order.taker == user:
|
||||
cls.add_profile_rating(order.maker.profile, rating)
|
||||
else:
|
||||
return False, {'bad_request':'You cannot rate your counterparty yet.'}
|
||||
|
||||
return True, None
|
@ -18,8 +18,8 @@ BOND_SIZE = float(config('BOND_SIZE'))
|
||||
class LNPayment(models.Model):
|
||||
|
||||
class Types(models.IntegerChoices):
|
||||
NORM = 0, 'Regular invoice' # Only outgoing HTLCs will be regular invoices (Non-hold)
|
||||
hold = 1, 'hold invoice'
|
||||
NORM = 0, 'Regular invoice' # Only outgoing buyer payment will be a regular invoice (Non-hold)
|
||||
HOLD = 1, 'hold invoice'
|
||||
|
||||
class Concepts(models.IntegerChoices):
|
||||
MAKEBOND = 0, 'Maker bond'
|
||||
@ -32,24 +32,27 @@ class LNPayment(models.Model):
|
||||
LOCKED = 1, 'Locked'
|
||||
SETLED = 2, 'Settled'
|
||||
RETNED = 3, 'Returned'
|
||||
MISSNG = 4, 'Missing'
|
||||
EXPIRE = 4, 'Expired'
|
||||
VALIDI = 5, 'Valid'
|
||||
PAYING = 6, 'Paying ongoing'
|
||||
FAILRO = 7, 'Failed routing'
|
||||
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)
|
||||
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HOLD)
|
||||
concept = models.PositiveSmallIntegerField(choices=Concepts.choices, null=False, default=Concepts.MAKEBOND)
|
||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.INVGEN)
|
||||
routing_retries = models.PositiveSmallIntegerField(null=False, default=0)
|
||||
|
||||
# payment info
|
||||
invoice = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
|
||||
payment_hash = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
|
||||
description = models.CharField(max_length=300, unique=False, null=True, default=None, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
expires_at = models.DateTimeField()
|
||||
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=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()
|
||||
|
||||
# involved parties
|
||||
sender = models.ForeignKey(User, related_name='sender', on_delete=models.CASCADE, null=True, default=None)
|
||||
@ -79,7 +82,7 @@ class Order(models.Model):
|
||||
DIS = 11, 'In dispute'
|
||||
CCA = 12, 'Collaboratively cancelled'
|
||||
PAY = 13, 'Sending satoshis to buyer'
|
||||
SUC = 14, 'Sucessfully settled'
|
||||
SUC = 14, 'Sucessful trade'
|
||||
FAI = 15, 'Failed lightning network routing'
|
||||
MLD = 16, 'Maker lost dispute'
|
||||
TLD = 17, 'Taker lost dispute'
|
||||
|
@ -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)
|
30
api/utils.py
30
api/utils.py
@ -1,11 +1,10 @@
|
||||
|
||||
import requests, ring, os
|
||||
from decouple import config
|
||||
import requests
|
||||
import ring
|
||||
|
||||
storage = {}
|
||||
market_cache = {}
|
||||
|
||||
@ring.dict(storage, expire=30) #keeps in cache for 30 seconds
|
||||
@ring.dict(market_cache, expire=30) #keeps in cache for 30 seconds
|
||||
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)
|
||||
@ -13,4 +12,25 @@ def get_exchange_rate(currency):
|
||||
market_prices = requests.get(config('MARKET_PRICE_API')).json()
|
||||
exchange_rate = float(market_prices[currency]['last'])
|
||||
|
||||
return exchange_rate
|
||||
return exchange_rate
|
||||
|
||||
lnd_v_cache = {}
|
||||
|
||||
@ring.dict(lnd_v_cache, expire=3600) #keeps in cache for 3600 seconds
|
||||
def get_lnd_version():
|
||||
|
||||
stream = os.popen('lnd --version')
|
||||
lnd_version = stream.read()[:-1]
|
||||
|
||||
return lnd_version
|
||||
|
||||
robosats_commit_cache = {}
|
||||
|
||||
@ring.dict(robosats_commit_cache, expire=3600)
|
||||
def get_commit_robosats():
|
||||
|
||||
stream = os.popen('git log -n 1 --pretty=format:"%H"')
|
||||
lnd_version = stream.read()
|
||||
|
||||
return lnd_version
|
||||
|
||||
|
74
api/views.py
74
api/views.py
@ -1,4 +1,5 @@
|
||||
from re import T
|
||||
from django.db.models import query
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||
from rest_framework.views import APIView
|
||||
@ -8,8 +9,9 @@ from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from .serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer
|
||||
from .models import LNPayment, Order
|
||||
from .models import LNPayment, MarketTick, Order
|
||||
from .logics import Logics
|
||||
from .utils import get_lnd_version, get_commit_robosats
|
||||
|
||||
from .nick_generator.nick_generator import NickGenerator
|
||||
from robohash import Robohash
|
||||
@ -18,7 +20,7 @@ from math import log2
|
||||
import numpy as np
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
from datetime import timedelta, datetime
|
||||
from django.utils import timezone
|
||||
from decouple import config
|
||||
|
||||
@ -58,7 +60,7 @@ class MakerView(CreateAPIView):
|
||||
premium=premium,
|
||||
satoshis=satoshis,
|
||||
is_explicit=is_explicit,
|
||||
expires_at=timezone.now()+timedelta(minutes=EXP_MAKER_BOND_INVOICE), # TODO Move to class method
|
||||
expires_at=timezone.now()+timedelta(seconds=EXP_MAKER_BOND_INVOICE), # TODO Move to class method
|
||||
maker=request.user)
|
||||
|
||||
# TODO move to Order class method when new instance is created!
|
||||
@ -93,11 +95,11 @@ class OrderView(viewsets.ViewSet):
|
||||
# This is our order.
|
||||
order = order[0]
|
||||
|
||||
# 1) If order expired
|
||||
# 1) If order has expired
|
||||
if order.status == Order.Status.EXP:
|
||||
return Response({'bad_request':'This order has expired'},status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 2) If order cancelled
|
||||
# 2) If order has been cancelled
|
||||
if order.status == Order.Status.UCA:
|
||||
return Response({'bad_request':'This order has been cancelled by the maker'},status.HTTP_400_BAD_REQUEST)
|
||||
if order.status == Order.Status.CCA:
|
||||
@ -105,7 +107,7 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
data = ListOrderSerializer(order).data
|
||||
|
||||
# if user is under a limit (penalty), inform him
|
||||
# if user is under a limit (penalty), inform him.
|
||||
is_penalized, time_out = Logics.is_penalized(request.user)
|
||||
if is_penalized:
|
||||
data['penalty'] = time_out
|
||||
@ -123,7 +125,7 @@ class OrderView(viewsets.ViewSet):
|
||||
elif not data['is_participant'] and order.status != Order.Status.PUB:
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
# For participants add position side, nicks and status as message
|
||||
# For participants add positions, nicks and status as a message
|
||||
data['is_buyer'] = Logics.is_buyer(order,request.user)
|
||||
data['is_seller'] = Logics.is_seller(order,request.user)
|
||||
data['maker_nick'] = str(order.maker)
|
||||
@ -132,10 +134,10 @@ class OrderView(viewsets.ViewSet):
|
||||
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 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
|
||||
@ -180,8 +182,10 @@ class OrderView(viewsets.ViewSet):
|
||||
else:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 8) If status is 'CHA'or '' or '' and all HTLCS are in LOCKED
|
||||
elif order.status == Order.Status.CHA: # TODO Add the other status
|
||||
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
|
||||
elif order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Add the other status
|
||||
|
||||
# If all bonds are locked.
|
||||
if order.maker_bond.status == order.taker_bond.status == order.trade_escrow.status == LNPayment.Status.LOCKED:
|
||||
# add whether a collaborative cancel is pending
|
||||
data['pending_cancel'] = order.is_pending_cancel
|
||||
@ -191,7 +195,7 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
def take_update_confirm_dispute_cancel(self, request, format=None):
|
||||
'''
|
||||
Here take place all of the user updates to the order object.
|
||||
Here takes place all of updatesto the order object.
|
||||
That is: take, confim, cancel, dispute, update_invoice or rate.
|
||||
'''
|
||||
order_id = request.GET.get(self.lookup_url_kwarg)
|
||||
@ -206,7 +210,7 @@ class OrderView(viewsets.ViewSet):
|
||||
invoice = serializer.data.get('invoice')
|
||||
rating = serializer.data.get('rating')
|
||||
|
||||
# 1) If action is take, it is be taker request!
|
||||
# 1) If action is take, it is a taker request!
|
||||
if action == 'take':
|
||||
if order.status == Order.Status.PUB:
|
||||
valid, context = Logics.validate_already_maker_or_taker(request.user)
|
||||
@ -251,7 +255,7 @@ class OrderView(viewsets.ViewSet):
|
||||
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'},
|
||||
'Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues'},
|
||||
status.HTTP_501_NOT_IMPLEMENTED)
|
||||
|
||||
return self.get(request)
|
||||
@ -275,6 +279,16 @@ class UserView(APIView):
|
||||
- Creates login credentials (new User object)
|
||||
Response with Avatar and Nickname.
|
||||
'''
|
||||
|
||||
# if request.user.id:
|
||||
# context = {}
|
||||
# context['nickname'] = request.user.username
|
||||
# participant = not Logics.validate_already_maker_or_taker(request.user)
|
||||
# context['bad_request'] = f'You are already logged in as {request.user}'
|
||||
# if participant:
|
||||
# context['bad_request'] = f'You are already logged in as as {request.user} and have an active order'
|
||||
# return Response(context,status.HTTP_200_OK)
|
||||
|
||||
token = request.GET.get(self.lookup_url_kwarg)
|
||||
|
||||
# Compute token entropy
|
||||
@ -392,10 +406,34 @@ class InfoView(ListAPIView):
|
||||
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
|
||||
context['num_public_sell_orders'] = len(Order.objects.filter(type=Order.Types.SELL, status=Order.Status.PUB))
|
||||
|
||||
# Number of active users (logged in in last 30 minutes)
|
||||
active_user_time_range = (timezone.now() - timedelta(minutes=30), timezone.now())
|
||||
context['num_active_robotsats'] = len(User.objects.filter(last_login__range=active_user_time_range))
|
||||
|
||||
# Compute average premium and volume of today
|
||||
today = datetime.today()
|
||||
|
||||
queryset = MarketTick.objects.filter(timestamp__day=today.day)
|
||||
if not len(queryset) == 0:
|
||||
premiums = []
|
||||
volumes = []
|
||||
for tick in queryset:
|
||||
premiums.append(tick.premium)
|
||||
volumes.append(tick.volume)
|
||||
avg_premium = sum(premiums) / len(premiums)
|
||||
total_volume = sum(volumes)
|
||||
else:
|
||||
avg_premium = None
|
||||
total_volume = None
|
||||
|
||||
context['today_avg_nonkyc_btc_premium'] = avg_premium
|
||||
context['today_total_volume'] = total_volume
|
||||
context['lnd_version'] = get_lnd_version()
|
||||
context['robosats_running_commit_hash'] = get_commit_robosats()
|
||||
context['fee'] = FEE
|
||||
context['bond_size'] = float(config('BOND_SIZE'))
|
||||
|
||||
return Response(context, status.HTTP_200_OK)
|
||||
|
||||
|
576
frontend/package-lock.json
generated
576
frontend/package-lock.json
generated
@ -2106,6 +2106,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||
"integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
|
||||
"requires": {
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.1.tgz",
|
||||
@ -2140,6 +2148,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
|
||||
"integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==",
|
||||
"requires": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||
@ -2167,6 +2183,24 @@
|
||||
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mdast": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
|
||||
"integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==",
|
||||
"requires": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"@types/mdurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz",
|
||||
"integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA=="
|
||||
},
|
||||
"@types/ms": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "17.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.6.tgz",
|
||||
@ -2220,6 +2254,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||
@ -2711,6 +2750,11 @@
|
||||
"babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0"
|
||||
}
|
||||
},
|
||||
"bail": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -2946,6 +2990,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"character-entities": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.1.tgz",
|
||||
"integrity": "sha512-OzmutCf2Kmc+6DrFrrPS8/tDh2+DpnrfzdICHWhcVC9eOd0N1PXmQEE1a8iM4IziIAG+8tmTq3K+oo0ubH6RRQ=="
|
||||
},
|
||||
"chrome-trace-event": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
|
||||
@ -3069,6 +3118,11 @@
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||
},
|
||||
"comma-separated-tokens": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz",
|
||||
"integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg=="
|
||||
},
|
||||
"command-exists": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
@ -3283,6 +3337,14 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decode-named-character-reference": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.1.tgz",
|
||||
"integrity": "sha512-YV/0HQHreRwKb7uBopyIkLG17jG6Sv2qUchk9qSoVJ2f+flwRsPNBO0hAnjt6mTNYUT+vw9Gy2ihXg4sUWPi2w==",
|
||||
"requires": {
|
||||
"character-entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
@ -3356,11 +3418,21 @@
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"dequal": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
|
||||
"integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug=="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"diff": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
|
||||
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
@ -3634,6 +3706,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
|
||||
@ -3974,6 +4051,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hast-util-whitespace": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz",
|
||||
"integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg=="
|
||||
},
|
||||
"hermes-engine": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.9.0.tgz",
|
||||
@ -4100,6 +4182,11 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"inline-style-parser": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
|
||||
"integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
|
||||
},
|
||||
"interpret": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
|
||||
@ -4223,6 +4310,11 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"is-plain-obj": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz",
|
||||
"integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw=="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
@ -4963,11 +5055,78 @@
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"mdast-util-definitions": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz",
|
||||
"integrity": "sha512-5hcR7FL2EuZ4q6lLMUK5w4lHT2H3vqL9quPvYZ/Ku5iifrirfMHiGdhxdXMUbUkDmz5I+TYMd7nbaxUhbQkfpQ==",
|
||||
"requires": {
|
||||
"@types/mdast": "^3.0.0",
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-visit": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-visit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz",
|
||||
"integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^5.0.0",
|
||||
"unist-util-visit-parents": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mdast-util-from-markdown": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz",
|
||||
"integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==",
|
||||
"requires": {
|
||||
"@types/mdast": "^3.0.0",
|
||||
"@types/unist": "^2.0.0",
|
||||
"decode-named-character-reference": "^1.0.0",
|
||||
"mdast-util-to-string": "^3.1.0",
|
||||
"micromark": "^3.0.0",
|
||||
"micromark-util-decode-numeric-character-reference": "^1.0.0",
|
||||
"micromark-util-decode-string": "^1.0.0",
|
||||
"micromark-util-normalize-identifier": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"unist-util-stringify-position": "^3.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"mdast-util-to-hast": {
|
||||
"version": "11.3.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-11.3.0.tgz",
|
||||
"integrity": "sha512-4o3Cli3hXPmm1LhB+6rqhfsIUBjnKFlIUZvudaermXB+4/KONdd/W4saWWkC+LBLbPMqhFSSTSRgafHsT5fVJw==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"@types/mdast": "^3.0.0",
|
||||
"@types/mdurl": "^1.0.0",
|
||||
"mdast-util-definitions": "^5.0.0",
|
||||
"mdurl": "^1.0.0",
|
||||
"unist-builder": "^3.0.0",
|
||||
"unist-util-generated": "^2.0.0",
|
||||
"unist-util-position": "^4.0.0",
|
||||
"unist-util-visit": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mdast-util-to-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz",
|
||||
"integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA=="
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@ -5436,6 +5595,218 @@
|
||||
"nullthrows": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"micromark": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz",
|
||||
"integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==",
|
||||
"requires": {
|
||||
"@types/debug": "^4.0.0",
|
||||
"debug": "^4.0.0",
|
||||
"decode-named-character-reference": "^1.0.0",
|
||||
"micromark-core-commonmark": "^1.0.1",
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-combine-extensions": "^1.0.0",
|
||||
"micromark-util-decode-numeric-character-reference": "^1.0.0",
|
||||
"micromark-util-encode": "^1.0.0",
|
||||
"micromark-util-normalize-identifier": "^1.0.0",
|
||||
"micromark-util-resolve-all": "^1.0.0",
|
||||
"micromark-util-sanitize-uri": "^1.0.0",
|
||||
"micromark-util-subtokenize": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.1",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-core-commonmark": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz",
|
||||
"integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==",
|
||||
"requires": {
|
||||
"decode-named-character-reference": "^1.0.0",
|
||||
"micromark-factory-destination": "^1.0.0",
|
||||
"micromark-factory-label": "^1.0.0",
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-factory-title": "^1.0.0",
|
||||
"micromark-factory-whitespace": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-classify-character": "^1.0.0",
|
||||
"micromark-util-html-tag-name": "^1.0.0",
|
||||
"micromark-util-normalize-identifier": "^1.0.0",
|
||||
"micromark-util-resolve-all": "^1.0.0",
|
||||
"micromark-util-subtokenize": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.1",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-factory-destination": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
||||
"integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==",
|
||||
"requires": {
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-factory-label": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz",
|
||||
"integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==",
|
||||
"requires": {
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-factory-space": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz",
|
||||
"integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==",
|
||||
"requires": {
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-factory-title": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz",
|
||||
"integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==",
|
||||
"requires": {
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-factory-whitespace": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz",
|
||||
"integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==",
|
||||
"requires": {
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-character": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz",
|
||||
"integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==",
|
||||
"requires": {
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-chunked": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz",
|
||||
"integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==",
|
||||
"requires": {
|
||||
"micromark-util-symbol": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-classify-character": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz",
|
||||
"integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==",
|
||||
"requires": {
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-combine-extensions": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz",
|
||||
"integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==",
|
||||
"requires": {
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-decode-numeric-character-reference": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz",
|
||||
"integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==",
|
||||
"requires": {
|
||||
"micromark-util-symbol": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-decode-string": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz",
|
||||
"integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==",
|
||||
"requires": {
|
||||
"decode-named-character-reference": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-decode-numeric-character-reference": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-encode": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz",
|
||||
"integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA=="
|
||||
},
|
||||
"micromark-util-html-tag-name": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.0.0.tgz",
|
||||
"integrity": "sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g=="
|
||||
},
|
||||
"micromark-util-normalize-identifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==",
|
||||
"requires": {
|
||||
"micromark-util-symbol": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-resolve-all": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz",
|
||||
"integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==",
|
||||
"requires": {
|
||||
"micromark-util-types": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-sanitize-uri": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz",
|
||||
"integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==",
|
||||
"requires": {
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-encode": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-subtokenize": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz",
|
||||
"integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==",
|
||||
"requires": {
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-util-symbol": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
|
||||
"integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ=="
|
||||
},
|
||||
"micromark-util-types": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz",
|
||||
"integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w=="
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@ -5518,6 +5889,11 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -6002,6 +6378,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"property-information": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
|
||||
"integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w=="
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
@ -6078,6 +6459,27 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-markdown": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-7.1.2.tgz",
|
||||
"integrity": "sha512-ibMcc0EbfmbwApqJD8AUr0yls8BSrKzIbHaUsPidQljxToCqFh34nwtu3CXNEItcVJNzpjDHrhK8A+MAh2JW3A==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"@types/unist": "^2.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-whitespace": "^2.0.0",
|
||||
"prop-types": "^15.0.0",
|
||||
"property-information": "^6.0.0",
|
||||
"react-is": "^17.0.0",
|
||||
"remark-parse": "^10.0.0",
|
||||
"remark-rehype": "^9.0.0",
|
||||
"space-separated-tokens": "^2.0.0",
|
||||
"style-to-object": "^0.3.0",
|
||||
"unified": "^10.0.0",
|
||||
"unist-util-visit": "^4.0.0",
|
||||
"vfile": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.66.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.66.4.tgz",
|
||||
@ -6509,6 +6911,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"remark-parse": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
|
||||
"integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==",
|
||||
"requires": {
|
||||
"@types/mdast": "^3.0.0",
|
||||
"mdast-util-from-markdown": "^1.0.0",
|
||||
"unified": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"remark-rehype": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-9.1.0.tgz",
|
||||
"integrity": "sha512-oLa6YmgAYg19zb0ZrBACh40hpBLteYROaPLhBXzLgjqyHQrN+gVP9N/FJvfzuNNuzCutktkroXEZBrxAxKhh7Q==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"@types/mdast": "^3.0.0",
|
||||
"mdast-util-to-hast": "^11.0.0",
|
||||
"unified": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
@ -6610,6 +7033,14 @@
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
||||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
|
||||
},
|
||||
"sade": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||
"requires": {
|
||||
"mri": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@ -7175,6 +7606,11 @@
|
||||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
|
||||
},
|
||||
"space-separated-tokens": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz",
|
||||
"integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw=="
|
||||
},
|
||||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
@ -7294,6 +7730,14 @@
|
||||
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
||||
"dev": true
|
||||
},
|
||||
"style-to-object": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
|
||||
"integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
|
||||
"requires": {
|
||||
"inline-style-parser": "0.1.1"
|
||||
}
|
||||
},
|
||||
"stylis": {
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
|
||||
@ -7448,6 +7892,11 @@
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"trough": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-2.0.2.tgz",
|
||||
"integrity": "sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
@ -7503,6 +7952,27 @@
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz",
|
||||
"integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ=="
|
||||
},
|
||||
"unified": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.1.tgz",
|
||||
"integrity": "sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"bail": "^2.0.0",
|
||||
"extend": "^3.0.0",
|
||||
"is-buffer": "^2.0.0",
|
||||
"is-plain-obj": "^4.0.0",
|
||||
"trough": "^2.0.0",
|
||||
"vfile": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-buffer": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
@ -7514,6 +7984,67 @@
|
||||
"set-value": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"unist-builder": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz",
|
||||
"integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-generated": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz",
|
||||
"integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw=="
|
||||
},
|
||||
"unist-util-is": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
|
||||
"integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ=="
|
||||
},
|
||||
"unist-util-position": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.1.tgz",
|
||||
"integrity": "sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA=="
|
||||
},
|
||||
"unist-util-stringify-position": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz",
|
||||
"integrity": "sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz",
|
||||
"integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^5.0.0",
|
||||
"unist-util-visit-parents": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-visit-parents": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz",
|
||||
"integrity": "sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"unist-util-visit-parents": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz",
|
||||
"integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
@ -7607,6 +8138,24 @@
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
},
|
||||
"uvu": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz",
|
||||
"integrity": "sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==",
|
||||
"requires": {
|
||||
"dequal": "^2.0.0",
|
||||
"diff": "^5.0.0",
|
||||
"kleur": "^4.0.3",
|
||||
"sade": "^1.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"kleur": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
|
||||
"integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"value-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||
@ -7617,6 +8166,33 @@
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"vfile": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.0.tgz",
|
||||
"integrity": "sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"is-buffer": "^2.0.0",
|
||||
"unist-util-stringify-position": "^3.0.0",
|
||||
"vfile-message": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-buffer": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vfile-message": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.0.tgz",
|
||||
"integrity": "sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-stringify-position": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"vlq": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
|
||||
|
@ -29,6 +29,7 @@
|
||||
"@mui/material": "^5.2.7",
|
||||
"@mui/system": "^5.2.6",
|
||||
"material-ui-image": "^3.3.2",
|
||||
"react-markdown": "^7.1.2",
|
||||
"react-native": "^0.66.4",
|
||||
"react-native-svg": "^12.1.1",
|
||||
"react-qr-code": "^2.0.3",
|
||||
|
@ -196,7 +196,7 @@ export default class BookPage extends Component {
|
||||
</Grid>)
|
||||
:
|
||||
<Grid item xs={12} align="center">
|
||||
<Paper elevation={0} style={{width: 1100, maxHeight: 600, overflow: 'auto'}}>
|
||||
<Paper elevation={0} style={{width: 900, maxHeight: 500, overflow: 'auto'}}>
|
||||
<List >
|
||||
{this.bookListItems()}
|
||||
</List>
|
||||
|
@ -5,6 +5,7 @@ import UserGenPage from "./UserGenPage";
|
||||
import MakerPage from "./MakerPage";
|
||||
import BookPage from "./BookPage";
|
||||
import OrderPage from "./OrderPage";
|
||||
import InfoPage from "./InfoPage";
|
||||
|
||||
export default class HomePage extends Component {
|
||||
constructor(props) {
|
||||
@ -17,6 +18,7 @@ export default class HomePage extends Component {
|
||||
<Switch>
|
||||
<Route exact path='/' component={UserGenPage}/>
|
||||
<Route path='/home'><p>You are at the start page</p></Route>
|
||||
<Route path='/info' component={InfoPage}/>
|
||||
<Route path='/make' component={MakerPage}/>
|
||||
<Route path='/book' component={BookPage}/>
|
||||
<Route path="/order/:orderId" component={OrderPage}/>
|
||||
|
38
frontend/src/components/InfoPage.js
Normal file
38
frontend/src/components/InfoPage.js
Normal file
@ -0,0 +1,38 @@
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import {Paper, Grid, CircularProgress, Button, Link} from "@mui/material"
|
||||
import React, { Component } from 'react'
|
||||
|
||||
export default class InfoPage extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
info: null,
|
||||
loading: true,
|
||||
};
|
||||
this.getInfoMarkdown()
|
||||
}
|
||||
|
||||
getInfoMarkdown() {
|
||||
fetch('/static/assets/info.md')
|
||||
.then((response) => response.text())
|
||||
.then((data) => this.setState({info:data, loading:false}));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{this.state.loading ? <Grid item xs={12} align="center">
|
||||
<CircularProgress />
|
||||
</Grid> : ""}
|
||||
<Paper elevation={12} style={{ padding: 10, width: 900, maxHeight: 500, overflow: 'auto'}}>
|
||||
<ReactMarkdown children={this.state.info} />
|
||||
</Paper>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button color="secondary" variant="contained" to="/" component={Link}>
|
||||
Back
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}
|
@ -181,7 +181,6 @@ export default class MakerPage extends Component {
|
||||
onChange={this.handleAmountChange}
|
||||
/>
|
||||
<Select
|
||||
label="Select Payment Currency"
|
||||
required="true"
|
||||
defaultValue={this.defaultCurrency}
|
||||
inputProps={{
|
||||
|
@ -67,7 +67,7 @@ export default class OrderPage extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
isExplicit: false,
|
||||
delay: 10000, // Refresh every 10 seconds
|
||||
delay: 2000, // Refresh every 2 seconds by default
|
||||
currencies_dict: {"1":"USD"}
|
||||
};
|
||||
this.orderId = this.props.match.params.orderId;
|
||||
@ -109,7 +109,7 @@ export default class OrderPage extends Component {
|
||||
escrowInvoice: data.escrow_invoice,
|
||||
escrowSatoshis: data.escrow_satoshis,
|
||||
invoiceAmount: data.invoice_amount,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -129,9 +129,6 @@ export default class OrderPage extends Component {
|
||||
tick = () => {
|
||||
this.getOrderDetails();
|
||||
}
|
||||
handleDelayChange = (e) => {
|
||||
this.setState({ delay: Number(e.target.value) });
|
||||
}
|
||||
|
||||
// Fix to use proper react props
|
||||
handleClickBackButton=()=>{
|
||||
@ -149,7 +146,9 @@ export default class OrderPage extends Component {
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (console.log(data) & this.getOrderDetails(data.id)));
|
||||
.then((data) => (this.setState({badRequest:data.bad_request})
|
||||
& console.log(data)
|
||||
& this.getOrderDetails(data.id)));
|
||||
}
|
||||
getCurrencyDict() {
|
||||
fetch('/static/assets/currencies.json')
|
||||
@ -278,13 +277,19 @@ export default class OrderPage extends Component {
|
||||
</>
|
||||
}
|
||||
|
||||
{/* Makers can cancel before commiting the bond (status 0)*/}
|
||||
{this.state.isMaker & this.state.statusCode == 0 ?
|
||||
{/* Makers can cancel before trade escrow deposited (status <9)*/}
|
||||
{/* Only free cancel before bond locked (status 0)*/}
|
||||
{this.state.isMaker & this.state.statusCode < 9 ?
|
||||
<Grid item xs={12} align="center">
|
||||
<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">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from "react";
|
||||
import { Paper, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider} from "@mui/material"
|
||||
import { Link, Paper, Rating, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider} from "@mui/material"
|
||||
import QRCode from "react-qr-code";
|
||||
|
||||
function getCookie(name) {
|
||||
@ -27,6 +27,9 @@ function pn(x) {
|
||||
export default class TradeBox extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
badInvoice: false,
|
||||
}
|
||||
}
|
||||
|
||||
showQRInvoice=()=>{
|
||||
@ -34,16 +37,16 @@ export default class TradeBox extends Component {
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="body2" variant="body2">
|
||||
Robots around here usually show commitment
|
||||
Robosats show commitment to their peers
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
{this.props.data.isMaker ?
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1">
|
||||
<b>Lock {pn(this.props.data.bondSatoshis)} Sats to PUBLISH order </b>
|
||||
</Typography>
|
||||
:
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1">
|
||||
<b>Lock {pn(this.props.data.bondSatoshis)} Sats to TAKE the order </b>
|
||||
</Typography>
|
||||
}
|
||||
@ -67,11 +70,21 @@ 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}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1">
|
||||
<b>Deposit {pn(this.props.data.escrowSatoshis)} Sats as trade collateral </b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
@ -89,6 +102,7 @@ export default class TradeBox extends Component {
|
||||
color = "secondary"
|
||||
/>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -110,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>
|
||||
);
|
||||
}
|
||||
@ -119,7 +134,7 @@ export default class TradeBox extends Component {
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<b> Your order is public, wait for a taker. </b>
|
||||
<b> Your order is public. Wait for a taker. </b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
@ -135,7 +150,6 @@ export default class TradeBox extends Component {
|
||||
return to you (no action needed).</p>
|
||||
</Typography>
|
||||
</ListItem>
|
||||
|
||||
{/* TODO API sends data for a more confortable wait */}
|
||||
<Divider/>
|
||||
<ListItem>
|
||||
@ -151,21 +165,27 @@ export default class TradeBox extends Component {
|
||||
<ListItem>
|
||||
<ListItemText primary="33%" secondary="Premium percentile" />
|
||||
</ListItem>
|
||||
<Divider/>
|
||||
|
||||
</List>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
handleInputInvoiceChanged=(e)=>{
|
||||
this.setState({
|
||||
invoice: e.target.value,
|
||||
invoice: e.target.value,
|
||||
badInvoice: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Fix this. It's clunky because it takes time. this.props.data does not refresh until next refresh of OrderPage.
|
||||
|
||||
handleClickSubmitInvoiceButton=()=>{
|
||||
this.setState({badInvoice:false});
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
@ -176,7 +196,8 @@ export default class TradeBox extends Component {
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.props.data = data));
|
||||
.then((data) => this.setState({badInvoice:data.bad_invoice})
|
||||
& console.log(data));
|
||||
}
|
||||
|
||||
showInputInvoice(){
|
||||
@ -186,7 +207,7 @@ export default class TradeBox extends Component {
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="subtitle1" variant="subtitle1">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1">
|
||||
<b> Submit a LN invoice for {pn(this.props.data.invoiceAmount)} Sats </b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
@ -199,6 +220,8 @@ export default class TradeBox extends Component {
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<TextField
|
||||
error={this.state.badInvoice}
|
||||
helperText={this.state.badInvoice ? this.state.badInvoice : "" }
|
||||
label={"Payout Lightning Invoice"}
|
||||
required
|
||||
inputProps={{
|
||||
@ -211,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>
|
||||
)
|
||||
}
|
||||
@ -231,6 +255,7 @@ export default class TradeBox extends Component {
|
||||
you will get your bond back automatically.</p>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
@ -252,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>
|
||||
)
|
||||
}
|
||||
@ -280,6 +306,19 @@ handleClickOpenDisputeButton=()=>{
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.props.data = data));
|
||||
}
|
||||
handleRatingChange=(e)=>{
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
body: JSON.stringify({
|
||||
'action': "rate",
|
||||
'rating': e.target.value,
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => (this.props.data = data));
|
||||
}
|
||||
|
||||
showFiatSentButton(){
|
||||
return(
|
||||
@ -295,22 +334,18 @@ handleClickOpenDisputeButton=()=>{
|
||||
// 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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -341,21 +376,33 @@ handleClickOpenDisputeButton=()=>{
|
||||
{receivedFiatButton ? this.showFiatReceivedButton() : ""}
|
||||
{openDisputeButton ? this.showOpenDisputeButton() : ""}
|
||||
</Grid>
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// showFiatReceivedButton(){
|
||||
|
||||
// }
|
||||
|
||||
// showOpenDisputeButton(){
|
||||
|
||||
// }
|
||||
|
||||
// showRateSelect(){
|
||||
|
||||
// }
|
||||
showRateSelect(){
|
||||
return(
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="h6" variant="h6">
|
||||
🎉Trade finished!🥳
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography component="body2" variant="body2" align="center">
|
||||
What do you think of <b>{this.props.data.isMaker ? this.props.data.takerNick : this.props.data.makerNick}</b>?
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Rating name="size-large" defaultValue={2} size="large" onChange={this.handleRatingChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button color='primary' to='/' component={Link}>Start Again</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
@ -379,14 +426,25 @@ handleClickOpenDisputeButton=()=>{
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""}
|
||||
|
||||
{/* In Chatroom - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||
{/* In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat(true,false,true) : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 9 ? this.showChat(false,false,true) : ""}
|
||||
|
||||
{/* In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat(false,false,true) : ""}
|
||||
{this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""}
|
||||
|
||||
{/* Trade Finished */}
|
||||
{this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15 ? this.showRateSelect() : ""}
|
||||
{(this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15) ? this.showRateSelect() : ""}
|
||||
{(this.props.data.isBuyer & this.props.data.statusCode == 14) ? this.showRateSelect() : ""}
|
||||
|
||||
{/* Trade Finished - Payment Routing Failed */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""}
|
||||
|
||||
{/* Trade Finished - Payment Routing Failed - TODO Needs more planning */}
|
||||
{this.props.data.statusCode == 11 ? this.showInDispute() : ""}
|
||||
|
||||
|
||||
{/* TODO */}
|
||||
{/* */}
|
||||
{/* */}
|
||||
|
@ -137,7 +137,7 @@ export default class UserGenPage extends Component {
|
||||
<Grid item xs={12} align="center">
|
||||
<ButtonGroup variant="contained" aria-label="outlined primary button group">
|
||||
<Button color='primary' to='/make/' component={Link}>Make Order</Button>
|
||||
<Button color='inherit' to='/home' component={Link}>INFO</Button>
|
||||
<Button color='inherit' to='/info' component={Link}>INFO</Button>
|
||||
<Button color='secondary' to='/book/' component={Link}>View Book</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
|
@ -1,8 +1,31 @@
|
||||
{
|
||||
"1":"USD",
|
||||
"2":"EUR",
|
||||
"3":"ETH",
|
||||
"4":"AUD",
|
||||
"5":"BRL",
|
||||
"6":"CAD"
|
||||
}
|
||||
"3":"JPY",
|
||||
"4":"GBP",
|
||||
"5":"AUD",
|
||||
"6":"CAD",
|
||||
"7":"CHF",
|
||||
"8":"CNY",
|
||||
"9":"HKD",
|
||||
"10":"NZD",
|
||||
"11":"SEK",
|
||||
"12":"KRW",
|
||||
"13":"SGD",
|
||||
"14":"NOK",
|
||||
"15":"MXN",
|
||||
"16":"KRW",
|
||||
"17":"RUB",
|
||||
"18":"ZAR",
|
||||
"19":"TRY",
|
||||
"20":"BRL",
|
||||
"21": "CLP",
|
||||
"22": "CZK",
|
||||
"23": "DKK",
|
||||
"24": "HKR",
|
||||
"25": "HUF",
|
||||
"26": "INR",
|
||||
"27": "ISK",
|
||||
"28": "PLN",
|
||||
"29": "RON"
|
||||
}
|
81
frontend/static/assets/info.md
Normal file
81
frontend/static/assets/info.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Buy and sell non-KYC Bitcoin using the lightning network.
|
||||
|
||||
## What is this?
|
||||
|
||||
{project_name} is a BTC/FIAT peer-to-peer exchange over lightning. It simplifies matchmaking and minimizes the trust needed to trade with a peer.
|
||||
|
||||
## That’s cool, so how it works?
|
||||
|
||||
Alice wants to sell sats, posts a sell order. Bob wants to buy sats, and takes Alice's order. Alice posts the sats as collateral using a hodl LN invoice. Bob also posts some sats as a bond to prove he is real. {project_name} locks the sats until Bob confirms he sent the fiat to Alice. Once Alice confirms she received the fiat, she tells {project_name} to release her sats to Bob. Enjoy your sats Bob!
|
||||
|
||||
At no point, Alice and Bob have to trust the funds to each other. In case Alice and Bob have a conflict, {project_name} staff will resolve the dispute.
|
||||
|
||||
(TODO: Long explanation and tutorial step by step, link)
|
||||
|
||||
## Nice, and fiat payments method are...?
|
||||
|
||||
Basically all of them. It is up to you to select your preferred payment methods. You will need to search for a peer who also accepts that method. Lightning is fast, so we highly recommend using instant fiat payment rails. Be aware trades have a expiry time of 8 hours. Paypal or credit card are not advice due to chargeback risk.
|
||||
|
||||
## Trust
|
||||
|
||||
The buyer and the seller never have to trust each other. Some trust on {project_name} is needed. Linking the seller’s hodl invoice and buyer payment is not atomic (yet, research ongoing). In addition, disputes are solved by the {project_name} staff.
|
||||
|
||||
Note: this is not an escrow service. While trust requirements are minimized, {project_name} could run away with your sats. It could be argued that it is not worth it, as it would instantly destroy {project_name} reputation. However, you should hesitate and only trade small quantities at a time. For larger amounts and safety assurance use an escrow service such as Bisq or Hodlhodl.
|
||||
|
||||
You can build more trust on {project_name} by inspecting the source code, link.
|
||||
|
||||
## If {project_name} suddenly disappears during a trade my sats…
|
||||
|
||||
Your sats will most likely return to you. Any hodl invoice that is not settled would be automatically returned even if {project_name} goes down forever. This is true for both, locked bonds and traded sats. However, in the window between the buyer confirms FIAT SENT and the sats have not been released yet by the seller, the fund could be lost.
|
||||
|
||||
## Limits
|
||||
|
||||
Max trade size is 500K Sats to minimize failures in lightning routing. The limit will be raised as LN grows.
|
||||
|
||||
## Privacy
|
||||
|
||||
User token is generated locally as the unique identifier (back it up on paper! If lost {project_name} cannot help recover it). {project_name} doesn’t know anything about you and doesn’t want to know.
|
||||
|
||||
Your trading peer is the only one who can potentially guess anything about you. Keep chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.
|
||||
|
||||
The chat with your peer is end-to-end encrypted, {project_name} cannot read. It can only be decrypted with your user token. The chat encryption makes it hard to resolve disputes. Therefore, by opening a dispute you are sending a viewkey to {project_name} staff. The encrypted chat cannot be revisited as it is deleted automatically when the trade is finalized (check the source code).
|
||||
|
||||
For best anonymity use Tor Browser and access the .onion hidden service.
|
||||
|
||||
## So {project_name} is a decentralized exchange?
|
||||
Not quite, though it shares some elements.
|
||||
|
||||
A simple comparisson:
|
||||
* Privacy worst to best: Coinbase/Binance/others < hodlhodl < {project_name} < Bisq
|
||||
* Safety (not your keys, not your coins): Coinbase/Binance/others < {project_name} < hodlhodl < Bisq
|
||||
*(take with a pinch of salt)*
|
||||
|
||||
So, if bisq is best for both privacy and safety, why {project_name} exists? Bisq is great, but it is difficult, slow, high-fee and needs extra steps to move to lightning. {project_name} aims to be as easy as Binance/Coinbase greatly improving on privacy and requiring minimal trust.
|
||||
|
||||
## Any risk?
|
||||
|
||||
Sure, this is a beta bot, things could go wrong. Trade small amounts!
|
||||
|
||||
The seller faces the same chargeback risk as with any other peer-to-peer exchange. Avoid accepting payment methods with easy chargeback!
|
||||
|
||||
## What are the fees?
|
||||
|
||||
{project_name} takes a 0.2% fee of the trade to cover lightning routing costs. This is akin to a Binance trade fee (but hey, you do not have to sell your soul to the devil, nor pay the withdrawal fine...).
|
||||
|
||||
The loser of a dispute pays a 1% fee that is slashed from the collateral posted when the trade starts. This fee is necessary to disincentive cheating and keep the site healthy. It also helps to cover the staff cost of dispute solving.
|
||||
|
||||
Note: your selected fiat payment rails might have other fees, these are to be covered by the buyer.
|
||||
|
||||
## I am a pro and {project_name} is too simple, it lacks features…
|
||||
|
||||
Indeed, this site is a simple front-end that aims for user friendliness and forces best privacy for casual users.
|
||||
|
||||
If you are a big maker, liquidity provider, or want to create many trades simultaneously use the API: {API_LINK_DOCUMENTATION}
|
||||
|
||||
## Is it legal to use {project_name} in my country?
|
||||
|
||||
In many countries using {project_name} is not different than buying something from a peer on Ebay or Craiglist. Your regulation may vary, you need to figure out.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This tool is provided as is. It is in active development and can be buggy. Be aware that you could lose your funds: trade with the utmost caution. There is no private support. Support is only offered via public channels (link telegram groups). {project_name} will never contact you. And {project_name} will definitely never ask for your user token.
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Robosats</title>
|
||||
<title>RoboSats - Simple and Private Bitcoin Exchange</title>
|
||||
{% load static %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<link
|
||||
|
@ -3,7 +3,7 @@ from .views import index
|
||||
|
||||
urlpatterns = [
|
||||
path('', index),
|
||||
path('home/', index),
|
||||
path('info/', index),
|
||||
path('login/', index),
|
||||
path('make/', index),
|
||||
path('book/', index),
|
||||
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
@ -25,7 +26,7 @@ SECRET_KEY = 'django-insecure-6^&6uw$b5^en%(cu2kc7_o)(mgpazx#j_znwlym0vxfamn2uo-
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
ALLOWED_HOSTS = [config('HOST_NAME'),'127.0.0.1']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
37
setup.md
37
setup.md
@ -6,8 +6,6 @@
|
||||
### Install virtual environments
|
||||
```
|
||||
pip install virtualenvwrapper
|
||||
pip install python-decouple
|
||||
pip install ring
|
||||
```
|
||||
|
||||
### Add to .bashrc
|
||||
@ -45,11 +43,41 @@ python3 manage.py migrate
|
||||
python3 manage.py runserver
|
||||
```
|
||||
|
||||
### Install python dependencies
|
||||
### Install other python dependencies
|
||||
```
|
||||
pip install robohash
|
||||
pip install python-decouple
|
||||
pip install ring
|
||||
```
|
||||
|
||||
### Install LND python dependencies
|
||||
```
|
||||
cd api/lightning
|
||||
pip install grpcio grpcio-tools googleapis-common-protos
|
||||
git clone https://github.com/googleapis/googleapis.git
|
||||
curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
|
||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto
|
||||
```
|
||||
We also use the *Invoices* and *Router* subservices for invoice validation and payment routing.
|
||||
```
|
||||
curl -o invoices.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto
|
||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. invoices.proto
|
||||
curl -o router.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto
|
||||
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. router.proto
|
||||
```
|
||||
Relative imports are not working at the moment, so some editing is needed in
|
||||
`api/lightning` files `lightning_pb2_grpc.py`, `invoices_pb2_grpc.py`, `invoices_pb2.py`, `router_pb2_grpc.py` and `router_pb2.py`.
|
||||
|
||||
For example in `lightning_pb2_grpc.py` , add "from . " :
|
||||
|
||||
`import lightning_pb2 as lightning__pb2`
|
||||
|
||||
to
|
||||
|
||||
`from . import lightning_pb2 as lightning__pb2`
|
||||
|
||||
Same for every other file
|
||||
|
||||
## React development environment
|
||||
### Install npm
|
||||
`sudo apt install npm`
|
||||
@ -71,8 +99,9 @@ npm install react-native
|
||||
npm install react-native-svg
|
||||
npm install react-qr-code
|
||||
npm install @mui/material
|
||||
npm install react-markdown
|
||||
```
|
||||
Note we are using mostly MaterialUI V5, but Image loading from V4 extentions (so both V4 and V5 are needed)
|
||||
Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4 (@material-ui/core) extentions (so both V4 and V5 are needed)
|
||||
|
||||
### Launch the React render
|
||||
from frontend/ directory
|
||||
|
Loading…
Reference in New Issue
Block a user