mirror of
https://github.com/RoboSats/robosats.git
synced 2024-09-11 14:05:51 +03:00
Merge branch 'RoboSats:new-tor-engine' into new-tor-engine
This commit is contained in:
commit
9ab8381eaf
@ -58,6 +58,12 @@ SECRET_KEY = 'django-insecure-6^&6uw$b5^en%(cu2kc7_o)(mgpazx#j_znwlym0vxfamn2uo-
|
||||
# e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
|
||||
ONION_LOCATION = ''
|
||||
|
||||
# Geoblocked countries (will reject F2F trades).
|
||||
# List of A3 country codes (see fhttps://en.wikipedia.org/wiki/ISO_3166-1_alpha-3)
|
||||
# Leave empty '' to allow all countries.
|
||||
# Example 'NOR,USA,CZE'.
|
||||
GEOBLOCKED_COUNTRIES = 'ABW,AFG,AGO'
|
||||
|
||||
# Link to robosats alternative site (shown in frontend in statsfornerds so users can switch mainnet/testnet)
|
||||
ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion'
|
||||
ALTERNATIVE_NAME = 'RoboSats Mainnet'
|
||||
|
6
.github/workflows/integration-tests.yml
vendored
6
.github/workflows/integration-tests.yml
vendored
@ -20,9 +20,9 @@ jobs:
|
||||
strategy:
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
python-tag: ['3.11.6-slim-bookworm', '3.12.1-slim-bookworm']
|
||||
lnd-version: ['v0.17.3-beta']
|
||||
cln-version: ['v23.11.2']
|
||||
python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm']
|
||||
lnd-version: ['v0.17.4-beta']
|
||||
cln-version: ['v23.11.2','v24.02']
|
||||
ln-vendor: ['LND'] #, 'CLN']
|
||||
|
||||
steps:
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.11.8-slim-bookworm
|
||||
FROM python:3.12.3-slim-bookworm
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG DEVELOPMENT=False
|
||||
|
||||
|
11
README.md
11
README.md
@ -53,6 +53,17 @@ Alice wants to buy satoshis privately:
|
||||
## Contribute to the Robotic Satoshis Open Source Project
|
||||
Check out our [Contribution Guide](https://learn.robosats.com/contribute/) to find how you can make RoboSats great.
|
||||
|
||||
RoboSats is a monorepo, arguably a messy one at the moment.
|
||||
- The top level is a Django application (the coordinator backend) with apps `/api`, `/control`, and `/chat`. Django settings are in `/robosats` and `/tests` has integration tests for the RoboSats backend.
|
||||
- The `/frontend` directory contains the ReactJS client.
|
||||
- The `/nodeapp` directory contains the docker orchestration and utilities for the self-hosted application (Umbrel, StartOS, etc)
|
||||
- The `/mobile` directory contains our React Native app (a wrapper around our ReactJS app in `/frontend`)
|
||||
- The `/docs` directory has the learn.robosats.com static Jekyll site markdown docs.
|
||||
- The `/web` directory is a light wrapper around our client app `/frontend` intended to host a RoboSats dex client to be used for the public. We use this one in dex.robosats.com
|
||||
|
||||
You can run the whole stack for local development following the instructions in [setup.md](/setup.md)
|
||||
|
||||
Officially mantained docker orchestration for coordinators can be found in the repo [robosats-deploy](https://github.com/RoboSats/robosats-deploy)
|
||||
### ⚡Developer Rewards ⚡
|
||||
Check out the [Developer Rewards Panel](https://github.com/users/Reckless-Satoshi/projects/2/views/5) for tasks paid in Sats.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import math
|
||||
from datetime import timedelta
|
||||
|
||||
from decouple import config
|
||||
from decouple import config, Csv
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q, Sum
|
||||
from django.utils import timezone
|
||||
@ -9,7 +9,7 @@ from django.utils import timezone
|
||||
from api.lightning.node import LNNode
|
||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||
from api.tasks import send_devfund_donation, send_notification
|
||||
from api.utils import get_minning_fee, validate_onchain_address
|
||||
from api.utils import get_minning_fee, validate_onchain_address, location_country
|
||||
from chat.models import Message
|
||||
|
||||
FEE = float(config("FEE"))
|
||||
@ -29,6 +29,8 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float(
|
||||
config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED")
|
||||
)
|
||||
|
||||
GEOBLOCKED_COUNTRIES = config("GEOBLOCKED_COUNTRIES", cast=Csv(), default=[])
|
||||
|
||||
|
||||
class Logics:
|
||||
@classmethod
|
||||
@ -137,6 +139,19 @@ class Logics:
|
||||
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def validate_location(cls, order) -> bool:
|
||||
if not (order.latitude or order.longitude):
|
||||
return True, None
|
||||
|
||||
country = location_country(order.longitude, order.latitude)
|
||||
if country in GEOBLOCKED_COUNTRIES:
|
||||
return False, {
|
||||
"bad_request": f"The coordinator does not support orders in {country}"
|
||||
}
|
||||
else:
|
||||
return True, None
|
||||
|
||||
def validate_amount_within_range(order, amount):
|
||||
if amount > float(order.max_amount) or amount < float(order.min_amount):
|
||||
return False, {
|
||||
|
@ -139,7 +139,7 @@ class Telegram:
|
||||
text = f"⚖️ Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}."
|
||||
self.send_message(user.robot.telegram_chat_id, text)
|
||||
|
||||
admin_chat_id = config("TELEGRAM_ADMIN_CHAT_ID")
|
||||
admin_chat_id = config("TELEGRAM_COORDINATOR_CHAT_ID")
|
||||
|
||||
if len(admin_chat_id) == 0:
|
||||
return
|
||||
|
@ -219,14 +219,17 @@ class OrderViewSchema:
|
||||
- `update_invoice`
|
||||
- This action only is valid if you are the buyer. The `invoice`
|
||||
field needs to be present in the body and the value must be a
|
||||
valid LN invoice as cleartext PGP message signed with the robot key. Make sure to perform this action only when
|
||||
valid LN invoice as cleartext PGP message signed (SHA512) with the robot key.
|
||||
The amount of the invoice should be `invoice_amount` minus the routing
|
||||
budget whose parts per million should be specified by `routing_budget_ppm`.
|
||||
Make sure to perform this action only when
|
||||
both the bonds are locked. i.e The status of your order is
|
||||
at least `6` (Waiting for trade collateral and buyer invoice)
|
||||
- `update_address`
|
||||
- This action is only valid if you are the buyer. This action is
|
||||
used to set an on-chain payout address if you wish to have your
|
||||
payout be received on-chain. Only valid if there is an address in the body as
|
||||
cleartext PGP message signed with the robot key. This enables on-chain swap for the
|
||||
cleartext PGP message signed (SHA512) with the robot key. This enables on-chain swap for the
|
||||
order, so even if you earlier had submitted a LN invoice, it
|
||||
will be ignored. You get to choose the `mining_fee_rate` as
|
||||
well. Mining fee rate is specified in sats/vbyte.
|
||||
@ -246,9 +249,7 @@ class OrderViewSchema:
|
||||
mid-trade so use this action carefully:
|
||||
|
||||
- As a maker if you cancel an order after you have locked your
|
||||
maker bond, you are returned your bond. This may change in
|
||||
the future to prevent DDoSing the LN node and you won't be
|
||||
returned the maker bond.
|
||||
maker bond, you are returned your bond.
|
||||
- As a taker there is a time penalty involved if you `take` an
|
||||
order and cancel it without locking the taker bond.
|
||||
- For both taker or maker, if you cancel the order when both
|
||||
@ -387,12 +388,13 @@ class RobotViewSchema:
|
||||
An authenticated request (has the token's sha256 hash encoded as base 91 in the Authorization header) will be
|
||||
returned the information about the state of a robot.
|
||||
|
||||
Make sure you generate your token using cryptographically secure methods. [Here's]() the function the Javascript
|
||||
client uses to generate the tokens. Since the server only receives the hash of the
|
||||
Make sure you generate your token using cryptographically secure methods.
|
||||
Since the server only receives the hash of the
|
||||
token, it is responsibility of the client to create a strong token. Check
|
||||
[here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.js)
|
||||
[here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.ts)
|
||||
to see how the Javascript client creates a random strong token and how it validates entropy is optimal for tokens
|
||||
created by the user at will.
|
||||
The PGP key should be an EdDSA ed25519/cert,sign+cv25519/encr key.
|
||||
|
||||
`public_key` - PGP key associated with the user (Armored ASCII format)
|
||||
`encrypted_private_key` - Private PGP key. This is only stored on the backend for later fetching by
|
||||
@ -403,7 +405,7 @@ class RobotViewSchema:
|
||||
A gpg key can be created by:
|
||||
|
||||
```shell
|
||||
gpg --full-gen-key
|
||||
gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" --full-gen-key
|
||||
```
|
||||
|
||||
it's public key can be exported in ascii armored format with:
|
||||
@ -531,7 +533,7 @@ class InfoViewSchema:
|
||||
class RewardViewSchema:
|
||||
post = {
|
||||
"summary": "Withdraw reward",
|
||||
"description": "Withdraw user reward by submitting an invoice. The invoice must be send as cleartext PGP message signed with the robot key",
|
||||
"description": "Withdraw user reward by submitting an invoice. The invoice must be send as cleartext PGP message signed (SHA512) with the robot key",
|
||||
"responses": {
|
||||
200: {
|
||||
"type": "object",
|
||||
|
@ -1,4 +1,5 @@
|
||||
from decouple import config
|
||||
from decimal import Decimal
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import MarketTick, Order
|
||||
@ -583,7 +584,7 @@ class UpdateOrderSerializer(serializers.Serializer):
|
||||
)
|
||||
routing_budget_ppm = serializers.IntegerField(
|
||||
default=0,
|
||||
min_value=0,
|
||||
min_value=Decimal(0),
|
||||
max_value=100_001,
|
||||
allow_null=True,
|
||||
required=False,
|
||||
|
27
api/utils.py
27
api/utils.py
@ -479,6 +479,33 @@ def is_valid_token(token: str) -> bool:
|
||||
return all(c in charset for c in token)
|
||||
|
||||
|
||||
def location_country(lon: float, lat: float) -> str:
|
||||
"""
|
||||
Returns the country code of a lon/lat location
|
||||
"""
|
||||
|
||||
from shapely.geometry import shape, Point
|
||||
from shapely.prepared import prep
|
||||
|
||||
# Load the GeoJSON data from a local file
|
||||
with open("frontend/static/assets/geo/countries-coastline-10km.geo.json") as f:
|
||||
countries_geojeson = json.load(f)
|
||||
|
||||
# Prepare the countries for reverse geocoding
|
||||
countries = {}
|
||||
for feature in countries_geojeson["features"]:
|
||||
geom = feature["geometry"]
|
||||
country_code = feature["properties"]["A3"]
|
||||
countries[country_code] = prep(shape(geom))
|
||||
|
||||
point = Point(lon, lat)
|
||||
for country_code, geom in countries.items():
|
||||
if geom.contains(point):
|
||||
return country_code
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def objects_to_hyperlinks(logs: str) -> str:
|
||||
"""
|
||||
Parses strings that have Object(ID,NAME) that match API models.
|
||||
|
@ -166,6 +166,10 @@ class MakerView(CreateAPIView):
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
valid, context = Logics.validate_location(order)
|
||||
if not valid:
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
order.save()
|
||||
order.log(
|
||||
f"Order({order.id},{order}) created by Robot({request.user.robot.id},{request.user})"
|
||||
|
@ -1,5 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from decimal import Decimal
|
||||
from chat.models import Message
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ class ChatSerializer(serializers.ModelSerializer):
|
||||
allow_null=True,
|
||||
default=None,
|
||||
required=False,
|
||||
min_value=0,
|
||||
min_value=Decimal(0),
|
||||
help_text="Offset for message index to get as response",
|
||||
)
|
||||
|
||||
@ -66,7 +66,7 @@ class PostMessageSerializer(serializers.ModelSerializer):
|
||||
|
||||
order_id = serializers.IntegerField(
|
||||
required=True,
|
||||
min_value=0,
|
||||
min_value=Decimal(0),
|
||||
help_text="Your peer's public key",
|
||||
)
|
||||
|
||||
@ -74,7 +74,7 @@ class PostMessageSerializer(serializers.ModelSerializer):
|
||||
allow_null=True,
|
||||
default=None,
|
||||
required=False,
|
||||
min_value=0,
|
||||
min_value=Decimal(0),
|
||||
help_text="Offset for message index to get as response",
|
||||
)
|
||||
|
||||
|
@ -62,6 +62,14 @@ In Canada, [Interac e-Transfer](https://www.interac.ca/en/consumers/support/faq-
|
||||
|
||||
The best practice for users trying to transact with a payment method with a high risk of losing funds is discussed in this section.
|
||||
|
||||
### Instant SEPA Payment Guidelines
|
||||
|
||||
Instant SEPA is a widely adopted payment method across Europe, offering fast and efficient cashless transactions. However, it comes with a significant risk for sellers, including the potential for chargebacks. To mitigate these risks, it is advisable for sellers to request the buyer's information before sharing their SEPA details. This information could include the buyer's country, full name, and bank account number. By obtaining this information, sellers can reduce the risk of fraudulent transactions, such as triangle attacks, while buyers, sharing this information does not decrease their privacy, as they are not exposing any additional information that the seller would not have access to anyway after the SEPA transfer.
|
||||
|
||||
For buyers, it is crucial to comply with sellers' if they request personal information when they are initiating SEPA transactions. Failure to provide this information can lead to the seller raising an immediate dispute, which sellers are likely to win (the seller will also earn the buyer's bond in this specific case). Therefore, it is in the best interest of buyers to cooperate with sellers' requests for information.
|
||||
|
||||
Sellers are encouraged to share a link to this guide with their buyers when requesting information. This ensures that both parties are informed and understand the importance of this step when using Instant SEPA.
|
||||
|
||||
### Revolut via payment links
|
||||
|
||||
In a Revolut payment, a `@revtag` is usually exchanged in the chat and can be verified in the payment history of the app making proof of payments easy.
|
||||
@ -70,12 +78,14 @@ However, payment links, which have the format https://revolut.me/p/XXXXX, don't
|
||||
|
||||
In a dispute, there's no recipient address reference and both buyer and seller could cheat. The payment link could be redeemed by an unknown third party complicit with either buyer or seller.
|
||||
|
||||
Therefore, insist on receiving the `@revtag` when making a payment with Revolut to avoid these risks. The `@revtag` can also be received as a link. This link would look like this: https://revolut.me/@revtag.
|
||||
Therefore, insist on receiving the `@revtag` when making a payment with Revolut to avoid these risks. The `@revtag` can also be received as a link. This link would look like this: https://revolut.me/@revtag.
|
||||
|
||||
### Paypal
|
||||
Paypal is one of the widely used fiat payment methods. However, with <a href="https://www.paypal.com/us/webapps/mpp/ua/buyer-protection">PayPal buyer protection policy</a>, buyer can do fraudulent action by creating a refund request in PayPal after the trading process in RoboSats is finished and therefore taking both fiat and bitcoin all by themselves.
|
||||
Paypal is one of the widely used fiat payment methods. However, as a seller Paypal is the highest risk you can take. Using Paypal as payment method is not advised.
|
||||
|
||||
This fraud can be prevented by agreeing with the buyer to have them send money using the “send money to a friend or family member” option. This will make the buyer become the one liable for the transaction fee and make it less likely for them to request a refund.
|
||||
If you still wish to use Paypal there is a few things to take into account. With <a href="https://www.paypal.com/us/webapps/mpp/ua/buyer-protection">PayPal buyer protection policy</a>, buyers can do fraudulent action by creating a refund request in PayPal after the trading process in RoboSats is finished and therefore taking both fiat and bitcoin all by themselves.
|
||||
|
||||
This fraud could be prevented by agreeing with the buyer to have them send money using the “send money to a friend or family member” option. This will make the buyer become the one liable for the transaction fee and make it less likely for them to request a refund.
|
||||
|
||||
### For seller
|
||||
If you are a seller and your peer both agreed to use “send money to a friend or family member” but your peer used the "send money for Goods or Services" option, you should return the fiat payment and ask your peer to send with an agreed method. If they insist to break the agreement, you may ask them to voluntarily end the trade or end the trade by calling a dispute.
|
||||
@ -83,26 +93,4 @@ If you are a seller and your peer both agreed to use “send money to a friend o
|
||||
### For buyer
|
||||
If you are a buyer and you need to use “send money to a friend or family member” to pay fiat to your peer, you can choose the specified payment type by following these steps.
|
||||
|
||||
#### PayPal Desktop
|
||||
In PayPal desktop, it is located below the drop-down currency list, it should be labeled as "Sending to a friend".
|
||||
If it is labeled otherwise, you'll need to click "Change" on the right to change the payment type.
|
||||
<div align="center">
|
||||
<img src="/assets/images/fiat-payment-methods/PayPal-main-desktop.png" width="370"/>
|
||||
</div>
|
||||
Then select "Sending to a friend" in the payment type choosing page.
|
||||
<div align="center">
|
||||
<img src="/assets/images/fiat-payment-methods/PayPal-choose-desktop.png" width="370"/>
|
||||
</div>
|
||||
|
||||
#### PayPal Mobile
|
||||
In PayPal mobile, it is located below the payment method (In this case is VISA), it should be labeled as "Friends or Family".
|
||||
If it is labeled otherwise, you'll need to tab ">" on the right to change the payment type.
|
||||
<div align="center">
|
||||
<img src="/assets/images/fiat-payment-methods/PayPal-main-phone.png" width="230"/>
|
||||
</div>
|
||||
Then select "Friends or Family" in the payment type choosing page.
|
||||
<div align="center">
|
||||
<img src="/assets/images/fiat-payment-methods/PayPal-choose-phone.png" width="230"/>
|
||||
</div>
|
||||
|
||||
{% include improve %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: RoboSats REST API
|
||||
version: 0.5.4
|
||||
version: 0.6.0
|
||||
x-logo:
|
||||
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
|
||||
backgroundColor: '#FFFFFF'
|
||||
@ -1077,6 +1077,7 @@ components:
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StatusEnum'
|
||||
default: 0
|
||||
minimum: 0
|
||||
maximum: 32767
|
||||
created_at:
|
||||
@ -1100,6 +1101,7 @@ components:
|
||||
nullable: true
|
||||
has_range:
|
||||
type: boolean
|
||||
default: false
|
||||
min_amount:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1112,14 +1114,17 @@ components:
|
||||
nullable: true
|
||||
payment_method:
|
||||
type: string
|
||||
default: not specified
|
||||
maxLength: 70
|
||||
is_explicit:
|
||||
type: boolean
|
||||
default: false
|
||||
premium:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
|
||||
nullable: true
|
||||
default: '0.00'
|
||||
satoshis:
|
||||
type: integer
|
||||
maximum: 5000000
|
||||
@ -1135,10 +1140,12 @@ components:
|
||||
type: integer
|
||||
maximum: 28800
|
||||
minimum: 1800
|
||||
default: 10799
|
||||
bond_size:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
|
||||
default: '3.00'
|
||||
latitude:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1205,6 +1212,7 @@ components:
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
|
||||
nullable: true
|
||||
default: '0.00'
|
||||
satoshis:
|
||||
type: integer
|
||||
maximum: 5000000
|
||||
@ -1214,14 +1222,17 @@ components:
|
||||
type: integer
|
||||
maximum: 86400
|
||||
minimum: 597.6
|
||||
default: 86399
|
||||
escrow_duration:
|
||||
type: integer
|
||||
maximum: 28800
|
||||
minimum: 1800
|
||||
default: 10799
|
||||
bond_size:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
|
||||
default: '3.00'
|
||||
latitude:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1261,6 +1272,7 @@ components:
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StatusEnum'
|
||||
default: 0
|
||||
minimum: 0
|
||||
maximum: 32767
|
||||
created_at:
|
||||
@ -1284,6 +1296,7 @@ components:
|
||||
nullable: true
|
||||
has_range:
|
||||
type: boolean
|
||||
default: false
|
||||
min_amount:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1296,9 +1309,11 @@ components:
|
||||
nullable: true
|
||||
payment_method:
|
||||
type: string
|
||||
default: not specified
|
||||
maxLength: 70
|
||||
is_explicit:
|
||||
type: boolean
|
||||
default: false
|
||||
premium:
|
||||
type: string
|
||||
description: Premium over the CEX price set by the maker
|
||||
@ -1324,6 +1339,7 @@ components:
|
||||
type: integer
|
||||
maximum: 28800
|
||||
minimum: 1800
|
||||
default: 10799
|
||||
total_secs_exp:
|
||||
type: integer
|
||||
description: Duration of time (in seconds) to expire, according to the current
|
||||
@ -1495,10 +1511,12 @@ components:
|
||||
type: integer
|
||||
maximum: 86400
|
||||
minimum: 597.6
|
||||
default: 86399
|
||||
bond_size:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
|
||||
default: '3.00'
|
||||
trade_fee_percent:
|
||||
type: integer
|
||||
description: The fee for the trade (fees differ for maker and taker)
|
||||
@ -1580,6 +1598,7 @@ components:
|
||||
nullable: true
|
||||
has_range:
|
||||
type: boolean
|
||||
default: false
|
||||
min_amount:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1592,14 +1611,17 @@ components:
|
||||
nullable: true
|
||||
payment_method:
|
||||
type: string
|
||||
default: not specified
|
||||
maxLength: 70
|
||||
is_explicit:
|
||||
type: boolean
|
||||
default: false
|
||||
premium:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
|
||||
nullable: true
|
||||
default: '0.00'
|
||||
satoshis:
|
||||
type: integer
|
||||
maximum: 5000000
|
||||
@ -1623,6 +1645,7 @@ components:
|
||||
type: integer
|
||||
maximum: 28800
|
||||
minimum: 1800
|
||||
default: 10799
|
||||
satoshis_now:
|
||||
type: integer
|
||||
description: The amount of sats to be traded at the present moment (not
|
||||
@ -1631,6 +1654,7 @@ components:
|
||||
type: string
|
||||
format: decimal
|
||||
pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
|
||||
default: '3.00'
|
||||
latitude:
|
||||
type: string
|
||||
format: decimal
|
||||
@ -1867,6 +1891,7 @@ components:
|
||||
fee:
|
||||
type: string
|
||||
format: decimal
|
||||
default: '0.0000'
|
||||
TypeEnum:
|
||||
enum:
|
||||
- 0
|
||||
|
174
frontend/package-lock.json
generated
174
frontend/package-lock.json
generated
@ -18,16 +18,16 @@
|
||||
"@mui/lab": "^5.0.0-alpha.136",
|
||||
"@mui/material": "^5.15.9",
|
||||
"@mui/system": "^5.15.11",
|
||||
"@mui/x-data-grid": "^6.19.2",
|
||||
"@mui/x-date-pickers": "^6.19.2",
|
||||
"@mui/x-data-grid": "^7.3.0",
|
||||
"@mui/x-date-pickers": "^7.2.0",
|
||||
"@nivo/core": "^0.85.1",
|
||||
"@nivo/line": "^0.85.1",
|
||||
"base-ex": "^0.8.1",
|
||||
"country-flag-icons": "^1.5.9",
|
||||
"country-flag-icons": "^1.5.11",
|
||||
"date-fns": "^2.30.0",
|
||||
"file-replace-loader": "^1.4.0",
|
||||
"i18next": "^23.2.11",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"i18next-http-backend": "^2.5.0",
|
||||
"install": "^0.13.0",
|
||||
"js-sha256": "^0.11.0",
|
||||
@ -1880,9 +1880,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||
"version": "7.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
|
||||
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -2215,12 +2215,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz",
|
||||
"integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==",
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
|
||||
"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.1"
|
||||
"@floating-ui/core": "^1.0.0",
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
@ -3106,9 +3106,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "5.15.9",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.9.tgz",
|
||||
"integrity": "sha512-CSDpVevGaxsvMkiYBZ8ztki1z/eT0mM2MqUT21eCRiMz3DU4zQw5rXG5ML/yTuJF9Z2Wv9SliIeaRAuSR/9Nig==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz",
|
||||
"integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
@ -3181,16 +3181,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.15.9",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.9.tgz",
|
||||
"integrity": "sha512-kbHTZDcFmN8GHKzRpImUEl9AJfFWI/0Kl+DsYVT3kHzQWUuHiKm3uHXR1RCOqr7H8IgHFPdbxItmCSQ/mj7zgg==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz",
|
||||
"integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/base": "5.0.0-beta.36",
|
||||
"@mui/core-downloads-tracker": "^5.15.9",
|
||||
"@mui/system": "^5.15.9",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.9",
|
||||
"@mui/base": "5.0.0-beta.40",
|
||||
"@mui/core-downloads-tracker": "^5.15.15",
|
||||
"@mui/system": "^5.15.15",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
@ -3225,14 +3225,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/base": {
|
||||
"version": "5.0.0-beta.36",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz",
|
||||
"integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==",
|
||||
"version": "5.0.0-beta.40",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@floating-ui/react-dom": "^2.0.8",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.9",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
@ -3264,12 +3264,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "5.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz",
|
||||
"integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz",
|
||||
"integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/utils": "^5.15.11",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -3290,9 +3290,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "5.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz",
|
||||
"integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
|
||||
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
@ -3321,15 +3321,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "5.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.11.tgz",
|
||||
"integrity": "sha512-9j35suLFq+MgJo5ktVSHPbkjDLRMBCV17NMBdEQurh6oWyGnLM4uhU4QGZZQ75o0vuhjJghOCA1jkO3+79wKsA==",
|
||||
"version": "5.15.15",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
|
||||
"integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/private-theming": "^5.15.11",
|
||||
"@mui/styled-engine": "^5.15.11",
|
||||
"@mui/types": "^7.2.13",
|
||||
"@mui/utils": "^5.15.11",
|
||||
"@mui/private-theming": "^5.15.14",
|
||||
"@mui/styled-engine": "^5.15.14",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
@ -3381,9 +3381,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "5.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz",
|
||||
"integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==",
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz",
|
||||
"integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@types/prop-types": "^15.7.11",
|
||||
@ -3408,13 +3408,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "6.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.19.2.tgz",
|
||||
"integrity": "sha512-+wizP1jEzCKB5BSQ6OD5TP6RspEbWmFWcxi1XBgKrzryUZii1o4G2BW1+d/n4p3xETCUMKRkYfItrOJGlM/dBw==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.3.0.tgz",
|
||||
"integrity": "sha512-IIDS6Yvxe+1eRj65q8cgnJg5yF2aIJYuHrY00W/UaFyjxwj3xSzqg3bdEfbjE2gHGS7lEJJbXSenPNGybzW99A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"@mui/utils": "^5.14.16",
|
||||
"clsx": "^2.0.0",
|
||||
"@babel/runtime": "^7.24.0",
|
||||
"@mui/system": "^5.15.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.8"
|
||||
},
|
||||
@ -3423,33 +3424,33 @@
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.4.1",
|
||||
"@mui/system": "^5.4.1",
|
||||
"@mui/material": "^5.15.14",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid/node_modules/clsx": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers": {
|
||||
"version": "6.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.19.2.tgz",
|
||||
"integrity": "sha512-/bdWZabexuz+1rKG15XryxiMGb5D0XVx65NU7CZYKm/1+HuUzc0FX9smKEa/YVZnLSNsAp6SULIyPZtAKE+3AA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.2.0.tgz",
|
||||
"integrity": "sha512-hsXugZ+n1ZnHRYzf7+PFrjZ44T+FyGZmTreBmH0M2RUaAblgK+A1V3KNLT+r4Y9gJLH+92LwePxQ9xyfR+E51A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"@mui/base": "^5.0.0-beta.20",
|
||||
"@mui/utils": "^5.14.14",
|
||||
"@types/react-transition-group": "^4.4.8",
|
||||
"clsx": "^2.0.0",
|
||||
"@babel/runtime": "^7.24.0",
|
||||
"@mui/base": "^5.0.0-beta.40",
|
||||
"@mui/system": "^5.15.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
@ -3458,14 +3459,13 @@
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.8.6",
|
||||
"@mui/system": "^5.8.0",
|
||||
"date-fns": "^2.25.0",
|
||||
"@mui/material": "^5.15.14",
|
||||
"date-fns": "^2.25.0 || ^3.2.0",
|
||||
"date-fns-jalali": "^2.13.0-0",
|
||||
"dayjs": "^1.10.7",
|
||||
"luxon": "^3.0.2",
|
||||
@ -3506,16 +3506,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers/node_modules/@mui/base": {
|
||||
"version": "5.0.0-beta.21",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.21.tgz",
|
||||
"integrity": "sha512-eTKWx3WV/nwmRUK4z4K1MzlMyWCsi3WJ3RtV4DiXZeRh4qd4JCyp1Zzzi8Wv9xM4dEBmqQntFoei716PzwmFfA==",
|
||||
"version": "5.0.0-beta.40",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"@floating-ui/react-dom": "^2.0.2",
|
||||
"@mui/types": "^7.2.7",
|
||||
"@mui/utils": "^5.14.15",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@floating-ui/react-dom": "^2.0.8",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.0.0",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -3523,7 +3523,7 @@
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
@ -3537,9 +3537,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers/node_modules/clsx": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@ -5840,9 +5840,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/country-flag-icons": {
|
||||
"version": "1.5.9",
|
||||
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.9.tgz",
|
||||
"integrity": "sha512-9jrjv2w7kRbqNtdtMdK2j3gmDIZzd5l9L2pZiQjF9J0mUcB+NKIGDNADTDHBEp8EQtjOkCOcciJGGSOpERdXPQ=="
|
||||
"version": "1.5.11",
|
||||
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.11.tgz",
|
||||
"integrity": "sha512-B+mvFywunkRJs270k7kCBjhogvIA0uNn6GAXv6m2cPn3rrwqZzZVr2gBWcz+Cz7OGVWlcbERlYRIX0S6OGr8Bw=="
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
@ -7873,9 +7873,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz",
|
||||
"integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==",
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz",
|
||||
"integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
|
@ -55,18 +55,18 @@
|
||||
"@mui/base": "^5.0.0-beta.7",
|
||||
"@mui/icons-material": "^5.15.9",
|
||||
"@mui/lab": "^5.0.0-alpha.136",
|
||||
"@mui/material": "^5.15.9",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@mui/system": "^5.15.11",
|
||||
"@mui/x-data-grid": "^6.19.2",
|
||||
"@mui/x-date-pickers": "^6.19.2",
|
||||
"@mui/x-data-grid": "^7.3.0",
|
||||
"@mui/x-date-pickers": "^7.2.0",
|
||||
"@nivo/core": "^0.85.1",
|
||||
"@nivo/line": "^0.85.1",
|
||||
"base-ex": "^0.8.1",
|
||||
"country-flag-icons": "^1.5.9",
|
||||
"country-flag-icons": "^1.5.11",
|
||||
"date-fns": "^2.30.0",
|
||||
"file-replace-loader": "^1.4.0",
|
||||
"i18next": "^23.2.11",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"i18next-http-backend": "^2.5.0",
|
||||
"install": "^0.13.0",
|
||||
"js-sha256": "^0.11.0",
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
type GridPaginationModel,
|
||||
type GridColDef,
|
||||
type GridValidRowModel,
|
||||
GridSlotsComponent,
|
||||
} from '@mui/x-data-grid';
|
||||
import currencyDict from '../../../static/assets/currencies.json';
|
||||
import { type PublicOrder } from '../../models';
|
||||
@ -38,31 +39,13 @@ import RobotAvatar from '../RobotAvatar';
|
||||
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
|
||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||
import headerStyleFix from '../DataGrid/HeaderFix';
|
||||
|
||||
const ClickThroughDataGrid = styled(DataGrid)({
|
||||
'& .MuiDataGrid-overlayWrapperInner': {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
// Temporary fix for regression for hidden column labels on Mobile:
|
||||
// https://github.com/mui/mui-x/issues/9776#issuecomment-1648306844
|
||||
'@media (hover: none)': {
|
||||
'&& .MuiDataGrid-menuIcon': {
|
||||
width: 0,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
'&& .MuiDataGrid-sortIcon': {
|
||||
width: 0,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
},
|
||||
'&& .MuiDataGrid-columnHeader--sorted .MuiDataGrid-menuIcon': {
|
||||
width: 'auto',
|
||||
visibility: 'visible',
|
||||
},
|
||||
'&& .MuiDataGrid-columnHeader--sorted .MuiDataGrid-sortIcon': {
|
||||
width: 'auto',
|
||||
visibility: 'visible',
|
||||
},
|
||||
...{ headerStyleFix },
|
||||
});
|
||||
|
||||
const premiumColor = function (baseColor: string, accentColor: string, point: number): string {
|
||||
@ -204,12 +187,12 @@ const BookTable = ({
|
||||
renderCell: (params: any) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<ListItemAvatar sx={{ position: 'relative', left: '-1.3em', bottom: '0.6em' }}>
|
||||
<RobotAvatar
|
||||
hashId={params.row.maker_hash_id}
|
||||
style={{ width: '3.215em', height: '3.215em' }}
|
||||
@ -221,7 +204,10 @@ const BookTable = ({
|
||||
small={true}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={params.row.maker_nick} />
|
||||
<ListItemText
|
||||
primary={params.row.maker_nick}
|
||||
sx={{ position: 'relative', left: '-1.3em', bottom: '0.6em' }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
);
|
||||
},
|
||||
@ -236,7 +222,7 @@ const BookTable = ({
|
||||
renderCell: (params: any) => {
|
||||
return (
|
||||
<div
|
||||
style={{ position: 'relative', left: '-0.34em', cursor: 'pointer' }}
|
||||
style={{ position: 'relative', left: '-0.34em', cursor: 'pointer', bottom: '0.2em' }}
|
||||
onClick={() => {
|
||||
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||
}}
|
||||
@ -270,12 +256,12 @@ const BookTable = ({
|
||||
renderCell: (params: any) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
style={{ cursor: 'pointer', position: 'relative', left: '-1.54em' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
onClickCoordinator(params.row.coordinatorShortAlias);
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<ListItemAvatar sx={{ position: 'relative', left: '-1.54em', bottom: '0.4em' }}>
|
||||
<RobotAvatar
|
||||
shortAlias={params.row.coordinatorShortAlias}
|
||||
style={{ width: '3.215em', height: '3.215em' }}
|
||||
@ -364,8 +350,9 @@ const BookTable = ({
|
||||
}}
|
||||
>
|
||||
{currencyCode}
|
||||
<div style={{ width: '0.3em' }} />
|
||||
<FlagWithProps code={currencyCode} />
|
||||
<div style={{ position: 'relative', left: '0.3em', bottom: '0.7em' }}>
|
||||
<FlagWithProps code={currencyCode} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -386,12 +373,14 @@ const BookTable = ({
|
||||
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||
}}
|
||||
>
|
||||
<PaymentStringAsIcons
|
||||
othersText={t('Others')}
|
||||
verbose={true}
|
||||
size={1.7 * fontSize}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
<div style={{ position: 'relative', top: '0.4em' }}>
|
||||
<PaymentStringAsIcons
|
||||
othersText={t('Others')}
|
||||
verbose={true}
|
||||
size={1.7 * fontSize}
|
||||
text={params.row.payment_method}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -410,7 +399,8 @@ const BookTable = ({
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
left: '-4px',
|
||||
left: '-0.25em',
|
||||
top: '0.3em',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
@ -545,7 +535,7 @@ const BookTable = ({
|
||||
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
||||
return (
|
||||
<Box
|
||||
sx={{ position: 'relative', display: 'inline-flex', left: '0.3em' }}
|
||||
sx={{ position: 'relative', display: 'inline-flex', left: '0.3em', top: '0.5em' }}
|
||||
onClick={() => {
|
||||
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||
}}
|
||||
@ -880,19 +870,19 @@ const BookTable = ({
|
||||
};
|
||||
|
||||
const gridComponents = useMemo(() => {
|
||||
const components: GridComponentProps = {
|
||||
LoadingOverlay: LinearProgress,
|
||||
const components: GridSlotsComponent = {
|
||||
loadingOverlay: LinearProgress,
|
||||
};
|
||||
|
||||
if (showNoResults) {
|
||||
components.NoResultsOverlay = NoResultsOverlay;
|
||||
components.NoRowsOverlay = NoResultsOverlay;
|
||||
components.noResultsOverlay = NoResultsOverlay;
|
||||
components.noRowsOverlay = NoResultsOverlay;
|
||||
}
|
||||
if (showFooter) {
|
||||
components.Footer = Footer;
|
||||
components.footer = Footer;
|
||||
}
|
||||
if (showControls) {
|
||||
components.Toolbar = BookControl;
|
||||
components.toolbar = BookControl;
|
||||
}
|
||||
return components;
|
||||
}, [showNoResults, showFooter, showControls, fullscreen]);
|
||||
@ -935,8 +925,8 @@ const BookTable = ({
|
||||
setColumnVisibilityModel(newColumnVisibilityModel);
|
||||
}}
|
||||
hideFooter={!showFooter}
|
||||
components={gridComponents}
|
||||
componentsProps={{
|
||||
slots={gridComponents}
|
||||
slotProps={{
|
||||
toolbar: {
|
||||
width,
|
||||
paymentMethod: paymentMethods,
|
||||
@ -967,12 +957,12 @@ const BookTable = ({
|
||||
loading={federation.loading}
|
||||
columns={columns}
|
||||
hideFooter={!showFooter}
|
||||
components={gridComponents}
|
||||
slots={gridComponents}
|
||||
columnVisibilityModel={columnVisibilityModel}
|
||||
onColumnVisibilityModelChange={(newColumnVisibilityModel) => {
|
||||
setColumnVisibilityModel(newColumnVisibilityModel);
|
||||
}}
|
||||
componentsProps={{
|
||||
slotProps={{
|
||||
toolbar: {
|
||||
width,
|
||||
paymentMethod: paymentMethods,
|
||||
|
24
frontend/src/components/DataGrid/HeaderFix.ts
Normal file
24
frontend/src/components/DataGrid/HeaderFix.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// Temporary fix for regression for hidden column labels on Mobile:
|
||||
// https://github.com/mui/mui-x/issues/9776#issuecomment-1648306844
|
||||
const headerStyleFix = {
|
||||
'@media (hover: none)': {
|
||||
'&& .MuiDataGrid-menuIcon': {
|
||||
width: 0,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
'&& .MuiDataGrid-sortIcon': {
|
||||
width: 0,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
},
|
||||
'&& .MuiDataGrid-columnHeader--sorted .MuiDataGrid-menuIcon': {
|
||||
width: 'auto',
|
||||
visibility: 'visible',
|
||||
},
|
||||
'&& .MuiDataGrid-columnHeader--sorted .MuiDataGrid-sortIcon': {
|
||||
width: 'auto',
|
||||
visibility: 'visible',
|
||||
},
|
||||
};
|
||||
|
||||
export default headerStyleFix;
|
@ -7,6 +7,7 @@ import RobotAvatar from '../RobotAvatar';
|
||||
import { Link, LinkOff } from '@mui/icons-material';
|
||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||
import headerStyleFix from '../DataGrid/HeaderFix';
|
||||
|
||||
interface FederationTableProps {
|
||||
maxWidth?: number;
|
||||
@ -225,6 +226,7 @@ const FederationTable = ({
|
||||
}
|
||||
>
|
||||
<DataGrid
|
||||
sx={headerStyleFix}
|
||||
localeText={localeText}
|
||||
rowHeight={3.714 * theme.typography.fontSize}
|
||||
headerHeight={3.25 * theme.typography.fontSize}
|
||||
|
@ -1028,7 +1028,7 @@ const MakerForm = ({
|
||||
views={['hours', 'minutes']}
|
||||
inputFormat='HH:mm'
|
||||
mask='__:__'
|
||||
componentsProps={{
|
||||
slotProps={{
|
||||
textField: {
|
||||
InputProps: {
|
||||
style: {
|
||||
@ -1061,7 +1061,7 @@ const MakerForm = ({
|
||||
views={['hours', 'minutes']}
|
||||
inputFormat='HH:mm'
|
||||
mask='__:__'
|
||||
componentsProps={{
|
||||
slotProps={{
|
||||
textField: {
|
||||
InputProps: {
|
||||
style: {
|
||||
|
@ -20,6 +20,9 @@ import { createTheme, type Theme } from '@mui/material/styles';
|
||||
import i18n from '../i18n/Web';
|
||||
import getWorldmapGeojson from '../geo/Web';
|
||||
import { apiClient } from '../services/api';
|
||||
import SettingsSelfhosted from '../models/Settings.default.basic.selfhosted';
|
||||
import SettingsSelfhostedPro from '../models/Settings.default.pro.selfhosted';
|
||||
import SettingsPro from '../models/Settings.default.pro';
|
||||
|
||||
const getWindowSize = function (fontSize: number): { width: number; height: number } {
|
||||
// returns window size in EM units
|
||||
@ -101,6 +104,19 @@ const getOrigin = (network = 'mainnet'): Origin => {
|
||||
return origin;
|
||||
};
|
||||
|
||||
const getSettings = (): Settings => {
|
||||
let settings = new Settings();
|
||||
if (window.RobosatsSettings === 'selfhosted-basic') {
|
||||
settings = new SettingsSelfhosted();
|
||||
} else if (window.RobosatsSettings === 'selfhosted-pro') {
|
||||
settings = new SettingsSelfhostedPro();
|
||||
} else if (window.RobosatsSettings === 'web-pro') {
|
||||
settings = new SettingsPro();
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
export interface WindowSize {
|
||||
width: number;
|
||||
height: number;
|
||||
@ -175,7 +191,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
||||
const hostUrl = initialAppContext.hostUrl;
|
||||
const origin = initialAppContext.origin;
|
||||
|
||||
const [settings, setSettings] = useState<Settings>(initialAppContext.settings);
|
||||
const [settings, setSettings] = useState<Settings>(getSettings());
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
return makeTheme(settings);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { systemClient } from '../services/System';
|
||||
import BaseSettings from './Settings.model';
|
||||
|
||||
class Settings extends BaseSettings {
|
||||
class SettingsSelfhosted extends BaseSettings {
|
||||
constructor() {
|
||||
super();
|
||||
const fontSizeCookie = systemClient.getItem('settings_fontsize_basic');
|
||||
@ -12,4 +12,4 @@ class Settings extends BaseSettings {
|
||||
public selfhostedClient: boolean = true;
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
export default SettingsSelfhosted;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { systemClient } from '../services/System';
|
||||
import BaseSettings from './Settings.model';
|
||||
|
||||
class Settings extends BaseSettings {
|
||||
class SettingsSelfhostedPro extends BaseSettings {
|
||||
constructor() {
|
||||
super();
|
||||
const fontSizeCookie = systemClient.getItem('settings_fontsize_pro');
|
||||
@ -12,4 +12,4 @@ class Settings extends BaseSettings {
|
||||
public selfhostedClient: boolean = true;
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
export default SettingsSelfhostedPro;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { systemClient } from '../services/System';
|
||||
import BaseSettings from './Settings.model';
|
||||
|
||||
class Settings extends BaseSettings {
|
||||
class SettingsPro extends BaseSettings {
|
||||
constructor() {
|
||||
super();
|
||||
const fontSizeCookie = systemClient.getItem('settings_fontsize_pro');
|
||||
@ -11,4 +11,4 @@ class Settings extends BaseSettings {
|
||||
public frontend: 'basic' | 'pro' = 'pro';
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
export default SettingsPro;
|
||||
|
1
frontend/src/services/Native/index.d.ts
vendored
1
frontend/src/services/Native/index.d.ts
vendored
@ -4,6 +4,7 @@ declare global {
|
||||
interface Window {
|
||||
ReactNativeWebView?: ReactNativeWebView;
|
||||
NativeRobosats?: NativeRobosats;
|
||||
RobosatsSettings: 'web-basic' | 'web-pro' | 'selfhosted-basic' | 'selfhosted-pro';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ const computeSats = ({
|
||||
}: computeSatsProps): string | undefined => {
|
||||
const rateWithPremium = rate + premium / 100;
|
||||
let sats = (amount / rateWithPremium) * 100000000;
|
||||
sats = sats * (1 + fee) * (1 - routingBudget);
|
||||
sats = sats * (1 - fee) * (1 - routingBudget);
|
||||
return pn(Math.round(sats));
|
||||
};
|
||||
|
||||
|
@ -32,93 +32,6 @@ const configWeb: Configuration = {
|
||||
},
|
||||
};
|
||||
|
||||
const configWebSelfhosted: Configuration = {
|
||||
...config,
|
||||
module: {
|
||||
...config.module,
|
||||
rules: [
|
||||
...(config?.module?.rules || []),
|
||||
{
|
||||
test: path.resolve(__dirname, 'src/models/Settings.default.basic.ts'),
|
||||
loader: 'file-replace-loader',
|
||||
options: {
|
||||
condition: 'if-replacement-exists',
|
||||
replacement: path.resolve(__dirname, 'src/models/Settings.default.basic.selfhosted.ts'),
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'static/frontend'),
|
||||
filename: 'basic.selfhosted.js',
|
||||
},
|
||||
};
|
||||
|
||||
const configWebPro: Configuration = {
|
||||
...config,
|
||||
module: {
|
||||
...config.module,
|
||||
rules: [
|
||||
...(config?.module?.rules || []),
|
||||
{
|
||||
test: path.resolve(__dirname, 'src/basic/Main.tsx'),
|
||||
loader: 'file-replace-loader',
|
||||
options: {
|
||||
condition: 'if-replacement-exists',
|
||||
replacement: path.resolve(__dirname, 'src/pro/Main.tsx'),
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: path.resolve(__dirname, 'src/models/Settings.default.basic.ts'),
|
||||
loader: 'file-replace-loader',
|
||||
options: {
|
||||
condition: 'if-replacement-exists',
|
||||
replacement: path.resolve(__dirname, 'src/models/Settings.default.pro.ts'),
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'static/frontend'),
|
||||
filename: 'pro.js',
|
||||
},
|
||||
};
|
||||
|
||||
const configWebProSelfhosted: Configuration = {
|
||||
...config,
|
||||
module: {
|
||||
...config.module,
|
||||
rules: [
|
||||
...(config?.module?.rules || []),
|
||||
{
|
||||
test: path.resolve(__dirname, 'src/basic/Main.tsx'),
|
||||
loader: 'file-replace-loader',
|
||||
options: {
|
||||
condition: 'if-replacement-exists',
|
||||
replacement: path.resolve(__dirname, 'src/pro/Main.tsx'),
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: path.resolve(__dirname, 'src/models/Settings.default.basic.ts'),
|
||||
loader: 'file-replace-loader',
|
||||
options: {
|
||||
condition: 'if-replacement-exists',
|
||||
replacement: path.resolve(__dirname, 'src/models/Settings.default.pro.selfhosted.ts'),
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'static/frontend'),
|
||||
filename: 'pro.selfhosted.js',
|
||||
},
|
||||
};
|
||||
|
||||
const configMobile: Configuration = {
|
||||
...config,
|
||||
module: {
|
||||
@ -177,4 +90,4 @@ const configMobile: Configuration = {
|
||||
},
|
||||
};
|
||||
|
||||
export default [configWeb, configWebPro, configWebSelfhosted, configWebProSelfhosted, configMobile];
|
||||
export default [configWeb, configMobile];
|
||||
|
1469
mobile/package-lock.json
generated
1469
mobile/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@react-native-community/eslint-config": "^3.2.0",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react-native": "^0.71.3",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
@ -38,12 +38,12 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jest": "^29.5.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^29.7.0",
|
||||
"metro-react-native-babel-preset": "^0.75.1",
|
||||
"prettier": "^3.2.5",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18"
|
||||
|
@ -55,6 +55,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/frontend/basic.selfhosted.js"></script>
|
||||
<script>
|
||||
window.RobosatsSettings = 'selfhosted-basic'
|
||||
</script>
|
||||
<script src="/static/frontend/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -57,6 +57,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/frontend/pro.selfhosted.js"></script>
|
||||
<script>
|
||||
window.RobosatsSettings = 'selfhosted-pro'
|
||||
</script>
|
||||
<script src="/static/frontend/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,13 +1,13 @@
|
||||
django==4.2.9
|
||||
django==5.0.4
|
||||
django-admin-relation-links==0.2.5
|
||||
django-celery-beat==2.6.0
|
||||
django-celery-results==2.5.1
|
||||
django-model-utils==4.4.0
|
||||
django-model-utils==4.5.0
|
||||
django-redis==5.4.0
|
||||
djangorestframework==3.15.0
|
||||
channels==4.0.0
|
||||
channels==4.1.0
|
||||
channels-redis==4.2.0
|
||||
celery==5.3.6
|
||||
celery==5.4.0
|
||||
grpcio==1.62.0
|
||||
googleapis-common-protos==1.63.0
|
||||
grpcio-tools==1.62.0
|
||||
@ -17,14 +17,15 @@ python-decouple==3.8
|
||||
requests==2.31.0
|
||||
ring==0.10.1
|
||||
git+https://github.com/RoboSats/Robohash.git
|
||||
gunicorn==21.2.0
|
||||
gunicorn==22.0.0
|
||||
psycopg2==2.9.9
|
||||
SQLAlchemy==2.0.16
|
||||
django-import-export==3.3.7
|
||||
django-import-export==3.3.8
|
||||
requests[socks]
|
||||
shapely==2.0.4
|
||||
python-gnupg==0.5.2
|
||||
daphne==4.1.0
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular-sidecar==2024.3.4
|
||||
daphne==4.1.2
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular-sidecar==2024.4.1
|
||||
django-cors-headers==4.3.1
|
||||
base91==1.0.1
|
||||
|
@ -1,4 +1,4 @@
|
||||
coverage==7.4.4
|
||||
ruff==0.3.4
|
||||
coverage==7.5.0
|
||||
ruff==0.4.2
|
||||
drf-openapi-tester==2.3.3
|
||||
pre-commit==3.6.2
|
||||
pre-commit==3.7.0
|
@ -8,8 +8,8 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser, User, update_last_login
|
||||
from django.utils import timezone
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from robohash import Robohash
|
||||
|
||||
from api.nick_generator.nick_generator import NickGenerator
|
||||
@ -79,8 +79,11 @@ class RobotTokenSHA256AuthenticationMiddleWare:
|
||||
return response
|
||||
|
||||
if not is_valid_token(token_sha256_b91):
|
||||
raise AuthenticationFailed(
|
||||
"Robot token SHA256 was provided in the header. However it is not a valid 39 or 40 characters Base91 string."
|
||||
return JsonResponse(
|
||||
{
|
||||
"bad_request": "Robot token SHA256 was provided in the header. However it is not a valid 39 or 40 characters Base91 string."
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
# Check if it is an existing robot.
|
||||
@ -123,8 +126,11 @@ class RobotTokenSHA256AuthenticationMiddleWare:
|
||||
encrypted_private_key = request.COOKIES.get("encrypted_private_key", "")
|
||||
|
||||
if not public_key or not encrypted_private_key:
|
||||
raise AuthenticationFailed(
|
||||
"On the first request to a RoboSats coordinator, you must provide as well a valid public and encrypted private PGP keys"
|
||||
return JsonResponse(
|
||||
{
|
||||
"bad_request": "On the first request to a RoboSats coordinator, you must provide as well a valid public and encrypted private PGP keys"
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
(
|
||||
valid,
|
||||
@ -133,7 +139,7 @@ class RobotTokenSHA256AuthenticationMiddleWare:
|
||||
encrypted_private_key,
|
||||
) = validate_pgp_keys(public_key, encrypted_private_key)
|
||||
if not valid:
|
||||
raise AuthenticationFailed(bad_keys_context)
|
||||
return JsonResponse({"bad_request": bad_keys_context}, status=400)
|
||||
|
||||
# Hash the token_sha256, only 1 iteration.
|
||||
# This is the second SHA256 of the user token, aka RoboSats ID
|
||||
|
@ -239,6 +239,38 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertIsNone(data["taker"], "New order's taker is not null")
|
||||
self.assert_order_logs(data["id"])
|
||||
|
||||
def test_make_order_on_blocked_country(self):
|
||||
"""
|
||||
Test the creation of an F2F order on a geoblocked location
|
||||
"""
|
||||
trade = Trade(
|
||||
self.client,
|
||||
# latitude and longitud in Aruba. One of the countries blocked in the example conf.
|
||||
maker_form={
|
||||
"type": 0,
|
||||
"currency": 1,
|
||||
"has_range": True,
|
||||
"min_amount": 21,
|
||||
"max_amount": 101.7,
|
||||
"payment_method": "Advcash Cash F2F",
|
||||
"is_explicit": False,
|
||||
"premium": 3.34,
|
||||
"public_duration": 69360,
|
||||
"escrow_duration": 8700,
|
||||
"bond_size": 3.5,
|
||||
"latitude": -11.8014, # Angola AGO
|
||||
"longitude": 17.3575,
|
||||
},
|
||||
) # init of Trade calls make_order() with the default maker form.
|
||||
data = trade.response.json()
|
||||
|
||||
self.assertEqual(trade.response.status_code, 400)
|
||||
self.assertResponse(trade.response)
|
||||
|
||||
self.assertEqual(
|
||||
data["bad_request"], "The coordinator does not support orders in AGO"
|
||||
)
|
||||
|
||||
def test_get_order_created(self):
|
||||
"""
|
||||
Tests the creation of an order and the first request to see details,
|
||||
|
@ -98,8 +98,8 @@ class Trade:
|
||||
|
||||
response = self.client.post(path, maker_form, **headers)
|
||||
|
||||
self.response = response
|
||||
if response.status_code == 201:
|
||||
self.response = response
|
||||
self.order_id = response.json()["id"]
|
||||
|
||||
def get_order(self, robot_index=1, first_encounter=False):
|
||||
|
@ -55,6 +55,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.RobosatsSettings = 'web-basic'
|
||||
</script>
|
||||
<script src="/static/frontend/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -57,6 +57,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/frontend/pro.js"></script>
|
||||
<script>
|
||||
window.RobosatsSettings = 'web-pro'
|
||||
</script>
|
||||
<script src="/static/frontend/main.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user