diff --git a/api/admin.py b/api/admin.py
index 509718ec..7f80632d 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -5,6 +5,7 @@ from django.contrib.auth.admin import UserAdmin
from api.models import OnchainPayment, Order, LNPayment, Profile, MarketTick, Currency
from api.logics import Logics
from statistics import median
+
admin.site.unregister(Group)
admin.site.unregister(User)
@@ -12,10 +13,11 @@ admin.site.unregister(User)
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
- fields = ("avatar_tag", )
+ fields = ("avatar_tag",)
readonly_fields = ["avatar_tag"]
show_change_link = True
+
# extended users with avatars
@admin.register(User)
class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
@@ -30,14 +32,13 @@ class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
"is_staff",
)
list_display_links = ("id", "username")
- change_links = (
- "profile",
- )
- ordering = ("-id", )
+ change_links = ("profile",)
+ ordering = ("-id",)
def avatar_tag(self, obj):
return obj.profile.avatar_tag()
+
@admin.register(Order)
class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = (
@@ -79,19 +80,34 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"taker_bond",
"trade_escrow",
)
- list_filter = ("is_disputed", "is_fiat_sent", "is_swap","type", "currency", "status")
- search_fields = ["id","amount","min_amount","max_amount"]
+ list_filter = (
+ "is_disputed",
+ "is_fiat_sent",
+ "is_swap",
+ "type",
+ "currency",
+ "status",
+ )
+ search_fields = ["id", "amount", "min_amount", "max_amount"]
- actions = ['maker_wins', 'taker_wins', 'return_everything','compite_median_trade_time']
+ actions = [
+ "maker_wins",
+ "taker_wins",
+ "return_everything",
+ "compite_median_trade_time",
+ ]
- @admin.action(description='Solve dispute: maker wins')
+ @admin.action(description="Solve dispute: maker wins")
def maker_wins(self, request, queryset):
- '''
+ """
Solves a dispute on favor of the maker.
Adds Sats to compensations (earned_rewards) of the maker profile.
- '''
+ """
for order in queryset:
- if order.status in [Order.Status.DIS, Order.Status.WFR] and order.is_disputed:
+ if (
+ order.status in [Order.Status.DIS, Order.Status.WFR]
+ and order.is_disputed
+ ):
own_bond_sats = order.maker_bond.num_satoshis
if Logics.is_buyer(order, order.maker):
if order.is_swap:
@@ -105,19 +121,30 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
order.maker.profile.earned_rewards = own_bond_sats + trade_sats
order.maker.profile.save()
order.save()
- self.message_user(request,f"Dispute of order {order.id} solved successfully on favor of the maker", messages.SUCCESS)
+ self.message_user(
+ request,
+ f"Dispute of order {order.id} solved successfully on favor of the maker",
+ messages.SUCCESS,
+ )
else:
- self.message_user(request,f"Order {order.id} is not in a disputed state", messages.ERROR)
+ self.message_user(
+ request,
+ f"Order {order.id} is not in a disputed state",
+ messages.ERROR,
+ )
- @admin.action(description='Solve dispute: taker wins')
+ @admin.action(description="Solve dispute: taker wins")
def taker_wins(self, request, queryset):
- '''
+ """
Solves a dispute on favor of the taker.
Adds Sats to compensations (earned_rewards) of the taker profile.
- '''
+ """
for order in queryset:
- if order.status in [Order.Status.DIS, Order.Status.WFR] and order.is_disputed:
+ if (
+ order.status in [Order.Status.DIS, Order.Status.WFR]
+ and order.is_disputed
+ ):
own_bond_sats = order.maker_bond.num_satoshis
if Logics.is_buyer(order, order.taker):
if order.is_swap:
@@ -131,56 +158,90 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
order.taker.profile.earned_rewards = own_bond_sats + trade_sats
order.taker.profile.save()
order.save()
- self.message_user(request,f"Dispute of order {order.id} solved successfully on favor of the taker", messages.SUCCESS)
+ self.message_user(
+ request,
+ f"Dispute of order {order.id} solved successfully on favor of the taker",
+ messages.SUCCESS,
+ )
else:
- self.message_user(request,f"Order {order.id} is not in a disputed state", messages.ERROR)
+ self.message_user(
+ request,
+ f"Order {order.id} is not in a disputed state",
+ messages.ERROR,
+ )
- @admin.action(description='Solve dispute: return everything')
+ @admin.action(description="Solve dispute: return everything")
def return_everything(self, request, queryset):
- '''
+ """
Solves a dispute by pushing back every bond and escrow to their sender.
- '''
+ """
for order in queryset:
- if order.status in [Order.Status.DIS, Order.Status.WFR] and order.is_disputed:
- order.maker_bond.sender.profile.earned_rewards += order.maker_bond.num_satoshis
+ if (
+ order.status in [Order.Status.DIS, Order.Status.WFR]
+ and order.is_disputed
+ ):
+ order.maker_bond.sender.profile.earned_rewards += (
+ order.maker_bond.num_satoshis
+ )
order.maker_bond.sender.profile.save()
- order.taker_bond.sender.profile.earned_rewards += order.taker_bond.num_satoshis
+ order.taker_bond.sender.profile.earned_rewards += (
+ order.taker_bond.num_satoshis
+ )
order.taker_bond.sender.profile.save()
- order.trade_escrow.sender.profile.earned_rewards += order.trade_escrow.num_satoshis
+ order.trade_escrow.sender.profile.earned_rewards += (
+ order.trade_escrow.num_satoshis
+ )
order.trade_escrow.sender.profile.save()
order.status = Order.Status.CCA
order.save()
- self.message_user(request,f"Dispute of order {order.id} solved successfully, everything returned as compensations", messages.SUCCESS)
+ self.message_user(
+ request,
+ f"Dispute of order {order.id} solved successfully, everything returned as compensations",
+ messages.SUCCESS,
+ )
else:
- self.message_user(request,f"Order {order.id} is not in a disputed state", messages.ERROR)
+ self.message_user(
+ request,
+ f"Order {order.id} is not in a disputed state",
+ messages.ERROR,
+ )
- @admin.action(description='Compute median trade completion time')
+ @admin.action(description="Compute median trade completion time")
def compite_median_trade_time(self, request, queryset):
- '''
- Computes the median time from an order taken to finishing
+ """
+ Computes the median time from an order taken to finishing
successfully for the set of selected orders.
- '''
+ """
times = []
for order in queryset:
if order.contract_finalization_time:
timedelta = order.contract_finalization_time - order.last_satoshis_time
times.append(timedelta.total_seconds())
-
+
if len(times) > 0:
median_time_secs = median(times)
- mins = int(median_time_secs/60)
- secs = int(median_time_secs - mins*60)
- self.message_user(request, f"The median time to complete the trades is {mins}m {secs}s", messages.SUCCESS)
+ mins = int(median_time_secs / 60)
+ secs = int(median_time_secs - mins * 60)
+ self.message_user(
+ request,
+ f"The median time to complete the trades is {mins}m {secs}s",
+ messages.SUCCESS,
+ )
else:
- self.message_user(request, "There is no successfully finished orders in the selection", messages.ERROR)
-
+ self.message_user(
+ request,
+ "There is no successfully finished orders in the selection",
+ messages.ERROR,
+ )
+
def amt(self, obj):
if obj.has_range and obj.amount == None:
- return str(float(obj.min_amount))+"-"+ str(float(obj.max_amount))
+ return str(float(obj.min_amount)) + "-" + str(float(obj.max_amount))
else:
- return float(obj.amount)
+ return float(obj.amount)
+
@admin.register(LNPayment)
class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@@ -210,8 +271,15 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"order_paid_LN",
)
list_filter = ("type", "concept", "status")
- ordering = ("-expires_at", )
- search_fields = ["payment_hash","num_satoshis","sender__username","receiver__username","description"]
+ ordering = ("-expires_at",)
+ search_fields = [
+ "payment_hash",
+ "num_satoshis",
+ "sender__username",
+ "receiver__username",
+ "description",
+ ]
+
@admin.register(OnchainPayment)
class OnchainPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@@ -231,9 +299,10 @@ class OnchainPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"balance",
"order_paid_TX",
)
- list_display_links = ("id","address", "concept")
+ list_display_links = ("id", "address", "concept")
list_filter = ("concept", "status")
- search_fields = ["address","num_satoshis","receiver__username","txid"]
+ search_fields = ["address", "num_satoshis", "receiver__username", "txid"]
+
@admin.register(Profile)
class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@@ -261,7 +330,7 @@ class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display_links = ("avatar_tag", "id")
change_links = ["user"]
readonly_fields = ["avatar_tag"]
- search_fields = ["user__username","id"]
+ search_fields = ["user__username", "id"]
readonly_fields = ("public_key", "encrypted_private_key")
@@ -270,13 +339,12 @@ class CurrencieAdmin(admin.ModelAdmin):
list_display = ("id", "currency", "exchange_rate", "timestamp")
list_display_links = ("id", "currency")
readonly_fields = ("currency", "exchange_rate", "timestamp")
- ordering = ("id", )
+ ordering = ("id",)
+
@admin.register(MarketTick)
class MarketTickAdmin(admin.ModelAdmin):
- list_display = ("timestamp", "price", "volume", "premium", "currency",
- "fee")
- readonly_fields = ("timestamp", "price", "volume", "premium", "currency",
- "fee")
+ list_display = ("timestamp", "price", "volume", "premium", "currency", "fee")
+ readonly_fields = ("timestamp", "price", "volume", "premium", "currency", "fee")
list_filter = ["currency"]
- ordering = ("-timestamp", )
+ ordering = ("-timestamp",)
diff --git a/api/lightning/node.py b/api/lightning/node.py
index f4739ad4..1e83ee59 100644
--- a/api/lightning/node.py
+++ b/api/lightning/node.py
@@ -24,8 +24,9 @@ except:
# Read macaroon from file or .env variable string encoded as base64
try:
- MACAROON = open(os.path.join(config("LND_DIR"), config("MACAROON_path")),
- "rb").read()
+ MACAROON = open(
+ os.path.join(config("LND_DIR"), config("MACAROON_path")), "rb"
+ ).read()
except:
MACAROON = b64decode(config("LND_MACAROON_BASE64"))
@@ -49,13 +50,10 @@ class LNNode:
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.",
+ 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)",
+ 4: "Payment details incorrect (unknown hash, invalid amt or invalid final cltv delta)",
5: "Insufficient local balance.",
}
@@ -63,9 +61,9 @@ class LNNode:
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())])
+ response = cls.lightningstub.DecodePayReq(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
return response
@classmethod
@@ -73,46 +71,56 @@ class LNNode:
"""Returns estimated fee for onchain payouts"""
# We assume segwit. Use robosats donation address as shortcut so there is no need of user inputs
- request = lnrpc.EstimateFeeRequest(AddrToAmount={'bc1q3cpp7ww92n6zp04hv40kd3eyy5avgughx6xqnx':amount_sats},
- target_conf=target_conf,
- min_confs=min_confs,
- spend_unconfirmed=False)
+ request = lnrpc.EstimateFeeRequest(
+ AddrToAmount={"bc1q3cpp7ww92n6zp04hv40kd3eyy5avgughx6xqnx": amount_sats},
+ target_conf=target_conf,
+ min_confs=min_confs,
+ spend_unconfirmed=False,
+ )
- response = cls.lightningstub.EstimateFee(request,
- metadata=[("macaroon",
- MACAROON.hex())])
+ response = cls.lightningstub.EstimateFee(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
- return {'mining_fee_sats': response.fee_sat, 'mining_fee_rate': response.sat_per_vbyte}
+ return {
+ "mining_fee_sats": response.fee_sat,
+ "mining_fee_rate": response.sat_per_vbyte,
+ }
wallet_balance_cache = {}
+
@ring.dict(wallet_balance_cache, expire=10) # keeps in cache for 10 seconds
@classmethod
def wallet_balance(cls):
"""Returns onchain balance"""
request = lnrpc.WalletBalanceRequest()
- response = cls.lightningstub.WalletBalance(request,
- metadata=[("macaroon",
- MACAROON.hex())])
+ response = cls.lightningstub.WalletBalance(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
- return {'total_balance': response.total_balance,
- 'confirmed_balance': response.confirmed_balance,
- 'unconfirmed_balance': response.unconfirmed_balance}
+ return {
+ "total_balance": response.total_balance,
+ "confirmed_balance": response.confirmed_balance,
+ "unconfirmed_balance": response.unconfirmed_balance,
+ }
channel_balance_cache = {}
+
@ring.dict(channel_balance_cache, expire=10) # keeps in cache for 10 seconds
@classmethod
def channel_balance(cls):
"""Returns channels balance"""
request = lnrpc.ChannelBalanceRequest()
- response = cls.lightningstub.ChannelBalance(request,
- metadata=[("macaroon",
- MACAROON.hex())])
+ response = cls.lightningstub.ChannelBalance(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
-
- return {'local_balance': response.local_balance.sat,
- 'remote_balance': response.remote_balance.sat,
- 'unsettled_local_balance': response.unsettled_local_balance.sat,
- 'unsettled_remote_balance': response.unsettled_remote_balance.sat}
+ return {
+ "local_balance": response.local_balance.sat,
+ "remote_balance": response.remote_balance.sat,
+ "unsettled_local_balance": response.unsettled_local_balance.sat,
+ "unsettled_remote_balance": response.unsettled_remote_balance.sat,
+ }
@classmethod
def pay_onchain(cls, onchainpayment):
@@ -121,15 +129,17 @@ class LNNode:
if config("DISABLE_ONCHAIN", cast=bool):
return False
- request = lnrpc.SendCoinsRequest(addr=onchainpayment.address,
- amount=int(onchainpayment.sent_satoshis),
- sat_per_vbyte=int(onchainpayment.mining_fee_rate),
- label=str("Payout order #" + str(onchainpayment.order_paid_TX.id)),
- spend_unconfirmed=True)
+ request = lnrpc.SendCoinsRequest(
+ addr=onchainpayment.address,
+ amount=int(onchainpayment.sent_satoshis),
+ sat_per_vbyte=int(onchainpayment.mining_fee_rate),
+ label=str("Payout order #" + str(onchainpayment.order_paid_TX.id)),
+ spend_unconfirmed=True,
+ )
- response = cls.lightningstub.SendCoins(request,
- metadata=[("macaroon",
- MACAROON.hex())])
+ response = cls.lightningstub.SendCoins(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
onchainpayment.txid = response.txid
onchainpayment.save()
@@ -139,28 +149,27 @@ class LNNode:
@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())])
+ 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())])
+ 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, invoice_expiry,
- cltv_expiry_blocks):
+ def gen_hold_invoice(
+ cls, num_satoshis, description, invoice_expiry, cltv_expiry_blocks
+ ):
"""Generates hold invoice"""
hold_payment = {}
@@ -179,18 +188,20 @@ class LNNode:
), # actual expiry is padded by 50%, if tight, wrong client system clock will say invoice is expired.
cltv_expiry=cltv_expiry_blocks,
)
- response = cls.invoicesstub.AddHoldInvoice(request,
- metadata=[("macaroon",
- MACAROON.hex())])
+ 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))
+ datetime.fromtimestamp(payreq_decoded.timestamp)
+ )
hold_payment["expires_at"] = hold_payment["created_at"] + timedelta(
- seconds=payreq_decoded.expiry)
+ seconds=payreq_decoded.expiry
+ )
hold_payment["cltv_expiry"] = cltv_expiry_blocks
return hold_payment
@@ -201,11 +212,11 @@ class LNNode:
from api.models import LNPayment
request = invoicesrpc.LookupInvoiceMsg(
- payment_hash=bytes.fromhex(lnpayment.payment_hash))
- response = cls.invoicesstub.LookupInvoiceV2(request,
- metadata=[("macaroon",
- MACAROON.hex())
- ])
+ payment_hash=bytes.fromhex(lnpayment.payment_hash)
+ )
+ response = cls.invoicesstub.LookupInvoiceV2(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
# Will fail if 'unable to locate invoice'. Happens if invoice expiry
# time has passed (but these are 15% padded at the moment). Should catch it
@@ -225,11 +236,9 @@ class LNNode:
@classmethod
def resetmc(cls):
request = routerrpc.ResetMissionControlRequest()
- response = cls.routerstub.ResetMissionControl(request,
- metadata=[
- ("macaroon",
- MACAROON.hex())
- ])
+ response = cls.routerstub.ResetMissionControl(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
return True
@classmethod
@@ -258,7 +267,10 @@ class LNNode:
route_hints = payreq_decoded.route_hints
# Max amount RoboSats will pay for routing
- max_routing_fee_sats = max(num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")), float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")))
+ max_routing_fee_sats = max(
+ num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
+ float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
+ )
if route_hints:
routes_cost = []
@@ -268,15 +280,17 @@ class LNNode:
# ...add up the cost of every hinted hop...
for hop_hint in hinted_route.hop_hints:
route_cost += hop_hint.fee_base_msat / 1000
- route_cost += hop_hint.fee_proportional_millionths * num_satoshis / 1000000
+ route_cost += (
+ hop_hint.fee_proportional_millionths * num_satoshis / 1000000
+ )
# ...and store the cost of the route to the array
routes_cost.append(route_cost)
# If the cheapest possible private route is more expensive than what RoboSats is willing to pay
- if min(routes_cost) >= max_routing_fee_sats :
+ if min(routes_cost) >= max_routing_fee_sats:
payout["context"] = {
- "bad_invoice": "The invoice submitted only has a trick on the routing hints, you might be using an incompatible wallet (probably Muun? Use an onchain address instead!). Check the wallet compatibility guide at wallets.robosats.com"
+ "bad_invoice": "The invoice submitted only has a trick on the routing hints, you might be using an incompatible wallet (probably Muun? Use an onchain address instead!). Check the wallet compatibility guide at wallets.robosats.com"
}
return payout
@@ -288,16 +302,18 @@ class LNNode:
if not payreq_decoded.num_satoshis == num_satoshis:
payout["context"] = {
- "bad_invoice":
- "The invoice provided is not for " +
- "{:,}".format(num_satoshis) + " Sats"
+ "bad_invoice": "The invoice provided is not for "
+ + "{:,}".format(num_satoshis)
+ + " Sats"
}
return payout
payout["created_at"] = timezone.make_aware(
- datetime.fromtimestamp(payreq_decoded.timestamp))
+ datetime.fromtimestamp(payreq_decoded.timestamp)
+ )
payout["expires_at"] = payout["created_at"] + timedelta(
- seconds=payreq_decoded.expiry)
+ seconds=payreq_decoded.expiry
+ )
if payout["expires_at"] < timezone.now():
payout["context"] = {
@@ -315,21 +331,24 @@ class LNNode:
def pay_invoice(cls, lnpayment):
"""Sends sats. Used for rewards payouts"""
from api.models import LNPayment
-
+
fee_limit_sat = int(
max(
- lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
+ lnpayment.num_satoshis
+ * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
- )) # 200 ppm or 10 sats
+ )
+ ) # 200 ppm or 10 sats
timeout_seconds = int(config("REWARDS_TIMEOUT_SECONDS"))
- request = routerrpc.SendPaymentRequest(payment_request=lnpayment.invoice,
- fee_limit_sat=fee_limit_sat,
- timeout_seconds=timeout_seconds)
+ request = routerrpc.SendPaymentRequest(
+ payment_request=lnpayment.invoice,
+ fee_limit_sat=fee_limit_sat,
+ timeout_seconds=timeout_seconds,
+ )
- for response in cls.routerstub.SendPaymentV2(request,
- metadata=[("macaroon",
- MACAROON.hex())
- ]):
+ for response in cls.routerstub.SendPaymentV2(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ ):
if response.status == 0: # Status 0 'UNKNOWN'
# Not sure when this status happens
@@ -354,7 +373,7 @@ class LNNode:
if response.status == 2: # STATUS 'SUCCEEDED'
lnpayment.status = LNPayment.Status.SUCCED
- lnpayment.fee = float(response.fee_msat)/1000
+ lnpayment.fee = float(response.fee_msat) / 1000
lnpayment.preimage = response.payment_preimage
lnpayment.save()
return True, None
@@ -364,12 +383,10 @@ class LNNode:
@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())
- ])
+ request = invoicesrpc.LookupInvoiceMsg(payment_hash=bytes.fromhex(payment_hash))
+ response = cls.invoicesstub.LookupInvoiceV2(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
return (
response.state == 1
diff --git a/api/logics.py b/api/logics.py
index 3131bbcd..9191e6c8 100644
--- a/api/logics.py
+++ b/api/logics.py
@@ -26,13 +26,15 @@ EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE"))
BLOCK_TIME = float(config("BLOCK_TIME"))
-MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED"))
+MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(
+ config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")
+)
INVOICE_AND_ESCROW_DURATION = int(config("INVOICE_AND_ESCROW_DURATION"))
FIAT_EXCHANGE_DURATION = int(config("FIAT_EXCHANGE_DURATION"))
-class Logics:
+class Logics:
@classmethod
def validate_already_maker_or_taker(cls, user):
"""Validates if a use is already not part of an active order"""
@@ -51,39 +53,34 @@ class Logics:
Order.Status.WFR,
]
"""Checks if the user is already partipant of an active order"""
- queryset = Order.objects.filter(maker=user,
- status__in=active_order_status)
+ queryset = Order.objects.filter(maker=user, status__in=active_order_status)
if queryset.exists():
return (
False,
- {
- "bad_request": "You are already maker of an active order"
- },
+ {"bad_request": "You are already maker of an active order"},
queryset[0],
)
- queryset = Order.objects.filter(taker=user,
- status__in=active_order_status)
+ queryset = Order.objects.filter(taker=user, status__in=active_order_status)
if queryset.exists():
return (
False,
- {
- "bad_request": "You are already taker of an active order"
- },
+ {"bad_request": "You are already taker of an active order"},
queryset[0],
)
# Edge case when the user is in an order that is failing payment and he is the buyer
- queryset = Order.objects.filter(Q(maker=user) | Q(taker=user),
- status__in=[Order.Status.FAI,Order.Status.PAY])
+ queryset = Order.objects.filter(
+ Q(maker=user) | Q(taker=user),
+ status__in=[Order.Status.FAI, Order.Status.PAY],
+ )
if queryset.exists():
order = queryset[0]
if cls.is_buyer(order, user):
return (
False,
{
- "bad_request":
- "You are still pending a payment from a recent order"
+ "bad_request": "You are still pending a payment from a recent order"
},
order,
)
@@ -91,12 +88,12 @@ class Logics:
return True, None, None
def validate_pgp_keys(pub_key, enc_priv_key):
- ''' Validates PGP valid keys. Formats them in a way understandable by the frontend '''
+ """Validates PGP valid keys. Formats them in a way understandable by the frontend"""
gpg = gnupg.GPG()
# Standarize format with linux linebreaks '\n'. Windows users submitting their own keys have '\r\n' breaking communication.
- enc_priv_key = enc_priv_key.replace('\r\n', '\n')
- pub_key = pub_key.replace('\r\n', '\n')
+ enc_priv_key = enc_priv_key.replace("\r\n", "\n")
+ pub_key = pub_key.replace("\r\n", "\n")
# Try to import the public key
import_pub_result = gpg.import_keys(pub_key)
@@ -104,16 +101,16 @@ class Logics:
return (
False,
{
- "bad_request":
- f"Your PGP public key does not seem valid.\n"+
- f"Stderr: {str(import_pub_result.stderr)}\n"+
- f"ReturnCode: {str(import_pub_result.returncode)}\n"+
- f"Summary: {str(import_pub_result.summary)}\n"+
- f"Results: {str(import_pub_result.results)}\n"+
- f"Imported: {str(import_pub_result.imported)}\n"
- },
- None,
- None)
+ "bad_request": f"Your PGP public key does not seem valid.\n"
+ + f"Stderr: {str(import_pub_result.stderr)}\n"
+ + f"ReturnCode: {str(import_pub_result.returncode)}\n"
+ + f"Summary: {str(import_pub_result.summary)}\n"
+ + f"Results: {str(import_pub_result.results)}\n"
+ + f"Imported: {str(import_pub_result.imported)}\n"
+ },
+ None,
+ None,
+ )
# Exports the public key again for uniform formatting.
pub_key = gpg.export_keys(import_pub_result.fingerprints[0])
@@ -123,16 +120,16 @@ class Logics:
return (
False,
{
- "bad_request":
- f"Your PGP encrypted private key does not seem valid.\n"+
- f"Stderr: {str(import_priv_result.stderr)}\n"+
- f"ReturnCode: {str(import_priv_result.returncode)}\n"+
- f"Summary: {str(import_priv_result.summary)}\n"+
- f"Results: {str(import_priv_result.results)}\n"+
- f"Sec Imported: {str(import_priv_result.sec_imported)}\n"
- },
- None,
- None)
+ "bad_request": f"Your PGP encrypted private key does not seem valid.\n"
+ + f"Stderr: {str(import_priv_result.stderr)}\n"
+ + f"ReturnCode: {str(import_priv_result.returncode)}\n"
+ + f"Summary: {str(import_priv_result.summary)}\n"
+ + f"Results: {str(import_priv_result.results)}\n"
+ + f"Sec Imported: {str(import_priv_result.sec_imported)}\n"
+ },
+ None,
+ None,
+ )
return True, None, pub_key, enc_priv_key
@@ -142,48 +139,50 @@ class Logics:
if not order.has_range:
if order.t0_satoshis > MAX_TRADE:
return False, {
- "bad_request":
- "Your order is too big. It is worth " +
- "{:,}".format(order.t0_satoshis) +
- " Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
- " Sats"
+ "bad_request": "Your order is too big. It is worth "
+ + "{:,}".format(order.t0_satoshis)
+ + " Sats now, but the limit is "
+ + "{:,}".format(MAX_TRADE)
+ + " Sats"
}
if order.t0_satoshis < MIN_TRADE:
return False, {
- "bad_request":
- "Your order is too small. It is worth " +
- "{:,}".format(order.t0_satoshis) +
- " Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
- " Sats"
+ "bad_request": "Your order is too small. It is worth "
+ + "{:,}".format(order.t0_satoshis)
+ + " Sats now, but the limit is "
+ + "{:,}".format(MIN_TRADE)
+ + " Sats"
}
elif order.has_range:
- min_sats = cls.calc_sats(order.min_amount, order.currency.exchange_rate, order.premium)
- max_sats = cls.calc_sats(order.max_amount, order.currency.exchange_rate, order.premium)
- if min_sats > max_sats/1.5:
+ min_sats = cls.calc_sats(
+ order.min_amount, order.currency.exchange_rate, order.premium
+ )
+ max_sats = cls.calc_sats(
+ order.max_amount, order.currency.exchange_rate, order.premium
+ )
+ if min_sats > max_sats / 1.5:
return False, {
- "bad_request":
- "Maximum range amount must be at least 50 percent higher than the minimum amount"
+ "bad_request": "Maximum range amount must be at least 50 percent higher than the minimum amount"
}
elif max_sats > MAX_TRADE:
return False, {
- "bad_request":
- "Your order maximum amount is too big. It is worth " +
- "{:,}".format(int(max_sats)) +
- " Sats now, but the limit is " + "{:,}".format(MAX_TRADE) +
- " Sats"
+ "bad_request": "Your order maximum amount is too big. It is worth "
+ + "{:,}".format(int(max_sats))
+ + " Sats now, but the limit is "
+ + "{:,}".format(MAX_TRADE)
+ + " Sats"
}
elif min_sats < MIN_TRADE:
return False, {
- "bad_request":
- "Your order minimum amount is too small. It is worth " +
- "{:,}".format(int(min_sats)) +
- " Sats now, but the limit is " + "{:,}".format(MIN_TRADE) +
- " Sats"
+ "bad_request": "Your order minimum amount is too small. It is worth "
+ + "{:,}".format(int(min_sats))
+ + " Sats now, but the limit is "
+ + "{:,}".format(MIN_TRADE)
+ + " Sats"
}
- elif min_sats < max_sats/8:
+ elif min_sats < max_sats / 8:
return False, {
- "bad_request":
- f"Your order amount range is too large. Max amount can only be 8 times bigger than min amount"
+ "bad_request": f"Your order amount range is too large. Max amount can only be 8 times bigger than min amount"
}
return True, None
@@ -191,8 +190,7 @@ class Logics:
def validate_amount_within_range(order, amount):
if amount > float(order.max_amount) or amount < float(order.min_amount):
return False, {
- "bad_request":
- "The amount specified is outside the range specified by the maker"
+ "bad_request": "The amount specified is outside the range specified by the maker"
}
return True, None
@@ -215,11 +213,12 @@ class Logics:
}
else:
if order.has_range:
- order.amount= amount
+ order.amount = amount
order.taker = user
order.status = Order.Status.TAK
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.TAK))
+ seconds=order.t_to_expire(Order.Status.TAK)
+ )
order.save()
return True, None
@@ -227,18 +226,20 @@ class Logics:
is_maker = order.maker == user
is_taker = order.taker == user
return (is_maker and order.type == Order.Types.BUY) or (
- is_taker and order.type == Order.Types.SELL)
+ is_taker and order.type == Order.Types.SELL
+ )
def is_seller(order, user):
is_maker = order.maker == user
is_taker = order.taker == user
return (is_maker and order.type == Order.Types.SELL) or (
- is_taker and order.type == Order.Types.BUY)
+ is_taker and order.type == Order.Types.BUY
+ )
def calc_sats(amount, exchange_rate, premium):
exchange_rate = float(exchange_rate)
premium_rate = exchange_rate * (1 + float(premium) / 100)
- return (float(amount) /premium_rate) * 100 * 1000 * 1000
+ return (float(amount) / premium_rate) * 100 * 1000 * 1000
@classmethod
def satoshis_now(cls, order):
@@ -247,7 +248,9 @@ class Logics:
satoshis_now = order.satoshis
else:
amount = order.amount if order.amount != None else order.max_amount
- satoshis_now = cls.calc_sats(amount, order.currency.exchange_rate, order.premium)
+ satoshis_now = cls.calc_sats(
+ amount, order.currency.exchange_rate, order.premium
+ )
return int(satoshis_now)
@@ -266,8 +269,8 @@ class Logics:
significant_digits = 5
price = round(
- price,
- significant_digits - int(math.floor(math.log10(abs(price)))) - 1)
+ price, significant_digits - int(math.floor(math.log10(abs(price)))) - 1
+ )
return price, premium
@@ -308,7 +311,7 @@ class Logics:
order.status = Order.Status.EXP
order.expiry_reason = Order.ExpiryReasons.NTAKEN
order.save()
- send_message.delay(order.id,'order_expired_untaken')
+ send_message.delay(order.id, "order_expired_untaken")
return True
elif order.status == Order.Status.TAK:
@@ -337,7 +340,7 @@ class Logics:
cls.settle_bond(order.maker_bond)
cls.return_bond(order.taker_bond)
# If seller is offline the escrow LNpayment does not exist
- try:
+ try:
cls.cancel_escrow(order)
except:
pass
@@ -352,7 +355,7 @@ class Logics:
else:
cls.settle_bond(order.taker_bond)
# If seller is offline the escrow LNpayment does not even exist
- try:
+ try:
cls.cancel_escrow(order)
except:
pass
@@ -362,7 +365,7 @@ class Logics:
order.trade_escrow = None
order.payout = None
cls.publish_order(order)
- send_message.delay(order.id,'order_published')
+ send_message.delay(order.id, "order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(taker_bond, order.maker.profile)
return True
@@ -393,7 +396,7 @@ class Logics:
order.taker_bond = None
order.trade_escrow = None
cls.publish_order(order)
- send_message.delay(order.id,'order_published')
+ send_message.delay(order.id, "order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(taker_bond, order.maker.profile)
return True
@@ -412,7 +415,8 @@ class Logics:
if order.taker:
profile = order.taker.profile
profile.penalty_expiration = timezone.now() + timedelta(
- seconds=PENALTY_TIMEOUT)
+ seconds=PENALTY_TIMEOUT
+ )
profile.save()
# Make order public again
@@ -424,9 +428,9 @@ class Logics:
@classmethod
def open_dispute(cls, order, user=None):
- # Always settle escrow and bonds during a dispute. Disputes
- # can take long to resolve, it might trigger force closure
- # for unresolved HTLCs) Dispute winner will have to submit a
+ # Always settle escrow and bonds during a dispute. Disputes
+ # can take long to resolve, it might trigger force closure
+ # for unresolved HTLCs) Dispute winner will have to submit a
# new invoice for value of escrow + bond.
valid_status_open_dispute = [
@@ -435,8 +439,10 @@ class Logics:
]
if order.status not in valid_status_open_dispute:
- return False, {"bad_request": "You cannot open a dispute of this order at this stage"}
-
+ return False, {
+ "bad_request": "You cannot open a dispute of this order at this stage"
+ }
+
if not order.trade_escrow.status == LNPayment.Status.SETLED:
cls.settle_escrow(order)
cls.settle_bond(order.maker_bond)
@@ -445,7 +451,8 @@ class Logics:
order.is_disputed = True
order.status = Order.Status.DIS
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.DIS))
+ seconds=order.t_to_expire(Order.Status.DIS)
+ )
order.save()
# User could be None if a dispute is open automatically due to weird expiration.
@@ -456,10 +463,11 @@ class Logics:
profile.orders_disputes_started = [str(order.id)]
else:
profile.orders_disputes_started = list(
- profile.orders_disputes_started).append(str(order.id))
+ profile.orders_disputes_started
+ ).append(str(order.id))
profile.save()
- send_message.delay(order.id,'dispute_opened')
+ send_message.delay(order.id, "dispute_opened")
return True, None
def dispute_statement(order, user, statement):
@@ -467,15 +475,14 @@ class Logics:
if not order.status == Order.Status.DIS:
return False, {
- "bad_request":
- "Only orders in dispute accept dispute statements"
+ "bad_request": "Only orders in dispute accept dispute statements"
}
if len(statement) > 5000:
return False, {
"bad_statement": "The statement is longer than 5000 characters"
}
-
+
if len(statement) < 100:
return False, {
"bad_statement": "The statement is too short. Make sure to be thorough."
@@ -487,72 +494,89 @@ class Logics:
order.taker_statement = statement
# If both statements are in, move status to wait for dispute resolution
- if order.maker_statement not in [None,""] and order.taker_statement not in [None,""]:
+ if order.maker_statement not in [None, ""] and order.taker_statement not in [
+ None,
+ "",
+ ]:
order.status = Order.Status.WFR
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.WFR))
+ seconds=order.t_to_expire(Order.Status.WFR)
+ )
order.save()
return True, None
def compute_swap_fee_rate(balance):
-
- shape = str(config('SWAP_FEE_SHAPE'))
+ shape = str(config("SWAP_FEE_SHAPE"))
if shape == "linear":
- MIN_SWAP_FEE = float(config('MIN_SWAP_FEE'))
- MIN_POINT = float(config('MIN_POINT'))
- MAX_SWAP_FEE = float(config('MAX_SWAP_FEE'))
- MAX_POINT = float(config('MAX_POINT'))
+ MIN_SWAP_FEE = float(config("MIN_SWAP_FEE"))
+ MIN_POINT = float(config("MIN_POINT"))
+ MAX_SWAP_FEE = float(config("MAX_SWAP_FEE"))
+ MAX_POINT = float(config("MAX_POINT"))
if float(balance.onchain_fraction) > MIN_POINT:
swap_fee_rate = MIN_SWAP_FEE
else:
slope = (MAX_SWAP_FEE - MIN_SWAP_FEE) / (MAX_POINT - MIN_POINT)
- swap_fee_rate = slope * (balance.onchain_fraction - MAX_POINT) + MAX_SWAP_FEE
+ swap_fee_rate = (
+ slope * (balance.onchain_fraction - MAX_POINT) + MAX_SWAP_FEE
+ )
elif shape == "exponential":
- MIN_SWAP_FEE = float(config('MIN_SWAP_FEE'))
- MAX_SWAP_FEE = float(config('MAX_SWAP_FEE'))
- SWAP_LAMBDA = float(config('SWAP_LAMBDA'))
- swap_fee_rate = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * math.exp(-SWAP_LAMBDA * float(balance.onchain_fraction))
+ MIN_SWAP_FEE = float(config("MIN_SWAP_FEE"))
+ MAX_SWAP_FEE = float(config("MAX_SWAP_FEE"))
+ SWAP_LAMBDA = float(config("SWAP_LAMBDA"))
+ swap_fee_rate = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * math.exp(
+ -SWAP_LAMBDA * float(balance.onchain_fraction)
+ )
return swap_fee_rate * 100
@classmethod
def create_onchain_payment(cls, order, user, preliminary_amount):
- '''
+ """
Creates an empty OnchainPayment for order.payout_tx.
It sets the fees to be applied to this order if onchain Swap is used.
If the user submits a LN invoice instead. The returned OnchainPayment goes unused.
- '''
+ """
# Make sure no invoice payout is attached to order
order.payout = None
# Create onchain_payment
onchain_payment = OnchainPayment.objects.create(receiver=user)
-
+
# Compute a safer available onchain liquidity: (confirmed_utxos - reserve - pending_outgoing_txs))
# Accounts for already committed outgoing TX for previous users.
confirmed = onchain_payment.balance.onchain_confirmed
reserve = 0.01 * onchain_payment.balance.total # We assume a reserve of 1%
- pending_txs = OnchainPayment.objects.filter(status=OnchainPayment.Status.VALID).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
-
+ pending_txs = OnchainPayment.objects.filter(
+ status=OnchainPayment.Status.VALID
+ ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
+
if pending_txs == None:
pending_txs = 0
-
+
available_onchain = confirmed - reserve - pending_txs
- if preliminary_amount > available_onchain: # Not enough onchain balance to commit for this swap.
+ if (
+ preliminary_amount > available_onchain
+ ): # Not enough onchain balance to commit for this swap.
return False
- suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount)["mining_fee_rate"]
+ suggested_mining_fee_rate = LNNode.estimate_fee(amount_sats=preliminary_amount)[
+ "mining_fee_rate"
+ ]
# Hardcap mining fee suggested at 50 sats/vbyte
if suggested_mining_fee_rate > 50:
suggested_mining_fee_rate = 50
- onchain_payment.suggested_mining_fee_rate = max(1.05, LNNode.estimate_fee(amount_sats=preliminary_amount)["mining_fee_rate"])
- onchain_payment.swap_fee_rate = cls.compute_swap_fee_rate(onchain_payment.balance)
+ onchain_payment.suggested_mining_fee_rate = max(
+ 1.05, LNNode.estimate_fee(amount_sats=preliminary_amount)["mining_fee_rate"]
+ )
+ onchain_payment.swap_fee_rate = cls.compute_swap_fee_rate(
+ onchain_payment.balance
+ )
onchain_payment.save()
order.payout_tx = onchain_payment
@@ -575,18 +599,22 @@ class Logics:
fee_sats = order.last_satoshis * fee_fraction
- reward_tip = int(config('REWARD_TIP')) if user.profile.is_referred else 0
+ reward_tip = int(config("REWARD_TIP")) if user.profile.is_referred else 0
context = {}
# context necessary for the user to submit a LN invoice
- context["invoice_amount"] = round(order.last_satoshis - fee_sats - reward_tip) # Trading fee to buyer is charged here.
+ context["invoice_amount"] = round(
+ order.last_satoshis - fee_sats - reward_tip
+ ) # Trading fee to buyer is charged here.
# context necessary for the user to submit an onchain address
MIN_SWAP_AMOUNT = int(config("MIN_SWAP_AMOUNT"))
if context["invoice_amount"] < MIN_SWAP_AMOUNT:
context["swap_allowed"] = False
- context["swap_failure_reason"] = "Order amount is too small to be eligible for a swap"
+ context[
+ "swap_failure_reason"
+ ] = "Order amount is too small to be eligible for a swap"
return True, context
if config("DISABLE_ONCHAIN", cast=bool):
@@ -596,10 +624,14 @@ class Logics:
if order.payout_tx == None:
# Creates the OnchainPayment object and checks node balance
- valid = cls.create_onchain_payment(order, user, preliminary_amount=context["invoice_amount"])
+ valid = cls.create_onchain_payment(
+ order, user, preliminary_amount=context["invoice_amount"]
+ )
if not valid:
context["swap_allowed"] = False
- context["swap_failure_reason"] = "Not enough onchain liquidity available to offer a swap"
+ context[
+ "swap_failure_reason"
+ ] = "Not enough onchain liquidity available to offer a swap"
return True, context
context["swap_allowed"] = True
@@ -611,45 +643,45 @@ class Logics:
@classmethod
def escrow_amount(cls, order, user):
"""Computes escrow invoice amount. Uses order.last_satoshis,
- that is the final trade amount set at Taker Bond time"""
+ that is the final trade amount set at Taker Bond time"""
if user == order.maker:
fee_fraction = FEE * MAKER_FEE_SPLIT
elif user == order.taker:
fee_fraction = FEE * (1 - MAKER_FEE_SPLIT)
- fee_sats = order.last_satoshis * fee_fraction
+ fee_sats = order.last_satoshis * fee_fraction
- reward_tip = int(config('REWARD_TIP')) if user.profile.is_referred else 0
+ reward_tip = int(config("REWARD_TIP")) if user.profile.is_referred else 0
if cls.is_seller(order, user):
- escrow_amount = round(order.last_satoshis + fee_sats + reward_tip) # Trading fee to seller is charged here.
+ escrow_amount = round(
+ order.last_satoshis + fee_sats + reward_tip
+ ) # Trading fee to seller is charged here.
return True, {"escrow_amount": escrow_amount}
@classmethod
def update_address(cls, order, user, address, mining_fee_rate):
-
+
# Empty address?
if not address:
- return False, {
- "bad_address":
- "You submitted an empty invoice"
- }
+ return False, {"bad_address": "You submitted an empty invoice"}
# only the buyer can post a buyer address
if not cls.is_buyer(order, user):
return False, {
- "bad_request":
- "Only the buyer of this order can provide a payout address."
+ "bad_request": "Only the buyer of this order can provide a payout address."
}
# not the right time to submit
- if (not (order.taker_bond.status == order.maker_bond.status ==
- LNPayment.Status.LOCKED)
- and not order.status == Order.Status.FAI):
- return False, {
- "bad_request":
- "You cannot submit an adress are not locked."
- }
+ if (
+ not (
+ order.taker_bond.status
+ == order.maker_bond.status
+ == LNPayment.Status.LOCKED
+ )
+ and not order.status == Order.Status.FAI
+ ):
+ return False, {"bad_request": "You cannot submit an adress are not locked."}
# not a valid address (does not accept Taproot as of now)
valid, context = validate_onchain_address(address)
if not valid:
@@ -659,13 +691,11 @@ class Logics:
# not a valid mining fee
if float(mining_fee_rate) < 1:
return False, {
- "bad_address":
- "The mining fee is too low, must be higher than 1 Sat/vbyte"
+ "bad_address": "The mining fee is too low, must be higher than 1 Sat/vbyte"
}
elif float(mining_fee_rate) > 50:
return False, {
- "bad_address":
- "The mining fee is too high, must be less than 50 Sats/vbyte"
+ "bad_address": "The mining fee is too high, must be less than 50 Sats/vbyte"
}
order.payout_tx.mining_fee_rate = float(mining_fee_rate)
# If not mining ee provider use backend's suggested fee rate
@@ -676,7 +706,11 @@ class Logics:
tx.address = address
tx.mining_fee_sats = int(tx.mining_fee_rate * 141)
tx.num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
- tx.sent_satoshis = int(float(tx.num_satoshis) - float(tx.num_satoshis) * float(tx.swap_fee_rate)/100 - float(tx.mining_fee_sats))
+ tx.sent_satoshis = int(
+ float(tx.num_satoshis)
+ - float(tx.num_satoshis) * float(tx.swap_fee_rate) / 100
+ - float(tx.mining_fee_sats)
+ )
tx.status = OnchainPayment.Status.VALID
tx.save()
@@ -689,33 +723,32 @@ class Logics:
@classmethod
def update_invoice(cls, order, user, invoice):
-
+
# Empty invoice?
if not invoice:
- return False, {
- "bad_invoice":
- "You submitted an empty invoice"
- }
+ return False, {"bad_invoice": "You submitted an empty invoice"}
# only the buyer can post a buyer invoice
if not cls.is_buyer(order, user):
return False, {
- "bad_request":
- "Only the buyer of this order can provide a buyer invoice."
+ "bad_request": "Only the buyer of this order can provide a buyer invoice."
}
if not order.taker_bond:
return False, {"bad_request": "Wait for your order to be taken."}
- if (not (order.taker_bond.status == order.maker_bond.status ==
- LNPayment.Status.LOCKED)
- and not order.status == Order.Status.FAI):
+ if (
+ not (
+ order.taker_bond.status
+ == order.maker_bond.status
+ == LNPayment.Status.LOCKED
+ )
+ and not order.status == Order.Status.FAI
+ ):
return False, {
- "bad_request":
- "You cannot submit a invoice while bonds are not locked."
+ "bad_request": "You cannot submit a invoice while bonds are not locked."
}
if order.status == Order.Status.FAI:
if order.payout.status != LNPayment.Status.EXPIRE:
return False, {
- "bad_request":
- "You cannot submit an invoice only after expiration or 3 failed attempts"
+ "bad_request": "You cannot submit an invoice only after expiration or 3 failed attempts"
}
# cancel onchain_payout if existing
@@ -731,8 +764,7 @@ class Logics:
concept=LNPayment.Concepts.PAYBUYER,
type=LNPayment.Types.NORM,
sender=User.objects.get(username=ESCROW_USERNAME),
- order_paid_LN=
- order, # In case this user has other payouts, update the one related to this order.
+ order_paid_LN=order, # In case this user has other payouts, update the one related to this order.
receiver=user,
# if there is a LNPayment matching these above, it updates that one with defaults below.
defaults={
@@ -754,13 +786,14 @@ class Logics:
return True, None
@classmethod
- def move_state_updated_payout_method(cls,order):
+ def move_state_updated_payout_method(cls, order):
# 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(
- seconds=order.t_to_expire(Order.Status.CHA))
- send_message.delay(order.id,'fiat_exchange_starts')
+ seconds=order.t_to_expire(Order.Status.CHA)
+ )
+ send_message.delay(order.id, "fiat_exchange_starts")
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
elif order.status == Order.Status.WF2:
@@ -771,20 +804,20 @@ class Logics:
elif order.trade_escrow.status == LNPayment.Status.LOCKED:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.CHA))
- send_message.delay(order.id,'fiat_exchange_starts')
+ seconds=order.t_to_expire(Order.Status.CHA)
+ )
+ send_message.delay(order.id, "fiat_exchange_starts")
else:
order.status = Order.Status.WFE
# If the order status is 'Failed Routing'. Retry payment.
elif order.status == Order.Status.FAI:
- if LNNode.double_check_htlc_is_settled(
- order.trade_escrow.payment_hash):
+ if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
order.status = Order.Status.PAY
order.payout.status = LNPayment.Status.FLIGHT
order.payout.routing_attempts = 0
order.payout.save()
-
+
order.save()
return True
@@ -814,8 +847,7 @@ class Logics:
if user.profile.penalty_expiration:
if user.profile.penalty_expiration > timezone.now():
- time_out = (user.profile.penalty_expiration -
- timezone.now()).seconds
+ time_out = (user.profile.penalty_expiration - timezone.now()).seconds
return True, time_out
return False, None
@@ -853,12 +885,14 @@ class Logics:
"""The order dissapears from book and goes to cancelled. If strict, maker is charged the bond
to prevent DDOS on the LN node and order book. If not strict, maker is returned
the bond (more user friendly)."""
- elif order.status in [Order.Status.PUB, Order.Status.PAU] and order.maker == user:
+ elif (
+ order.status in [Order.Status.PUB, Order.Status.PAU] and order.maker == user
+ ):
# Return the maker bond (Maker gets returned the bond for cancelling public order)
- if cls.return_bond(order.maker_bond):
+ if cls.return_bond(order.maker_bond):
order.status = Order.Status.UCA
order.save()
- send_message.delay(order.id,'public_order_cancelled')
+ send_message.delay(order.id, "public_order_cancelled")
return True, None
# 2.b) When maker cancels after bond and before taker bond is locked
@@ -866,11 +900,11 @@ class Logics:
The bond maker bond is returned."""
elif order.status == Order.Status.TAK and order.maker == user:
# Return the maker bond (Maker gets returned the bond for cancelling public order)
- if cls.return_bond(order.maker_bond):
+ if cls.return_bond(order.maker_bond):
cls.cancel_bond(order.taker_bond)
order.status = Order.Status.UCA
order.save()
- send_message.delay(order.id,'public_order_cancelled')
+ send_message.delay(order.id, "public_order_cancelled")
return True, None
# 3) When taker cancels before bond
@@ -889,7 +923,9 @@ class Logics:
# 4.a) When maker cancel after bond (before escrow)
"""The order into cancelled status if maker cancels."""
- elif (order.status in [Order.Status.WF2,Order.Status.WFE] and order.maker == user):
+ elif (
+ order.status in [Order.Status.WF2, Order.Status.WFE] and order.maker == user
+ ):
# cancel onchain payment if existing
cls.cancel_onchain_payment(order)
# Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
@@ -905,8 +941,9 @@ class Logics:
# 4.b) When taker cancel after bond (before escrow)
"""The order into cancelled status if mtker cancels."""
- elif (order.status in [Order.Status.WF2, Order.Status.WFE]
- and order.taker == user):
+ elif (
+ order.status in [Order.Status.WF2, Order.Status.WFE] and order.taker == user
+ ):
# cancel onchain payment if existing
cls.cancel_onchain_payment(order)
# Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
@@ -916,7 +953,7 @@ class Logics:
order.payout = None
order.trade_escrow = None
cls.publish_order(order)
- send_message.delay(order.id,'order_published')
+ send_message.delay(order.id, "order_published")
# Reward maker with part of the taker bond
cls.add_slashed_rewards(order.taker_bond, order.maker.profile)
return True, None
@@ -926,9 +963,7 @@ class Logics:
When a user asks for cancel, 'order.m/t/aker_asked_cancel' goes True.
When the second user asks for cancel. Order is totally cancelled.
Must have a small cost for both parties to prevent node DDOS."""
- elif order.status in [
- Order.Status.WFI, Order.Status.CHA
- ]:
+ elif order.status in [Order.Status.WFI, Order.Status.CHA]:
# if the maker had asked, and now the taker does: cancel order, return everything
if order.maker_asked_cancel and user == order.taker:
@@ -965,14 +1000,15 @@ class Logics:
cls.return_escrow(order)
order.status = Order.Status.CCA
order.save()
- send_message.delay(order.id,'collaborative_cancelled')
+ send_message.delay(order.id, "collaborative_cancelled")
return
@classmethod
def publish_order(cls, order):
order.status = Order.Status.PUB
order.expires_at = order.created_at + timedelta(
- seconds=order.t_to_expire(Order.Status.PUB))
+ seconds=order.t_to_expire(Order.Status.PUB)
+ )
if order.has_range:
order.amount = None
order.last_satoshis = cls.satoshis_now(order)
@@ -982,10 +1018,10 @@ class Logics:
return
def compute_cltv_expiry_blocks(order, invoice_concept):
- ''' Computes timelock CLTV expiry of the last hop in blocks for hodl invoices
+ """Computes timelock CLTV expiry of the last hop in blocks for hodl invoices
invoice_concepts (str): maker_bond, taker_bond, trade_escrow
- '''
+ """
# Every invoice_concept must be locked by at least the fiat exchange duration
# Every invoice must also be locked for deposit_time (order.escrow_duration or WFE status)
cltv_expiry_secs = order.t_to_expire(Order.Status.CHA)
@@ -1000,7 +1036,13 @@ class Logics:
safe_cltv_expiry_secs = cltv_expiry_secs * MAX_MINING_NETWORK_SPEEDUP_EXPECTED
# Convert to blocks using assummed average block time (~8 mins/block)
cltv_expiry_blocks = int(safe_cltv_expiry_secs / (BLOCK_TIME * 60))
- print(invoice_concept," cltv_expiry_hours:",cltv_expiry_secs/3600," cltv_expiry_blocks:",cltv_expiry_blocks)
+ print(
+ invoice_concept,
+ " cltv_expiry_hours:",
+ cltv_expiry_secs / 3600,
+ " cltv_expiry_blocks:",
+ cltv_expiry_blocks,
+ )
return cltv_expiry_blocks
@@ -1010,7 +1052,7 @@ class Logics:
return True
elif LNNode.validate_hold_invoice_locked(order.maker_bond):
cls.publish_order(order)
- send_message.delay(order.id,'order_published')
+ send_message.delay(order.id, "order_published")
return True
return False
@@ -1021,8 +1063,7 @@ class Logics:
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."
+ "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
@@ -1038,7 +1079,7 @@ class Logics:
# If there was no maker_bond object yet, generates one
order.last_satoshis = cls.satoshis_now(order)
order.last_satoshis_time = timezone.now()
- bond_satoshis = int(order.last_satoshis * order.bond_size/100)
+ bond_satoshis = int(order.last_satoshis * order.bond_size / 100)
if user.profile.wants_stealth:
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
@@ -1051,19 +1092,17 @@ class Logics:
bond_satoshis,
description,
invoice_expiry=order.t_to_expire(Order.Status.WFB),
- cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "maker_bond")
+ cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "maker_bond"),
)
except Exception as e:
print(str(e))
if "failed to connect to all addresses" in str(e):
return False, {
- "bad_request":
- "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
+ "bad_request": "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
}
elif "wallet locked" in str(e):
return False, {
- "bad_request":
- "This is weird, RoboSats' lightning wallet is locked. Check in the Telegram group, maybe the staff has died."
+ "bad_request": "This is weird, RoboSats' lightning wallet is locked. Check in the Telegram group, maybe the staff has died."
}
order.maker_bond = LNPayment.objects.create(
@@ -1100,9 +1139,10 @@ class Logics:
order.taker_bond.status = LNPayment.Status.LOCKED
order.taker_bond.save()
- # With the bond confirmation the order is extended 'public_order_duration' hours
+ # With the bond confirmation the order is extended 'public_order_duration' hours
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.WF2))
+ seconds=order.t_to_expire(Order.Status.WF2)
+ )
order.status = Order.Status.WF2
order.save()
@@ -1111,13 +1151,13 @@ class Logics:
order.taker.profile.total_contracts += 1
order.maker.profile.save()
order.taker.profile.save()
-
+
# Log a market tick
try:
MarketTick.log_a_tick(order)
except:
pass
- send_message.delay(order.id,'order_taken_confirmed')
+ send_message.delay(order.id, "order_taken_confirmed")
return True
@classmethod
@@ -1136,8 +1176,7 @@ class Logics:
if order.expires_at < timezone.now():
cls.order_expires(order)
return False, {
- "bad_request":
- "Invoice expired. You did not confirm taking the order in time."
+ "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.
@@ -1153,15 +1192,14 @@ class Logics:
# If there was no taker_bond object yet, generates one
order.last_satoshis = cls.satoshis_now(order)
order.last_satoshis_time = timezone.now()
- bond_satoshis = int(order.last_satoshis * order.bond_size/100)
+ bond_satoshis = int(order.last_satoshis * order.bond_size / 100)
pos_text = "Buying" if cls.is_buyer(order, user) else "Selling"
if user.profile.wants_stealth:
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
else:
description = (
f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
- +
- " - Taker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally."
+ + " - Taker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally."
)
# Gen hold Invoice
@@ -1170,14 +1208,13 @@ class Logics:
bond_satoshis,
description,
invoice_expiry=order.t_to_expire(Order.Status.TAK),
- cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "taker_bond")
+ cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "taker_bond"),
)
except Exception as e:
if "status = StatusCode.UNAVAILABLE" in str(e):
return False, {
- "bad_request":
- "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
+ "bad_request": "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
}
order.taker_bond = LNPayment.objects.create(
@@ -1197,7 +1234,8 @@ class Logics:
)
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.TAK))
+ seconds=order.t_to_expire(Order.Status.TAK)
+ )
order.save()
return True, {
"bond_invoice": hold_payment["invoice"],
@@ -1213,8 +1251,9 @@ class Logics:
elif order.status == Order.Status.WFE:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.CHA))
- send_message.delay(order.id,'fiat_exchange_starts')
+ seconds=order.t_to_expire(Order.Status.CHA)
+ )
+ send_message.delay(order.id, "fiat_exchange_starts")
order.save()
@classmethod
@@ -1234,8 +1273,7 @@ class Logics:
if order.expires_at < timezone.now():
cls.order_expires(order)
return False, {
- "bad_request":
- "Invoice expired. You did not send the escrow in time."
+ "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.
@@ -1250,7 +1288,9 @@ class Logics:
}
# If there was no taker_bond object yet, generate one
- escrow_satoshis = cls.escrow_amount(order, user)[1]["escrow_amount"] # Amount was fixed when taker bond was locked, fee applied here
+ escrow_satoshis = cls.escrow_amount(order, user)[1][
+ "escrow_amount"
+ ] # Amount was fixed when taker bond was locked, fee applied here
if user.profile.wants_stealth:
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
else:
@@ -1262,14 +1302,15 @@ class Logics:
escrow_satoshis,
description,
invoice_expiry=order.t_to_expire(Order.Status.WF2),
- cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(order, "trade_escrow")
+ cltv_expiry_blocks=cls.compute_cltv_expiry_blocks(
+ order, "trade_escrow"
+ ),
)
except Exception as e:
if "status = StatusCode.UNAVAILABLE" in str(e):
return False, {
- "bad_request":
- "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
+ "bad_request": "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
}
order.trade_escrow = LNPayment.objects.create(
@@ -1341,7 +1382,7 @@ class Logics:
raise e
def cancel_onchain_payment(order):
- ''' Cancel onchain_payment if existing '''
+ """Cancel onchain_payment if existing"""
if order.payout_tx:
order.payout_tx.status = OnchainPayment.Status.CANCE
@@ -1370,7 +1411,7 @@ class Logics:
@classmethod
def pay_buyer(cls, order):
- '''Pays buyer invoice or onchain address'''
+ """Pays buyer invoice or onchain address"""
# Pay to buyer invoice
if not order.is_swap:
@@ -1379,7 +1420,7 @@ class Logics:
order.payout.status = LNPayment.Status.FLIGHT
order.payout.save()
order.save()
- send_message.delay(order.id,'trade_successful')
+ send_message.delay(order.id, "trade_successful")
order.contract_finalization_time = timezone.now()
order.save()
return True
@@ -1395,7 +1436,7 @@ class Logics:
order.payout_tx.save()
order.status = Order.Status.SUC
order.save()
- send_message.delay(order.id,'trade_successful')
+ send_message.delay(order.id, "trade_successful")
order.contract_finalization_time = timezone.now()
order.save()
return True
@@ -1407,9 +1448,7 @@ class Logics:
If user is buyer: fiat_sent goes to true.
If User is seller and fiat_sent is true: settle the escrow and pay buyer invoice!"""
- if (order.status == Order.Status.CHA
- or order.status == Order.Status.FSE
- ):
+ if order.status == Order.Status.CHA or order.status == Order.Status.FSE:
# If buyer, settle escrow and mark fiat sent
if cls.is_buyer(order, user):
@@ -1420,25 +1459,27 @@ class Logics:
elif cls.is_seller(order, user):
if not order.is_fiat_sent:
return False, {
- "bad_request":
- "You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer."
+ "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
- num_satoshis = order.payout_tx.num_satoshis if order.is_swap else order.payout.num_satoshis
+ num_satoshis = (
+ order.payout_tx.num_satoshis
+ if order.is_swap
+ else order.payout.num_satoshis
+ )
if order.trade_escrow.num_satoshis <= num_satoshis:
return False, {
- "bad_request":
- "Woah, something broke badly. Report in the public channels, or open a Github Issue."
+ "bad_request": "Woah, something broke badly. Report in the public channels, or open a Github Issue."
}
-
+
# !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
- if cls.settle_escrow(order):
+ if cls.settle_escrow(order):
order.trade_escrow.status = LNPayment.Status.SETLED
# Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
- # RETURN THE BONDS
+ # RETURN THE BONDS
cls.return_bond(order.taker_bond)
cls.return_bond(order.maker_bond)
##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
@@ -1454,18 +1495,16 @@ class Logics:
else:
return False, {
- "bad_request":
- "You cannot confirm the fiat payment at this stage"
+ "bad_request": "You cannot confirm the fiat payment at this stage"
}
order.save()
return True, None
- def pause_unpause_public_order(order,user):
+ def pause_unpause_public_order(order, user):
if not order.maker == user:
return False, {
- "bad_request":
- "You cannot pause or unpause an order you did not make"
+ "bad_request": "You cannot pause or unpause an order you did not make"
}
else:
if order.status == Order.Status.PUB:
@@ -1474,17 +1513,16 @@ class Logics:
order.status = Order.Status.PUB
else:
return False, {
- "bad_request":
- "You can only pause/unpause an order that is either public or paused"
+ "bad_request": "You can only pause/unpause an order that is either public or paused"
}
order.save()
return True, None
@classmethod
def rate_counterparty(cls, order, user, rating):
- '''
+ """
Not in use
- '''
+ """
rating_allowed_status = [
Order.Status.PAY,
@@ -1507,9 +1545,7 @@ class Logics:
order.taker_rated = True
order.save()
else:
- return False, {
- "bad_request": "You cannot rate your counterparty yet."
- }
+ return False, {"bad_request": "You cannot rate your counterparty yet."}
return True, None
@@ -1521,31 +1557,31 @@ class Logics:
@classmethod
def add_rewards(cls, order):
- '''
- This function is called when a trade is finished.
+ """
+ This function is called when a trade is finished.
If participants of the order were referred, the reward is given to the referees.
- '''
+ """
if order.maker.profile.is_referred:
profile = order.maker.profile.referred_by
- profile.pending_rewards += int(config('REWARD_TIP'))
+ profile.pending_rewards += int(config("REWARD_TIP"))
profile.save()
-
+
if order.taker.profile.is_referred:
profile = order.taker.profile.referred_by
- profile.pending_rewards += int(config('REWARD_TIP'))
+ profile.pending_rewards += int(config("REWARD_TIP"))
profile.save()
return
@classmethod
def add_slashed_rewards(cls, bond, profile):
- '''
+ """
When a bond is slashed due to overtime, rewards the user that was waiting.
If participants of the order were referred, the reward is given to the referees.
- '''
- reward_fraction = float(config('SLASHED_BOND_REWARD_SPLIT'))
- reward = int(bond.num_satoshis*reward_fraction)
+ """
+ reward_fraction = float(config("SLASHED_BOND_REWARD_SPLIT"))
+ reward = int(bond.num_satoshis * reward_fraction)
profile.earned_rewards += reward
profile.save()
@@ -1568,17 +1604,17 @@ class Logics:
try:
lnpayment = LNPayment.objects.create(
- concept= LNPayment.Concepts.WITHREWA,
- type= LNPayment.Types.NORM,
- sender= User.objects.get(username=ESCROW_USERNAME),
- status= LNPayment.Status.VALIDI,
+ concept=LNPayment.Concepts.WITHREWA,
+ type=LNPayment.Types.NORM,
+ sender=User.objects.get(username=ESCROW_USERNAME),
+ status=LNPayment.Status.VALIDI,
receiver=user,
- invoice= invoice,
- num_satoshis= num_satoshis,
- description= reward_payout["description"],
- payment_hash= reward_payout["payment_hash"],
- created_at= reward_payout["created_at"],
- expires_at= reward_payout["expires_at"],
+ invoice=invoice,
+ num_satoshis=num_satoshis,
+ description=reward_payout["description"],
+ payment_hash=reward_payout["payment_hash"],
+ created_at=reward_payout["created_at"],
+ expires_at=reward_payout["expires_at"],
)
# Might fail if payment_hash already exists in DB
except:
@@ -1589,72 +1625,103 @@ class Logics:
# Pays the invoice.
paid, failure_reason = LNNode.pay_invoice(lnpayment)
- if paid:
+ if paid:
user.profile.earned_rewards = 0
user.profile.claimed_rewards += num_satoshis
user.profile.save()
return True, None
# If fails, adds the rewards again.
- else:
+ else:
user.profile.earned_rewards = num_satoshis
user.profile.save()
context = {}
- context['bad_invoice'] = failure_reason
+ context["bad_invoice"] = failure_reason
return False, context
@classmethod
def summarize_trade(cls, order, user):
- '''
+ """
Summarizes a finished order. Returns a dict with
amounts, fees, costs, etc, for buyer and seller.
- '''
- if not order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
- return False, {'bad_summary':'Order has not finished yet'}
-
+ """
+ if not order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
+ return False, {"bad_summary": "Order has not finished yet"}
+
context = {}
- users = {'taker': order.taker, 'maker': order.maker}
+ users = {"taker": order.taker, "maker": order.maker}
for order_user in users:
summary = {}
- summary['trade_fee_percent'] = FEE * MAKER_FEE_SPLIT if order_user == 'maker' else FEE * (1 - MAKER_FEE_SPLIT)
- summary['bond_size_sats'] = order.maker_bond.num_satoshis if order_user == 'maker' else order.taker_bond.num_satoshis
- summary['bond_size_percent'] = order.bond_size
- summary['is_buyer'] = cls.is_buyer(order, users[order_user])
+ summary["trade_fee_percent"] = (
+ FEE * MAKER_FEE_SPLIT
+ if order_user == "maker"
+ else FEE * (1 - MAKER_FEE_SPLIT)
+ )
+ summary["bond_size_sats"] = (
+ order.maker_bond.num_satoshis
+ if order_user == "maker"
+ else order.taker_bond.num_satoshis
+ )
+ summary["bond_size_percent"] = order.bond_size
+ summary["is_buyer"] = cls.is_buyer(order, users[order_user])
- if summary['is_buyer']:
- summary['sent_fiat'] = order.amount
+ if summary["is_buyer"]:
+ summary["sent_fiat"] = order.amount
if order.is_swap:
- summary['received_sats'] = order.payout_tx.sent_satoshis
+ summary["received_sats"] = order.payout_tx.sent_satoshis
else:
- summary['received_sats'] = order.payout.num_satoshis
- summary['trade_fee_sats'] = round(order.last_satoshis - summary['received_sats'])
+ summary["received_sats"] = order.payout.num_satoshis
+ summary["trade_fee_sats"] = round(
+ order.last_satoshis - summary["received_sats"]
+ )
# Only add context for swap costs if the user is the swap recipient. Peer should not know whether it was a swap
if users[order_user] == user and order.is_swap:
- summary['is_swap'] = order.is_swap
- summary['received_onchain_sats'] = order.payout_tx.sent_satoshis
- summary['mining_fee_sats'] = order.payout_tx.mining_fee_sats
- summary['swap_fee_sats'] = round(order.payout_tx.num_satoshis - order.payout_tx.mining_fee_sats - order.payout_tx.sent_satoshis)
- summary['swap_fee_percent'] = order.payout_tx.swap_fee_rate
- summary['trade_fee_sats'] = round(order.last_satoshis - summary['received_sats'] - summary['mining_fee_sats'] - summary['swap_fee_sats'])
+ summary["is_swap"] = order.is_swap
+ summary["received_onchain_sats"] = order.payout_tx.sent_satoshis
+ summary["mining_fee_sats"] = order.payout_tx.mining_fee_sats
+ summary["swap_fee_sats"] = round(
+ order.payout_tx.num_satoshis
+ - order.payout_tx.mining_fee_sats
+ - order.payout_tx.sent_satoshis
+ )
+ summary["swap_fee_percent"] = order.payout_tx.swap_fee_rate
+ summary["trade_fee_sats"] = round(
+ order.last_satoshis
+ - summary["received_sats"]
+ - summary["mining_fee_sats"]
+ - summary["swap_fee_sats"]
+ )
else:
- summary['sent_sats'] = order.trade_escrow.num_satoshis
- summary['received_fiat'] = order.amount
- summary['trade_fee_sats'] = round(summary['sent_sats'] - order.last_satoshis )
- context[f'{order_user}_summary']=summary
+ summary["sent_sats"] = order.trade_escrow.num_satoshis
+ summary["received_fiat"] = order.amount
+ summary["trade_fee_sats"] = round(
+ summary["sent_sats"] - order.last_satoshis
+ )
+ context[f"{order_user}_summary"] = summary
platform_summary = {}
- platform_summary['contract_exchange_rate'] = float(order.amount) / (float(order.last_satoshis) / 100000000)
+ platform_summary["contract_exchange_rate"] = float(order.amount) / (
+ float(order.last_satoshis) / 100000000
+ )
if order.last_satoshis_time != None:
- platform_summary['contract_timestamp'] = order.last_satoshis_time
- platform_summary['contract_total_time'] = order.contract_finalization_time - order.last_satoshis_time
+ platform_summary["contract_timestamp"] = order.last_satoshis_time
+ platform_summary["contract_total_time"] = (
+ order.contract_finalization_time - order.last_satoshis_time
+ )
if not order.is_swap:
- platform_summary['routing_fee_sats'] = order.payout.fee
- platform_summary['trade_revenue_sats'] = int(order.trade_escrow.num_satoshis - order.payout.num_satoshis - order.payout.fee)
+ platform_summary["routing_fee_sats"] = order.payout.fee
+ platform_summary["trade_revenue_sats"] = int(
+ order.trade_escrow.num_satoshis
+ - order.payout.num_satoshis
+ - order.payout.fee
+ )
else:
- platform_summary['routing_fee_sats'] = 0
- platform_summary['trade_revenue_sats'] = int(order.trade_escrow.num_satoshis - order.payout_tx.num_satoshis)
- context['platform_summary'] = platform_summary
+ platform_summary["routing_fee_sats"] = 0
+ platform_summary["trade_revenue_sats"] = int(
+ order.trade_escrow.num_satoshis - order.payout_tx.num_satoshis
+ )
+ context["platform_summary"] = platform_summary
- return True, context
\ No newline at end of file
+ return True, context
diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py
index a72725f1..7fd3d2a5 100644
--- a/api/management/commands/clean_orders.py
+++ b/api/management/commands/clean_orders.py
@@ -36,7 +36,8 @@ class Command(BaseCommand):
queryset = Order.objects.exclude(status__in=do_nothing)
queryset = queryset.filter(
- expires_at__lt=timezone.now()) # expires at lower than now
+ expires_at__lt=timezone.now()
+ ) # expires at lower than now
debug = {}
debug["num_expired_orders"] = len(queryset)
@@ -45,11 +46,9 @@ class Command(BaseCommand):
debug["reason_failure"] = []
for idx, order in enumerate(queryset):
- context = str(order) + " was " + Order.Status(
- order.status).label
+ context = str(order) + " was " + Order.Status(order.status).label
try:
- if Logics.order_expires(
- order): # Order send to expire here
+ if Logics.order_expires(order): # Order send to expire here
debug["expired_orders"].append({idx: context})
# It should not happen, but if it cannot locate the hold invoice
@@ -57,7 +56,7 @@ class Command(BaseCommand):
except Exception as e:
debug["failed_order_expiry"].append({idx: context})
debug["reason_failure"].append({idx: str(e)})
-
+
if "unable to locate invoice" in str(e):
self.stdout.write(str(e))
order.status = Order.Status.EXP
diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py
index ec7887df..1e845c33 100644
--- a/api/management/commands/follow_invoices.py
+++ b/api/management/commands/follow_invoices.py
@@ -73,18 +73,17 @@ class Command(BaseCommand):
try:
# this is similar to LNNnode.validate_hold_invoice_locked
request = LNNode.invoicesrpc.LookupInvoiceMsg(
- payment_hash=bytes.fromhex(hold_lnpayment.payment_hash))
- response = stub.LookupInvoiceV2(request,
- metadata=[("macaroon",
- MACAROON.hex())])
- hold_lnpayment.status = lnd_state_to_lnpayment_status[
- response.state]
+ payment_hash=bytes.fromhex(hold_lnpayment.payment_hash)
+ )
+ response = stub.LookupInvoiceV2(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ )
+ hold_lnpayment.status = lnd_state_to_lnpayment_status[response.state]
# try saving expiry height
if hasattr(response, "htlcs"):
try:
- hold_lnpayment.expiry_height = response.htlcs[
- 0].expiry_height
+ hold_lnpayment.expiry_height = response.htlcs[0].expiry_height
except:
pass
@@ -97,8 +96,7 @@ class Command(BaseCommand):
# LND restarted.
if "wallet locked, unlock it" in str(e):
- self.stdout.write(
- str(timezone.now()) + " :: Wallet Locked")
+ self.stdout.write(str(timezone.now()) + " :: Wallet Locked")
# Other write to logs
else:
self.stdout.write(str(e))
@@ -114,13 +112,15 @@ class Command(BaseCommand):
# Report for debugging
new_status = LNPayment.Status(hold_lnpayment.status).label
- debug["invoices"].append({
- idx: {
- "payment_hash": str(hold_lnpayment.payment_hash),
- "old_status": old_status,
- "new_status": new_status,
+ debug["invoices"].append(
+ {
+ idx: {
+ "payment_hash": str(hold_lnpayment.payment_hash),
+ "old_status": old_status,
+ "new_status": new_status,
+ }
}
- })
+ )
at_least_one_changed = at_least_one_changed or changed
@@ -148,7 +148,8 @@ class Command(BaseCommand):
status__in=[LNPayment.Status.VALIDI, LNPayment.Status.FAILRO],
in_flight=False,
last_routing_time__lt=(
- timezone.now() - timedelta(minutes=int(config("RETRY_TIME")))),
+ timezone.now() - timedelta(minutes=int(config("RETRY_TIME")))
+ ),
)
queryset = queryset.union(queryset_retries)
@@ -167,7 +168,7 @@ class Command(BaseCommand):
# It is a maker bond => Publish order.
if hasattr(lnpayment, "order_made"):
Logics.publish_order(lnpayment.order_made)
- send_message.delay(lnpayment.order_made.id,'order_published')
+ send_message.delay(lnpayment.order_made.id, "order_published")
return
# It is a taker bond => close contract.
diff --git a/api/management/commands/telegram_watcher.py b/api/management/commands/telegram_watcher.py
index 5723e319..547951b8 100644
--- a/api/management/commands/telegram_watcher.py
+++ b/api/management/commands/telegram_watcher.py
@@ -7,50 +7,54 @@ from decouple import config
import requests
import time
+
class Command(BaseCommand):
help = "Polls telegram /getUpdates method"
- rest = 3 # seconds between consecutive polls
+ rest = 3 # seconds between consecutive polls
- bot_token = config('TELEGRAM_TOKEN')
- updates_url = f'https://api.telegram.org/bot{bot_token}/getUpdates'
+ bot_token = config("TELEGRAM_TOKEN")
+ updates_url = f"https://api.telegram.org/bot{bot_token}/getUpdates"
session = get_session()
telegram = Telegram()
+
def handle(self, *args, **options):
"""Infinite loop to check for telegram updates.
If it finds a new user (/start), enables it's taker found
notification and sends a 'Hey {username} {order_id}' message back"""
-
+
offset = 0
while True:
time.sleep(self.rest)
- params = {'offset' : offset + 1 , 'timeout' : 5}
+ params = {"offset": offset + 1, "timeout": 5}
response = self.session.get(self.updates_url, params=params).json()
- if len(list(response['result'])) == 0:
+ if len(list(response["result"])) == 0:
continue
- for result in response['result']:
+ for result in response["result"]:
- try: # if there is no key message, skips this result.
- text = result['message']['text']
+ try: # if there is no key message, skips this result.
+ text = result["message"]["text"]
except:
continue
-
- splitted_text = text.split(' ')
- if splitted_text[0] == '/start':
+
+ splitted_text = text.split(" ")
+ if splitted_text[0] == "/start":
token = splitted_text[-1]
- try :
+ try:
profile = Profile.objects.get(telegram_token=token)
except:
- print(f'No profile with token {token}')
+ print(f"No profile with token {token}")
continue
-
+
attempts = 5
while attempts >= 0:
try:
- profile.telegram_chat_id = result['message']['from']['id']
- profile.telegram_lang_code = result['message']['from']['language_code']
+ profile.telegram_chat_id = result["message"]["from"]["id"]
+ profile.telegram_lang_code = result["message"]["from"][
+ "language_code"
+ ]
self.telegram.welcome(profile.user)
profile.telegram_enabled = True
profile.save()
@@ -59,6 +63,4 @@ class Command(BaseCommand):
time.sleep(5)
attempts = attempts - 1
- offset = response['result'][-1]['update_id']
-
-
+ offset = response["result"][-1]["update_id"]
diff --git a/api/messages.py b/api/messages.py
index 3fa7b846..ce3111c4 100644
--- a/api/messages.py
+++ b/api/messages.py
@@ -3,37 +3,38 @@ from secrets import token_urlsafe
from api.models import Order
from api.utils import get_session
-class Telegram():
- ''' Simple telegram messages by requesting to API'''
+
+class Telegram:
+ """Simple telegram messages by requesting to API"""
session = get_session()
- site = config('HOST_NAME')
+ site = config("HOST_NAME")
def get_context(user):
"""returns context needed to enable TG notifications"""
context = {}
- if user.profile.telegram_enabled :
- context['tg_enabled'] = True
+ if user.profile.telegram_enabled:
+ context["tg_enabled"] = True
else:
- context['tg_enabled'] = False
-
+ context["tg_enabled"] = False
+
if user.profile.telegram_token == None:
user.profile.telegram_token = token_urlsafe(15)
user.profile.save()
- context['tg_token'] = user.profile.telegram_token
- context['tg_bot_name'] = config("TELEGRAM_BOT_NAME")
+ context["tg_token"] = user.profile.telegram_token
+ context["tg_bot_name"] = config("TELEGRAM_BOT_NAME")
return context
def send_message(self, user, text):
- """ sends a message to a user with telegram notifications enabled"""
+ """sends a message to a user with telegram notifications enabled"""
- bot_token=config('TELEGRAM_TOKEN')
+ bot_token = config("TELEGRAM_TOKEN")
chat_id = user.profile.telegram_chat_id
- message_url = f'https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id}&text={text}'
-
+ message_url = f"https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id}&text={text}"
+
# if it fails, it should keep trying
while True:
try:
@@ -41,13 +42,13 @@ class Telegram():
return
except:
pass
-
+
def welcome(self, user):
- ''' User enabled Telegram Notifications'''
+ """User enabled Telegram Notifications"""
lang = user.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hola {user.username}, te enviaré notificaciones sobre tus órdenes en RoboSats.'
+ if lang == "es":
+ text = f"Hola {user.username}, te enviaré notificaciones sobre tus órdenes en RoboSats."
else:
text = f"Hey {user.username}, I will send you notifications about your RoboSats orders."
self.send_message(user, text)
@@ -75,18 +76,18 @@ class Telegram():
def order_taken_confirmed(self, order):
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {order.taker.username}!🥳 Visita http://{self.site}/order/{order.id} para continuar.'
+ if lang == "es":
+ text = f"Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {order.taker.username}!🥳 Visita http://{self.site}/order/{order.id} para continuar."
else:
- text = f'Hey {order.maker.username}, your order was taken by {order.taker.username}!🥳 Visit http://{self.site}/order/{order.id} to proceed with the trade.'
+ text = f"Hey {order.maker.username}, your order was taken by {order.taker.username}!🥳 Visit http://{self.site}/order/{order.id} to proceed with the trade."
self.send_message(order.maker, text)
if order.taker.profile.telegram_enabled:
lang = order.taker.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {order.taker.username}, acabas de tomar la orden con ID {order.id}.'
+ if lang == "es":
+ text = f"Hey {order.taker.username}, acabas de tomar la orden con ID {order.id}."
else:
- text = f'Hey {order.taker.username}, you just took the order with ID {order.id}.'
+ text = f"Hey {order.taker.username}, you just took the order with ID {order.id}."
self.send_message(order.taker, text)
return
@@ -95,20 +96,20 @@ class Telegram():
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {user.username}, el depósito de garantÃa y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{self.site}/order/{order.id} para hablar con tu contraparte.'
+ if lang == "es":
+ text = f"Hey {user.username}, el depósito de garantÃa y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{self.site}/order/{order.id} para hablar con tu contraparte."
else:
- text = f'Hey {user.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{self.site}/order/{order.id} to talk with your counterpart.'
+ text = f"Hey {user.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{self.site}/order/{order.id} to talk with your counterpart."
self.send_message(user, text)
return
def order_expired_untaken(self, order):
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{self.site}/order/{order.id} para renovarla.'
+ if lang == "es":
+ text = f"Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{self.site}/order/{order.id} para renovarla."
else:
- text = f'Hey {order.maker.username}, your order with ID {order.id} has expired without a taker. Visit http://{self.site}/order/{order.id} to renew it.'
+ text = f"Hey {order.maker.username}, your order with ID {order.id} has expired without a taker. Visit http://{self.site}/order/{order.id} to renew it."
self.send_message(order.maker, text)
return
@@ -116,42 +117,42 @@ class Telegram():
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
- if lang == 'es':
- text = f'¡Tu orden con ID {order.id} ha finalizado exitosamente!⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar.'
+ if lang == "es":
+ text = f"¡Tu orden con ID {order.id} ha finalizado exitosamente!⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar."
else:
- text = f'Your order with ID {order.id} has finished successfully!âš¡ Join us @robosats and help us improve.'
+ text = f"Your order with ID {order.id} has finished successfully!âš¡ Join us @robosats and help us improve."
self.send_message(user, text)
return
def public_order_cancelled(self, order):
if order.maker.profile.telegram_enabled:
lang = order.maker.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}.'
+ if lang == "es":
+ text = f"Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}."
else:
- text = f'Hey {order.maker.username}, you have cancelled your public order with ID {order.id}.'
+ text = f"Hey {order.maker.username}, you have cancelled your public order with ID {order.id}."
self.send_message(order.maker, text)
return
-
+
def collaborative_cancelled(self, order):
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {user.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente.'
+ if lang == "es":
+ text = f"Hey {user.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente."
else:
- text = f'Hey {user.username}, your order with ID {str(order.id)} has been collaboratively cancelled.'
+ text = f"Hey {user.username}, your order with ID {str(order.id)} has been collaboratively cancelled."
self.send_message(user, text)
return
-
+
def dispute_opened(self, order):
for user in [order.maker, order.taker]:
if user.profile.telegram_enabled:
lang = user.profile.telegram_lang_code
- if lang == 'es':
- text = f'Hey {user.username}, la orden con ID {str(order.id)} ha entrado en disputa.'
+ if lang == "es":
+ text = f"Hey {user.username}, la orden con ID {str(order.id)} ha entrado en disputa."
else:
- text = f'Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}.'
+ text = f"Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}."
self.send_message(user, text)
return
@@ -163,8 +164,8 @@ class Telegram():
if len(queryset) == 0:
return
order = queryset.last()
- if lang == 'es':
- text = f'Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes.'
+ if lang == "es":
+ text = f"Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes."
else:
text = f"Hey {order.maker.username}, your order with ID {str(order.id)} is public in the order book."
self.send_message(order.maker, text)
diff --git a/api/models.py b/api/models.py
index 94996584..386c7314 100644
--- a/api/models.py
+++ b/api/models.py
@@ -29,12 +29,11 @@ DEFAULT_BOND_SIZE = float(config("DEFAULT_BOND_SIZE"))
class Currency(models.Model):
currency_dict = json.load(open("frontend/static/assets/currencies.json"))
- currency_choices = [(int(val), label)
- for val, label in list(currency_dict.items())]
+ currency_choices = [(int(val), label) for val, label in list(currency_dict.items())]
- currency = models.PositiveSmallIntegerField(choices=currency_choices,
- null=False,
- unique=True)
+ currency = models.PositiveSmallIntegerField(
+ choices=currency_choices, null=False, unique=True
+ )
exchange_rate = models.DecimalField(
max_digits=14,
decimal_places=4,
@@ -54,7 +53,6 @@ class Currency(models.Model):
class LNPayment(models.Model):
-
class Types(models.IntegerChoices):
NORM = 0, "Regular invoice"
HOLD = 1, "hold invoice"
@@ -80,77 +78,78 @@ class LNPayment(models.Model):
class FailureReason(models.IntegerChoices):
NOTYETF = 0, "Payment isn't failed (yet)"
- TIMEOUT = 1, "There are more routes to try, but the payment timeout was exceeded."
- NOROUTE = 2, "All possible routes were tried and failed permanently. Or there were no routes to the destination at all."
+ TIMEOUT = (
+ 1,
+ "There are more routes to try, but the payment timeout was exceeded.",
+ )
+ NOROUTE = (
+ 2,
+ "All possible routes were tried and failed permanently. Or there were no routes to the destination at all.",
+ )
NONRECO = 3, "A non-recoverable error has occurred."
- INCORRE = 4, "Payment details are incorrect (unknown hash, invalid amount or invalid final CLTV delta)."
+ INCORRE = (
+ 4,
+ "Payment details are incorrect (unknown hash, invalid amount or invalid final CLTV delta).",
+ )
NOBALAN = 5, "Insufficient unlocked balance in RoboSats' node."
# payment use details
- 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)
- failure_reason = models.PositiveSmallIntegerField(choices=FailureReason.choices,
- null=True,
- default=None)
+ 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
+ )
+ failure_reason = models.PositiveSmallIntegerField(
+ choices=FailureReason.choices, null=True, default=None
+ )
# payment info
- payment_hash = models.CharField(max_length=100,
- unique=True,
- default=None,
- blank=True,
- primary_key=True)
+ payment_hash = models.CharField(
+ max_length=100, unique=True, default=None, blank=True, primary_key=True
+ )
invoice = models.CharField(
- max_length=1200, unique=True, null=True, default=None,
- blank=True) # Some invoices with lots of routing hints might be long
- 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(100),
- MaxValueValidator(1.5 * MAX_TRADE),
- ])
+ max_length=1200, unique=True, null=True, default=None, blank=True
+ ) # Some invoices with lots of routing hints might be long
+ 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(100),
+ MaxValueValidator(1.5 * MAX_TRADE),
+ ]
+ )
# Fee in sats with mSats decimals fee_msat
- fee = models.DecimalField(max_digits=10, decimal_places=3, default=0, null=False, blank=False)
+ fee = models.DecimalField(
+ max_digits=10, decimal_places=3, default=0, null=False, blank=False
+ )
created_at = models.DateTimeField()
expires_at = models.DateTimeField()
- cltv_expiry = models.PositiveSmallIntegerField(null=True,
- default=None,
- blank=True)
- expiry_height = models.PositiveBigIntegerField(null=True,
- default=None,
- blank=True)
+ cltv_expiry = models.PositiveSmallIntegerField(null=True, default=None, blank=True)
+ expiry_height = models.PositiveBigIntegerField(null=True, default=None, blank=True)
# routing
routing_attempts = models.PositiveSmallIntegerField(null=False, default=0)
- last_routing_time = models.DateTimeField(null=True,
- default=None,
- blank=True)
+ last_routing_time = models.DateTimeField(null=True, default=None, blank=True)
in_flight = models.BooleanField(default=False, null=False, blank=False)
# involved parties
- sender = models.ForeignKey(User,
- related_name="sender",
- on_delete=models.SET_NULL,
- null=True,
- default=None)
- receiver = models.ForeignKey(User,
- related_name="receiver",
- on_delete=models.SET_NULL,
- null=True,
- default=None)
+ sender = models.ForeignKey(
+ User, related_name="sender", on_delete=models.SET_NULL, null=True, default=None
+ )
+ receiver = models.ForeignKey(
+ User,
+ related_name="receiver",
+ on_delete=models.SET_NULL,
+ null=True,
+ default=None,
+ )
def __str__(self):
return f"LN-{str(self.payment_hash)[:8]}: {self.Concepts(self.concept).label} - {self.Status(self.status).label}"
@@ -166,89 +165,89 @@ class LNPayment(models.Model):
# We created a truncated property for display 'hash'
return truncatechars(self.payment_hash, 10)
-class OnchainPayment(models.Model):
+class OnchainPayment(models.Model):
class Concepts(models.IntegerChoices):
PAYBUYER = 3, "Payment to buyer"
class Status(models.IntegerChoices):
- CREAT = 0, "Created" # User was given platform fees and suggested mining fees
- VALID = 1, "Valid" # Valid onchain address submitted
- MEMPO = 2, "In mempool" # Tx is sent to mempool
- CONFI = 3, "Confirmed" # Tx is confirme +2 blocks
- CANCE = 4, "Cancelled" # Cancelled tx
+ CREAT = 0, "Created" # User was given platform fees and suggested mining fees
+ VALID = 1, "Valid" # Valid onchain address submitted
+ MEMPO = 2, "In mempool" # Tx is sent to mempool
+ CONFI = 3, "Confirmed" # Tx is confirme +2 blocks
+ CANCE = 4, "Cancelled" # Cancelled tx
def get_balance():
balance = BalanceLog.objects.create()
return balance.time
# payment use details
- concept = models.PositiveSmallIntegerField(choices=Concepts.choices,
- null=False,
- default=Concepts.PAYBUYER)
- status = models.PositiveSmallIntegerField(choices=Status.choices,
- null=False,
- default=Status.CREAT)
+ concept = models.PositiveSmallIntegerField(
+ choices=Concepts.choices, null=False, default=Concepts.PAYBUYER
+ )
+ status = models.PositiveSmallIntegerField(
+ choices=Status.choices, null=False, default=Status.CREAT
+ )
# payment info
- address = models.CharField(max_length=100,
- unique=False,
- default=None,
- null=True,
- blank=True)
-
- txid = models.CharField(max_length=64,
- unique=True,
- null=True,
- default=None,
- blank=True)
+ address = models.CharField(
+ max_length=100, unique=False, default=None, null=True, blank=True
+ )
- num_satoshis = models.PositiveBigIntegerField(null=True,
- validators=[
- MinValueValidator(0.5 * MIN_SWAP_AMOUNT),
- MaxValueValidator(1.5 * MAX_TRADE),
- ])
- sent_satoshis = models.PositiveBigIntegerField(null=True,
- validators=[
- MinValueValidator(0.5 * MIN_SWAP_AMOUNT),
- MaxValueValidator(1.5 * MAX_TRADE),
- ])
+ txid = models.CharField(
+ max_length=64, unique=True, null=True, default=None, blank=True
+ )
+
+ num_satoshis = models.PositiveBigIntegerField(
+ null=True,
+ validators=[
+ MinValueValidator(0.5 * MIN_SWAP_AMOUNT),
+ MaxValueValidator(1.5 * MAX_TRADE),
+ ],
+ )
+ sent_satoshis = models.PositiveBigIntegerField(
+ null=True,
+ validators=[
+ MinValueValidator(0.5 * MIN_SWAP_AMOUNT),
+ MaxValueValidator(1.5 * MAX_TRADE),
+ ],
+ )
# fee in sats/vbyte with mSats decimals fee_msat
- suggested_mining_fee_rate = models.DecimalField(max_digits=6,
- decimal_places=3,
- default=1.05,
- null=False,
- blank=False)
- mining_fee_rate = models.DecimalField(max_digits=6,
- decimal_places=3,
- default=1.05,
- null=False,
- blank=False)
- mining_fee_sats = models.PositiveBigIntegerField(default=0,
- null=False,
- blank=False)
+ suggested_mining_fee_rate = models.DecimalField(
+ max_digits=6, decimal_places=3, default=1.05, null=False, blank=False
+ )
+ mining_fee_rate = models.DecimalField(
+ max_digits=6, decimal_places=3, default=1.05, null=False, blank=False
+ )
+ mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False)
# platform onchain/channels balance at creation, swap fee rate as percent of total volume
- balance = models.ForeignKey(BalanceLog,
- related_name="balance",
- on_delete=models.SET_NULL,
- null=True,
- default=get_balance)
+ balance = models.ForeignKey(
+ BalanceLog,
+ related_name="balance",
+ on_delete=models.SET_NULL,
+ null=True,
+ default=get_balance,
+ )
- swap_fee_rate = models.DecimalField(max_digits=4,
- decimal_places=2,
- default=float(config("MIN_SWAP_FEE"))*100,
- null=False,
- blank=False)
+ swap_fee_rate = models.DecimalField(
+ max_digits=4,
+ decimal_places=2,
+ default=float(config("MIN_SWAP_FEE")) * 100,
+ null=False,
+ blank=False,
+ )
created_at = models.DateTimeField(default=timezone.now)
# involved parties
- receiver = models.ForeignKey(User,
- related_name="tx_receiver",
- on_delete=models.SET_NULL,
- null=True,
- default=None)
+ receiver = models.ForeignKey(
+ User,
+ related_name="tx_receiver",
+ on_delete=models.SET_NULL,
+ null=True,
+ default=None,
+ )
def __str__(self):
return f"TX-{str(self.id)}: {self.Concepts(self.concept).label} - {self.Status(self.status).label}"
@@ -262,8 +261,8 @@ class OnchainPayment(models.Model):
# Display txid as 'hash' truncated
return truncatechars(self.txid, 10)
-class Order(models.Model):
+class Order(models.Model):
class Types(models.IntegerChoices):
BUY = 0, "BUY"
SELL = 1, "SELL"
@@ -297,30 +296,30 @@ class Order(models.Model):
NESINV = 4, "Neither escrow locked or invoice submitted"
# order info
- reference = models.UUIDField(default = uuid.uuid4, editable = False)
- status = models.PositiveSmallIntegerField(choices=Status.choices,
- null=False,
- default=Status.WFB)
+ reference = models.UUIDField(default=uuid.uuid4, editable=False)
+ status = models.PositiveSmallIntegerField(
+ choices=Status.choices, null=False, default=Status.WFB
+ )
created_at = models.DateTimeField(default=timezone.now)
expires_at = models.DateTimeField()
- expiry_reason = models.PositiveSmallIntegerField(choices=ExpiryReasons.choices,
- null=True,
- blank=True,
- default=None)
+ expiry_reason = models.PositiveSmallIntegerField(
+ choices=ExpiryReasons.choices, null=True, blank=True, default=None
+ )
# order details
type = models.PositiveSmallIntegerField(choices=Types.choices, null=False)
- currency = models.ForeignKey(Currency,
- null=True,
- on_delete=models.SET_NULL)
+ currency = models.ForeignKey(Currency, null=True, on_delete=models.SET_NULL)
amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
has_range = models.BooleanField(default=False, null=False, blank=False)
- min_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
- max_amount = models.DecimalField(max_digits=18, decimal_places=8, null=True, blank=True)
- payment_method = models.CharField(max_length=70,
- null=False,
- default="not specified",
- blank=True)
+ min_amount = models.DecimalField(
+ max_digits=18, decimal_places=8, null=True, blank=True
+ )
+ max_amount = models.DecimalField(
+ max_digits=18, decimal_places=8, null=True, blank=True
+ )
+ payment_method = models.CharField(
+ max_length=70, null=False, default="not specified", blank=True
+ )
bondless_taker = models.BooleanField(default=False, null=False, blank=False)
# order pricing method. A explicit amount of sats, or a relative premium above/below market.
is_explicit = models.BooleanField(default=False, null=False)
@@ -330,37 +329,37 @@ class Order(models.Model):
decimal_places=2,
default=0,
null=True,
- validators=[MinValueValidator(-100),
- MaxValueValidator(999)],
+ validators=[MinValueValidator(-100), MaxValueValidator(999)],
blank=True,
)
# explicit
satoshis = models.PositiveBigIntegerField(
null=True,
- validators=[
- MinValueValidator(MIN_TRADE),
- MaxValueValidator(MAX_TRADE)
- ],
+ validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)],
blank=True,
)
# optionally makers can choose the public order duration length (seconds)
public_duration = models.PositiveBigIntegerField(
- default=60*60*int(config("DEFAULT_PUBLIC_ORDER_DURATION"))-1,
+ default=60 * 60 * int(config("DEFAULT_PUBLIC_ORDER_DURATION")) - 1,
null=False,
validators=[
- MinValueValidator(60*60*float(config("MIN_PUBLIC_ORDER_DURATION"))), # Min is 10 minutes
- MaxValueValidator(60*60*float(config("MAX_PUBLIC_ORDER_DURATION"))), # Max is 24 Hours
+ MinValueValidator(
+ 60 * 60 * float(config("MIN_PUBLIC_ORDER_DURATION"))
+ ), # Min is 10 minutes
+ MaxValueValidator(
+ 60 * 60 * float(config("MAX_PUBLIC_ORDER_DURATION"))
+ ), # Max is 24 Hours
],
blank=False,
)
# optionally makers can choose the escrow lock / invoice submission step length (seconds)
escrow_duration = models.PositiveBigIntegerField(
- default=60 * int(config("INVOICE_AND_ESCROW_DURATION"))-1,
+ default=60 * int(config("INVOICE_AND_ESCROW_DURATION")) - 1,
null=False,
validators=[
- MinValueValidator(60*30), # Min is 30 minutes
- MaxValueValidator(60*60*8), # Max is 8 Hours
+ MinValueValidator(60 * 30), # Min is 30 minutes
+ MaxValueValidator(60 * 60 * 8), # Max is 8 Hours
],
blank=False,
)
@@ -372,8 +371,8 @@ class Order(models.Model):
default=DEFAULT_BOND_SIZE,
null=False,
validators=[
- MinValueValidator(float(config("MIN_BOND_SIZE"))), # 1 %
- MaxValueValidator(float(config("MAX_BOND_SIZE"))), # 15 %
+ MinValueValidator(float(config("MIN_BOND_SIZE"))), # 1 %
+ MaxValueValidator(float(config("MAX_BOND_SIZE"))), # 15 %
],
blank=False,
)
@@ -381,29 +380,24 @@ class Order(models.Model):
# how many sats at creation and at last check (relevant for marked to market)
t0_satoshis = models.PositiveBigIntegerField(
null=True,
- validators=[
- MinValueValidator(MIN_TRADE),
- MaxValueValidator(MAX_TRADE)
- ],
+ validators=[MinValueValidator(MIN_TRADE), MaxValueValidator(MAX_TRADE)],
blank=True,
) # sats at creation
last_satoshis = models.PositiveBigIntegerField(
null=True,
- validators=[MinValueValidator(0),
- MaxValueValidator(MAX_TRADE * 2)],
+ validators=[MinValueValidator(0), MaxValueValidator(MAX_TRADE * 2)],
blank=True,
) # sats last time checked. Weird if 2* trade max...
# timestamp of last_satoshis
last_satoshis_time = models.DateTimeField(null=True, default=None, blank=True)
# time the fiat exchange is confirmed and Sats released to buyer
- contract_finalization_time = models.DateTimeField(null=True, default=None, blank=True)
+ contract_finalization_time = models.DateTimeField(
+ null=True, default=None, blank=True
+ )
# order participants
maker = models.ForeignKey(
- User,
- related_name="maker",
- on_delete=models.SET_NULL,
- null=True,
- default=None) # unique = True, a maker can only make one order
+ User, related_name="maker", on_delete=models.SET_NULL, null=True, default=None
+ ) # unique = True, a maker can only make one order
taker = models.ForeignKey(
User,
related_name="taker",
@@ -416,21 +410,19 @@ class Order(models.Model):
taker_last_seen = models.DateTimeField(null=True, default=None, blank=True)
# When collaborative cancel is needed and one partner has cancelled.
- maker_asked_cancel = models.BooleanField(default=False, null=False)
+ maker_asked_cancel = models.BooleanField(default=False, null=False)
taker_asked_cancel = models.BooleanField(default=False, null=False)
is_fiat_sent = models.BooleanField(default=False, null=False)
# in dispute
is_disputed = models.BooleanField(default=False, null=False)
- maker_statement = models.TextField(max_length=5000,
- null=True,
- default=None,
- blank=True)
- taker_statement = models.TextField(max_length=5000,
- null=True,
- default=None,
- blank=True)
+ maker_statement = models.TextField(
+ max_length=5000, null=True, default=None, blank=True
+ )
+ taker_statement = models.TextField(
+ max_length=5000, null=True, default=None, blank=True
+ )
# LNpayments
# Order collateral
@@ -487,7 +479,7 @@ class Order(models.Model):
def __str__(self):
if self.has_range and self.amount == None:
- amt = str(float(self.min_amount))+"-"+ str(float(self.max_amount))
+ amt = str(float(self.min_amount)) + "-" + str(float(self.max_amount))
else:
amt = float(self.amount)
return f"Order {self.id}: {self.Types(self.type).label} BTC for {amt} {self.currency}"
@@ -495,27 +487,33 @@ class Order(models.Model):
def t_to_expire(self, status):
t_to_expire = {
- 0: int(config("EXP_MAKER_BOND_INVOICE")), # 'Waiting for maker bond'
- 1: self.public_duration, # 'Public'
- 2: 0, # 'Deleted'
- 3: int(config("EXP_TAKER_BOND_INVOICE")), # 'Waiting for taker bond'
- 4: 0, # 'Cancelled'
- 5: 0, # 'Expired'
- 6: int(self.escrow_duration), # 'Waiting for trade collateral and buyer invoice'
- 7: int(self.escrow_duration), # 'Waiting only for seller trade collateral'
- 8: int(self.escrow_duration), # 'Waiting only for buyer invoice'
- 9: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")), # 'Sending fiat - In chatroom'
- 10: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")),# 'Fiat sent - In chatroom'
- 11: 1 * 24 * 60 * 60, # 'In dispute'
- 12: 0, # 'Collaboratively cancelled'
- 13: 100 * 24 * 60 * 60, # 'Sending satoshis to buyer'
- 14: 100 * 24 * 60 * 60, # 'Sucessful trade'
- 15: 100 * 24 * 60 * 60, # 'Failed lightning network routing'
- 16: 100 * 24 * 60 * 60, # 'Wait for dispute resolution'
- 17: 100 * 24 * 60 * 60, # 'Maker lost dispute'
- 18: 100 * 24 * 60 * 60, # 'Taker lost dispute'
+ 0: int(config("EXP_MAKER_BOND_INVOICE")), # 'Waiting for maker bond'
+ 1: self.public_duration, # 'Public'
+ 2: 0, # 'Deleted'
+ 3: int(config("EXP_TAKER_BOND_INVOICE")), # 'Waiting for taker bond'
+ 4: 0, # 'Cancelled'
+ 5: 0, # 'Expired'
+ 6: int(
+ self.escrow_duration
+ ), # 'Waiting for trade collateral and buyer invoice'
+ 7: int(self.escrow_duration), # 'Waiting only for seller trade collateral'
+ 8: int(self.escrow_duration), # 'Waiting only for buyer invoice'
+ 9: 60
+ * 60
+ * int(config("FIAT_EXCHANGE_DURATION")), # 'Sending fiat - In chatroom'
+ 10: 60
+ * 60
+ * int(config("FIAT_EXCHANGE_DURATION")), # 'Fiat sent - In chatroom'
+ 11: 1 * 24 * 60 * 60, # 'In dispute'
+ 12: 0, # 'Collaboratively cancelled'
+ 13: 100 * 24 * 60 * 60, # 'Sending satoshis to buyer'
+ 14: 100 * 24 * 60 * 60, # 'Sucessful trade'
+ 15: 100 * 24 * 60 * 60, # 'Failed lightning network routing'
+ 16: 100 * 24 * 60 * 60, # 'Wait for dispute resolution'
+ 17: 100 * 24 * 60 * 60, # 'Maker lost dispute'
+ 18: 100 * 24 * 60 * 60, # 'Taker lost dispute'
}
-
+
return t_to_expire[status]
@@ -570,53 +568,27 @@ class Profile(models.Model):
decimal_places=1,
default=None,
null=True,
- validators=[MinValueValidator(0),
- MaxValueValidator(100)],
+ validators=[MinValueValidator(0), MaxValueValidator(100)],
blank=True,
)
# Used to deep link telegram chat in case telegram notifications are enabled
- telegram_token = models.CharField(
- max_length=20,
- null=True,
- blank=True
- )
- telegram_chat_id = models.BigIntegerField(
- null=True,
- default=None,
- blank=True
- )
- telegram_enabled = models.BooleanField(
- default=False,
- null=False
- )
- telegram_lang_code = models.CharField(
- max_length=10,
- null=True,
- blank=True
- )
- telegram_welcomed = models.BooleanField(
- default=False,
- null=False
- )
+ telegram_token = models.CharField(max_length=20, null=True, blank=True)
+ telegram_chat_id = models.BigIntegerField(null=True, default=None, blank=True)
+ telegram_enabled = models.BooleanField(default=False, null=False)
+ telegram_lang_code = models.CharField(max_length=10, null=True, blank=True)
+ telegram_welcomed = models.BooleanField(default=False, null=False)
# Referral program
- is_referred = models.BooleanField(
- default=False,
- null=False
- )
+ is_referred = models.BooleanField(default=False, null=False)
referred_by = models.ForeignKey(
- 'self',
+ "self",
related_name="referee",
on_delete=models.SET_NULL,
null=True,
default=None,
blank=True,
)
- referral_code = models.CharField(
- max_length=15,
- null=True,
- blank=True
- )
+ referral_code = models.CharField(max_length=15, null=True, blank=True)
# Recent rewards from referred trades that will be "earned" at a later point to difficult spionage.
pending_rewards = models.PositiveIntegerField(null=False, default=0)
# Claimable rewards
@@ -644,18 +616,13 @@ class Profile(models.Model):
)
# Penalty expiration (only used then taking/cancelling repeatedly orders in the book before comitting bond)
- penalty_expiration = models.DateTimeField(null=True,
- default=None,
- blank=True)
+ penalty_expiration = models.DateTimeField(null=True, default=None, blank=True)
# Platform rate
- platform_rating = models.PositiveIntegerField(null=True,
- default=None,
- blank=True)
+ platform_rating = models.PositiveIntegerField(null=True, default=None, blank=True)
# Stealth invoices
- wants_stealth = models.BooleanField(default=True,
- null=False)
+ wants_stealth = models.BooleanField(default=True, null=False)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
@@ -669,8 +636,9 @@ class Profile(models.Model):
@receiver(pre_delete, sender=User)
def del_avatar_from_disk(sender, instance, **kwargs):
try:
- avatar_file = Path(settings.AVATAR_ROOT +
- instance.profile.avatar.url.split("/")[-1])
+ avatar_file = Path(
+ settings.AVATAR_ROOT + instance.profile.avatar.url.split("/")[-1]
+ )
avatar_file.unlink()
except:
pass
@@ -686,8 +654,7 @@ class Profile(models.Model):
# method to create a fake table field in read only mode
def avatar_tag(self):
- return mark_safe('
' %
- self.get_avatar())
+ return mark_safe('
' % self.get_avatar())
class MarketTick(models.Model):
@@ -723,13 +690,10 @@ class MarketTick(models.Model):
decimal_places=2,
default=None,
null=True,
- validators=[MinValueValidator(-100),
- MaxValueValidator(999)],
+ validators=[MinValueValidator(-100), MaxValueValidator(999)],
blank=True,
)
- currency = models.ForeignKey(Currency,
- null=True,
- on_delete=models.SET_NULL)
+ currency = models.ForeignKey(Currency, null=True, on_delete=models.SET_NULL)
timestamp = models.DateTimeField(default=timezone.now)
# Relevant to keep record of the historical fee, so the insight on the premium can be better analyzed
@@ -737,8 +701,7 @@ class MarketTick(models.Model):
max_digits=4,
decimal_places=4,
default=FEE,
- validators=[MinValueValidator(0),
- MaxValueValidator(1)],
+ validators=[MinValueValidator(0), MaxValueValidator(1)],
)
def log_a_tick(order):
@@ -755,10 +718,9 @@ class MarketTick(models.Model):
market_exchange_rate = float(order.currency.exchange_rate)
premium = 100 * (price / market_exchange_rate - 1)
- tick = MarketTick.objects.create(price=price,
- volume=volume,
- premium=premium,
- currency=order.currency)
+ tick = MarketTick.objects.create(
+ price=price, volume=volume, premium=premium, currency=order.currency
+ )
tick.save()
@@ -767,4 +729,4 @@ class MarketTick(models.Model):
class Meta:
verbose_name = "Market tick"
- verbose_name_plural = "Market ticks"
\ No newline at end of file
+ verbose_name_plural = "Market ticks"
diff --git a/api/nick_generator/dicts/en/adjectives.py b/api/nick_generator/dicts/en/adjectives.py
index cf9d1757..15f8a4cd 100755
--- a/api/nick_generator/dicts/en/adjectives.py
+++ b/api/nick_generator/dicts/en/adjectives.py
@@ -4823,8 +4823,7 @@ adjectives = [
"Vindictive",
"Chatting",
"Nightmarish",
- "Niggardly"
- "Hated",
+ "Niggardly" "Hated",
"Satiric",
"Shattering",
"Fabled",
diff --git a/api/nick_generator/nick_generator.py b/api/nick_generator/nick_generator.py
index 395566c6..d60e2dfe 100755
--- a/api/nick_generator/nick_generator.py
+++ b/api/nick_generator/nick_generator.py
@@ -2,6 +2,7 @@ from .utils import human_format
import hashlib
import time
+
"""
Deterministic nick generator from SHA256 hash.
@@ -15,7 +16,6 @@ is a total of to 450*4800*12500*1000 =
class NickGenerator:
-
def __init__(
self,
lang="English",
@@ -43,11 +43,13 @@ class NickGenerator:
raise ValueError("Language not implemented.")
if verbose:
- print(f"{lang} SHA256 Nick Generator initialized with:" +
- f"\nUp to {len(adverbs)} adverbs." +
- f"\nUp to {len(adjectives)} adjectives." +
- f"\nUp to {len(nouns)} nouns." +
- f"\nUp to {max_num+1} numerics.\n")
+ print(
+ f"{lang} SHA256 Nick Generator initialized with:"
+ + f"\nUp to {len(adverbs)} adverbs."
+ + f"\nUp to {len(adjectives)} adjectives."
+ + f"\nUp to {len(nouns)} nouns."
+ + f"\nUp to {max_num+1} numerics.\n"
+ )
self.use_adv = use_adv
self.use_adj = use_adj
@@ -147,10 +149,7 @@ class NickGenerator:
i = i + 1
return "", 0, 0, i
- def compute_pool_size_loss(self,
- max_length=22,
- max_iter=1000000,
- num_runs=5000):
+ def compute_pool_size_loss(self, max_length=22, max_iter=1000000, num_runs=5000):
"""
Computes median an average loss of
nick pool diversity due to max_lenght
@@ -196,13 +195,16 @@ if __name__ == "__main__":
# Generates a short nick with length limit from SHA256
nick, nick_id, pool_size, iterations = GenNick.short_from_SHA256(
- hash, max_length, max_iter)
+ hash, max_length, max_iter
+ )
# Output
- print(f"Nick number {nick_id} has been selected among" +
- f" {human_format(pool_size)} possible nicks.\n" +
- f"Needed {iterations} iterations to find one " +
- f"this short.\nYour nick is {nick} !\n")
+ print(
+ f"Nick number {nick_id} has been selected among"
+ + f" {human_format(pool_size)} possible nicks.\n"
+ + f"Needed {iterations} iterations to find one "
+ + f"this short.\nYour nick is {nick} !\n"
+ )
print(f"Nick lenght is {len(nick)} characters.")
print(f"Nick landed at height {nick_id/(pool_size+1)} on the pool.")
print(f"Took {time.time()-t0} secs.\n")
@@ -216,9 +218,8 @@ if __name__ == "__main__":
string = str(random.uniform(0, 1000000))
hash = hashlib.sha256(str.encode(string)).hexdigest()
print(
- GenNick.short_from_SHA256(hash,
- max_length=max_length,
- max_iter=max_iter)[0])
+ GenNick.short_from_SHA256(hash, max_length=max_length, max_iter=max_iter)[0]
+ )
# Other analysis
GenNick.compute_pool_size_loss(max_length, max_iter, 200)
diff --git a/api/nick_generator/utils.py b/api/nick_generator/utils.py
index 9f2c957f..0786ca93 100755
--- a/api/nick_generator/utils.py
+++ b/api/nick_generator/utils.py
@@ -2,9 +2,7 @@ from math import log, floor
def human_format(number):
- units = [
- "", " Thousand", " Million", " Billion", " Trillion", " Quatrillion"
- ]
+ units = ["", " Thousand", " Million", " Billion", " Trillion", " Quatrillion"]
k = 1000.0
magnitude = int(floor(log(number, k)))
return "%.2f%s" % (number / k**magnitude, units[magnitude])
diff --git a/api/serializers.py b/api/serializers.py
index eb33e179..d12c150f 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -3,17 +3,26 @@ from .models import MarketTick, Order
from decouple import config
RETRY_TIME = int(config("RETRY_TIME"))
-MIN_PUBLIC_ORDER_DURATION_SECS=60*60*float(config("MIN_PUBLIC_ORDER_DURATION"))
-MAX_PUBLIC_ORDER_DURATION_SECS=60*60*float(config("MAX_PUBLIC_ORDER_DURATION"))
+MIN_PUBLIC_ORDER_DURATION_SECS = 60 * 60 * float(config("MIN_PUBLIC_ORDER_DURATION"))
+MAX_PUBLIC_ORDER_DURATION_SECS = 60 * 60 * float(config("MAX_PUBLIC_ORDER_DURATION"))
+
class InfoSerializer(serializers.Serializer):
num_public_buy_orders = serializers.IntegerField()
num_public_sell_orders = serializers.IntegerField()
- book_liquidity = serializers.IntegerField(help_text='Total amount of BTC in the order book')
+ book_liquidity = serializers.IntegerField(
+ help_text="Total amount of BTC in the order book"
+ )
active_robots_today = serializers.CharField()
- last_day_nonkyc_btc_premium = serializers.FloatField(help_text='Average premium (weighted by volume) of the orders in the last 24h')
- last_day_volume = serializers.FloatField(help_text='Total volume in BTC in the last 24h')
- lifetime_volume = serializers.FloatField(help_text='Total volume in BTC since exchange\'s inception')
+ last_day_nonkyc_btc_premium = serializers.FloatField(
+ help_text="Average premium (weighted by volume) of the orders in the last 24h"
+ )
+ last_day_volume = serializers.FloatField(
+ help_text="Total volume in BTC in the last 24h"
+ )
+ lifetime_volume = serializers.FloatField(
+ help_text="Total volume in BTC since exchange's inception"
+ )
lnd_version = serializers.CharField()
robosats_running_commit_hash = serializers.CharField()
alternative_site = serializers.CharField()
@@ -21,17 +30,20 @@ class InfoSerializer(serializers.Serializer):
node_alias = serializers.CharField()
node_id = serializers.CharField()
network = serializers.CharField()
- maker_fee = serializers.FloatField(help_text='Exchange\'s set maker fee')
- taker_fee = serializers.FloatField(help_text='Exchange\'s set taker fee ')
- bond_size = serializers.FloatField(help_text='Default bond size (percent)')
- current_swap_fee_rate = serializers.FloatField(help_text='Swap fees to perform on-chain transaction (percent)')
- nickname = serializers.CharField(help_text='Currenlty logged in Robot name')
- referral_code = serializers.CharField(help_text='Logged in users\'s referral code')
- earned_rewards = serializers.IntegerField(help_text='Logged in user\'s earned rewards in satoshis')
+ maker_fee = serializers.FloatField(help_text="Exchange's set maker fee")
+ taker_fee = serializers.FloatField(help_text="Exchange's set taker fee ")
+ bond_size = serializers.FloatField(help_text="Default bond size (percent)")
+ current_swap_fee_rate = serializers.FloatField(
+ help_text="Swap fees to perform on-chain transaction (percent)"
+ )
+ nickname = serializers.CharField(help_text="Currenlty logged in Robot name")
+ referral_code = serializers.CharField(help_text="Logged in users's referral code")
+ earned_rewards = serializers.IntegerField(
+ help_text="Logged in user's earned rewards in satoshis"
+ )
class ListOrderSerializer(serializers.ModelSerializer):
-
class Meta:
model = Order
fields = (
@@ -53,51 +65,46 @@ class ListOrderSerializer(serializers.ModelSerializer):
"maker",
"taker",
"escrow_duration",
- "bond_size"
+ "bond_size",
)
# Only used in oas_schemas
class SummarySerializer(serializers.Serializer):
sent_fiat = serializers.IntegerField(
- required=False,
- help_text="same as `amount` (only for buyer)"
+ required=False, help_text="same as `amount` (only for buyer)"
)
received_sats = serializers.IntegerField(
- required=False,
- help_text="same as `trade_satoshis` (only for buyer)"
+ required=False, help_text="same as `trade_satoshis` (only for buyer)"
)
is_swap = serializers.BooleanField(
- required=False,
- help_text="True if the payout was on-chain (only for buyer)"
+ required=False, help_text="True if the payout was on-chain (only for buyer)"
)
received_onchain_sats = serializers.IntegerField(
required=False,
- help_text="The on-chain sats received (only for buyer and if `is_swap` is `true`)"
+ help_text="The on-chain sats received (only for buyer and if `is_swap` is `true`)",
)
mining_fee_sats = serializers.IntegerField(
required=False,
- help_text="Mining fees paid in satoshis (only for buyer and if `is_swap` is `true`)"
+ help_text="Mining fees paid in satoshis (only for buyer and if `is_swap` is `true`)",
)
swap_fee_sats = serializers.IntegerField(
required=False,
- help_text="Exchange swap fee in sats (i.e excluding miner fees) (only for buyer and if `is_swap` is `true`)"
+ help_text="Exchange swap fee in sats (i.e excluding miner fees) (only for buyer and if `is_swap` is `true`)",
)
swap_fee_percent = serializers.FloatField(
required=False,
- help_text="same as `swap_fee_rate` (only for buyer and if `is_swap` is `true`"
+ help_text="same as `swap_fee_rate` (only for buyer and if `is_swap` is `true`",
)
sent_sats = serializers.IntegerField(
- required=False,
- help_text="The total sats you sent (only for seller)"
+ required=False, help_text="The total sats you sent (only for seller)"
)
received_fiat = serializers.IntegerField(
- required=False,
- help_text="same as `amount` (only for seller)"
+ required=False, help_text="same as `amount` (only for seller)"
)
trade_fee_sats = serializers.IntegerField(
required=False,
- help_text="Exchange fees in sats (Does not include swap fee and miner fee)"
+ help_text="Exchange fees in sats (Does not include swap fee and miner fee)",
)
@@ -105,45 +112,42 @@ class SummarySerializer(serializers.Serializer):
class PlatformSummarySerializer(serializers.Serializer):
contract_timestamp = serializers.DateTimeField(
required=False,
- help_text="Timestamp of when the contract was finalized (price and sats fixed)"
+ help_text="Timestamp of when the contract was finalized (price and sats fixed)",
)
contract_total_time = serializers.FloatField(
required=False,
- help_text="The time taken for the contract to complete (from taker taking the order to completion of order) in seconds"
+ help_text="The time taken for the contract to complete (from taker taking the order to completion of order) in seconds",
)
routing_fee_sats = serializers.IntegerField(
required=False,
- help_text="Sats payed by the exchange for routing fees. Mining fee in case of on-chain swap payout"
+ help_text="Sats payed by the exchange for routing fees. Mining fee in case of on-chain swap payout",
)
trade_revenue_sats = serializers.IntegerField(
- required=False,
- help_text="The sats the exchange earned from the trade"
+ required=False, help_text="The sats the exchange earned from the trade"
)
-
+
# Only used in oas_schemas
class OrderDetailSerializer(serializers.ModelSerializer):
total_secs_exp = serializers.IntegerField(
required=False,
help_text="Duration of time (in seconds) to expire, according to the current status of order."
- "This is duration of time after `created_at` (in seconds) that the order will automatically expire."
- "This value changes according to which stage the order is in"
+ "This is duration of time after `created_at` (in seconds) that the order will automatically expire."
+ "This value changes according to which stage the order is in",
)
penalty = serializers.DateTimeField(
required=False,
- help_text="Time when the user penalty will expire. Penalty applies when you create orders repeatedly without commiting a bond"
+ help_text="Time when the user penalty will expire. Penalty applies when you create orders repeatedly without commiting a bond",
)
is_maker = serializers.BooleanField(
- required=False,
- help_text="Whether you are the maker or not"
+ required=False, help_text="Whether you are the maker or not"
)
is_taker = serializers.BooleanField(
- required=False,
- help_text="Whether you are the taker or not"
+ required=False, help_text="Whether you are the taker or not"
)
is_participant = serializers.BooleanField(
required=False,
- help_text="True if you are either a taker or maker, False otherwise"
+ help_text="True if you are either a taker or maker, False otherwise",
)
maker_status = serializers.CharField(
required=False,
@@ -151,193 +155,170 @@ class OrderDetailSerializer(serializers.ModelSerializer):
"- **'Active'** (seen within last 2 min)\n"
"- **'Seen Recently'** (seen within last 10 min)\n"
"- **'Inactive'** (seen more than 10 min ago)\n\n"
- "Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty"
+ "Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty",
)
taker_status = serializers.BooleanField(
required=False,
- help_text="True if you are either a taker or maker, False otherwise"
+ help_text="True if you are either a taker or maker, False otherwise",
)
price_now = serializers.IntegerField(
required=False,
- help_text="Price of the order in the order's currency at the time of request (upto 5 significant digits)"
+ help_text="Price of the order in the order's currency at the time of request (upto 5 significant digits)",
)
premium = serializers.IntegerField(
- required=False,
- help_text="Premium over the CEX price at the current time"
+ required=False, help_text="Premium over the CEX price at the current time"
)
premium_percentile = serializers.IntegerField(
required=False,
- help_text="(Only if `is_maker`) Premium percentile of your order compared to other public orders in the same currency currently in the order book"
+ help_text="(Only if `is_maker`) Premium percentile of your order compared to other public orders in the same currency currently in the order book",
)
num_similar_orders = serializers.IntegerField(
required=False,
- help_text="(Only if `is_maker`) The number of public orders of the same currency currently in the order book"
+ help_text="(Only if `is_maker`) The number of public orders of the same currency currently in the order book",
)
tg_enabled = serializers.BooleanField(
required=False,
- help_text="(Only if `is_maker`) Whether Telegram notification is enabled or not"
+ help_text="(Only if `is_maker`) Whether Telegram notification is enabled or not",
)
tg_token = serializers.CharField(
required=False,
- help_text="(Only if `is_maker`) Your telegram bot token required to enable notifications."
+ help_text="(Only if `is_maker`) Your telegram bot token required to enable notifications.",
)
tg_bot_name = serializers.CharField(
required=False,
- help_text="(Only if `is_maker`) The Telegram username of the bot"
+ help_text="(Only if `is_maker`) The Telegram username of the bot",
)
is_buyer = serializers.BooleanField(
required=False,
- help_text="Whether you are a buyer of sats (you will be receiving sats)"
+ help_text="Whether you are a buyer of sats (you will be receiving sats)",
)
is_seller = serializers.BooleanField(
required=False,
- help_text="Whether you are a seller of sats or not (you will be sending sats)"
+ help_text="Whether you are a seller of sats or not (you will be sending sats)",
)
maker_nick = serializers.CharField(
- required=False,
- help_text="Nickname (Robot name) of the maker"
+ required=False, help_text="Nickname (Robot name) of the maker"
)
taker_nick = serializers.CharField(
- required=False,
- help_text="Nickname (Robot name) of the taker"
+ required=False, help_text="Nickname (Robot name) of the taker"
)
status_message = serializers.CharField(
required=False,
- help_text="The current status of the order corresponding to the `status`"
+ help_text="The current status of the order corresponding to the `status`",
)
is_fiat_sent = serializers.BooleanField(
- required=False,
- help_text="Whether or not the fiat amount is sent by the buyer"
+ required=False, help_text="Whether or not the fiat amount is sent by the buyer"
)
is_disputed = serializers.BooleanField(
- required=False,
- help_text="Whether or not the counterparty raised a dispute"
- )
- ur_nick = serializers.CharField(
- required=False,
- help_text="Your Nickname"
- )
- ur_nick = serializers.CharField(
- required=False,
- help_text="Your Nick"
+ required=False, help_text="Whether or not the counterparty raised a dispute"
)
+ ur_nick = serializers.CharField(required=False, help_text="Your Nickname")
+ ur_nick = serializers.CharField(required=False, help_text="Your Nick")
maker_locked = serializers.BooleanField(
- required=False,
- help_text="True if maker bond is locked, False otherwise"
+ required=False, help_text="True if maker bond is locked, False otherwise"
)
taker_locked = serializers.BooleanField(
- required=False,
- help_text="True if taker bond is locked, False otherwise"
+ required=False, help_text="True if taker bond is locked, False otherwise"
)
escrow_locked = serializers.BooleanField(
required=False,
- help_text="True if escrow is locked, False otherwise. Escrow is the sats to be sold, held by Robosats until the trade is finised."
+ help_text="True if escrow is locked, False otherwise. Escrow is the sats to be sold, held by Robosats until the trade is finised.",
)
trade_satoshis = serializers.IntegerField(
required=False,
- help_text="Seller sees the amount of sats they need to send. Buyer sees the amount of sats they will receive "
+ help_text="Seller sees the amount of sats they need to send. Buyer sees the amount of sats they will receive ",
)
bond_invoice = serializers.CharField(
- required=False,
- help_text="When `status` = `0`, `3`. Bond invoice to be paid"
+ required=False, help_text="When `status` = `0`, `3`. Bond invoice to be paid"
)
bond_satoshis = serializers.IntegerField(
- required=False,
- help_text="The bond amount in satoshis"
+ required=False, help_text="The bond amount in satoshis"
)
escrow_invoice = serializers.CharField(
required=False,
- help_text="For the seller, the escrow invoice to be held by RoboSats"
+ help_text="For the seller, the escrow invoice to be held by RoboSats",
)
escrow_satoshis = serializers.IntegerField(
- required=False,
- help_text="The escrow amount in satoshis"
+ required=False, help_text="The escrow amount in satoshis"
)
invoice_amount = serializers.IntegerField(
required=False,
- help_text="The amount in sats the buyer needs to submit an invoice of to receive the trade amount"
+ help_text="The amount in sats the buyer needs to submit an invoice of to receive the trade amount",
)
swap_allowed = serializers.BooleanField(
- required=False,
- help_text="Whether on-chain swap is allowed"
+ required=False, help_text="Whether on-chain swap is allowed"
)
swap_failure_reason = serializers.CharField(
- required=False,
- help_text="Reason for why on-chain swap is not available"
+ required=False, help_text="Reason for why on-chain swap is not available"
)
suggested_mining_fee_rate = serializers.IntegerField(
- required=False,
- help_text="fee in sats/vbyte for the on-chain swap"
+ required=False, help_text="fee in sats/vbyte for the on-chain swap"
)
- swap_fee_rate = serializers.FloatField(
+ swap_fee_rate = serializers.FloatField(
required=False,
- help_text="in percentage, the swap fee rate the platform charges"
+ help_text="in percentage, the swap fee rate the platform charges",
)
pending_cancel = serializers.BooleanField(
required=False,
- help_text="Your counterparty requested for a collaborative cancel when `status` is either `8`, `9` or `10`"
+ help_text="Your counterparty requested for a collaborative cancel when `status` is either `8`, `9` or `10`",
)
asked_for_cancel = serializers.BooleanField(
required=False,
- help_text="You requested for a collaborative cancel `status` is either `8`, `9` or `10`"
+ help_text="You requested for a collaborative cancel `status` is either `8`, `9` or `10`",
)
statement_submitted = serializers.BooleanField(
required=False,
- help_text="True if you have submitted a statement. Available when `status` is `11`"
+ help_text="True if you have submitted a statement. Available when `status` is `11`",
)
retries = serializers.IntegerField(
required=False,
- help_text="Number of times ln node has tried to make the payment to you (only if you are the buyer)"
+ help_text="Number of times ln node has tried to make the payment to you (only if you are the buyer)",
)
next_retry_time = serializers.DateTimeField(
required=False,
- help_text=f"The next time payment will be retried. Payment is retried every {RETRY_TIME} sec"
+ help_text=f"The next time payment will be retried. Payment is retried every {RETRY_TIME} sec",
)
failure_reason = serializers.CharField(
- required=False,
- help_text="The reason the payout failed"
+ required=False, help_text="The reason the payout failed"
)
invoice_expired = serializers.BooleanField(
required=False,
- help_text="True if the payout invoice expired. `invoice_amount` will be re-set and sent which means the user has to submit a new invoice to be payed"
+ help_text="True if the payout invoice expired. `invoice_amount` will be re-set and sent which means the user has to submit a new invoice to be payed",
)
trade_fee_percent = serializers.IntegerField(
required=False,
- help_text="The fee for the trade (fees differ for maker and taker)"
+ help_text="The fee for the trade (fees differ for maker and taker)",
)
bond_size_sats = serializers.IntegerField(
- required=False,
- help_text="The size of the bond in sats"
+ required=False, help_text="The size of the bond in sats"
)
bond_size_percent = serializers.IntegerField(
- required=False,
- help_text="same as `bond_size`"
+ required=False, help_text="same as `bond_size`"
)
maker_summary = SummarySerializer(required=False)
taker_summary = SummarySerializer(required=False)
platform_summary = PlatformSummarySerializer(required=True)
expiry_message = serializers.CharField(
required=False,
- help_text="The reason the order expired (message associated with the `expiry_reason`)"
+ help_text="The reason the order expired (message associated with the `expiry_reason`)",
)
num_satoshis = serializers.IntegerField(
required=False,
- help_text="only if status = `14` (Successful Trade) and is_buyer = `true`"
+ help_text="only if status = `14` (Successful Trade) and is_buyer = `true`",
)
sent_satoshis = serializers.IntegerField(
required=False,
- help_text="only if status = `14` (Successful Trade) and is_buyer = `true`"
+ help_text="only if status = `14` (Successful Trade) and is_buyer = `true`",
)
txid = serializers.CharField(
required=False,
- help_text="Transaction id of the on-chain swap payout. Only if status = `14` (Successful Trade) and is_buyer = `true`"
+ help_text="Transaction id of the on-chain swap payout. Only if status = `14` (Successful Trade) and is_buyer = `true`",
)
network = serializers.CharField(
required=False,
- help_text="The network eg. 'testnet', 'mainnet'. Only if status = `14` (Successful Trade) and is_buyer = `true`"
+ help_text="The network eg. 'testnet', 'mainnet'. Only if status = `14` (Successful Trade) and is_buyer = `true`",
)
-
class Meta:
model = Order
fields = (
@@ -392,7 +373,7 @@ class OrderDetailSerializer(serializers.ModelSerializer):
"escrow_satoshis",
"invoice_amount",
"swap_allowed",
- 'swap_failure_reason',
+ "swap_failure_reason",
"suggested_mining_fee_rate",
"swap_fee_rate",
"pending_cancel",
@@ -421,9 +402,16 @@ class OrderDetailSerializer(serializers.ModelSerializer):
class OrderPublicSerializer(serializers.ModelSerializer):
maker_nick = serializers.CharField(required=False)
- maker_status = serializers.CharField(help_text='Status of the nick - "Active" or "Inactive"', required=False)
- price = serializers.FloatField(help_text="Price in order's fiat currency", required=False)
- satoshis_now = serializers.IntegerField(help_text="The amount of sats to be traded at the present moment (not including the fees)", required=False)
+ maker_status = serializers.CharField(
+ help_text='Status of the nick - "Active" or "Inactive"', required=False
+ )
+ price = serializers.FloatField(
+ help_text="Price in order's fiat currency", required=False
+ )
+ satoshis_now = serializers.IntegerField(
+ help_text="The amount of sats to be traded at the present moment (not including the fees)",
+ required=False,
+ )
class Meta:
model = Order
@@ -448,7 +436,7 @@ class OrderPublicSerializer(serializers.ModelSerializer):
"price",
"escrow_duration",
"satoshis_now",
- "bond_size"
+ "bond_size",
)
@@ -461,19 +449,19 @@ class MakeOrderSerializer(serializers.ModelSerializer):
max_length=70,
default="not specified",
required=False,
- help_text="Can be any string. The UI recognizes [these payment methods](https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/src/components/payment-methods/Methods.js) and displays them with a logo."
+ help_text="Can be any string. The UI recognizes [these payment methods](https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/src/components/payment-methods/Methods.js) and displays them with a logo.",
)
is_explicit = serializers.BooleanField(
default=False,
- help_text='Whether the order is explicitly priced or not. If set to `true` then `satoshis` need to be specified'
+ help_text="Whether the order is explicitly priced or not. If set to `true` then `satoshis` need to be specified",
)
has_range = serializers.BooleanField(
default=False,
- help_text='Whether the order specifies a range of amount or a fixed amount.\n\nIf `true`, then `min_amount` and `max_amount` fields are **required**.\n\n If `false` then `amount` is **required**',
+ help_text="Whether the order specifies a range of amount or a fixed amount.\n\nIf `true`, then `min_amount` and `max_amount` fields are **required**.\n\n If `false` then `amount` is **required**",
)
bondless_taker = serializers.BooleanField(
default=False,
- help_text='Whether bondless takers are allowed for this order or not',
+ help_text="Whether bondless takers are allowed for this order or not",
)
class Meta:
@@ -495,19 +483,17 @@ class MakeOrderSerializer(serializers.ModelSerializer):
"bondless_taker",
)
+
class UpdateOrderSerializer(serializers.Serializer):
- invoice = serializers.CharField(max_length=2000,
- allow_null=True,
- allow_blank=True,
- default=None)
- address = serializers.CharField(max_length=100,
- allow_null=True,
- allow_blank=True,
- default=None)
- statement = serializers.CharField(max_length=10000,
- allow_null=True,
- allow_blank=True,
- default=None)
+ invoice = serializers.CharField(
+ max_length=2000, allow_null=True, allow_blank=True, default=None
+ )
+ address = serializers.CharField(
+ max_length=100, allow_null=True, allow_blank=True, default=None
+ )
+ statement = serializers.CharField(
+ max_length=10000, allow_null=True, allow_blank=True, default=None
+ )
action = serializers.ChoiceField(
choices=(
"pause",
@@ -529,64 +515,86 @@ class UpdateOrderSerializer(serializers.Serializer):
allow_blank=True,
default=None,
)
- amount = serializers.DecimalField(max_digits=18, decimal_places=8, allow_null=True, required=False, default=None)
- mining_fee_rate = serializers.DecimalField(max_digits=6, decimal_places=3, allow_null=True, required=False, default=None)
+ amount = serializers.DecimalField(
+ max_digits=18, decimal_places=8, allow_null=True, required=False, default=None
+ )
+ mining_fee_rate = serializers.DecimalField(
+ max_digits=6, decimal_places=3, allow_null=True, required=False, default=None
+ )
+
class UserGenSerializer(serializers.Serializer):
# Mandatory fields
token_sha256 = serializers.CharField(
- min_length=64,
- max_length=64,
- allow_null=False,
- allow_blank=False,
- required=True,
- help_text="SHA256 of user secret")
- public_key = serializers.CharField(max_length=2000,
- allow_null=False,
- allow_blank=False,
- required=True,
- help_text="Armored ASCII PGP public key block")
- encrypted_private_key = serializers.CharField(max_length=2000,
- allow_null=False,
- allow_blank=False,
- required=True,
- help_text="Armored ASCII PGP encrypted private key block")
+ min_length=64,
+ max_length=64,
+ allow_null=False,
+ allow_blank=False,
+ required=True,
+ help_text="SHA256 of user secret",
+ )
+ public_key = serializers.CharField(
+ max_length=2000,
+ allow_null=False,
+ allow_blank=False,
+ required=True,
+ help_text="Armored ASCII PGP public key block",
+ )
+ encrypted_private_key = serializers.CharField(
+ max_length=2000,
+ allow_null=False,
+ allow_blank=False,
+ required=True,
+ help_text="Armored ASCII PGP encrypted private key block",
+ )
# Optional fields
- ref_code = serializers.CharField(max_length=30,
- allow_null=True,
- allow_blank=True,
- required=False,
- default=None,
- help_text="Referal code")
- counts = serializers.ListField(child=serializers.IntegerField(),
- allow_null=True,
- required=False,
- default=None,
- help_text="Counts of the unique characters in the token")
- length = serializers.IntegerField(allow_null=True,
- default=None,
- required=False,
- min_value=1,
- help_text="Length of the token")
- unique_values = serializers.IntegerField(allow_null=True,
- default=None,
- required=False,
- min_value=1,
- help_text="Number of unique values in the token")
+ ref_code = serializers.CharField(
+ max_length=30,
+ allow_null=True,
+ allow_blank=True,
+ required=False,
+ default=None,
+ help_text="Referal code",
+ )
+ counts = serializers.ListField(
+ child=serializers.IntegerField(),
+ allow_null=True,
+ required=False,
+ default=None,
+ help_text="Counts of the unique characters in the token",
+ )
+ length = serializers.IntegerField(
+ allow_null=True,
+ default=None,
+ required=False,
+ min_value=1,
+ help_text="Length of the token",
+ )
+ unique_values = serializers.IntegerField(
+ allow_null=True,
+ default=None,
+ required=False,
+ min_value=1,
+ help_text="Number of unique values in the token",
+ )
+
class ClaimRewardSerializer(serializers.Serializer):
- invoice = serializers.CharField(max_length=2000,
- allow_null=True,
- allow_blank=True,
- default=None,
- help_text="A valid LN invoice with the reward amount to withdraw")
+ invoice = serializers.CharField(
+ max_length=2000,
+ allow_null=True,
+ allow_blank=True,
+ default=None,
+ help_text="A valid LN invoice with the reward amount to withdraw",
+ )
+
class PriceSerializer(serializers.Serializer):
pass
-class TickSerializer(serializers.ModelSerializer):
+class TickSerializer(serializers.ModelSerializer):
class Meta:
model = MarketTick
fields = (
@@ -599,5 +607,6 @@ class TickSerializer(serializers.ModelSerializer):
)
depth = 1
+
class StealthSerializer(serializers.Serializer):
wantsStealth = serializers.BooleanField()
diff --git a/api/tasks.py b/api/tasks.py
index 9767961e..8dbbcbbe 100644
--- a/api/tasks.py
+++ b/api/tasks.py
@@ -1,5 +1,6 @@
from celery import shared_task
+
@shared_task(name="users_cleansing")
def users_cleansing():
"""
@@ -21,7 +22,11 @@ def users_cleansing():
for user in queryset:
# Try an except, due to unknown cause for users lacking profiles.
try:
- if user.profile.pending_rewards > 0 or user.profile.earned_rewards > 0 or user.profile.claimed_rewards > 0:
+ if (
+ user.profile.pending_rewards > 0
+ or user.profile.earned_rewards > 0
+ or user.profile.claimed_rewards > 0
+ ):
continue
if not user.profile.total_contracts == 0:
continue
@@ -38,6 +43,7 @@ def users_cleansing():
}
return results
+
@shared_task(name="give_rewards")
def give_rewards():
"""
@@ -57,10 +63,14 @@ def give_rewards():
profile.pending_rewards = 0
profile.save()
- results[profile.user.username] = {'given_reward':given_reward,'earned_rewards':profile.earned_rewards}
+ results[profile.user.username] = {
+ "given_reward": given_reward,
+ "earned_rewards": profile.earned_rewards,
+ }
return results
+
@shared_task(name="follow_send_payment")
def follow_send_payment(hash):
"""Sends sats to buyer, continuous update"""
@@ -75,10 +85,10 @@ def follow_send_payment(hash):
lnpayment = LNPayment.objects.get(payment_hash=hash)
fee_limit_sat = int(
max(
- lnpayment.num_satoshis *
- float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
+ lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
- )) # 1000 ppm or 10 sats
+ )
+ ) # 1000 ppm or 10 sats
timeout_seconds = int(config("PAYOUT_TIMEOUT_SECONDS"))
request = LNNode.routerrpc.SendPaymentRequest(
@@ -89,15 +99,13 @@ def follow_send_payment(hash):
order = lnpayment.order_paid_LN
try:
- for response in LNNode.routerstub.SendPaymentV2(request,
- metadata=[
- ("macaroon",
- MACAROON.hex())
- ]):
-
+ for response in LNNode.routerstub.SendPaymentV2(
+ request, metadata=[("macaroon", MACAROON.hex())]
+ ):
+
lnpayment.in_flight = True
lnpayment.save()
-
+
if response.status == 0: # Status 0 'UNKNOWN'
# Not sure when this status happens
lnpayment.in_flight = False
@@ -125,18 +133,20 @@ def follow_send_payment(hash):
order.status = Order.Status.FAI
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.FAI))
+ seconds=order.t_to_expire(Order.Status.FAI)
+ )
order.save()
context = {
- "routing_failed":
- LNNode.payment_failure_context[response.failure_reason],
- "IN_FLIGHT":False,
+ "routing_failed": LNNode.payment_failure_context[
+ response.failure_reason
+ ],
+ "IN_FLIGHT": False,
}
print(context)
# If failed due to not route, reset mission control. (This won't scale well, just a temporary fix)
# ResetMC deactivate temporary for tests
- #if response.failure_reason==2:
+ # if response.failure_reason==2:
# LNNode.resetmc()
return False, context
@@ -144,12 +154,13 @@ def follow_send_payment(hash):
if response.status == 2: # Status 2 'SUCCEEDED'
print("SUCCEEDED")
lnpayment.status = LNPayment.Status.SUCCED
- lnpayment.fee = float(response.fee_msat)/1000
+ lnpayment.fee = float(response.fee_msat) / 1000
lnpayment.preimage = response.payment_preimage
lnpayment.save()
order.status = Order.Status.SUC
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.SUC))
+ seconds=order.t_to_expire(Order.Status.SUC)
+ )
order.save()
return True, None
@@ -162,17 +173,19 @@ def follow_send_payment(hash):
lnpayment.save()
order.status = Order.Status.FAI
order.expires_at = timezone.now() + timedelta(
- seconds=order.t_to_expire(Order.Status.FAI))
+ seconds=order.t_to_expire(Order.Status.FAI)
+ )
order.save()
context = {"routing_failed": "The payout invoice has expired"}
return False, context
+
@shared_task(name="payments_cleansing")
def payments_cleansing():
"""
- Deletes cancelled payments (hodl invoices never locked) that
+ Deletes cancelled payments (hodl invoices never locked) that
belong to orders expired more than 3 days ago.
- Deletes 'cancelled' or 'create' onchain_payments
+ Deletes 'cancelled' or 'create' onchain_payments
"""
from django.db.models import Q
@@ -185,10 +198,11 @@ def payments_cleansing():
# Usually expiry is 1 day for every finished order. So ~4 days until
# a never locked hodl invoice is removed.
finished_time = timezone.now() - timedelta(days=3)
- queryset = LNPayment.objects.filter(Q(status=LNPayment.Status.CANCEL),
- Q(order_made__expires_at__lt=finished_time)|
- Q(order_taken__expires_at__lt=finished_time))
-
+ queryset = LNPayment.objects.filter(
+ Q(status=LNPayment.Status.CANCEL),
+ Q(order_made__expires_at__lt=finished_time)
+ | Q(order_taken__expires_at__lt=finished_time),
+ )
# And do not have an active trade, any past contract or any reward.
deleted_lnpayments = []
@@ -200,10 +214,12 @@ def payments_cleansing():
deleted_lnpayments.append(name)
except:
pass
-
+
# same for onchain payments
- queryset = OnchainPayment.objects.filter(Q(status__in=[OnchainPayment.Status.CANCE, OnchainPayment.Status.CREAT]),
- Q(order_paid_TX__expires_at__lt=finished_time)|Q(order_paid_TX__isnull=True))
+ queryset = OnchainPayment.objects.filter(
+ Q(status__in=[OnchainPayment.Status.CANCE, OnchainPayment.Status.CREAT]),
+ Q(order_paid_TX__expires_at__lt=finished_time) | Q(order_paid_TX__isnull=True),
+ )
# And do not have an active trade, any past contract or any reward.
deleted_onchainpayments = []
@@ -224,6 +240,7 @@ def payments_cleansing():
}
return results
+
@shared_task(name="cache_external_market_prices", ignore_result=True)
def cache_market():
@@ -236,7 +253,9 @@ def cache_market():
exchange_rates = get_exchange_rates(currency_codes)
results = {}
- for i in range(len(Currency.currency_dict.values())): # currencies are indexed starting at 1 (USD)
+ for i in range(
+ len(Currency.currency_dict.values())
+ ): # currencies are indexed starting at 1 (USD)
rate = exchange_rates[i]
results[i] = {currency_codes[i], rate}
@@ -259,45 +278,48 @@ def cache_market():
return results
+
@shared_task(name="send_message", ignore_result=True)
def send_message(order_id, message):
from api.models import Order
+
order = Order.objects.get(id=order_id)
if not order.maker.profile.telegram_enabled:
return
from api.messages import Telegram
+
telegram = Telegram()
- if message == 'welcome':
+ if message == "welcome":
telegram.welcome(order)
-
- elif message == 'order_expired_untaken':
+
+ elif message == "order_expired_untaken":
telegram.order_expired_untaken(order)
- elif message == 'trade_successful':
+ elif message == "trade_successful":
telegram.trade_successful(order)
- elif message == 'public_order_cancelled':
+ elif message == "public_order_cancelled":
telegram.public_order_cancelled(order)
- elif message == 'taker_expired_b4bond':
+ elif message == "taker_expired_b4bond":
telegram.taker_expired_b4bond(order)
- elif message == 'order_published':
+ elif message == "order_published":
telegram.order_published(order)
-
- elif message == 'order_taken_confirmed':
+
+ elif message == "order_taken_confirmed":
telegram.order_taken_confirmed(order)
-
- elif message == 'fiat_exchange_starts':
+
+ elif message == "fiat_exchange_starts":
telegram.fiat_exchange_starts(order)
- elif message == 'dispute_opened':
+ elif message == "dispute_opened":
telegram.dispute_opened(order)
- elif message == 'collaborative_cancelled':
+ elif message == "collaborative_cancelled":
telegram.collaborative_cancelled(order)
- return
\ No newline at end of file
+ return
diff --git a/api/urls.py b/api/urls.py
index 446d656a..d893508c 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -1,16 +1,27 @@
from django.urls import path
-from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView, StealthView
+from .views import (
+ MakerView,
+ OrderView,
+ UserView,
+ BookView,
+ InfoView,
+ RewardView,
+ PriceView,
+ LimitView,
+ HistoricalView,
+ TickView,
+ StealthView,
+)
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
from chat.views import ChatView
urlpatterns = [
- path('schema/', SpectacularAPIView.as_view(), name='schema'),
- path('', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
+ path("schema/", SpectacularAPIView.as_view(), name="schema"),
+ path("", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
path("make/", MakerView.as_view()),
- path("order/",OrderView.as_view({
- "get": "get",
- "post": "take_update_confirm_dispute_cancel"
- }),
+ path(
+ "order/",
+ OrderView.as_view({"get": "get", "post": "take_update_confirm_dispute_cancel"}),
),
path("user/", UserView.as_view()),
path("book/", BookView.as_view()),
@@ -21,5 +32,5 @@ urlpatterns = [
path("historical/", HistoricalView.as_view()),
path("ticks/", TickView.as_view()),
path("stealth/", StealthView.as_view()),
- path("chat/", ChatView.as_view({"get": "get","post":"post"})),
+ path("chat/", ChatView.as_view({"get": "get", "post": "post"})),
]
diff --git a/api/utils.py b/api/utils.py
index 117a0768..e7134303 100644
--- a/api/utils.py
+++ b/api/utils.py
@@ -7,17 +7,20 @@ from decouple import config
from api.models import Order
-logger = logging.getLogger('api.utils')
+logger = logging.getLogger("api.utils")
+
+TOR_PROXY = config("TOR_PROXY", default="127.0.0.1:9050")
+USE_TOR = config("USE_TOR", cast=bool, default=True)
-TOR_PROXY = config('TOR_PROXY', default='127.0.0.1:9050')
-USE_TOR = config('USE_TOR', cast=bool, default=True)
def get_session():
session = requests.session()
# Tor uses the 9050 port as the default socks port
if USE_TOR:
- session.proxies = {'http': 'socks5://' + TOR_PROXY,
- 'https': 'socks5://' + TOR_PROXY}
+ session.proxies = {
+ "http": "socks5://" + TOR_PROXY,
+ "https": "socks5://" + TOR_PROXY,
+ }
return session
@@ -29,22 +32,19 @@ def bitcoind_rpc(method, params=None):
:return:
"""
- BITCOIND_RPCURL = config('BITCOIND_RPCURL')
- BITCOIND_RPCUSER = config('BITCOIND_RPCUSER')
- BITCOIND_RPCPASSWORD = config('BITCOIND_RPCPASSWORD')
+ BITCOIND_RPCURL = config("BITCOIND_RPCURL")
+ BITCOIND_RPCUSER = config("BITCOIND_RPCUSER")
+ BITCOIND_RPCPASSWORD = config("BITCOIND_RPCPASSWORD")
if params is None:
params = []
payload = json.dumps(
- {
- "jsonrpc": "2.0",
- "id": "robosats",
- "method": method,
- "params": params
- }
+ {"jsonrpc": "2.0", "id": "robosats", "method": method, "params": params}
)
- return requests.post(BITCOIND_RPCURL, auth=(BITCOIND_RPCUSER, BITCOIND_RPCPASSWORD), data=payload).json()['result']
+ return requests.post(
+ BITCOIND_RPCURL, auth=(BITCOIND_RPCUSER, BITCOIND_RPCPASSWORD), data=payload
+ ).json()["result"]
def validate_onchain_address(address):
@@ -53,17 +53,21 @@ def validate_onchain_address(address):
"""
try:
- validation = bitcoind_rpc('validateaddress', [address])
- if not validation['isvalid']:
+ validation = bitcoind_rpc("validateaddress", [address])
+ if not validation["isvalid"]:
return False, {"bad_address": "Invalid address"}
except Exception as e:
logger.error(e)
- return False, {"bad_address": 'Unable to validate address, check bitcoind backend'}
+ return False, {
+ "bad_address": "Unable to validate address, check bitcoind backend"
+ }
return True, None
market_cache = {}
+
+
@ring.dict(market_cache, expire=3) # keeps in cache for 3 seconds
def get_exchange_rates(currencies):
"""
@@ -74,8 +78,7 @@ def get_exchange_rates(currencies):
session = get_session()
- APIS = config("MARKET_PRICE_APIS",
- cast=lambda v: [s.strip() for s in v.split(",")])
+ APIS = config("MARKET_PRICE_APIS", cast=lambda v: [s.strip() for s in v.split(",")])
api_rates = []
for api_url in APIS:
@@ -86,7 +89,8 @@ def get_exchange_rates(currencies):
for currency in currencies:
try: # If a currency is missing place a None
blockchain_rates.append(
- float(blockchain_prices[currency]["last"]))
+ float(blockchain_prices[currency]["last"])
+ )
except:
blockchain_rates.append(np.nan)
api_rates.append(blockchain_rates)
@@ -96,8 +100,7 @@ def get_exchange_rates(currencies):
yadio_rates = []
for currency in currencies:
try:
- yadio_rates.append(float(
- yadio_prices["BTC"][currency]))
+ yadio_rates.append(float(yadio_prices["BTC"][currency]))
except:
yadio_rates.append(np.nan)
api_rates.append(yadio_rates)
@@ -133,6 +136,8 @@ def get_lnd_version():
robosats_commit_cache = {}
+
+
@ring.dict(robosats_commit_cache, expire=3600)
def get_robosats_commit():
@@ -140,13 +145,16 @@ def get_robosats_commit():
commit_hash = commit.read()
# .git folder is included in .dockerignore. But automatic build will drop in a commit_sha.txt file on root
- if commit_hash == None or commit_hash =="":
+ if commit_hash == None or commit_hash == "":
with open("commit_sha.txt") as f:
commit_hash = f.read()
return commit_hash
+
robosats_version_cache = {}
+
+
@ring.dict(robosats_commit_cache, expire=99999)
def get_robosats_version():
@@ -156,12 +164,16 @@ def get_robosats_version():
print(version_dict)
return version_dict
+
premium_percentile = {}
+
+
@ring.dict(premium_percentile, expire=300)
def compute_premium_percentile(order):
queryset = Order.objects.filter(
- currency=order.currency, status=Order.Status.PUB, type=order.type).exclude(id=order.id)
+ currency=order.currency, status=Order.Status.PUB, type=order.type
+ ).exclude(id=order.id)
print(len(queryset))
if len(queryset) <= 1:
@@ -171,15 +183,18 @@ def compute_premium_percentile(order):
order_rate = float(order.last_satoshis) / float(amount)
rates = []
for similar_order in queryset:
- similar_order_amount = similar_order.amount if not similar_order.has_range else similar_order.max_amount
- rates.append(
- float(similar_order.last_satoshis) / float(similar_order_amount))
+ similar_order_amount = (
+ similar_order.amount
+ if not similar_order.has_range
+ else similar_order.max_amount
+ )
+ rates.append(float(similar_order.last_satoshis) / float(similar_order_amount))
rates = np.array(rates)
return round(np.sum(rates < order_rate) / len(rates), 2)
-def weighted_median(values, sample_weight=None, quantiles= 0.5, values_sorted=False):
+def weighted_median(values, sample_weight=None, quantiles=0.5, values_sorted=False):
"""Very close to numpy.percentile, but it supports weights.
NOTE: quantiles should be in [0, 1]!
:param values: numpy.array with data
@@ -194,8 +209,9 @@ def weighted_median(values, sample_weight=None, quantiles= 0.5, values_sorted=Fa
if sample_weight is None:
sample_weight = np.ones(len(values))
sample_weight = np.array(sample_weight)
- assert np.all(quantiles >= 0) and np.all(quantiles <= 1), \
- 'quantiles should be in [0, 1]'
+ assert np.all(quantiles >= 0) and np.all(
+ quantiles <= 1
+ ), "quantiles should be in [0, 1]"
if not values_sorted:
sorter = np.argsort(values)
@@ -208,6 +224,7 @@ def weighted_median(values, sample_weight=None, quantiles= 0.5, values_sorted=Fa
return np.interp(quantiles, weighted_quantiles, values)
+
def compute_avg_premium(queryset):
premiums = []
volumes = []
@@ -221,11 +238,10 @@ def compute_avg_premium(queryset):
total_volume = sum(volumes)
# weighted_median_premium is the weighted median of the premiums by volume
- if len(premiums) > 0 and len(volumes)>0:
- weighted_median_premium = weighted_median(values=premiums,
- sample_weight=volumes,
- quantiles=0.5,
- values_sorted=False)
+ if len(premiums) > 0 and len(volumes) > 0:
+ weighted_median_premium = weighted_median(
+ values=premiums, sample_weight=volumes, quantiles=0.5, values_sorted=False
+ )
else:
weighted_median_premium = 0.0
return weighted_median_premium, total_volume
diff --git a/api/views.py b/api/views.py
index 2b782032..398be825 100644
--- a/api/views.py
+++ b/api/views.py
@@ -7,16 +7,45 @@ from rest_framework.response import Response
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
-from api.oas_schemas import BookViewSchema, HistoricalViewSchema, InfoViewSchema, LimitViewSchema, MakerViewSchema, OrderViewSchema, PriceViewSchema, RewardViewSchema, StealthViewSchema, TickViewSchema, UserViewSchema
+from api.oas_schemas import (
+ BookViewSchema,
+ HistoricalViewSchema,
+ InfoViewSchema,
+ LimitViewSchema,
+ MakerViewSchema,
+ OrderViewSchema,
+ PriceViewSchema,
+ RewardViewSchema,
+ StealthViewSchema,
+ TickViewSchema,
+ UserViewSchema,
+)
from chat.views import ChatView
-from api.serializers import InfoSerializer, ListOrderSerializer, MakeOrderSerializer, OrderPublicSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer, TickSerializer, StealthSerializer
+from api.serializers import (
+ InfoSerializer,
+ ListOrderSerializer,
+ MakeOrderSerializer,
+ OrderPublicSerializer,
+ UpdateOrderSerializer,
+ ClaimRewardSerializer,
+ PriceSerializer,
+ UserGenSerializer,
+ TickSerializer,
+ StealthSerializer,
+)
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
from control.models import AccountingDay, BalanceLog
from api.logics import Logics
from api.messages import Telegram
from secrets import token_urlsafe
-from api.utils import get_lnd_version, get_robosats_commit, get_robosats_version, compute_premium_percentile, compute_avg_premium
+from api.utils import (
+ get_lnd_version,
+ get_robosats_commit,
+ get_robosats_version,
+ compute_premium_percentile,
+ compute_avg_premium,
+)
from .nick_generator.nick_generator import NickGenerator
from robohash import Robohash
@@ -31,7 +60,7 @@ from decouple import config
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
RETRY_TIME = int(config("RETRY_TIME"))
-PUBLIC_DURATION = 60*60*int(config("DEFAULT_PUBLIC_ORDER_DURATION"))-1
+PUBLIC_DURATION = 60 * 60 * int(config("DEFAULT_PUBLIC_ORDER_DURATION")) - 1
ESCROW_DURATION = 60 * int(config("INVOICE_AND_ESCROW_DURATION"))
BOND_SIZE = int(config("DEFAULT_BOND_SIZE"))
@@ -50,10 +79,7 @@ class MakerView(CreateAPIView):
if not request.user.is_authenticated:
return Response(
- {
- "bad_request":
- "Woops! It seems you do not have a robot avatar"
- },
+ {"bad_request": "Woops! It seems you do not have a robot avatar"},
status.HTTP_400_BAD_REQUEST,
)
@@ -61,11 +87,12 @@ class MakerView(CreateAPIView):
return Response(status=status.HTTP_400_BAD_REQUEST)
# In case it gets overwhelming. Limit the number of public orders.
- if Order.objects.filter(status=Order.Status.PUB).count() >= int(config("MAX_PUBLIC_ORDERS")):
+ if Order.objects.filter(status=Order.Status.PUB).count() >= int(
+ config("MAX_PUBLIC_ORDERS")
+ ):
return Response(
{
- "bad_request":
- "Woah! RoboSats' book is at full capacity! Try again later"
+ "bad_request": "Woah! RoboSats' book is at full capacity! Try again later"
},
status.HTTP_400_BAD_REQUEST,
)
@@ -90,11 +117,16 @@ class MakerView(CreateAPIView):
bondless_taker = serializer.data.get("bondless_taker")
# Optional params
- if public_duration == None: public_duration = PUBLIC_DURATION
- if escrow_duration == None: escrow_duration = ESCROW_DURATION
- if bond_size == None: bond_size = BOND_SIZE
- if bondless_taker == None: bondless_taker = False
- if has_range == None: has_range = False
+ if public_duration == None:
+ public_duration = PUBLIC_DURATION
+ if escrow_duration == None:
+ escrow_duration = ESCROW_DURATION
+ if bond_size == None:
+ bond_size = BOND_SIZE
+ if bondless_taker == None:
+ bondless_taker = False
+ if has_range == None:
+ has_range = False
# TODO add a check - if `is_explicit` is true then `satoshis` need to be specified
@@ -109,21 +141,16 @@ class MakerView(CreateAPIView):
if has_range and (min_amount == None or max_amount == None):
return Response(
{
- "bad_request":
- "You must specify min_amount and max_amount for a range order"
+ "bad_request": "You must specify min_amount and max_amount for a range order"
},
status.HTTP_400_BAD_REQUEST,
)
elif not has_range and amount == None:
return Response(
- {
- "bad_request":
- "You must specify an order amount"
- },
+ {"bad_request": "You must specify an order amount"},
status.HTTP_400_BAD_REQUEST,
)
-
# Creates a new order
order = Order(
type=type,
@@ -136,8 +163,7 @@ class MakerView(CreateAPIView):
premium=premium,
satoshis=satoshis,
is_explicit=is_explicit,
- expires_at=timezone.now() + timedelta(
- seconds=EXP_MAKER_BOND_INVOICE),
+ expires_at=timezone.now() + timedelta(seconds=EXP_MAKER_BOND_INVOICE),
maker=request.user,
public_duration=public_duration,
escrow_duration=escrow_duration,
@@ -152,8 +178,7 @@ class MakerView(CreateAPIView):
return Response(context, status.HTTP_400_BAD_REQUEST)
order.save()
- return Response(ListOrderSerializer(order).data,
- status=status.HTTP_201_CREATED)
+ return Response(ListOrderSerializer(order).data, status=status.HTTP_201_CREATED)
class OrderView(viewsets.ViewSet):
@@ -170,8 +195,7 @@ class OrderView(viewsets.ViewSet):
if not request.user.is_authenticated:
return Response(
{
- "bad_request":
- "You must have a robot avatar to see the order details"
+ "bad_request": "You must have a robot avatar to see the order details"
},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -186,8 +210,9 @@ class OrderView(viewsets.ViewSet):
# check if exactly one order is found in the db
if len(order) != 1:
- return Response({"bad_request": "Invalid Order Id"},
- status.HTTP_404_NOT_FOUND)
+ return Response(
+ {"bad_request": "Invalid Order Id"}, status.HTTP_404_NOT_FOUND
+ )
# This is our order.
order = order[0]
@@ -200,10 +225,7 @@ class OrderView(viewsets.ViewSet):
)
if order.status == Order.Status.CCA:
return Response(
- {
- "bad_request":
- "This order has been cancelled collaborativelly"
- },
+ {"bad_request": "This order has been cancelled collaborativelly"},
status.HTTP_400_BAD_REQUEST,
)
@@ -239,11 +261,9 @@ class OrderView(viewsets.ViewSet):
# Add activity status of participants based on last_seen
if order.taker_last_seen != None:
- data["taker_status"] = Logics.user_activity_status(
- order.taker_last_seen)
+ data["taker_status"] = Logics.user_activity_status(order.taker_last_seen)
if order.maker_last_seen != None:
- data["maker_status"] = Logics.user_activity_status(
- order.maker_last_seen)
+ data["maker_status"] = Logics.user_activity_status(order.maker_last_seen)
# 3.b) Non participants can view details (but only if PUB)
if not data["is_participant"] and order.status == Order.Status.PUB:
@@ -253,16 +273,21 @@ class OrderView(viewsets.ViewSet):
if order.status >= Order.Status.PUB and order.status < Order.Status.WF2:
data["price_now"], data["premium_now"] = Logics.price_and_premium_now(order)
- # 4. a) If maker and Public/Paused, add premium percentile
+ # 4. a) If maker and Public/Paused, add premium percentile
# num similar orders, and maker information to enable telegram notifications.
- if data["is_maker"] and order.status in [Order.Status.PUB, Order.Status.PAU]:
+ if data["is_maker"] and order.status in [
+ Order.Status.PUB,
+ Order.Status.PAU,
+ ]:
data["premium_percentile"] = compute_premium_percentile(order)
data["num_similar_orders"] = len(
- Order.objects.filter(currency=order.currency,
- status=Order.Status.PUB))
+ Order.objects.filter(
+ currency=order.currency, status=Order.Status.PUB
+ )
+ )
# Adds/generate telegram token and whether it is enabled
# Deprecated
- data = {**data,**Telegram.get_context(request.user)}
+ data = {**data, **Telegram.get_context(request.user)}
# For participants add positions, nicks and status as a message and hold invoices status
data["is_buyer"] = Logics.is_buyer(order, request.user)
@@ -294,16 +319,21 @@ class OrderView(viewsets.ViewSet):
# 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):
+ if (
+ order.maker_bond.status
+ == order.taker_bond.status
+ == LNPayment.Status.LOCKED
+ ):
# Seller sees the amount he sends
if data["is_seller"]:
- data["trade_satoshis"] = Logics.escrow_amount(
- order, request.user)[1]["escrow_amount"]
+ data["trade_satoshis"] = Logics.escrow_amount(order, request.user)[
+ 1
+ ]["escrow_amount"]
# Buyer sees the amount he receives
elif data["is_buyer"]:
- data["trade_satoshis"] = Logics.payout_amount(
- order, request.user)[1]["invoice_amount"]
+ data["trade_satoshis"] = Logics.payout_amount(order, request.user)[
+ 1
+ ]["invoice_amount"]
# 5) If status is 'waiting for maker bond' and user is MAKER, reply with a MAKER hold invoice.
if order.status == Order.Status.WFB and data["is_maker"]:
@@ -322,25 +352,32 @@ class OrderView(viewsets.ViewSet):
return Response(context, status.HTTP_400_BAD_REQUEST)
# 7 a. ) If seller and status is 'WF2' or 'WFE'
- elif data["is_seller"] and (order.status == Order.Status.WF2
- or order.status == Order.Status.WFE):
+ elif data["is_seller"] and (
+ order.status == Order.Status.WF2 or order.status == Order.Status.WFE
+ ):
# If the two bonds are locked, reply with an ESCROW hold invoice.
- if (order.maker_bond.status == order.taker_bond.status ==
- LNPayment.Status.LOCKED):
- valid, context = Logics.gen_escrow_hold_invoice(
- order, request.user)
+ if (
+ order.maker_bond.status
+ == order.taker_bond.status
+ == LNPayment.Status.LOCKED
+ ):
+ valid, context = Logics.gen_escrow_hold_invoice(order, request.user)
if valid:
data = {**data, **context}
else:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 7.b) If user is Buyer and status is 'WF2' or 'WFI'
- elif data["is_buyer"] and (order.status == Order.Status.WF2
- or order.status == Order.Status.WFI):
+ elif data["is_buyer"] and (
+ order.status == Order.Status.WF2 or order.status == Order.Status.WFI
+ ):
# If the two bonds are locked, reply with an AMOUNT and onchain swap cost so he can send the buyer invoice/address.
- if (order.maker_bond.status == order.taker_bond.status ==
- LNPayment.Status.LOCKED):
+ if (
+ order.maker_bond.status
+ == order.taker_bond.status
+ == LNPayment.Status.LOCKED
+ ):
valid, context = Logics.payout_amount(order, request.user)
if valid:
data = {**data, **context}
@@ -348,23 +385,27 @@ class OrderView(viewsets.ViewSet):
return Response(context, status.HTTP_400_BAD_REQUEST)
# 8) If status is 'CHA' or 'FSE' and all HTLCS are in LOCKED
- elif order.status in [
- Order.Status.WFI, Order.Status.CHA, Order.Status.FSE
- ]:
+ elif order.status in [Order.Status.WFI, Order.Status.CHA, Order.Status.FSE]:
# If all bonds are locked.
- if (order.maker_bond.status == order.taker_bond.status ==
- order.trade_escrow.status == LNPayment.Status.LOCKED):
+ if (
+ order.maker_bond.status
+ == order.taker_bond.status
+ == order.trade_escrow.status
+ == LNPayment.Status.LOCKED
+ ):
# add whether a collaborative cancel is pending or has been asked
if (data["is_maker"] and order.taker_asked_cancel) or (
- data["is_taker"] and order.maker_asked_cancel):
+ data["is_taker"] and order.maker_asked_cancel
+ ):
data["pending_cancel"] = True
elif (data["is_maker"] and order.maker_asked_cancel) or (
- data["is_taker"] and order.taker_asked_cancel):
+ data["is_taker"] and order.taker_asked_cancel
+ ):
data["asked_for_cancel"] = True
else:
data["asked_for_cancel"] = False
-
- offset = request.GET.get('offset', None)
+
+ offset = request.GET.get("offset", None)
if offset:
data["chat"] = ChatView.get(None, request).data
@@ -373,35 +414,47 @@ class OrderView(viewsets.ViewSet):
# add whether the dispute statement has been received
if data["is_maker"]:
- data["statement_submitted"] = (order.maker_statement != None
- and order.maker_statement != "")
+ data["statement_submitted"] = (
+ order.maker_statement != None and order.maker_statement != ""
+ )
elif data["is_taker"]:
- data["statement_submitted"] = (order.taker_statement != None
- and order.taker_statement != "")
+ data["statement_submitted"] = (
+ order.taker_statement != None and order.taker_statement != ""
+ )
# 9) If status is 'Failed routing', reply with retry amounts, time of next retry and ask for invoice at third.
- elif (order.status == Order.Status.FAI
- and order.payout.receiver == request.user
- ): # might not be the buyer if after a dispute where winner wins
+ elif (
+ order.status == Order.Status.FAI and order.payout.receiver == request.user
+ ): # might not be the buyer if after a dispute where winner wins
data["retries"] = order.payout.routing_attempts
data["next_retry_time"] = order.payout.last_routing_time + timedelta(
- minutes=RETRY_TIME)
+ minutes=RETRY_TIME
+ )
if order.payout.failure_reason:
- data["failure_reason"] = LNPayment.FailureReason(order.payout.failure_reason).label
+ data["failure_reason"] = LNPayment.FailureReason(
+ order.payout.failure_reason
+ ).label
if order.payout.status == LNPayment.Status.EXPIRE:
data["invoice_expired"] = True
# Add invoice amount once again if invoice was expired.
- data["invoice_amount"] = Logics.payout_amount(order,request.user)[1]["invoice_amount"]
+ data["invoice_amount"] = Logics.payout_amount(order, request.user)[1][
+ "invoice_amount"
+ ]
# 10) If status is 'Expired', "Sending", "Finished" or "failed routing", add info for renewal:
- elif order.status in [Order.Status.EXP, Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
+ elif order.status in [
+ Order.Status.EXP,
+ Order.Status.SUC,
+ Order.Status.PAY,
+ Order.Status.FAI,
+ ]:
data["public_duration"] = order.public_duration
data["bond_size"] = order.bond_size
data["bondless_taker"] = order.bondless_taker
# Adds trade summary
- if order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
+ if order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]:
valid, context = Logics.summarize_trade(order, request.user)
if valid:
data = {**data, **context}
@@ -414,16 +467,17 @@ class OrderView(viewsets.ViewSet):
# If status is 'Succes' add final stats and txid if it is a swap
if order.status == Order.Status.SUC:
# If buyer and is a swap, add TXID
- if Logics.is_buyer(order,request.user):
+ if Logics.is_buyer(order, request.user):
if order.is_swap:
data["num_satoshis"] = order.payout_tx.num_satoshis
data["sent_satoshis"] = order.payout_tx.sent_satoshis
- if order.payout_tx.status in [OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]:
+ if order.payout_tx.status in [
+ OnchainPayment.Status.MEMPO,
+ OnchainPayment.Status.CONFI,
+ ]:
data["txid"] = order.payout_tx.txid
data["network"] = str(config("NETWORK"))
-
-
return Response(data, status.HTTP_200_OK)
@extend_schema(**OrderViewSchema.take_update_confirm_dispute_cancel)
@@ -452,8 +506,7 @@ class OrderView(viewsets.ViewSet):
# 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)
+ valid, context, _ = Logics.validate_already_maker_or_taker(request.user)
if not valid:
return Response(context, status=status.HTTP_409_CONFLICT)
@@ -487,15 +540,15 @@ class OrderView(viewsets.ViewSet):
# 2) If action is 'update invoice'
elif action == "update_invoice":
- valid, context = Logics.update_invoice(order, request.user,
- invoice)
+ valid, context = Logics.update_invoice(order, request.user, invoice)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
-
+
# 2.b) If action is 'update address'
elif action == "update_address":
- valid, context = Logics.update_address(order, request.user,
- address, mining_fee_rate)
+ valid, context = Logics.update_address(
+ order, request.user, address, mining_fee_rate
+ )
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
@@ -518,15 +571,13 @@ class OrderView(viewsets.ViewSet):
return Response(context, status.HTTP_400_BAD_REQUEST)
elif action == "submit_statement":
- valid, context = Logics.dispute_statement(order, request.user,
- statement)
+ valid, context = Logics.dispute_statement(order, request.user, statement)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 6) If action is rate
elif action == "rate_user" and rating:
- valid, context = Logics.rate_counterparty(order, request.user,
- rating)
+ valid, context = Logics.rate_counterparty(order, request.user, rating)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
@@ -546,10 +597,8 @@ class OrderView(viewsets.ViewSet):
else:
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"
+ "bad_request": "The Robotic Satoshis working in the warehouse did not understand you. "
+ + "Please, fill a Bug Issue in Github https://github.com/reckless-satoshi/robosats/issues"
},
status.HTTP_501_NOT_IMPLEMENTED,
)
@@ -558,15 +607,12 @@ class OrderView(viewsets.ViewSet):
class UserView(APIView):
- NickGen = NickGenerator(lang="English",
- use_adv=False,
- use_adj=True,
- use_noun=True,
- max_num=999)
+ NickGen = NickGenerator(
+ lang="English", use_adv=False, use_adj=True, use_noun=True, max_num=999
+ )
serializer_class = UserGenSerializer
-
def post(self, request, format=None):
"""
Get a new user derived from a high entropy token
@@ -581,7 +627,7 @@ class UserView(APIView):
context = {}
serializer = self.serializer_class(data=request.data)
- # Return bad request if serializer is not valid
+ # Return bad request if serializer is not valid
if not serializer.is_valid():
context = {"bad_request": "Invalid serializer"}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
@@ -590,12 +636,15 @@ class UserView(APIView):
if request.user.is_authenticated:
context = {"nickname": request.user.username}
not_participant, _, order = Logics.validate_already_maker_or_taker(
- request.user)
+ request.user
+ )
# Does not allow this 'mistake' if an active order
if not not_participant:
context["active_order_id"] = order.id
- context["bad_request"] = f"You are already logged in as {request.user} and have an active order"
+ context[
+ "bad_request"
+ ] = f"You are already logged in as {request.user} and have an active order"
return Response(context, status.HTTP_400_BAD_REQUEST)
# The new way. The token is never sent. Only its SHA256
@@ -603,16 +652,21 @@ class UserView(APIView):
public_key = serializer.data.get("public_key")
encrypted_private_key = serializer.data.get("encrypted_private_key")
ref_code = serializer.data.get("ref_code")
-
+
if not public_key or not encrypted_private_key:
context["bad_request"] = "Must provide valid 'pub' and 'enc_priv' PGP keys"
return Response(context, status.HTTP_400_BAD_REQUEST)
- valid, bad_keys_context, public_key, encrypted_private_key = Logics.validate_pgp_keys(public_key, encrypted_private_key)
+ (
+ valid,
+ bad_keys_context,
+ public_key,
+ encrypted_private_key,
+ ) = Logics.validate_pgp_keys(public_key, encrypted_private_key)
if not valid:
return Response(bad_keys_context, status.HTTP_400_BAD_REQUEST)
- # Now the server only receives a hash of the token. So server trusts the client
+ # Now the server only receives a hash of the token. So server trusts the client
# with computing length, counts and unique_values to confirm the high entropy of the token
# In any case, it is up to the client if they want to create a bad high entropy token.
@@ -640,7 +694,7 @@ class UserView(APIView):
pass
# Hash the token_sha256, only 1 iteration. (this is the second SHA256 of the user token, aka RoboSats ID)
- hash = hashlib.sha256(token_sha256.encode('utf-8')).hexdigest()
+ hash = hashlib.sha256(token_sha256.encode("utf-8")).hexdigest()
# Generate nickname deterministically
nickname = self.NickGen.short_from_SHA256(hash, max_length=18)[0]
@@ -658,17 +712,17 @@ class UserView(APIView):
# Create new credentials and login if nickname is new
if len(User.objects.filter(username=nickname)) == 0:
- User.objects.create_user(username=nickname,
- password=token_sha256,
- is_staff=False)
+ User.objects.create_user(
+ username=nickname, password=token_sha256, is_staff=False
+ )
user = authenticate(request, username=nickname, password=token_sha256)
login(request, user)
- context['referral_code'] = token_urlsafe(8)
- user.profile.referral_code = context['referral_code']
+ context["referral_code"] = token_urlsafe(8)
+ user.profile.referral_code = context["referral_code"]
user.profile.avatar = "static/assets/avatars/" + nickname + ".png"
-
- # Noticed some PGP keys replaced at re-login. Should not happen.
+
+ # Noticed some PGP keys replaced at re-login. Should not happen.
# Let's implement this sanity check "If profile has not keys..."
if not user.profile.public_key:
user.profile.public_key = public_key
@@ -700,17 +754,21 @@ class UserView(APIView):
context["wants_stealth"] = user.profile.wants_stealth
# Adds/generate telegram token and whether it is enabled
- context = {**context,**Telegram.get_context(user)}
+ context = {**context, **Telegram.get_context(user)}
# return active order or last made order if any
- has_no_active_order, _, order = Logics.validate_already_maker_or_taker(request.user)
+ has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
+ request.user
+ )
if not has_no_active_order:
context["active_order_id"] = order.id
else:
- last_order = Order.objects.filter(Q(maker=request.user) | Q(taker=request.user)).last()
+ last_order = Order.objects.filter(
+ Q(maker=request.user) | Q(taker=request.user)
+ ).last()
if last_order:
context["last_order_id"] = last_order.id
-
+
# Sends the welcome back message, only if created +3 mins ago
if request.user.date_joined < (timezone.now() - timedelta(minutes=3)):
context["found"] = "We found your Robot avatar. Welcome back!"
@@ -737,8 +795,7 @@ class UserView(APIView):
if not not_participant:
return Response(
{
- "bad_request":
- "Maybe a mistake? User cannot be deleted while he is part of an order"
+ "bad_request": "Maybe a mistake? User cannot be deleted while he is part of an order"
},
status.HTTP_400_BAD_REQUEST,
)
@@ -746,8 +803,7 @@ class UserView(APIView):
if user.profile.total_contracts > 0:
return Response(
{
- "bad_request":
- "Maybe a mistake? User cannot be deleted as it has completed trades"
+ "bad_request": "Maybe a mistake? User cannot be deleted as it has completed trades"
},
status.HTTP_400_BAD_REQUEST,
)
@@ -775,12 +831,11 @@ class BookView(ListAPIView):
if int(currency) == 0 and int(type) != 2:
queryset = Order.objects.filter(type=type, status=Order.Status.PUB)
elif int(type) == 2 and int(currency) != 0:
- queryset = Order.objects.filter(currency=currency,
- status=Order.Status.PUB)
+ queryset = Order.objects.filter(currency=currency, status=Order.Status.PUB)
elif not (int(currency) == 0 and int(type) == 2):
- queryset = Order.objects.filter(currency=currency,
- type=type,
- status=Order.Status.PUB)
+ queryset = Order.objects.filter(
+ currency=currency, type=type, status=Order.Status.PUB
+ )
if len(queryset) == 0:
return Response(
@@ -795,11 +850,12 @@ class BookView(ListAPIView):
data["satoshis_now"] = Logics.satoshis_now(order)
# Compute current premium for those orders that are explicitly priced.
- data["price"], data["premium"] = Logics.price_and_premium_now(
- order)
- data["maker_status"] = Logics.user_activity_status(
- order.maker_last_seen)
- for key in ("status","taker"): # Non participants should not see the status or who is the taker
+ data["price"], data["premium"] = Logics.price_and_premium_now(order)
+ data["maker_status"] = Logics.user_activity_status(order.maker_last_seen)
+ for key in (
+ "status",
+ "taker",
+ ): # Non participants should not see the status or who is the taker
del data[key]
book_data.append(data)
@@ -816,18 +872,23 @@ class InfoView(ListAPIView):
context = {}
context["num_public_buy_orders"] = len(
- Order.objects.filter(type=Order.Types.BUY,
- status=Order.Status.PUB))
+ Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB)
+ )
context["num_public_sell_orders"] = len(
- Order.objects.filter(type=Order.Types.SELL,
- status=Order.Status.PUB))
- context["book_liquidity"] = Order.objects.filter(status=Order.Status.PUB).aggregate(Sum('last_satoshis'))['last_satoshis__sum']
- context["book_liquidity"] = 0 if context["book_liquidity"] == None else context["book_liquidity"]
+ Order.objects.filter(type=Order.Types.SELL, status=Order.Status.PUB)
+ )
+ context["book_liquidity"] = Order.objects.filter(
+ status=Order.Status.PUB
+ ).aggregate(Sum("last_satoshis"))["last_satoshis__sum"]
+ context["book_liquidity"] = (
+ 0 if context["book_liquidity"] == None else context["book_liquidity"]
+ )
# Number of active users (logged in in last 30 minutes)
today = datetime.today()
context["active_robots_today"] = len(
- User.objects.filter(last_login__day=today.day))
+ User.objects.filter(last_login__day=today.day)
+ )
# Compute average premium and volume of today
last_day = timezone.now() - timedelta(days=1)
@@ -860,11 +921,15 @@ class InfoView(ListAPIView):
context["node_alias"] = config("NODE_ALIAS")
context["node_id"] = config("NODE_ID")
context["network"] = config("NETWORK")
- context["maker_fee"] = float(config("FEE"))*float(config("MAKER_FEE_SPLIT"))
- context["taker_fee"] = float(config("FEE"))*(1 - float(config("MAKER_FEE_SPLIT")))
+ context["maker_fee"] = float(config("FEE")) * float(config("MAKER_FEE_SPLIT"))
+ context["taker_fee"] = float(config("FEE")) * (
+ 1 - float(config("MAKER_FEE_SPLIT"))
+ )
context["bond_size"] = float(config("DEFAULT_BOND_SIZE"))
- context["current_swap_fee_rate"] = Logics.compute_swap_fee_rate(BalanceLog.objects.latest('time'))
+ context["current_swap_fee_rate"] = Logics.compute_swap_fee_rate(
+ BalanceLog.objects.latest("time")
+ )
if request.user.is_authenticated:
context["nickname"] = request.user.username
@@ -872,13 +937,16 @@ class InfoView(ListAPIView):
context["earned_rewards"] = request.user.profile.earned_rewards
context["wants_stealth"] = request.user.profile.wants_stealth
# Adds/generate telegram token and whether it is enabled
- context = {**context,**Telegram.get_context(request.user)}
+ context = {**context, **Telegram.get_context(request.user)}
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
- request.user)
+ request.user
+ )
if not has_no_active_order:
context["active_order_id"] = order.id
else:
- last_order = Order.objects.filter(Q(maker=request.user) | Q(taker=request.user)).last()
+ last_order = Order.objects.filter(
+ Q(maker=request.user) | Q(taker=request.user)
+ ).last()
if last_order:
context["last_order_id"] = last_order.id
@@ -894,10 +962,7 @@ class RewardView(CreateAPIView):
if not request.user.is_authenticated:
return Response(
- {
- "bad_request":
- "Woops! It seems you do not have a robot avatar"
- },
+ {"bad_request": "Woops! It seems you do not have a robot avatar"},
status.HTTP_400_BAD_REQUEST,
)
@@ -909,7 +974,7 @@ class RewardView(CreateAPIView):
valid, context = Logics.withdraw_rewards(request.user, invoice)
if not valid:
- context['successful_withdrawal'] = False
+ context["successful_withdrawal"] = False
return Response(context, status.HTTP_400_BAD_REQUEST)
return Response({"successful_withdrawal": True}, status.HTTP_200_OK)
@@ -923,17 +988,19 @@ class PriceView(ListAPIView):
def get(self, request):
payload = {}
- queryset = Currency.objects.all().order_by('currency')
+ queryset = Currency.objects.all().order_by("currency")
for currency in queryset:
code = Currency.currency_dict[str(currency.currency)]
try:
- last_tick = MarketTick.objects.filter(currency=currency).latest('timestamp')
+ last_tick = MarketTick.objects.filter(currency=currency).latest(
+ "timestamp"
+ )
payload[code] = {
- 'price': last_tick.price,
- 'volume': last_tick.volume,
- 'premium': last_tick.premium,
- 'timestamp': last_tick.timestamp,
+ "price": last_tick.price,
+ "volume": last_tick.volume,
+ "premium": last_tick.premium,
+ "timestamp": last_tick.timestamp,
}
except:
payload[code] = None
@@ -948,48 +1015,48 @@ class TickView(ListAPIView):
@extend_schema(**TickViewSchema.get)
def get(self, request):
- data = self.serializer_class(self.queryset.all(), many=True, read_only=True).data
+ data = self.serializer_class(
+ self.queryset.all(), many=True, read_only=True
+ ).data
return Response(data, status=status.HTTP_200_OK)
class LimitView(ListAPIView):
-
@extend_schema(**LimitViewSchema.get)
def get(self, request):
-
+
# Trade limits as BTC
- min_trade = float(config('MIN_TRADE')) / 100000000
- max_trade = float(config('MAX_TRADE')) / 100000000
- max_bondless_trade = float(config('MAX_TRADE_BONDLESS_TAKER')) / 100000000
+ min_trade = float(config("MIN_TRADE")) / 100000000
+ max_trade = float(config("MAX_TRADE")) / 100000000
+ max_bondless_trade = float(config("MAX_TRADE_BONDLESS_TAKER")) / 100000000
payload = {}
- queryset = Currency.objects.all().order_by('currency')
+ queryset = Currency.objects.all().order_by("currency")
for currency in queryset:
code = Currency.currency_dict[str(currency.currency)]
exchange_rate = float(currency.exchange_rate)
payload[currency.currency] = {
- 'code': code,
- 'price': exchange_rate,
- 'min_amount': min_trade * exchange_rate,
- 'max_amount': max_trade * exchange_rate,
- 'max_bondless_amount': max_bondless_trade * exchange_rate,
+ "code": code,
+ "price": exchange_rate,
+ "min_amount": min_trade * exchange_rate,
+ "max_amount": max_trade * exchange_rate,
+ "max_bondless_amount": max_bondless_trade * exchange_rate,
}
return Response(payload, status.HTTP_200_OK)
class HistoricalView(ListAPIView):
-
@extend_schema(**HistoricalViewSchema.get)
def get(self, request):
payload = {}
- queryset = AccountingDay.objects.all().order_by('day')
+ queryset = AccountingDay.objects.all().order_by("day")
for accounting_day in queryset:
payload[str(accounting_day.day)] = {
- 'volume': accounting_day.contracted,
- 'num_contracts': accounting_day.num_contracts,
+ "volume": accounting_day.contracted,
+ "num_contracts": accounting_day.num_contracts,
}
return Response(payload, status.HTTP_200_OK)
@@ -998,16 +1065,14 @@ class HistoricalView(ListAPIView):
class StealthView(UpdateAPIView):
serializer_class = StealthSerializer
+
@extend_schema(**StealthViewSchema.put)
def put(self, request):
serializer = self.serializer_class(data=request.data)
if not request.user.is_authenticated:
return Response(
- {
- "bad_request":
- "Woops! It seems you do not have a robot avatar"
- },
+ {"bad_request": "Woops! It seems you do not have a robot avatar"},
status.HTTP_400_BAD_REQUEST,
)
diff --git a/chat/admin.py b/chat/admin.py
index ec78db0f..96c01a28 100644
--- a/chat/admin.py
+++ b/chat/admin.py
@@ -1,6 +1,7 @@
from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin
from chat.models import ChatRoom, Message
+
# Register your models here.
@@ -17,9 +18,10 @@ class ChatRoomAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"taker_connect_date",
"room_group_name",
)
- change_links = ["order","maker","taker"]
+ change_links = ["order", "maker", "taker"]
search_fields = ["id"]
+
@admin.register(Message)
class MessageAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = (
@@ -31,7 +33,7 @@ class MessageAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
"receiver_link",
"created_at",
)
- change_links = ["chatroom","order","sender","receiver"]
- search_fields = ["id","index"]
- ordering = ["-chatroom_id","-index"]
- list_filter = ("chatroom",)
\ No newline at end of file
+ change_links = ["chatroom", "order", "sender", "receiver"]
+ search_fields = ["id", "index"]
+ ordering = ["-chatroom_id", "-index"]
+ list_filter = ("chatroom",)
diff --git a/chat/consumers.py b/chat/consumers.py
index 66c238a7..07b127ce 100644
--- a/chat/consumers.py
+++ b/chat/consumers.py
@@ -6,8 +6,8 @@ from asgiref.sync import async_to_sync
import json
-class ChatRoomConsumer(AsyncWebsocketConsumer):
+class ChatRoomConsumer(AsyncWebsocketConsumer):
@database_sync_to_async
def allow_in_chatroom(self):
order = Order.objects.get(id=self.order_id)
@@ -23,37 +23,37 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
@database_sync_to_async
def save_connect_user(self):
- '''Creates or updates the ChatRoom object'''
+ """Creates or updates the ChatRoom object"""
order = Order.objects.get(id=self.order_id)
if order.maker == self.user:
ChatRoom.objects.update_or_create(
- id=self.order_id,
- order=order,
+ id=self.order_id,
+ order=order,
room_group_name=self.room_group_name,
defaults={
"maker": self.user,
"maker_connected": True,
- }
- )
+ },
+ )
elif order.taker == self.user:
ChatRoom.objects.update_or_create(
- id=self.order_id,
- order=order,
+ id=self.order_id,
+ order=order,
room_group_name=self.room_group_name,
defaults={
"taker": self.user,
"taker_connected": True,
- }
- )
+ },
+ )
return None
@database_sync_to_async
def save_new_PGP_message(self, PGP_message):
- '''Creates a Message object'''
+ """Creates a Message object"""
order = Order.objects.get(id=self.order_id)
chatroom = ChatRoom.objects.get(order=order)
@@ -71,39 +71,33 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
receiver = order.taker
msg_obj = Message.objects.create(
- order=order,
- chatroom=chatroom,
- index=index,
- sender=sender,
- receiver=receiver,
- PGP_message=PGP_message,
- )
+ order=order,
+ chatroom=chatroom,
+ index=index,
+ sender=sender,
+ receiver=receiver,
+ PGP_message=PGP_message,
+ )
return msg_obj
@database_sync_to_async
def save_disconnect_user(self):
- '''Creates or updates the ChatRoom object'''
-
+ """Creates or updates the ChatRoom object"""
+
order = Order.objects.get(id=self.order_id)
if order.maker == self.user:
ChatRoom.objects.update_or_create(
- id=self.order_id,
- defaults={
- "maker_connected": False
- }
- )
+ id=self.order_id, defaults={"maker_connected": False}
+ )
elif order.taker == self.user:
ChatRoom.objects.update_or_create(
- id=self.order_id,
- defaults={
- "taker_connected": False
- }
- )
+ id=self.order_id, defaults={"taker_connected": False}
+ )
return None
@database_sync_to_async
def is_peer_connected(self):
- '''Returns whether the consumer's peer is connected'''
+ """Returns whether the consumer's peer is connected"""
chatroom = ChatRoom.objects.get(id=self.order_id)
@@ -115,7 +109,7 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
@database_sync_to_async
def get_peer_PGP_public_key(self):
- '''Returns peer PGP public key'''
+ """Returns peer PGP public key"""
order = Order.objects.get(id=self.order_id)
@@ -127,19 +121,21 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
@database_sync_to_async
def get_all_PGP_messages(self):
- '''Returns all PGP messages'''
+ """Returns all PGP messages"""
order = Order.objects.get(id=self.order_id)
messages = Message.objects.filter(order=order)
msgs = []
for message in messages:
- msgs.append({
- "index": message.index,
- "time": str(message.created_at),
- "message": message.PGP_message,
- "nick": str(message.sender),
- })
+ msgs.append(
+ {
+ "index": message.index,
+ "time": str(message.created_at),
+ "message": message.PGP_message,
+ "nick": str(message.sender),
+ }
+ )
return msgs
@@ -153,8 +149,7 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
if allowed:
await self.save_connect_user()
- await self.channel_layer.group_add(self.room_group_name,
- self.channel_name)
+ await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
@@ -173,13 +168,12 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
async def disconnect(self, close_code):
await self.save_disconnect_user()
- await self.channel_layer.group_discard(self.room_group_name,
- self.channel_name)
+ await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chatroom_message",
- "message": 'peer-disconnected',
+ "message": "peer-disconnected",
"nick": self.scope["user"].username,
"peer_connected": False,
},
@@ -189,9 +183,9 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
peer_connected = await self.is_peer_connected()
-
+
# Encrypted messages are stored. They are served later when a user reconnects.
- if message[0:27] == '-----BEGIN PGP MESSAGE-----':
+ if message[0:27] == "-----BEGIN PGP MESSAGE-----":
# save to database
msg_obj = await self.save_new_PGP_message(message)
@@ -210,9 +204,9 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
"peer_connected": peer_connected,
},
)
-
+
# Encrypted messages are served when the user requests them
- elif message[0:23] == '-----SERVE HISTORY-----':
+ elif message[0:23] == "-----SERVE HISTORY-----":
# If there is any stored message, serve them.
msgs = await self.get_all_PGP_messages()
peer_connected = await self.is_peer_connected()
@@ -221,10 +215,10 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
self.room_group_name,
{
"type": "PGP_message",
- "index": msg['index'],
- "time": msg['time'],
- "message": msg['message'],
- "nick": msg['nick'],
+ "index": msg["index"],
+ "time": msg["time"],
+ "message": msg["message"],
+ "nick": msg["nick"],
"peer_connected": peer_connected,
},
)
@@ -245,11 +239,15 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
nick = event["nick"]
peer_connected = event["peer_connected"]
- await self.send(text_data=json.dumps({
- "message": message,
- "user_nick": nick,
- "peer_connected": peer_connected,
- }))
+ await self.send(
+ text_data=json.dumps(
+ {
+ "message": message,
+ "user_nick": nick,
+ "peer_connected": peer_connected,
+ }
+ )
+ )
async def PGP_message(self, event):
message = event["message"]
@@ -258,10 +256,14 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
peer_connected = event["peer_connected"]
time = event["time"]
- await self.send(text_data=json.dumps({
- "index": index,
- "message": message,
- "user_nick": nick,
- "peer_connected": peer_connected,
- "time":time,
- }))
+ await self.send(
+ text_data=json.dumps(
+ {
+ "index": index,
+ "message": message,
+ "user_nick": nick,
+ "peer_connected": peer_connected,
+ "time": time,
+ }
+ )
+ )
diff --git a/chat/models.py b/chat/models.py
index 5e637ba9..55079f02 100644
--- a/chat/models.py
+++ b/chat/models.py
@@ -3,24 +3,29 @@ from api.models import User, Order
from django.utils import timezone
import uuid
-class ChatRoom(models.Model):
- '''
- Simple ChatRoom model. Needed to facilitate communication: Is my counterpart in the room?
- '''
- id = models.PositiveBigIntegerField(primary_key=True, null=False,default=None, blank=True)
+class ChatRoom(models.Model):
+ """
+ Simple ChatRoom model. Needed to facilitate communication: Is my counterpart in the room?
+ """
+
+ id = models.PositiveBigIntegerField(
+ primary_key=True, null=False, default=None, blank=True
+ )
order = models.ForeignKey(
Order,
related_name="chatroom",
on_delete=models.SET_NULL,
null=True,
- default=None)
+ default=None,
+ )
maker = models.ForeignKey(
User,
related_name="chat_maker",
on_delete=models.SET_NULL,
null=True,
- default=None)
+ default=None,
+ )
taker = models.ForeignKey(
User,
related_name="chat_taker",
@@ -30,8 +35,8 @@ class ChatRoom(models.Model):
blank=True,
)
- maker_connected = models.BooleanField(default=False, null=False)
- taker_connected = models.BooleanField(default=False, null=False)
+ maker_connected = models.BooleanField(default=False, null=False)
+ taker_connected = models.BooleanField(default=False, null=False)
maker_connect_date = models.DateTimeField(auto_now_add=True)
taker_connect_date = models.DateTimeField(auto_now_add=True)
@@ -46,41 +51,39 @@ class ChatRoom(models.Model):
def __str__(self):
return f"Chat:{str(self.id)}"
+
class Message(models.Model):
class Meta:
- get_latest_by = 'index'
+ get_latest_by = "index"
# id = models.PositiveBigIntegerField(primary_key=True, default=uuid.uuid4, editable=False)
order = models.ForeignKey(
- Order,
- related_name="message",
- on_delete=models.CASCADE,
- null=True,
- default=None)
+ Order, related_name="message", on_delete=models.CASCADE, null=True, default=None
+ )
chatroom = models.ForeignKey(
ChatRoom,
related_name="chatroom",
on_delete=models.CASCADE,
null=True,
- default=None)
- index = models.PositiveIntegerField(null=False,default=None, blank=True)
+ default=None,
+ )
+ index = models.PositiveIntegerField(null=False, default=None, blank=True)
sender = models.ForeignKey(
User,
related_name="message_sender",
on_delete=models.SET_NULL,
null=True,
- default=None)
+ default=None,
+ )
receiver = models.ForeignKey(
User,
related_name="message_receiver",
on_delete=models.SET_NULL,
null=True,
- default=None)
+ default=None,
+ )
- PGP_message = models.TextField(max_length=5000,
- null=True,
- default=None,
- blank=True)
+ PGP_message = models.TextField(max_length=5000, null=True, default=None, blank=True)
created_at = models.DateTimeField(default=timezone.now)
diff --git a/chat/routing.py b/chat/routing.py
index 0b05a8cd..43ffec36 100644
--- a/chat/routing.py
+++ b/chat/routing.py
@@ -2,6 +2,5 @@ from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
- re_path(r"ws/chat/(?P\w+)/$",
- consumers.ChatRoomConsumer.as_asgi()),
+ re_path(r"ws/chat/(?P\w+)/$", consumers.ChatRoomConsumer.as_asgi()),
]
diff --git a/chat/serializers.py b/chat/serializers.py
index 22faac71..7e39142b 100644
--- a/chat/serializers.py
+++ b/chat/serializers.py
@@ -1,8 +1,8 @@
from rest_framework import serializers
from chat.models import Message
-class ChatSerializer(serializers.ModelSerializer):
+class ChatSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = (
@@ -13,14 +13,17 @@ class ChatSerializer(serializers.ModelSerializer):
)
depth = 0
+
class PostMessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
- fields = ("PGP_message","order","offset")
+ fields = ("PGP_message", "order", "offset")
depth = 0
-
- offset = serializers.IntegerField(allow_null=True,
- default=None,
- required=False,
- min_value=0,
- help_text="Offset for message index to get as response")
\ No newline at end of file
+
+ offset = serializers.IntegerField(
+ allow_null=True,
+ default=None,
+ required=False,
+ min_value=0,
+ help_text="Offset for message index to get as response",
+ )
diff --git a/chat/tasks.py b/chat/tasks.py
index 61c30d77..60f9bf23 100644
--- a/chat/tasks.py
+++ b/chat/tasks.py
@@ -1,9 +1,10 @@
from celery import shared_task
+
@shared_task(name="chatrooms_cleansing")
def chatrooms_cleansing():
"""
- Deletes chatrooms and encrypted messages of orders
+ Deletes chatrooms and encrypted messages of orders
that have completely finished more than 3 days ago.
"""
@@ -12,24 +13,28 @@ def chatrooms_cleansing():
from datetime import timedelta
from django.utils import timezone
- finished_states = [Order.Status.SUC,
- Order.Status.TLD,
- Order.Status.MLD,
- Order.Status.CCA,
- Order.Status.UCA]
+ finished_states = [
+ Order.Status.SUC,
+ Order.Status.TLD,
+ Order.Status.MLD,
+ Order.Status.CCA,
+ Order.Status.UCA,
+ ]
# Orders that have expired more than 3 days ago
- # Usually expiry takes place 1 day after a finished order. So, ~4 days
+ # Usually expiry takes place 1 day after a finished order. So, ~4 days
# until encrypted messages are deleted.
finished_time = timezone.now() - timedelta(days=3)
- queryset = Order.objects.filter(status__in=finished_states, expires_at__lt=finished_time)
+ queryset = Order.objects.filter(
+ status__in=finished_states, expires_at__lt=finished_time
+ )
# And do not have an active trade, any past contract or any reward.
deleted_chatrooms = []
for order in queryset:
# Try an except. In case some chatroom is already missing.
try:
- chatroom = ChatRoom.objects.get(id = order.id)
+ chatroom = ChatRoom.objects.get(id=order.id)
deleted_chatrooms.append(str(chatroom))
chatroom.delete()
except:
@@ -39,4 +44,4 @@ def chatrooms_cleansing():
"num_deleted": len(deleted_chatrooms),
"deleted_chatrooms": deleted_chatrooms,
}
- return results
\ No newline at end of file
+ return results
diff --git a/chat/views.py b/chat/views.py
index af7ef7ea..e5d471d7 100644
--- a/chat/views.py
+++ b/chat/views.py
@@ -10,11 +10,14 @@ from django.utils import timezone
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
+
class ChatView(viewsets.ViewSet):
serializer_class = PostMessageSerializer
- lookup_url_kwarg = ["order_id","offset"]
+ lookup_url_kwarg = ["order_id", "offset"]
- queryset = Message.objects.filter(order__status__in=[Order.Status.CHA, Order.Status.FSE])
+ queryset = Message.objects.filter(
+ order__status__in=[Order.Status.CHA, Order.Status.FSE]
+ )
def get(self, request, format=None):
"""
@@ -26,63 +29,57 @@ class ChatView(viewsets.ViewSet):
if order_id is None:
return Response(
- {
- "bad_request":
- "Order ID does not exist"
- },
+ {"bad_request": "Order ID does not exist"},
status.HTTP_400_BAD_REQUEST,
- )
+ )
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
- {
- "bad_request":
- "You are not participant in this order"
- },
+ {"bad_request": "You are not participant in this order"},
status.HTTP_400_BAD_REQUEST,
)
if not order.status in [Order.Status.CHA, Order.Status.FSE]:
return Response(
- {
- "bad_request":
- "Order is not in chat status"
- },
+ {"bad_request": "Order is not in chat status"},
status.HTTP_400_BAD_REQUEST,
)
queryset = Message.objects.filter(order=order, index__gt=offset)
chatroom = ChatRoom.objects.get(order=order)
-
- # Poor idea: is_peer_connected() mockup. Update connection status based on last time a GET request was sent
+
+ # Poor idea: is_peer_connected() mockup. Update connection status based on last time a GET request was sent
if chatroom.maker == request.user:
- chatroom.taker_connected = order.taker_last_seen > (timezone.now() - timedelta(minutes=1))
+ chatroom.taker_connected = order.taker_last_seen > (
+ timezone.now() - timedelta(minutes=1)
+ )
chatroom.maker_connected = True
chatroom.save()
peer_connected = chatroom.taker_connected
elif chatroom.taker == request.user:
- chatroom.maker_connected = order.maker_last_seen > (timezone.now() - timedelta(minutes=1))
+ chatroom.maker_connected = order.maker_last_seen > (
+ timezone.now() - timedelta(minutes=1)
+ )
chatroom.taker_connected = True
chatroom.save()
peer_connected = chatroom.maker_connected
-
-
+
messages = []
for message in queryset:
d = ChatSerializer(message).data
print(d)
# Re-serialize so the response is identical to the consumer message
data = {
- 'index':d['index'],
- 'time':d['created_at'],
- 'message':d['PGP_message'],
- 'nick': User.objects.get(id=d['sender']).username
- }
+ "index": d["index"],
+ "time": d["created_at"],
+ "message": d["PGP_message"],
+ "nick": User.objects.get(id=d["sender"]).username,
+ }
messages.append(data)
- response = {'peer_connected': peer_connected, 'messages':messages}
+ response = {"peer_connected": peer_connected, "messages": messages}
return Response(response, status.HTTP_200_OK)
@@ -92,7 +89,7 @@ class ChatView(viewsets.ViewSet):
"""
serializer = self.serializer_class(data=request.data)
- # Return bad request if serializer is not valid
+ # Return bad request if serializer is not valid
if not serializer.is_valid():
context = {"bad_request": "Invalid serializer"}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
@@ -102,30 +99,21 @@ class ChatView(viewsets.ViewSet):
if order_id is None:
return Response(
- {
- "bad_request":
- "Order ID does not exist"
- },
+ {"bad_request": "Order ID does not exist"},
status.HTTP_400_BAD_REQUEST,
- )
-
+ )
+
order = Order.objects.get(id=order_id)
if not (request.user == order.maker or request.user == order.taker):
return Response(
- {
- "bad_request":
- "You are not participant in this order"
- },
+ {"bad_request": "You are not participant in this order"},
status.HTTP_400_BAD_REQUEST,
)
if not order.status in [Order.Status.CHA, Order.Status.FSE]:
return Response(
- {
- "bad_request":
- "Order is not in chat status"
- },
+ {"bad_request": "Order is not in chat status"},
status.HTTP_400_BAD_REQUEST,
)
@@ -137,26 +125,26 @@ class ChatView(viewsets.ViewSet):
receiver = order.maker
chatroom, _ = ChatRoom.objects.get_or_create(
- id=order_id,
- order=order,
- room_group_name=f"chat_order_{order_id}",
- defaults={
- "maker": order.maker,
- "maker_connected": order.maker == request.user,
- "taker": order.taker,
- "taker_connected": order.taker == request.user,
- }
- )
+ id=order_id,
+ order=order,
+ room_group_name=f"chat_order_{order_id}",
+ defaults={
+ "maker": order.maker,
+ "maker_connected": order.maker == request.user,
+ "taker": order.taker,
+ "taker_connected": order.taker == request.user,
+ },
+ )
last_index = Message.objects.filter(order=order, chatroom=chatroom).count()
new_message = Message.objects.create(
- index=last_index+1,
+ index=last_index + 1,
PGP_message=serializer.data.get("PGP_message"),
order=order,
chatroom=chatroom,
sender=sender,
receiver=receiver,
- )
+ )
# Send websocket message
if chatroom.maker == request.user:
@@ -168,13 +156,13 @@ class ChatView(viewsets.ViewSet):
async_to_sync(channel_layer.group_send)(
f"chat_order_{order_id}",
{
- "type": "PGP_message",
- "index": new_message.index,
- "message": new_message.PGP_message,
- "time": str(new_message.created_at),
- "nick": new_message.sender.username,
- "peer_connected": peer_connected,
- }
+ "type": "PGP_message",
+ "index": new_message.index,
+ "message": new_message.PGP_message,
+ "time": str(new_message.created_at),
+ "nick": new_message.sender.username,
+ "peer_connected": peer_connected,
+ },
)
# if offset is given, reply with messages
@@ -187,16 +175,15 @@ class ChatView(viewsets.ViewSet):
print(d)
# Re-serialize so the response is identical to the consumer message
data = {
- 'index':d['index'],
- 'time':d['created_at'],
- 'message':d['PGP_message'],
- 'nick': User.objects.get(id=d['sender']).username
- }
+ "index": d["index"],
+ "time": d["created_at"],
+ "message": d["PGP_message"],
+ "nick": User.objects.get(id=d["sender"]).username,
+ }
messages.append(data)
- response = {'peer_connected': peer_connected, 'messages':messages}
+ response = {"peer_connected": peer_connected, "messages": messages}
else:
response = {}
return Response(response, status.HTTP_200_OK)
-
\ No newline at end of file
diff --git a/control/admin.py b/control/admin.py
index 2740c408..3d1aea5a 100755
--- a/control/admin.py
+++ b/control/admin.py
@@ -4,6 +4,7 @@ from import_export.admin import ImportExportModelAdmin
# Register your models here.
+
@admin.register(AccountingDay)
class AccountingDayAdmin(ImportExportModelAdmin):
@@ -29,6 +30,7 @@ class AccountingDayAdmin(ImportExportModelAdmin):
change_links = ["day"]
search_fields = ["day"]
+
@admin.register(BalanceLog)
class BalanceLogAdmin(ImportExportModelAdmin):
@@ -57,4 +59,4 @@ class BalanceLogAdmin(ImportExportModelAdmin):
"ln_remote_unsettled",
]
change_links = ["time"]
- search_fields = ["time"]
\ No newline at end of file
+ search_fields = ["time"]
diff --git a/control/apps.py b/control/apps.py
index 14765b63..d010bd75 100755
--- a/control/apps.py
+++ b/control/apps.py
@@ -2,5 +2,5 @@ from django.apps import AppConfig
class ControlConfig(AppConfig):
- default_auto_field = 'django.db.models.BigAutoField'
- name = 'control'
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "control"
diff --git a/control/models.py b/control/models.py
index cc3af699..b13aaee9 100755
--- a/control/models.py
+++ b/control/models.py
@@ -3,79 +3,129 @@ from django.utils import timezone
from api.lightning.node import LNNode
+
class AccountingDay(models.Model):
day = models.DateTimeField(primary_key=True, auto_now=False, auto_now_add=False)
# Every field is denominated in Sats with (3 decimals for millisats)
# Total volume contracted
- contracted = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ contracted = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Number of contracts
num_contracts = models.BigIntegerField(default=0, null=False, blank=False)
# Net volume of trading invoices settled (excludes disputes)
- net_settled = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ net_settled = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Net volume of trading invoices paid (excludes rewards and disputes)
- net_paid = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ net_paid = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Sum of net settled and net paid
- net_balance = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ net_balance = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Total volume of invoices settled
- inflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ inflow = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Total volume of invoices paid
- outflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ outflow = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Total cost in routing fees
- routing_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ routing_fees = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Total cost in minig fees
- mining_fees = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ mining_fees = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Total inflows minus outflows and routing fees
- cashflow = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ cashflow = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Balance on earned rewards (referral rewards, slashed bonds and solved disputes)
- outstanding_earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ outstanding_earned_rewards = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Balance on pending disputes (not resolved yet)
- outstanding_pending_disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ outstanding_pending_disputes = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Rewards claimed lifetime
- lifetime_rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ lifetime_rewards_claimed = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Balance change from last day on earned rewards (referral rewards, slashed bonds and solved disputes)
- earned_rewards = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ earned_rewards = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Balance change on pending disputes (not resolved yet)
- disputes = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ disputes = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
# Rewards claimed on day
- rewards_claimed = models.DecimalField(max_digits=15, decimal_places=3, default=0, null=False, blank=False)
+ rewards_claimed = models.DecimalField(
+ max_digits=15, decimal_places=3, default=0, null=False, blank=False
+ )
+
class BalanceLog(models.Model):
-
def get_total():
- return LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance']
+ return (
+ LNNode.wallet_balance()["total_balance"]
+ + LNNode.channel_balance()["local_balance"]
+ )
+
def get_frac():
- return LNNode.wallet_balance()['total_balance'] / (LNNode.wallet_balance()['total_balance'] + LNNode.channel_balance()['local_balance'])
- def get_oc_total():
- return LNNode.wallet_balance()['total_balance']
- def get_oc_conf():
- return LNNode.wallet_balance()['confirmed_balance']
+ return LNNode.wallet_balance()["total_balance"] / (
+ LNNode.wallet_balance()["total_balance"]
+ + LNNode.channel_balance()["local_balance"]
+ )
+
+ def get_oc_total():
+ return LNNode.wallet_balance()["total_balance"]
+
+ def get_oc_conf():
+ return LNNode.wallet_balance()["confirmed_balance"]
+
def get_oc_unconf():
- return LNNode.wallet_balance()['unconfirmed_balance']
+ return LNNode.wallet_balance()["unconfirmed_balance"]
+
def get_ln_local():
- return LNNode.channel_balance()['local_balance']
+ return LNNode.channel_balance()["local_balance"]
+
def get_ln_remote():
- return LNNode.channel_balance()['remote_balance']
+ return LNNode.channel_balance()["remote_balance"]
+
def get_ln_local_unsettled():
- return LNNode.channel_balance()['unsettled_local_balance']
+ return LNNode.channel_balance()["unsettled_local_balance"]
+
def get_ln_remote_unsettled():
- return LNNode.channel_balance()['unsettled_remote_balance']
+ return LNNode.channel_balance()["unsettled_remote_balance"]
time = models.DateTimeField(primary_key=True, default=timezone.now)
# Every field is denominated in Sats
total = models.PositiveBigIntegerField(default=get_total)
- onchain_fraction = models.DecimalField(max_digits=6, decimal_places=5, default=get_frac)
+ onchain_fraction = models.DecimalField(
+ max_digits=6, decimal_places=5, default=get_frac
+ )
onchain_total = models.PositiveBigIntegerField(default=get_oc_total)
onchain_confirmed = models.PositiveBigIntegerField(default=get_oc_conf)
onchain_unconfirmed = models.PositiveBigIntegerField(default=get_oc_unconf)
ln_local = models.PositiveBigIntegerField(default=get_ln_local)
ln_remote = models.PositiveBigIntegerField(default=get_ln_remote)
ln_local_unsettled = models.PositiveBigIntegerField(default=get_ln_local_unsettled)
- ln_remote_unsettled = models.PositiveBigIntegerField(default=get_ln_remote_unsettled)
+ ln_remote_unsettled = models.PositiveBigIntegerField(
+ default=get_ln_remote_unsettled
+ )
def __str__(self):
return f"Balance at {self.time.strftime('%d/%m/%Y %H:%M:%S')}"
+
class Dispute(models.Model):
- pass
\ No newline at end of file
+ pass
diff --git a/control/tasks.py b/control/tasks.py
index 019f61b6..b7250a81 100644
--- a/control/tasks.py
+++ b/control/tasks.py
@@ -1,10 +1,11 @@
from celery import shared_task
+
@shared_task(name="do_accounting")
def do_accounting():
- '''
+ """
Does all accounting from the beginning of time
- '''
+ """
from api.models import Order, LNPayment, OnchainPayment, Profile, MarketTick
from control.models import AccountingDay
@@ -18,61 +19,84 @@ def do_accounting():
today = timezone.now().date()
try:
- last_accounted_day = AccountingDay.objects.latest('day').day.date()
- accounted_yesterday = AccountingDay.objects.latest('day')
+ last_accounted_day = AccountingDay.objects.latest("day").day.date()
+ accounted_yesterday = AccountingDay.objects.latest("day")
except:
last_accounted_day = None
accounted_yesterday = None
if last_accounted_day == today:
- return {'message':'no days to account for'}
+ return {"message": "no days to account for"}
elif last_accounted_day != None:
initial_day = last_accounted_day + timedelta(days=1)
elif last_accounted_day == None:
- initial_day = all_payments.earliest('created_at').created_at.date()
+ initial_day = all_payments.earliest("created_at").created_at.date()
-
day = initial_day
result = {}
while day <= today:
- day_payments = all_payments.filter(created_at__gte=day,created_at__lte=day+timedelta(days=1))
- day_onchain_payments = OnchainPayment.objects.filter(created_at__gte=day,created_at__lte=day+timedelta(days=1))
- day_ticks = all_ticks.filter(timestamp__gte=day,timestamp__lte=day+timedelta(days=1))
+ day_payments = all_payments.filter(
+ created_at__gte=day, created_at__lte=day + timedelta(days=1)
+ )
+ day_onchain_payments = OnchainPayment.objects.filter(
+ created_at__gte=day, created_at__lte=day + timedelta(days=1)
+ )
+ day_ticks = all_ticks.filter(
+ timestamp__gte=day, timestamp__lte=day + timedelta(days=1)
+ )
# Coarse accounting based on LNpayment and OnchainPayment objects
- contracted = day_ticks.aggregate(Sum('volume'))['volume__sum']
+ contracted = day_ticks.aggregate(Sum("volume"))["volume__sum"]
num_contracts = day_ticks.count()
- inflow = day_payments.filter(type=LNPayment.Types.HOLD,status=LNPayment.Status.SETLED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
- onchain_outflow = day_onchain_payments.filter(status__in=[OnchainPayment.Status.MEMPO,OnchainPayment.Status.CONFI]).aggregate(Sum('sent_satoshis'))['sent_satoshis__sum']
+ inflow = day_payments.filter(
+ type=LNPayment.Types.HOLD, status=LNPayment.Status.SETLED
+ ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
+ onchain_outflow = day_onchain_payments.filter(
+ status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
+ ).aggregate(Sum("sent_satoshis"))["sent_satoshis__sum"]
onchain_outflow = 0 if onchain_outflow == None else int(onchain_outflow)
- offchain_outflow = day_payments.filter(type=LNPayment.Types.NORM,status=LNPayment.Status.SUCCED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
+ offchain_outflow = day_payments.filter(
+ type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
+ ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
offchain_outflow = 0 if offchain_outflow == None else int(offchain_outflow)
- routing_fees = day_payments.filter(type=LNPayment.Types.NORM,status=LNPayment.Status.SUCCED).aggregate(Sum('fee'))['fee__sum']
- mining_fees = day_onchain_payments.filter(status__in=[OnchainPayment.Status.MEMPO,OnchainPayment.Status.CONFI]).aggregate(Sum('mining_fee_sats'))['mining_fee_sats__sum']
- rewards_claimed = day_payments.filter(type=LNPayment.Types.NORM,concept=LNPayment.Concepts.WITHREWA,status=LNPayment.Status.SUCCED).aggregate(Sum('num_satoshis'))['num_satoshis__sum']
+ routing_fees = day_payments.filter(
+ type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
+ ).aggregate(Sum("fee"))["fee__sum"]
+ mining_fees = day_onchain_payments.filter(
+ status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
+ ).aggregate(Sum("mining_fee_sats"))["mining_fee_sats__sum"]
+ rewards_claimed = day_payments.filter(
+ type=LNPayment.Types.NORM,
+ concept=LNPayment.Concepts.WITHREWA,
+ status=LNPayment.Status.SUCCED,
+ ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
contracted = 0 if contracted == None else contracted
inflow = 0 if inflow == None else inflow
outflow = offchain_outflow + onchain_outflow
- routing_fees = 0 if routing_fees == None else routing_fees
+ routing_fees = 0 if routing_fees == None else routing_fees
rewards_claimed = 0 if rewards_claimed == None else rewards_claimed
mining_fees = 0 if mining_fees == None else mining_fees
accounted_day = AccountingDay.objects.create(
- day = day,
- contracted = contracted,
- num_contracts = num_contracts,
- inflow = inflow,
- outflow = outflow,
- routing_fees = routing_fees,
- mining_fees = mining_fees,
- cashflow = inflow - outflow - routing_fees,
- rewards_claimed = rewards_claimed,
- )
-
+ day=day,
+ contracted=contracted,
+ num_contracts=num_contracts,
+ inflow=inflow,
+ outflow=outflow,
+ routing_fees=routing_fees,
+ mining_fees=mining_fees,
+ cashflow=inflow - outflow - routing_fees,
+ rewards_claimed=rewards_claimed,
+ )
+
# Fine Net Daily accounting based on orders
# Only account for orders where everything worked out right
- payouts = day_payments.filter(type=LNPayment.Types.NORM,concept=LNPayment.Concepts.PAYBUYER, status=LNPayment.Status.SUCCED)
+ payouts = day_payments.filter(
+ type=LNPayment.Types.NORM,
+ concept=LNPayment.Concepts.PAYBUYER,
+ status=LNPayment.Status.SUCCED,
+ )
escrows_settled = 0
payouts_paid = 0
costs = 0
@@ -80,31 +104,42 @@ def do_accounting():
escrows_settled += int(payout.order_paid_LN.trade_escrow.num_satoshis)
payouts_paid += int(payout.num_satoshis)
costs += int(payout.fee)
-
+
# Same for orders that use onchain payments.
- payouts_tx = day_onchain_payments.filter(status__in=[OnchainPayment.Status.MEMPO,OnchainPayment.Status.CONFI])
+ payouts_tx = day_onchain_payments.filter(
+ status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
+ )
for payout_tx in payouts_tx:
escrows_settled += int(payout_tx.order_paid_TX.trade_escrow.num_satoshis)
payouts_paid += int(payout_tx.sent_satoshis)
costs += int(payout_tx.mining_fee_sats)
-
# account for those orders where bonds were lost
# + Settled bonds / bond_split
- bonds_settled = day_payments.filter(type=LNPayment.Types.HOLD,concept__in=[LNPayment.Concepts.TAKEBOND,LNPayment.Concepts.MAKEBOND], status=LNPayment.Status.SETLED)
+ bonds_settled = day_payments.filter(
+ type=LNPayment.Types.HOLD,
+ concept__in=[LNPayment.Concepts.TAKEBOND, LNPayment.Concepts.MAKEBOND],
+ status=LNPayment.Status.SETLED,
+ )
if len(bonds_settled) > 0:
- collected_slashed_bonds = (bonds_settled.aggregate(Sum('num_satoshis'))['num_satoshis__sum'])* float(config('SLASHED_BOND_REWARD_SPLIT'))
+ collected_slashed_bonds = (
+ bonds_settled.aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
+ ) * float(config("SLASHED_BOND_REWARD_SPLIT"))
else:
collected_slashed_bonds = 0
-
+
accounted_day.net_settled = escrows_settled + collected_slashed_bonds
accounted_day.net_paid = payouts_paid + costs
- accounted_day.net_balance = float(accounted_day.net_settled) - float(accounted_day.net_paid)
+ accounted_day.net_balance = float(accounted_day.net_settled) - float(
+ accounted_day.net_paid
+ )
# Differential accounting based on change of outstanding states and disputes unreslved
if day == today:
- pending_disputes = Order.objects.filter(status__in=[Order.Status.DIS,Order.Status.WFR])
+ pending_disputes = Order.objects.filter(
+ status__in=[Order.Status.DIS, Order.Status.WFR]
+ )
if len(pending_disputes) > 0:
outstanding_pending_disputes = 0
for order in pending_disputes:
@@ -112,28 +147,44 @@ def do_accounting():
else:
outstanding_pending_disputes = 0
- accounted_day.outstanding_earned_rewards = Profile.objects.all().aggregate(Sum('earned_rewards'))['earned_rewards__sum']
+ accounted_day.outstanding_earned_rewards = Profile.objects.all().aggregate(
+ Sum("earned_rewards")
+ )["earned_rewards__sum"]
accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
- accounted_day.lifetime_rewards_claimed = Profile.objects.all().aggregate(Sum('claimed_rewards'))['claimed_rewards__sum']
+ accounted_day.lifetime_rewards_claimed = Profile.objects.all().aggregate(
+ Sum("claimed_rewards")
+ )["claimed_rewards__sum"]
if accounted_yesterday != None:
- accounted_day.earned_rewards = accounted_day.outstanding_earned_rewards - accounted_yesterday.outstanding_earned_rewards
- accounted_day.disputes = outstanding_pending_disputes - accounted_yesterday.outstanding_earned_rewards
+ accounted_day.earned_rewards = (
+ accounted_day.outstanding_earned_rewards
+ - accounted_yesterday.outstanding_earned_rewards
+ )
+ accounted_day.disputes = (
+ outstanding_pending_disputes
+ - accounted_yesterday.outstanding_earned_rewards
+ )
# Close the loop
accounted_day.save()
accounted_yesterday = accounted_day
- result[str(day)]={'contracted':contracted,'inflow':inflow,'outflow':outflow}
+ result[str(day)] = {
+ "contracted": contracted,
+ "inflow": inflow,
+ "outflow": outflow,
+ }
day = day + timedelta(days=1)
return result
+
@shared_task(name="compute_node_balance", ignore_result=True)
def compute_node_balance():
- '''
+ """
Queries LND for channel and wallet balance
- '''
+ """
from control.models import BalanceLog
+
BalanceLog.objects.create()
-
+
return
diff --git a/robosats/__init__.py b/robosats/__init__.py
index ce285f3c..0165ba0d 100644
--- a/robosats/__init__.py
+++ b/robosats/__init__.py
@@ -4,4 +4,4 @@ from __future__ import absolute_import, unicode_literals
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
-__all__ = ("celery_app", )
+__all__ = ("celery_app",)
diff --git a/robosats/celery/__init__.py b/robosats/celery/__init__.py
index 8185d708..7d83369c 100644
--- a/robosats/celery/__init__.py
+++ b/robosats/celery/__init__.py
@@ -35,11 +35,11 @@ app.conf.beat_schedule = {
"task": "users_cleansing",
"schedule": crontab(hour=0, minute=0),
},
- "chatrooms-cleansing": { # Cleans 3+ days old encrypted messages and chatrooms at midnight
+ "chatrooms-cleansing": { # Cleans 3+ days old encrypted messages and chatrooms at midnight
"task": "chatrooms_cleansing",
"schedule": crontab(hour=0, minute=0),
},
- "lnpayments-cleansing": { # Cleans 3+ days old unlocked hodl invoices
+ "lnpayments-cleansing": { # Cleans 3+ days old unlocked hodl invoices
"task": "payments_cleansing",
"schedule": crontab(hour=0, minute=0),
},
@@ -55,10 +55,10 @@ app.conf.beat_schedule = {
"task": "cache_external_market_prices",
"schedule": timedelta(seconds=60),
},
- "compute-node-balance": { # Logs LND channel and wallet balance
- "task":"compute_node_balance",
+ "compute-node-balance": { # Logs LND channel and wallet balance
+ "task": "compute_node_balance",
"schedule": timedelta(minutes=60),
- }
+ },
}
app.conf.timezone = "UTC"
diff --git a/robosats/routing.py b/robosats/routing.py
index 36d2ca11..2aa404e4 100644
--- a/robosats/routing.py
+++ b/robosats/routing.py
@@ -2,11 +2,13 @@ from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
-application = ProtocolTypeRouter({
- "websocket":
- AuthMiddlewareStack(
- URLRouter(
- chat.routing.websocket_urlpatterns,
- # TODO add api.routing.websocket_urlpatterns when Order page works with websocket
- )),
-})
+application = ProtocolTypeRouter(
+ {
+ "websocket": AuthMiddlewareStack(
+ URLRouter(
+ chat.routing.websocket_urlpatterns,
+ # TODO add api.routing.websocket_urlpatterns when Order page works with websocket
+ )
+ ),
+ }
+)
diff --git a/robosats/settings.py b/robosats/settings.py
index d8ecf741..ed08a977 100644
--- a/robosats/settings.py
+++ b/robosats/settings.py
@@ -53,21 +53,21 @@ SESSION_COOKIE_HTTPONLY = False
# Logging settings
if os.environ.get("LOG_TO_CONSOLE"):
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'console': {
- 'class': 'logging.StreamHandler',
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
},
},
- 'root': {
- 'handlers': ['console'],
- 'level': 'WARNING',
+ "root": {
+ "handlers": ["console"],
+ "level": "WARNING",
},
- 'loggers': {
- 'api.utils': {
- 'handlers': ['console'],
- 'level': 'WARNING',
+ "loggers": {
+ "api.utils": {
+ "handlers": ["console"],
+ "level": "WARNING",
},
},
}
@@ -95,12 +95,12 @@ INSTALLED_APPS = [
]
REST_FRAMEWORK = {
- 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
+ "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
- 'TITLE': 'RoboSats REST API v0',
- 'DESCRIPTION': textwrap.dedent(
+ "TITLE": "RoboSats REST API v0",
+ "DESCRIPTION": textwrap.dedent(
"""
REST API Documentation for [RoboSats](https://learn.robosats.com) - A Simple and Private LN P2P Exchange
@@ -114,21 +114,21 @@ SPECTACULAR_SETTINGS = {
"""
),
- 'VERSION': '0.1.0',
- 'SERVE_INCLUDE_SCHEMA': False,
- 'SWAGGER_UI_DIST': 'SIDECAR', # shorthand to use the sidecar instead
- 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
- 'REDOC_UI_SETTINGS': {
- 'expandResponses': '200,201',
+ "VERSION": "0.1.0",
+ "SERVE_INCLUDE_SCHEMA": False,
+ "SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead
+ "SWAGGER_UI_FAVICON_HREF": "SIDECAR",
+ "REDOC_UI_SETTINGS": {
+ "expandResponses": "200,201",
},
- 'EXTENSIONS_INFO': {
- 'x-logo': {
- 'url': 'https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png',
- 'backgroundColor': '#FFFFFF',
- 'altText': 'RoboSats logo'
+ "EXTENSIONS_INFO": {
+ "x-logo": {
+ "url": "https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png",
+ "backgroundColor": "#FFFFFF",
+ "altText": "RoboSats logo",
}
},
- 'REDOC_DIST': 'SIDECAR',
+ "REDOC_DIST": "SIDECAR",
}
from .celery.conf import *
@@ -173,7 +173,7 @@ DATABASES = {
"NAME": config("POSTGRES_DB"),
"USER": config("POSTGRES_USER"),
"PASSWORD": config("POSTGRES_PASSWORD"),
- 'HOST': config("POSTGRES_HOST"),
+ "HOST": config("POSTGRES_HOST"),
"PORT": config("POSTGRES_PORT"),
}
}
@@ -183,20 +183,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
- "NAME":
- "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
- "NAME":
- "django.contrib.auth.password_validation.MinimumLengthValidator",
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
- "NAME":
- "django.contrib.auth.password_validation.CommonPasswordValidator",
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
- "NAME":
- "django.contrib.auth.password_validation.NumericPasswordValidator",
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
@@ -230,9 +226,7 @@ CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": config("REDIS_URL"),
- "OPTIONS": {
- "CLIENT_CLASS": "django_redis.client.DefaultClient"
- },
+ "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
}
}