Merge branch 'RoboSats:new-tor-engine' into new-tor-engine

This commit is contained in:
KoalaSat 2024-04-30 16:01:45 +02:00 committed by GitHub
commit 9ab8381eaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1302 additions and 863 deletions

View File

@ -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'

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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, {

View File

@ -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

View File

@ -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",

View File

@ -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,

View File

@ -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.

View File

@ -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})"

View File

@ -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",
)

View File

@ -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 %}

View File

@ -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

View File

@ -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"
}

View File

@ -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",

View File

@ -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,

View 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;

View File

@ -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}

View File

@ -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: {

View File

@ -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);
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -4,6 +4,7 @@ declare global {
interface Window {
ReactNativeWebView?: ReactNativeWebView;
NativeRobosats?: NativeRobosats;
RobosatsSettings: 'web-basic' | 'web-pro' | 'selfhosted-basic' | 'selfhosted-pro';
}
}

View File

@ -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));
};

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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):

View File

@ -55,6 +55,9 @@
</div>
</div>
</div>
<script>
window.RobosatsSettings = 'web-basic'
</script>
<script src="/static/frontend/main.js"></script>
</body>
</html>

View File

@ -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>