Merge branch 'main' into desktopApp

This commit is contained in:
Amit Panwar 2024-06-23 12:01:50 +05:30 committed by GitHub
commit 3fe457b4b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
93 changed files with 2516 additions and 2599 deletions

View File

@ -6,6 +6,15 @@ on:
semver: semver:
required: true required: true
type: string type: string
secrets:
KEYSTORE:
required: true
KEY_ALIAS:
required: true
KEY_PASS:
required: true
KEY_STORE_PASS:
required: true
push: push:
branches: [ "main" ] branches: [ "main" ]
paths: [ "mobile", "frontend" ] paths: [ "mobile", "frontend" ]
@ -23,7 +32,7 @@ jobs:
- name: 'Download Android Web.bundle Artifact (built frontend)' - name: 'Download Android Web.bundle Artifact (built frontend)'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -17,7 +17,7 @@ jobs:
- name: 'Download Basic main.js Artifact' - name: 'Download Basic main.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success
@ -33,7 +33,7 @@ jobs:
- name: 'Download pro.js Artifact' - name: 'Download pro.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -22,8 +22,8 @@ jobs:
matrix: matrix:
python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm'] python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm']
lnd-version: ['v0.17.4-beta'] lnd-version: ['v0.17.4-beta']
cln-version: ['v23.11.2'] #,'v24.02'] cln-version: ['v24.05']
ln-vendor: ['LND'] #, 'CLN'] ln-vendor: ['LND', 'CLN']
steps: steps:
- name: 'Checkout' - name: 'Checkout'
@ -56,7 +56,6 @@ jobs:
env: env:
LND_VERSION: ${{ matrix.lnd-version }} LND_VERSION: ${{ matrix.lnd-version }}
CLN_VERSION: ${{ matrix.cln-version }} CLN_VERSION: ${{ matrix.cln-version }}
BITCOIND_VERSION: ${{ matrix.bitcoind-version }}
ROBOSATS_ENVS_FILE: ".env-sample" ROBOSATS_ENVS_FILE: ".env-sample"
- name: Wait for coordinator (django server) - name: Wait for coordinator (django server)

View File

@ -71,6 +71,7 @@ jobs:
android-build: android-build:
uses: RoboSats/robosats/.github/workflows/android-build.yml@main uses: RoboSats/robosats/.github/workflows/android-build.yml@main
needs: [frontend-build, check-versions] needs: [frontend-build, check-versions]
secrets: inherit
with: with:
semver: ${{ needs.check-versions.outputs.semver }} semver: ${{ needs.check-versions.outputs.semver }}

View File

@ -26,7 +26,7 @@ jobs:
- name: 'Download basic.selfhosted.js Artifact' - name: 'Download basic.selfhosted.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success
@ -42,7 +42,7 @@ jobs:
- name: 'Download pro.selfhosted.js Artifact' - name: 'Download pro.selfhosted.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -26,7 +26,7 @@ jobs:
- name: 'Download main.js Artifact' - name: 'Download main.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success
@ -42,7 +42,7 @@ jobs:
- name: 'Download pro.js Artifact' - name: 'Download pro.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v3 uses: dawidd6/action-download-artifact@v6
with: with:
workflow: frontend-build.yml workflow: frontend-build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -21,17 +21,13 @@ admin.site.unregister(TokenProxy)
class RobotInline(admin.StackedInline): class RobotInline(admin.StackedInline):
model = Robot model = Robot
can_delete = False can_delete = False
fields = ("avatar_tag",)
readonly_fields = ["avatar_tag"]
show_change_link = True show_change_link = True
# extended users with avatars
@admin.register(User) @admin.register(User)
class EUserAdmin(AdminChangeLinksMixin, UserAdmin): class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
inlines = [RobotInline] inlines = [RobotInline]
list_display = ( list_display = (
"avatar_tag",
"id", "id",
"robot_link", "robot_link",
"username", "username",
@ -43,25 +39,18 @@ class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
change_links = ("robot",) change_links = ("robot",)
ordering = ("-id",) ordering = ("-id",)
def avatar_tag(self, obj):
return obj.robot.avatar_tag()
# extended tokens with raw id fields
# extended tokens with raw id fields and avatars
@admin.register(TokenProxy) @admin.register(TokenProxy)
class ETokenAdmin(AdminChangeLinksMixin, TokenAdmin): class ETokenAdmin(AdminChangeLinksMixin, TokenAdmin):
raw_id_fields = ["user"] raw_id_fields = ["user"]
list_display = ( list_display = (
"avatar_tag",
"key", "key",
"user_link", "user_link",
) )
list_display_links = ("key",) list_display_links = ("key",)
change_links = ("user",) change_links = ("user",)
def avatar_tag(self, obj):
return obj.user.robot.avatar_tag()
class LNPaymentInline(admin.StackedInline): class LNPaymentInline(admin.StackedInline):
model = LNPayment model = LNPayment
@ -510,7 +499,6 @@ class OnchainPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@admin.register(Robot) @admin.register(Robot)
class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin): class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ( list_display = (
"avatar_tag",
"id", "id",
"user_link", "user_link",
"telegram_enabled", "telegram_enabled",
@ -523,9 +511,8 @@ class UserRobotAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
) )
raw_id_fields = ("user",) raw_id_fields = ("user",)
list_editable = ["earned_rewards"] list_editable = ["earned_rewards"]
list_display_links = ("avatar_tag", "id") list_display_links = ["id"]
change_links = ["user"] change_links = ["user"]
readonly_fields = ["avatar_tag"]
search_fields = ["user__username", "id"] search_fields = ["user__username", "id"]
readonly_fields = ("hash_id", "public_key", "encrypted_private_key") readonly_fields = ("hash_id", "public_key", "encrypted_private_key")

View File

@ -90,9 +90,9 @@ class CLNNode:
@classmethod @classmethod
def decode_payreq(cls, invoice): def decode_payreq(cls, invoice):
"""Decodes a lightning payment request (invoice)""" """Decodes a lightning payment request (invoice)"""
request = hold_pb2.DecodeBolt11Request(bolt11=invoice) nodestub = node_pb2_grpc.NodeStub(cls.node_channel)
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel) request = node_pb2.DecodeRequest(string=invoice)
response = holdstub.DecodeBolt11(request) response = nodestub.Decode(request)
return response return response
@classmethod @classmethod
@ -236,7 +236,7 @@ class CLNNode:
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel) holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
response = holdstub.HoldInvoiceCancel(request) response = holdstub.HoldInvoiceCancel(request)
return response.state == hold_pb2.HoldInvoiceCancelResponse.Holdstate.CANCELED return response.state == hold_pb2.Holdstate.CANCELED
@classmethod @classmethod
def settle_hold_invoice(cls, preimage): def settle_hold_invoice(cls, preimage):
@ -247,7 +247,7 @@ class CLNNode:
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel) holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
response = holdstub.HoldInvoiceSettle(request) response = holdstub.HoldInvoiceSettle(request)
return response.state == hold_pb2.HoldInvoiceSettleResponse.Holdstate.SETTLED return response.state == hold_pb2.Holdstate.SETTLED
@classmethod @classmethod
def gen_hold_invoice( def gen_hold_invoice(
@ -272,7 +272,7 @@ class CLNNode:
request = hold_pb2.HoldInvoiceRequest( request = hold_pb2.HoldInvoiceRequest(
description=description, description=description,
amount_msat=primitives__pb2.Amount(msat=num_satoshis * 1_000), amount_msat=hold_pb2.Amount(msat=num_satoshis * 1_000),
label=f"Order:{order_id}-{lnpayment_concept}-{time}", label=f"Order:{order_id}-{lnpayment_concept}-{time}",
expiry=invoice_expiry, expiry=invoice_expiry,
cltv=cltv_expiry_blocks, cltv=cltv_expiry_blocks,
@ -286,7 +286,7 @@ class CLNNode:
hold_payment["preimage"] = preimage.hex() hold_payment["preimage"] = preimage.hex()
hold_payment["payment_hash"] = response.payment_hash.hex() hold_payment["payment_hash"] = response.payment_hash.hex()
hold_payment["created_at"] = timezone.make_aware( hold_payment["created_at"] = timezone.make_aware(
datetime.fromtimestamp(payreq_decoded.timestamp) datetime.fromtimestamp(payreq_decoded.created_at)
) )
hold_payment["expires_at"] = timezone.make_aware( hold_payment["expires_at"] = timezone.make_aware(
datetime.fromtimestamp(response.expires_at) datetime.fromtimestamp(response.expires_at)
@ -309,13 +309,13 @@ class CLNNode:
# Will fail if 'unable to locate invoice'. Happens if invoice expiry # Will fail if 'unable to locate invoice'. Happens if invoice expiry
# time has passed (but these are 15% padded at the moment). Should catch it # time has passed (but these are 15% padded at the moment). Should catch it
# and report back that the invoice has expired (better robustness) # and report back that the invoice has expired (better robustness)
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.OPEN: if response.state == hold_pb2.Holdstate.OPEN:
pass pass
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.SETTLED: if response.state == hold_pb2.Holdstate.SETTLED:
pass pass
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.CANCELED: if response.state == hold_pb2.Holdstate.CANCELED:
pass pass
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.ACCEPTED: if response.state == hold_pb2.Holdstate.ACCEPTED:
lnpayment.expiry_height = response.htlc_expiry lnpayment.expiry_height = response.htlc_expiry
lnpayment.status = LNPayment.Status.LOCKED lnpayment.status = LNPayment.Status.LOCKED
lnpayment.save(update_fields=["expiry_height", "status"]) lnpayment.save(update_fields=["expiry_height", "status"])
@ -359,7 +359,7 @@ class CLNNode:
except Exception as e: except Exception as e:
# If it fails at finding the invoice: it has been expired for more than an hour (and could be paid or just expired). # If it fails at finding the invoice: it has been expired for more than an hour (and could be paid or just expired).
# In RoboSats DB we make a distinction between cancelled and returned # In RoboSats DB we make a distinction between cancelled and returned
# (cln-grpc-hodl has separate state for hodl-invoices, which it forgets after an invoice expired more than an hour ago) # (holdinvoice plugin has separate state for hodl-invoices, which it forgets after an invoice expired more than an hour ago)
if "empty result for listdatastore_state" in str(e): if "empty result for listdatastore_state" in str(e):
print(str(e)) print(str(e))
request2 = node_pb2.ListinvoicesRequest( request2 = node_pb2.ListinvoicesRequest(
@ -418,7 +418,7 @@ class CLNNode:
# Some wallet providers (e.g. Muun) force routing through a private channel with high fees >1500ppm # Some wallet providers (e.g. Muun) force routing through a private channel with high fees >1500ppm
# These payments will fail. So it is best to let the user know in advance this invoice is not valid. # These payments will fail. So it is best to let the user know in advance this invoice is not valid.
route_hints = payreq_decoded.route_hints.hints route_hints = payreq_decoded.routes.hints
# Max amount RoboSats will pay for routing # Max amount RoboSats will pay for routing
if routing_budget_ppm == 0: if routing_budget_ppm == 0:
@ -438,8 +438,10 @@ class CLNNode:
route_cost = 0 route_cost = 0
# ...add up the cost of every hinted hop... # ...add up the cost of every hinted hop...
for hop_hint in hinted_route.hops: for hop_hint in hinted_route.hops:
route_cost += hop_hint.feebase.msat / 1_000 route_cost += hop_hint.fee_base_msat.msat / 1_000
route_cost += hop_hint.feeprop * num_satoshis / 1_000_000 route_cost += (
hop_hint.fee_proportional_millionths * num_satoshis / 1_000_000
)
# ...and store the cost of the route to the array # ...and store the cost of the route to the array
routes_cost.append(route_cost) routes_cost.append(route_cost)
@ -466,7 +468,7 @@ class CLNNode:
return payout return payout
payout["created_at"] = timezone.make_aware( payout["created_at"] = timezone.make_aware(
datetime.fromtimestamp(payreq_decoded.timestamp) datetime.fromtimestamp(payreq_decoded.created_at)
) )
payout["expires_at"] = payout["created_at"] + timedelta( payout["expires_at"] = payout["created_at"] + timedelta(
seconds=payreq_decoded.expiry seconds=payreq_decoded.expiry
@ -869,4 +871,4 @@ class CLNNode:
else: else:
raise e raise e
return response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.SETTLED return response.state == hold_pb2.Holdstate.SETTLED

View File

@ -1,5 +1,6 @@
import json import json
from decimal import Decimal
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
@ -18,7 +19,7 @@ class Currency(models.Model):
decimal_places=4, decimal_places=4,
default=None, default=None,
null=True, null=True,
validators=[MinValueValidator(0)], validators=[MinValueValidator(Decimal(0))],
) )
timestamp = models.DateTimeField(default=timezone.now) timestamp = models.DateTimeField(default=timezone.now)

View File

@ -1,5 +1,6 @@
import uuid import uuid
from decimal import Decimal
from decouple import config from decouple import config
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
@ -27,21 +28,24 @@ class MarketTick(models.Model):
decimal_places=2, decimal_places=2,
default=None, default=None,
null=True, null=True,
validators=[MinValueValidator(0)], validators=[MinValueValidator(Decimal(0))],
) )
volume = models.DecimalField( volume = models.DecimalField(
max_digits=8, max_digits=8,
decimal_places=8, decimal_places=8,
default=None, default=None,
null=True, null=True,
validators=[MinValueValidator(0)], validators=[MinValueValidator(Decimal(0))],
) )
premium = models.DecimalField( premium = models.DecimalField(
max_digits=5, max_digits=5,
decimal_places=2, decimal_places=2,
default=None, default=None,
null=True, null=True,
validators=[MinValueValidator(-100), MaxValueValidator(999)], validators=[
MinValueValidator(Decimal(-100)),
MaxValueValidator(Decimal(999))
],
blank=True, blank=True,
) )
currency = models.ForeignKey("api.Currency", null=True, on_delete=models.SET_NULL) currency = models.ForeignKey("api.Currency", null=True, on_delete=models.SET_NULL)
@ -52,7 +56,10 @@ class MarketTick(models.Model):
max_digits=4, max_digits=4,
decimal_places=4, decimal_places=4,
default=0, default=0,
validators=[MinValueValidator(0), MaxValueValidator(1)], validators=[
MinValueValidator(Decimal(0)),
MaxValueValidator(Decimal(1))
],
) )
def log_a_tick(order): def log_a_tick(order):

View File

@ -1,3 +1,4 @@
from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
@ -58,7 +59,10 @@ class OnchainPayment(models.Model):
default=2.05, default=2.05,
null=False, null=False,
blank=False, blank=False,
validators=[MinValueValidator(1), MaxValueValidator(999)], validators=[
MinValueValidator(Decimal(1)),
MaxValueValidator(Decimal(999))
],
) )
mining_fee_rate = models.DecimalField( mining_fee_rate = models.DecimalField(
max_digits=6, max_digits=6,
@ -66,7 +70,10 @@ class OnchainPayment(models.Model):
default=2.05, default=2.05,
null=False, null=False,
blank=False, blank=False,
validators=[MinValueValidator(1), MaxValueValidator(999)], validators=[
MinValueValidator(Decimal(1)),
MaxValueValidator(Decimal(999))
],
) )
mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False) mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False)

View File

@ -1,6 +1,7 @@
# We use custom seeded UUID generation during testing # We use custom seeded UUID generation during testing
import uuid import uuid
from decimal import Decimal
from decouple import config from decouple import config
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -90,7 +91,10 @@ class Order(models.Model):
decimal_places=2, decimal_places=2,
default=0, default=0,
null=True, null=True,
validators=[MinValueValidator(-100), MaxValueValidator(999)], validators=[
MinValueValidator(Decimal(-100)),
MaxValueValidator(Decimal(999))
],
blank=True, blank=True,
) )
# explicit # explicit
@ -135,8 +139,8 @@ class Order(models.Model):
default=settings.DEFAULT_BOND_SIZE, default=settings.DEFAULT_BOND_SIZE,
null=False, null=False,
validators=[ validators=[
MinValueValidator(settings.MIN_BOND_SIZE), # 2 % MinValueValidator(Decimal(settings.MIN_BOND_SIZE)), # 2 %
MaxValueValidator(settings.MAX_BOND_SIZE), # 15 % MaxValueValidator(Decimal(settings.MAX_BOND_SIZE)), # 15 %
], ],
blank=False, blank=False,
) )
@ -147,8 +151,8 @@ class Order(models.Model):
decimal_places=6, decimal_places=6,
null=True, null=True,
validators=[ validators=[
MinValueValidator(-90), MinValueValidator(Decimal(-90)),
MaxValueValidator(90), MaxValueValidator(Decimal(90)),
], ],
blank=True, blank=True,
) )
@ -157,8 +161,8 @@ class Order(models.Model):
decimal_places=6, decimal_places=6,
null=True, null=True,
validators=[ validators=[
MinValueValidator(-180), MinValueValidator(Decimal(-180)),
MaxValueValidator(180), MaxValueValidator(Decimal(180)),
], ],
blank=True, blank=True,
) )

View File

@ -494,7 +494,8 @@ class OrderPublicSerializer(serializers.ModelSerializer):
maker_nick = serializers.CharField(required=False) maker_nick = serializers.CharField(required=False)
maker_hash_id = serializers.CharField(required=False) maker_hash_id = serializers.CharField(required=False)
maker_status = serializers.CharField( maker_status = serializers.CharField(
help_text='Status of the nick - "Active" or "Inactive"', required=False help_text='Status of the nick - "Active", "Seen Recently" or "Inactive"',
required=False,
) )
price = serializers.FloatField( price = serializers.FloatField(
help_text="Price in order's fiat currency", required=False help_text="Price in order's fiat currency", required=False

View File

@ -1,5 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path
from decouple import config from decouple import config
from django.conf import settings from django.conf import settings
@ -55,9 +54,6 @@ from control.models import AccountingDay, BalanceLog
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE")) EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
RETRY_TIME = int(config("RETRY_TIME")) RETRY_TIME = int(config("RETRY_TIME"))
avatar_path = Path(settings.AVATAR_ROOT)
avatar_path.mkdir(parents=True, exist_ok=True)
class MakerView(CreateAPIView): class MakerView(CreateAPIView):
serializer_class = MakeOrderSerializer serializer_class = MakeOrderSerializer

View File

@ -3,6 +3,8 @@
# Some useful handy commands that hopefully are never needed # Some useful handy commands that hopefully are never needed
# docker-compose -f docker-tests.yml --env-file tests/compose.env down --volumes
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test createwallet default # docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test createwallet default
# docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test -generate 101 # docker exec -it btc bitcoin-cli -chain=regtest -rpcpassword=test -rpcuser=test -generate 101
@ -27,6 +29,8 @@ services:
- "6379:6379" - "6379:6379"
volumes: volumes:
- bitcoin:/bitcoin/.bitcoin/ - bitcoin:/bitcoin/.bitcoin/
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
entrypoint: ["/entrypoint.sh"]
command: command:
--txindex=1 --txindex=1
--printtoconsole --printtoconsole
@ -77,16 +81,16 @@ services:
network_mode: service:bitcoind network_mode: service:bitcoind
coordinator-CLN: coordinator-CLN:
image: elementsproject/lightningd:${CLN_VERSION:-v23.08.1} image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
restart: always restart: always
container_name: coordinator-CLN container_name: coordinator-CLN
environment: environment:
LIGHTNINGD_NETWORK: 'regtest' LIGHTNINGD_NETWORK: 'regtest'
volumes: volumes:
- cln:/root/.lightning - cln:/root/.lightning
- ./docker/cln/plugins/cln-grpc-hold:/root/.lightning/plugins/cln-grpc-hold - ./docker/cln/plugins/holdinvoice:/root/.lightning/plugins/holdinvoice
- bitcoin:/root/.bitcoin - bitcoin:/root/.bitcoin
command: --regtest --wumbo --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --log-level=debug --rest-host=0.0.0.0 --rest-port=3010 --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/cln-grpc-hold --database-upgrade=true command: --regtest --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --developer --dev-bitcoind-poll=1 --dev-fast-gossip --log-level=debug --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/holdinvoice --database-upgrade=true
depends_on: depends_on:
- bitcoind - bitcoind
network_mode: service:bitcoind network_mode: service:bitcoind

View File

@ -1,7 +1,7 @@
FROM debian:bullseye-slim as builder FROM debian:bullseye-slim as builder
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG LIGHTNINGD_VERSION=v23.08 ARG LIGHTNINGD_VERSION=v24.05
RUN apt-get update -qq && \ RUN apt-get update -qq && \
apt-get install -qq -y --no-install-recommends \ apt-get install -qq -y --no-install-recommends \
autoconf \ autoconf \
@ -18,13 +18,13 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
RUN rustup toolchain install stable --component rustfmt --allow-downgrade RUN rustup toolchain install stable --component rustfmt --allow-downgrade
WORKDIR /opt/lightningd WORKDIR /opt/lightningd
RUN git clone --recursive --branch cln-grpc-hold https://github.com/daywalker90/lightning.git /tmp/cln-grpc-hold RUN git clone https://github.com/daywalker90/holdinvoice.git /tmp/holdinvoice
RUN cd /tmp/cln-grpc-hold \ RUN cd /tmp/holdinvoice \
&& cargo build --release && cargo build --release
FROM elementsproject/lightningd:v23.08 as final FROM elementsproject/lightningd:v24.05 as final
COPY --from=builder /tmp/cln-grpc-hold/target/release/cln-grpc-hold /tmp/cln-grpc-hold COPY --from=builder /tmp/holdinvoice/target/release/holdinvoice /tmp/holdinvoice
COPY config /tmp/config COPY config /tmp/config
COPY entrypoint.sh entrypoint.sh COPY entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh RUN chmod +x entrypoint.sh

View File

@ -5,6 +5,6 @@ addr=statictor:127.0.0.1:9051
grpc-port=9999 grpc-port=9999
grpc-hold-port=9998 grpc-hold-port=9998
always-use-proxy=true always-use-proxy=true
important-plugin=/root/.lightning/plugins/cln-grpc-hold important-plugin=/root/.lightning/plugins/holdinvoice
# wallet=postgres://user:pass@localhost:5433/cln # wallet=postgres://user:pass@localhost:5433/cln
# bookkeeper-db=postgres://user:pass@localhost:5433/cln # bookkeeper-db=postgres://user:pass@localhost:5433/cln

View File

@ -17,9 +17,9 @@ if [ "$EXPOSE_TCP" == "true" ]; then
socat "TCP4-listen:$LIGHTNINGD_RPC_PORT,fork,reuseaddr" "UNIX-CONNECT:${networkdatadir}/lightning-rpc" & socat "TCP4-listen:$LIGHTNINGD_RPC_PORT,fork,reuseaddr" "UNIX-CONNECT:${networkdatadir}/lightning-rpc" &
fg %- fg %-
else else
# Always copy the cln-grpc-hodl plugin into the plugins directory on start up # Always copy the holdinvoice plugin into the plugins directory on start up
mkdir -p /root/.lightning/plugins mkdir -p /root/.lightning/plugins
cp /tmp/cln-grpc-hold /root/.lightning/plugins/cln-grpc-hold cp /tmp/holdinvoice /root/.lightning/plugins/holdinvoice
if [ ! -f /root/.lightning/config ]; then if [ ! -f /root/.lightning/config ]; then
cp /tmp/config /root/.lightning/config cp /tmp/config /root/.lightning/config
fi fi

Binary file not shown.

BIN
docker/cln/plugins/holdinvoice Executable file

Binary file not shown.

View File

@ -28,15 +28,17 @@ This is a non-exhaustive compilation based on past experience of users. We have
| Wallet | Version | Device | UX<sup>1</sup> | Bonds<sup>2</sup> | Payout<sup>3</sup> | Comp<sup>4</sup> | Total<sup>5</sup> | | Wallet | Version | Device | UX<sup>1</sup> | Bonds<sup>2</sup> | Payout<sup>3</sup> | Comp<sup>4</sup> | Total<sup>5</sup> |
|:---|:---|:--:|:--:|:--:|:--:|:--:|:--:| |:---|:---|:--:|:--:|:--:|:--:|:--:|:--:|
|[Alby](#alby-browser-extension)|[v1.14.2](https://github.com/getAlby/lightning-browser-extension)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| |[Alby](#alby-browser-extension)|[v1.14.2](https://github.com/getAlby/lightning-browser-extension)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|[Aqua](#aqua-mobile)|[v0.1.55](https://aquawallet.io/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.unclear}}|{{page.good}} |{{page.thumbsup}}|
|[Blink](#blink-mobile-former-bitcoin-beach-wallet)|[2.2.73](https://www.blink.sv/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| |[Blink](#blink-mobile-former-bitcoin-beach-wallet)|[2.2.73](https://www.blink.sv/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|[Blixt](#blixt-androidios-lnd-light-backend-on-device)|[v0.4.1](https://github.com/hsjoberg/blixt-wallet)|{{page.phone}}|{{page.soso}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Blixt](#blixt-androidios-lnd-light-backend-on-device)|[v0.4.1](https://github.com/hsjoberg/blixt-wallet)|{{page.phone}}|{{page.soso}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|[Blue](#bluewallet-mobile)|[1.4.4](https://bluewallet.io/)|{{page.phone}}|{{page.good}}|{{page.unclear}}|{{page.unclear}}|{{page.good}}|{{page.unclear}}| |[Blue](#bluewallet-mobile)|[1.4.4](https://bluewallet.io/)|{{page.phone}}|{{page.good}}|{{page.unclear}}|{{page.unclear}}|{{page.good}}|{{page.unclear}}|
|[Breez](#breez-mobile)|[0.16](https://breez.technology/mobile/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Breez](#breez-mobile)|[0.16](https://breez.technology/mobile/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|[Cash App](#cash-app-mobile)|[4.7](https://cash.app/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| |[Cash App](#cash-app-mobile)|[4.7](https://cash.app/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}|
|[Core Lightning](#core-lightning--cln-cli-interface)|[v0.11.1](https://github.com/ElementsProject/lightning)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Core Lightning](#core-lightning--cln-cli-interface)|[v0.11.1](https://github.com/ElementsProject/lightning)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|[Electrum](#electrum-desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|| |[Electrum](#electrum-mobile--desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}||
|[LND](#lnd-cli-interface)|[v0.14.2](https://github.com/LightningNetwork/lnd)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[LND](#lnd-cli-interface)|[v0.14.2](https://github.com/LightningNetwork/lnd)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
|[Mash](https://app.mash.com/wallet)|[Beta](https://mash.com/consumer-experience/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} | {{page.thumbsup}}| |[Mash](https://app.mash.com/wallet)|[Beta](https://mash.com/consumer-experience/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} | {{page.thumbsup}}|
|[Mutiny](#mutiny-mobile--web-browser-wallet)|[1.7.1](https://www.mutinywallet.com/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsdown}}||
|[Muun](#muun-mobile)|[47.3](https://muun.com/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.bad}}|{{page.bad}}|{{page.thumbsdown}}| |[Muun](#muun-mobile)|[47.3](https://muun.com/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.bad}}|{{page.bad}}|{{page.thumbsdown}}|
|[Phoenix](#phoenix-mobile)|[35-1.4.20](https://phoenix.acinq.co/)|{{page.phone}}|{{page.good}}|{{page.soso}}|{{page.soso}}|{{page.soso}}|{{page.unclear}}| |[Phoenix](#phoenix-mobile)|[35-1.4.20](https://phoenix.acinq.co/)|{{page.phone}}|{{page.good}}|{{page.soso}}|{{page.soso}}|{{page.soso}}|{{page.unclear}}|
|[SBW](https://github.com/RoboSats/robosats/issues/44#issue-1135544303)|[2.4.27](https://github.com/btcontract/wallet/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[SBW](https://github.com/RoboSats/robosats/issues/44#issue-1135544303)|[2.4.27](https://github.com/btcontract/wallet/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|
@ -57,6 +59,12 @@ Instructions to install Alby in Tor Browser:
1. Install the Alby extension from the [Firefox add-ons store](https://addons.mozilla.org/en-US/firefox/addon/alby/) 1. Install the Alby extension from the [Firefox add-ons store](https://addons.mozilla.org/en-US/firefox/addon/alby/)
2. Click on the Alby extension and follow the prompts to setup your wallet. 2. Click on the Alby extension and follow the prompts to setup your wallet.
### Aqua (Mobile)
Overall the wallet works as expected. Hold invoices work reliably.
What is inconvenient:
- Lightning payments are encapsulated into Liquid Bitcoin so there is a small additional fee for conversion in/out
- Bond refund is locked for 3 days
### Blink (Mobile, former Bitcoin Beach Wallet) ### Blink (Mobile, former Bitcoin Beach Wallet)
Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transaction history. Payouts to the Blink wallet function as intended. Custodial wallet by Galoy which originated from the Bitcoin Beach project in El Salvador (formerly known as "Bitcoin Beach Wallet"). Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transaction history. Payouts to the Blink wallet function as intended. Custodial wallet by Galoy which originated from the Bitcoin Beach project in El Salvador (formerly known as "Bitcoin Beach Wallet").
@ -75,8 +83,11 @@ Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transac
### Core Lightning / CLN (CLI Interface) ### Core Lightning / CLN (CLI Interface)
Works as expected. The `lightning-cli pay <invoice>` command does not conclude while the payment is pending, but can use `lightning-cli paystatus <invoice>` to monitor the state. Works as expected. The `lightning-cli pay <invoice>` command does not conclude while the payment is pending, but can use `lightning-cli paystatus <invoice>` to monitor the state.
### Electrum (Desktop) ### Electrum (Mobile & Desktop)
Works as expected. Some payments and locks may fail depending on the Lightning node the channel is created to. Channels to ASINQ work fine. Overall the wallet works as expected. The interface is precise and clear.
What is inconvenient:
- all your Lightning channels have to be created to the node: ACINQ
### LND (CLI Interface) ### LND (CLI Interface)
Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not user friendly and therefore not recommended to interact with RoboSats by beginners. However, everything works just fine. If you are using LNCLI regularly, then you will find no issue using it with RoboSats. Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not user friendly and therefore not recommended to interact with RoboSats by beginners. However, everything works just fine. If you are using LNCLI regularly, then you will find no issue using it with RoboSats.
@ -85,6 +96,13 @@ Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not
### Mash Wallet App (Mobile PWA & Desktop Web-Wallet) ### Mash Wallet App (Mobile PWA & Desktop Web-Wallet)
Overall the [Mash](https://mash.com/consumer-experience/) wallet works end2end with Robosats on both selling & buying over lightning. Majority of relevant invoice details in the mash wallet are shown and clear to users throughout the process. When the transactions are complete, they open in the mobile app on both sender/receiver sides to highlight that the transactions are completed.The one UX hick-up is that the pending invoices list doesn't explicitly show HOLD invoices and there is a "spinning" screen on first HOLD invoice payment. The team has a bug open to fix this issue shortly (this note is from Aug 21st 2023). Overall the [Mash](https://mash.com/consumer-experience/) wallet works end2end with Robosats on both selling & buying over lightning. Majority of relevant invoice details in the mash wallet are shown and clear to users throughout the process. When the transactions are complete, they open in the mobile app on both sender/receiver sides to highlight that the transactions are completed.The one UX hick-up is that the pending invoices list doesn't explicitly show HOLD invoices and there is a "spinning" screen on first HOLD invoice payment. The team has a bug open to fix this issue shortly (this note is from Aug 21st 2023).
### Mutiny (Mobile & Web Browser Wallet)
The wallet should work as expected, but the interface, transaction states, and the structure of the funds can sometimes be very confusing in the current release version.
Use the default free Fedimint(Chaumian eCash) account, with the possibility to use zero fee Lightning transfers.
What is inconvenient:
- occasionally wallet restart is needed
- more than two pending hold invoices at the same time may cause a rejection of the new transaction
### Muun (Mobile) ### Muun (Mobile)
Similar to Blixt or LND, Muun plays nicely with hold invoices. You can be a seller in RoboSats using Muun and the user experience will be great. However, in order to be a buyer when using Muun, you need to submit an on-chain address for the payout as a Lightning invoice won't work. Muun is _fee siphoning attacking_ any sender to Muun wallet. There is a mandatory hop through a private channel with a fee of +1500ppm. RoboSats will strictly not route a buyer payout for a net loss. Given that RoboSats trading fees are {{site.robosats.total_fee}}% and it needs to cover the routing fees, **RoboSats will never find a suitable route to a Muun wallet user**. At the moment, RoboSats will scan your invoice for routing hints that can potentially encode a _fee siphoning attack_. If this trick is found, then the invoice will be rejected: submit an on-chain address instead for an on-the-fly swap. Refer to [Understand > On-Chain Payouts](/docs/on-chain-payouts/) for more information about on-the-fly swaps. Important to note that Muun has issues during times of high on chain fee spikes. Regardless, the workaround to receive to Muun is: either submit an on chain address or choose a higher routing budget after enabling the "Advanced Options" switch. Similar to Blixt or LND, Muun plays nicely with hold invoices. You can be a seller in RoboSats using Muun and the user experience will be great. However, in order to be a buyer when using Muun, you need to submit an on-chain address for the payout as a Lightning invoice won't work. Muun is _fee siphoning attacking_ any sender to Muun wallet. There is a mandatory hop through a private channel with a fee of +1500ppm. RoboSats will strictly not route a buyer payout for a net loss. Given that RoboSats trading fees are {{site.robosats.total_fee}}% and it needs to cover the routing fees, **RoboSats will never find a suitable route to a Muun wallet user**. At the moment, RoboSats will scan your invoice for routing hints that can potentially encode a _fee siphoning attack_. If this trick is found, then the invoice will be rejected: submit an on-chain address instead for an on-the-fly swap. Refer to [Understand > On-Chain Payouts](/docs/on-chain-payouts/) for more information about on-the-fly swaps. Important to note that Muun has issues during times of high on chain fee spikes. Regardless, the workaround to receive to Muun is: either submit an on chain address or choose a higher routing budget after enabling the "Advanced Options" switch.
@ -93,6 +111,7 @@ One of the simplest and one of the best. The hodl invoice shows as "on fly", it
*Update 26-10-23: At this moment it has no development or support *Update 26-10-23: At this moment it has no development or support
### Phoenix (Mobile) ### Phoenix (Mobile)
DEV team stated that they do not want to support hold invoices.
*Update 21-10-23. Phoenix used to work as described here, but many things changed to worse with the last update of the wallet. *Update 21-10-23. Phoenix used to work as described here, but many things changed to worse with the last update of the wallet.
Phoenix works very well as an order taker. Phoenix will also work well as an order maker as long as the order settings `public duration` + `deposit duration` are lower than 10 hours. Otherwise, you might have problems locking the maker bond. If the total duraton of bonds/escrow invoices exceeds 450 blocks, then Phoenix will not allow users to lock the bond (`Cannot add htlc (...) reason=expiry too big`). Phoenix works very well as an order taker. Phoenix will also work well as an order maker as long as the order settings `public duration` + `deposit duration` are lower than 10 hours. Otherwise, you might have problems locking the maker bond. If the total duraton of bonds/escrow invoices exceeds 450 blocks, then Phoenix will not allow users to lock the bond (`Cannot add htlc (...) reason=expiry too big`).

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: RoboSats REST API title: RoboSats REST API
version: 0.6.0 version: 0.6.2
x-logo: x-logo:
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
backgroundColor: '#FFFFFF' backgroundColor: '#FFFFFF'
@ -1638,7 +1638,7 @@ components:
type: string type: string
maker_status: maker_status:
type: string type: string
description: Status of the nick - "Active" or "Inactive" description: Status of the nick - "Active", "Seen Recently" or "Inactive"
price: price:
type: number type: number
format: double format: double

View File

@ -1539,7 +1539,7 @@ components:
type: string type: string
maker_status: maker_status:
type: string type: string
description: Status of the nick - "Active" or "Inactive" description: Status of the nick - "Active", "Seen Recently" or "Inactive"
price: price:
type: integer type: integer
description: Price in order's fiat currency description: Price in order's fiat currency

View File

@ -1712,7 +1712,7 @@ components:
type: string type: string
maker_status: maker_status:
type: string type: string
description: Status of the nick - "Active" or "Inactive" description: Status of the nick - "Active", "Seen Recently" or "Inactive"
price: price:
type: number type: number
format: double format: double

View File

@ -0,0 +1,12 @@
<p>RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.</p>
<p><br><b>Features:</b></p><ul>
<li>Privacy focused: your robot avatar is deterministically generated, no need for registration.</li>
<li>More than 10 languages available and over 60 fiat currencies</li>
<li>Safe: simply lock a lightning hodl invoice and show you are real and committed.</li>
<li>No data collection. Your communication with your peer is PGP encrypted, only you can read it.</li>
<li>Lightning fast: the average sovereign trade finishes in ~ 8 minutes. Faster than a single block confirmation!</li>
<li>Fully collateralized escrow: your peer is always committed and cannot run away with the funds.</li>
<li>Strong incentives system: attempts of cheating are penalized with the slashing of the Sats in the fidelity bond.</li>
<li>Guides and video tutorials available at https://learn.robosats.com/watch/en</li>
</ul>
<p>You can join other cool Robots and get community support at <a href="https://t.me/robosats">our Telegram group</a>.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1 @@
Simple and private app to exchange bitcoin for national currencies.

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.6.0", "version": "0.6.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -41,7 +41,7 @@
"eslint-plugin-react": "^7.34.0", "eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.6.1", "jest": "^29.6.1",
"prettier": "^3.2.5", "prettier": "^3.3.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.2", "typescript": "^5.4.2",
"webpack": "^5.89.0", "webpack": "^5.89.0",
@ -57,10 +57,10 @@
"@mui/lab": "^5.0.0-alpha.136", "@mui/lab": "^5.0.0-alpha.136",
"@mui/material": "^5.15.14", "@mui/material": "^5.15.14",
"@mui/system": "^5.15.11", "@mui/system": "^5.15.11",
"@mui/x-data-grid": "^7.3.0", "@mui/x-data-grid": "^7.6.0",
"@mui/x-date-pickers": "^7.2.0", "@mui/x-date-pickers": "^7.2.0",
"@nivo/core": "^0.85.1", "@nivo/core": "^0.86.0",
"@nivo/line": "^0.85.1", "@nivo/line": "^0.86.0",
"base-ex": "^0.8.1", "base-ex": "^0.8.1",
"country-flag-icons": "^1.5.11", "country-flag-icons": "^1.5.11",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",

View File

@ -67,9 +67,11 @@ const RobotProfile = ({
}; };
const handleChangeSlot = (e: SelectChangeEvent<number | 'loading'>): void => { const handleChangeSlot = (e: SelectChangeEvent<number | 'loading'>): void => {
garage.currentSlot = e.target.value; if (e?.target?.value) {
setInputToken(garage.getSlot()?.token ?? ''); garage.setCurrentSlot(e.target.value as string);
setLoading(true); setInputToken(garage.getSlot()?.token ?? '');
setLoading(true);
}
}; };
const slot = garage.getSlot(); const slot = garage.getSlot();

View File

@ -44,7 +44,7 @@ const RobotPage = (): JSX.Element => {
const token = urlToken ?? garage.currentSlot; const token = urlToken ?? garage.currentSlot;
if (token !== undefined && token !== null && page === 'robot') { if (token !== undefined && token !== null && page === 'robot') {
setInputToken(token); setInputToken(token);
if (window.NativeRobosats === undefined || torStatus === 'ON') { if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
getGenerateRobot(token); getGenerateRobot(token);
setView('profile'); setView('profile');
} }
@ -71,7 +71,7 @@ const RobotPage = (): JSX.Element => {
encPrivKey: key.encryptedPrivateKeyArmored, encPrivKey: key.encryptedPrivateKeyArmored,
}); });
void federation.fetchRobot(garage, token); void federation.fetchRobot(garage, token);
garage.currentSlot = token; garage.setCurrentSlot(token);
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error('Error:', error);
@ -83,7 +83,7 @@ const RobotPage = (): JSX.Element => {
garage.deleteSlot(); garage.deleteSlot();
}; };
if (!(window.NativeRobosats === undefined) && !(torStatus === 'ON')) { if (settings.useProxy && !(window.NativeRobosats === undefined) && !(torStatus === 'ON')) {
return ( return (
<Paper <Paper
elevation={12} elevation={12}

View File

@ -90,12 +90,6 @@ const MakerForm = ({
const minRangeAmountMultiple = 1.6; const minRangeAmountMultiple = 1.6;
const amountSafeThresholds = [1.03, 0.98]; const amountSafeThresholds = [1.03, 0.98];
useEffect(() => {
// Why?
// const slot = garage.getSlot();
// if (slot?.token) void federation.fetchRobot(garage, slot?.token);
}, [garage.currentSlot]);
useEffect(() => { useEffect(() => {
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]); setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
}, [coordinatorUpdatedAt]); }, [coordinatorUpdatedAt]);

View File

@ -178,7 +178,7 @@ const OrderDetails = ({
: coordinator.info?.taker_fee ?? 0; : coordinator.info?.taker_fee ?? 0;
const defaultRoutingBudget = 0.001; const defaultRoutingBudget = 0.001;
const btc_now = order.satoshis_now / 100000000; const btc_now = order.satoshis_now / 100000000;
const rate = order.amount > 0 ? order.amount / btc_now : Number(order.max_amount) / btc_now; const rate = Number(order.max_amount ?? order.amount) / btc_now;
if (isBuyer) { if (isBuyer) {
if (order.amount > 0) { if (order.amount > 0) {

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react'; import React, { useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext'; import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import { import {
@ -28,17 +28,19 @@ import {
QrCode, QrCode,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { systemClient } from '../../services/System'; import { systemClient } from '../../services/System';
import { TorIcon } from '../Icons';
import SwapCalls from '@mui/icons-material/SwapCalls'; import SwapCalls from '@mui/icons-material/SwapCalls';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
interface SettingsFormProps { interface SettingsFormProps {
dense?: boolean; dense?: boolean;
} }
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
const { fav, setFav, origin, hostUrl, settings, setSettings } = const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext); const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const fontSizes = [ const fontSizes = [
@ -237,6 +239,29 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
</ListItem> </ListItem>
{window.NativeRobosats !== undefined && (
<ListItem>
<ListItemIcon>
<TorIcon />
</ListItemIcon>
<ToggleButtonGroup
exclusive={true}
value={settings.useProxy}
onChange={(_e, useProxy) => {
setSettings({ ...settings, useProxy });
systemClient.setItem('settings_use_proxy', String(useProxy));
}}
>
<ToggleButton value={true} color='primary'>
{t('Build-in')}
</ToggleButton>
<ToggleButton value={false} color='secondary'>
{t('Disabled')}
</ToggleButton>
</ToggleButtonGroup>
</ListItem>
)}
</List> </List>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -55,10 +55,10 @@ const TorIndicator = ({
}; };
const TorConnectionBadge = (): JSX.Element => { const TorConnectionBadge = (): JSX.Element => {
const { torStatus } = useContext<UseAppStoreType>(AppContext); const { torStatus, settings } = useContext<UseAppStoreType>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
if (window?.NativeRobosats == null) { if (window?.NativeRobosats == null || !settings.useProxy) {
return <></>; return <></>;
} }

View File

@ -222,6 +222,7 @@ export const SuccessfulPrompt = ({
takerSummary={order.taker_summary} takerSummary={order.taker_summary}
platformSummary={order.platform_summary} platformSummary={order.platform_summary}
orderId={order.id} orderId={order.id}
coordinatorLongAlias={federation.getCoordinator(order.shortAlias)?.longAlias}
/> />
</Grid> </Grid>
) : ( ) : (

View File

@ -43,6 +43,7 @@ interface Props {
makerHashId: string; makerHashId: string;
takerHashId: string; takerHashId: string;
currencyCode: string; currencyCode: string;
coordinatorLongAlias: string;
makerSummary: TradeRobotSummary; makerSummary: TradeRobotSummary;
takerSummary: TradeRobotSummary; takerSummary: TradeRobotSummary;
platformSummary: TradeCoordinatorSummary; platformSummary: TradeCoordinatorSummary;
@ -54,6 +55,7 @@ const TradeSummary = ({
makerHashId, makerHashId,
takerHashId, takerHashId,
currencyCode, currencyCode,
coordinatorLongAlias,
makerSummary, makerSummary,
takerSummary, takerSummary,
platformSummary, platformSummary,
@ -72,6 +74,7 @@ const TradeSummary = ({
const onClickExport = function (): void { const onClickExport = function (): void {
const summary = { const summary = {
coordinator: coordinatorLongAlias,
order_id: orderId, order_id: orderId,
currency: currencyCode, currency: currencyCode,
maker: makerSummary, maker: makerSummary,

View File

@ -111,12 +111,14 @@ export const FederationContextProvider = ({
}, []); }, []);
useEffect(() => { useEffect(() => {
// On bitcoin network change we reset book, limits and federation info and fetch everything again if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
if (window.NativeRobosats === undefined || torStatus === 'ON') {
void federation.updateUrl(origin, settings, hostUrl); void federation.updateUrl(origin, settings, hostUrl);
void federation.update(); void federation.update();
const token = garage.getSlot()?.getRobot()?.token;
if (token) void federation.fetchRobot(garage, token);
} }
}, [settings.network, torStatus]); }, [settings.network, settings.useProxy, torStatus]);
const onOrderReceived = (order: Order): void => { const onOrderReceived = (order: Order): void => {
let newDelay = defaultDelay; let newDelay = defaultDelay;
@ -178,15 +180,6 @@ export const FederationContextProvider = ({
if (page === 'offers') void federation.updateBook(); if (page === 'offers') void federation.updateBook();
}, [page]); }, [page]);
// use effects to fetchRobots on app start and network change
useEffect(() => {
const slot = garage.getSlot();
const robot = slot?.getRobot();
if (robot && garage.currentSlot && slot?.token && robot.encPrivKey && robot.pubKey) {
void federation.fetchRobot(garage, slot.token);
}
}, [settings.network]);
// use effects to fetchRobots on Profile open // use effects to fetchRobots on Profile open
useEffect(() => { useEffect(() => {
const slot = garage.getSlot(); const slot = garage.getSlot();

View File

@ -145,7 +145,7 @@ export class Coordinator {
public loadingInfo: boolean = false; public loadingInfo: boolean = false;
public limits: LimitList = {}; public limits: LimitList = {};
public loadingLimits: boolean = false; public loadingLimits: boolean = false;
public loadingRobot: boolean = true; public loadingRobot: string | null;
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => { updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
if (settings.selfhostedClient && this.shortAlias !== 'local') { if (settings.selfhostedClient && this.shortAlias !== 'local') {
@ -185,6 +185,7 @@ export class Coordinator {
if (this.loadingBook) return; if (this.loadingBook) return;
this.loadingBook = true; this.loadingBook = true;
this.book = [];
apiClient apiClient
.get(this.url, `${this.basePath}/api/book/`) .get(this.url, `${this.basePath}/api/book/`)
@ -297,7 +298,7 @@ export class Coordinator {
}; };
fetchRobot = async (garage: Garage, token: string): Promise<Robot | null> => { fetchRobot = async (garage: Garage, token: string): Promise<Robot | null> => {
if (!this.enabled || !token) return null; if (!this.enabled || !token || this.loadingRobot === token) return null;
const robot = garage?.getSlot(token)?.getRobot() ?? null; const robot = garage?.getSlot(token)?.getRobot() ?? null;
const authHeaders = robot?.getAuthHeaders(); const authHeaders = robot?.getAuthHeaders();
@ -308,6 +309,8 @@ export class Coordinator {
if (!hasEnoughEntropy) return null; if (!hasEnoughEntropy) return null;
this.loadingRobot = token;
garage.updateRobot(token, this.shortAlias, { loading: true }); garage.updateRobot(token, this.shortAlias, { loading: true });
const newAttributes = await apiClient const newAttributes = await apiClient
@ -330,7 +333,8 @@ export class Coordinator {
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);
}); })
.finally(() => (this.loadingRobot = null));
garage.updateRobot(token, this.shortAlias, { garage.updateRobot(token, this.shortAlias, {
...newAttributes, ...newAttributes,

View File

@ -101,7 +101,7 @@ export class Federation {
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
this.updateEnabledCoordinators(); this.updateEnabledCoordinators();
for (const coor of Object.values(this.coordinators)) { for (const coor of Object.values(this.coordinators)) {
await coor.update(() => { coor.update(() => {
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1; this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
this.onCoordinatorSaved(); this.onCoordinatorSaved();
}); });
@ -110,10 +110,11 @@ export class Federation {
updateBook = async (): Promise<void> => { updateBook = async (): Promise<void> => {
this.loading = true; this.loading = true;
this.book = [];
this.triggerHook('onCoordinatorUpdate'); this.triggerHook('onCoordinatorUpdate');
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
for (const coor of Object.values(this.coordinators)) { for (const coor of Object.values(this.coordinators)) {
await coor.updateBook(() => { coor.updateBook(() => {
this.onCoordinatorSaved(); this.onCoordinatorSaved();
}); });
} }

View File

@ -106,6 +106,11 @@ class Garage {
return slot; return slot;
}; };
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
this.currentSlot = currentSlot;
this.triggerHook('onRobotUpdate');
};
// Robots // Robots
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = ( createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
token, token,

View File

@ -1,5 +1,6 @@
import i18n from '../i18n/Web'; import i18n from '../i18n/Web';
import { systemClient } from '../services/System'; import { systemClient } from '../services/System';
import { apiClient } from '../services/api';
import { getHost } from '../utils'; import { getHost } from '../utils';
export type Language = export type Language =
@ -42,8 +43,13 @@ class BaseSettings {
: i18n.resolvedLanguage.substring(0, 2); : i18n.resolvedLanguage.substring(0, 2);
const networkCookie = systemClient.getItem('settings_network'); const networkCookie = systemClient.getItem('settings_network');
this.network = networkCookie !== '' ? networkCookie : 'mainnet'; this.network = networkCookie && networkCookie !== '' ? networkCookie : 'mainnet';
this.host = getHost(); this.host = getHost();
const useProxy = systemClient.getItem('settings_use_proxy');
this.useProxy = window.NativeRobosats !== undefined && useProxy !== 'false';
apiClient.useProxy = this.useProxy;
} }
public frontend: 'basic' | 'pro' = 'basic'; public frontend: 'basic' | 'pro' = 'basic';
@ -56,6 +62,7 @@ class BaseSettings {
public host?: string; public host?: string;
public unsafeClient: boolean = false; public unsafeClient: boolean = false;
public selfhostedClient: boolean = false; public selfhostedClient: boolean = false;
public useProxy: boolean;
} }
export default BaseSettings; export default BaseSettings;

View File

@ -28,7 +28,7 @@ class SystemNativeClient implements SystemClient {
}; };
public setCookie: (key: string, value: string) => void = (key, value) => { public setCookie: (key: string, value: string) => void = (key, value) => {
delete window.NativeRobosats?.cookies[key]; window.NativeRobosats?.loadCookie({ key, value });
void window.NativeRobosats?.postMessage({ void window.NativeRobosats?.postMessage({
category: 'system', category: 'system',
type: 'setCookie', type: 'setCookie',

View File

@ -1,8 +1,12 @@
import { type ApiClient, type Auth } from '..'; import { type ApiClient, type Auth } from '..';
import { systemClient } from '../../System'; import { systemClient } from '../../System';
import ApiWebClient from '../ApiWebClient';
class ApiNativeClient implements ApiClient { class ApiNativeClient implements ApiClient {
private readonly assetsCache: Record<string, string> = {}; public useProxy = true;
private webClient: ApiClient = new ApiWebClient();
private readonly assetsPromises = new Map<string, Promise<string | undefined>>(); private readonly assetsPromises = new Map<string, Promise<string | undefined>>();
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
@ -51,6 +55,7 @@ class ApiNativeClient implements ApiClient {
public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> = public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> =
async (baseUrl, path, auth) => { async (baseUrl, path, auth) => {
if (!this.proxy) this.webClient.delete(baseUrl, path, auth);
return await window.NativeRobosats?.postMessage({ return await window.NativeRobosats?.postMessage({
category: 'http', category: 'http',
type: 'delete', type: 'delete',
@ -66,6 +71,7 @@ class ApiNativeClient implements ApiClient {
body: object, body: object,
auth?: Auth, auth?: Auth,
) => Promise<object | undefined> = async (baseUrl, path, body, auth) => { ) => Promise<object | undefined> = async (baseUrl, path, body, auth) => {
if (!this.proxy) this.webClient.post(baseUrl, path, body, auth);
return await window.NativeRobosats?.postMessage({ return await window.NativeRobosats?.postMessage({
category: 'http', category: 'http',
type: 'post', type: 'post',
@ -81,6 +87,7 @@ class ApiNativeClient implements ApiClient {
path, path,
auth, auth,
) => { ) => {
if (!this.proxy) this.webClient.get(baseUrl, path, auth);
return await window.NativeRobosats?.postMessage({ return await window.NativeRobosats?.postMessage({
category: 'http', category: 'http',
type: 'get', type: 'get',

View File

@ -1,6 +1,8 @@
import { type ApiClient, type Auth } from '..'; import { type ApiClient, type Auth } from '..';
class ApiWebClient implements ApiClient { class ApiWebClient implements ApiClient {
public useProxy = false;
private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => {
let headers = { let headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -7,6 +7,7 @@ export interface Auth {
} }
export interface ApiClient { export interface ApiClient {
useProxy: boolean;
post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>; post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>; put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise<object | undefined>;
get: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined>; get: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined>;

View File

@ -45,7 +45,6 @@ export default function federationLottery(federation: Federation): string[] {
// federation[shortAlias] = { badges:{ donatesToDevFund }}; // federation[shortAlias] = { badges:{ donatesToDevFund }};
// } // }
// console.log(federation)
// return federation; // return federation;
// } // }
@ -58,5 +57,4 @@ export default function federationLottery(federation: Federation): string[] {
// results.push(rankedCoordinators); // results.push(rankedCoordinators);
// } // }
// console.log(results)
// } // }

View File

@ -464,8 +464,8 @@
"The order has expired": "L'ordre ha expirat", "The order has expired": "L'ordre ha expirat",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "Reps via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "Reps via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "La teva última ordre #{{orderID}}", "Your last order #{{orderID}}": "La teva última ordre #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Fosc", "Dark": "Fosc",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Clar", "Light": "Clar",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "Nabídka vypršela", "The order has expired": "Nabídka vypršela",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nabídku nemůžeš zatím příjmout! Počkej {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nabídku nemůžeš zatím příjmout! Počkej {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Přirážka: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Přirážka: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Tvá poslední nabídka #{{orderID}}", "Your last order #{{orderID}}": "Tvá poslední nabídka #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "Die Order ist abgelaufen", "The order has expired": "Die Order ist abgelaufen",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kannst noch keine Order annehmen! Warte {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kannst noch keine Order annehmen! Warte {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}", "Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "The order has expired", "The order has expired": "The order has expired",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Your last order #{{orderID}}", "Your last order #{{orderID}}": "Your last order #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "La orden ha expirado", "The order has expired": "La orden ha expirado",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "¡No puedes tomar una orden aún! Espera {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "¡No puedes tomar una orden aún! Espera {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Tu última orden #{{orderID}}", "Your last order #{{orderID}}": "Tu última orden #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Oscuro", "Dark": "Oscuro",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Claro", "Light": "Claro",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "Eskaera iraungi da", "The order has expired": "Eskaera iraungi da",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Oraindik ezin duzu eskaerarik hartu! Itxaron{{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Oraindik ezin duzu eskaerarik hartu! Itxaron{{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}", "Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "L'ordre a expiré", "The order has expired": "L'ordre a expiré",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Vous ne pouvez pas encore prendre un ordre! Attendez {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Vous ne pouvez pas encore prendre un ordre! Attendez {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)",
"You receive via {{method}} {{amount}}": "Vous recevez via {{méthode}} {{montant}}", "You receive via {{method}} {{amount}}": "Vous recevez via {{méthode}} {{montant}}",
"You receive {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)",
"You send via Lightning {{amount}} Sats (Approx)": "Vous envoyez via Lightning {{amount}} Sats (environ)", "You send via Lightning {{amount}} Sats (Approx)": "Vous envoyez via Lightning {{amount}} Sats (environ)",
"You send via {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}", "Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Sombre", "Dark": "Sombre",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "L'ordine è scaduto", "The order has expired": "L'ordine è scaduto",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "La posizione appuntata è approssimativa. La posizione esatta del luogo dell'incontro deve essere indicata nella chat crittografata.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "La posizione appuntata è approssimativa. La posizione esatta del luogo dell'incontro deve essere indicata nella chat crittografata.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Non puoi ancora accettare un ordine! Aspetta {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Non puoi ancora accettare un ordine! Aspetta {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)",
"You receive via {{method}} {{amount}}": "Ricevi {{amount}} via {{method}}", "You receive via {{method}} {{amount}}": "Ricevi {{amount}} via {{method}}",
"You receive {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)",
"You send via Lightning {{amount}} Sats (Approx)": "Invii {{amount}} Sats via Lightning (approssimativo)", "You send via Lightning {{amount}} Sats (Approx)": "Invii {{amount}} Sats via Lightning (approssimativo)",
"You send via {{method}} {{amount}}": "Invii {{amount}} via {{method}}", "You send via {{method}} {{amount}}": "Invii {{amount}} via {{method}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}", "Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Scuro", "Dark": "Scuro",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Chiaro", "Light": "Chiaro",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "注文は期限切れになりました", "The order has expired": "注文は期限切れになりました",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "まだ注文を受け取ることはできません!{{timeMin}}分{{timeSec}}秒待ってください", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "まだ注文を受け取ることはできません!{{timeMin}}分{{timeSec}}秒待ってください",
"You receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Satsを受け取ります",
"You receive via {{method}} {{amount}}": "{{method}}で{{amount}}を受け取ります", "You receive via {{method}} {{amount}}": "{{method}}で{{amount}}を受け取ります",
"You receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Satsを受け取ります",
"You send via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Satsを送信します", "You send via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Satsを送信します",
"You send via {{method}} {{amount}}": "{{method}}で{{amount}}を送信します", "You send via {{method}} {{amount}}": "{{method}}で{{amount}}を送信します",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "前回のオーダー #{{orderID}}", "Your last order #{{orderID}}": "前回のオーダー #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "ダーク", "Dark": "ダーク",
"Disabled": "Disabled",
"Fiat": "フィアット", "Fiat": "フィアット",
"Light": "ライト", "Light": "ライト",
"Mainnet": "メインネット", "Mainnet": "メインネット",

View File

@ -464,8 +464,8 @@
"The order has expired": "Zamówienie wygasło", "The order has expired": "Zamówienie wygasło",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nie możesz jeszcze przyjąć zamówienia! Czekać {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nie możesz jeszcze przyjąć zamówienia! Czekać {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Your last order #{{orderID}}", "Your last order #{{orderID}}": "Your last order #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "A ordem expirou", "The order has expired": "A ordem expirou",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Você ainda não pode fazer um pedido! Espere {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Você ainda não pode fazer um pedido! Espere {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Sua última ordem #{{orderID}}", "Your last order #{{orderID}}": "Sua última ordem #{{orderID}}",
"finished order": "ordem finalizada", "finished order": "ordem finalizada",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "Срок действия ордера истёк", "The order has expired": "Срок действия ордера истёк",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "Закрепленное местоположение является приблизительным. Точное местоположение места встречи необходимо сообщить в зашифрованном чате.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "Закрепленное местоположение является приблизительным. Точное местоположение места встречи необходимо сообщить в зашифрованном чате.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Вы ещё не можете взять ордер! Подождите {{timeMin}}м {{timeSec}}с", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Вы ещё не можете взять ордер! Подождите {{timeMin}}м {{timeSec}}с",
"You receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)",
"You receive via {{method}} {{amount}}": "Вы получаете через {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "Вы получаете через {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)",
"You send via Lightning {{amount}} Sats (Approx)": "Вы отправляете через Lightning {{amount}} Сатоши (приблизительно)", "You send via Lightning {{amount}} Sats (Approx)": "Вы отправляете через Lightning {{amount}} Сатоши (приблизительно)",
"You send via {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}", "You send via {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}", "Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Темный", "Dark": "Темный",
"Disabled": "Disabled",
"Fiat": "Фиат", "Fiat": "Фиат",
"Light": "Светлый", "Light": "Светлый",
"Mainnet": "Основная сеть", "Mainnet": "Основная сеть",

View File

@ -464,8 +464,8 @@
"The order has expired": "Ordern har förfallit", "The order has expired": "Ordern har förfallit",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kan inte ta en order ännu! Vänta {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kan inte ta en order ännu! Vänta {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Din senaste order #{{orderID}}", "Your last order #{{orderID}}": "Din senaste order #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "Agizo limekwisha muda", "The order has expired": "Agizo limekwisha muda",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Hauwezi kuchukua agizo bado! Subiri {{timeMin}}m {{timeSec}}s", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Hauwezi kuchukua agizo bado! Subiri {{timeMin}}m {{timeSec}}s",
"You receive {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)",
"You receive via {{method}} {{amount}}": "Utapokea kupitia {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "Utapokea kupitia {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)",
"You send via Lightning {{amount}} Sats (Approx)": "Utatuma kupitia Lightning {{amount}} Sats (Takriban)", "You send via Lightning {{amount}} Sats (Approx)": "Utatuma kupitia Lightning {{amount}} Sats (Takriban)",
"You send via {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}", "You send via {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}", "Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Giza", "Dark": "Giza",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Nuru", "Light": "Nuru",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "รายการหมดอายุแล้ว", "The order has expired": "รายการหมดอายุแล้ว",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "คุณยังไม่สามารถดำเนินรายการได้! รออีก {{timeMin}} นาที {{timeSec}} วินาที", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "คุณยังไม่สามารถดำเนินรายการได้! รออีก {{timeMin}} นาที {{timeSec}} วินาที",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}", "Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Dark", "Dark": "Dark",
"Disabled": "Disabled",
"Fiat": "Fiat", "Fiat": "Fiat",
"Light": "Light", "Light": "Light",
"Mainnet": "Mainnet", "Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"The order has expired": "订单已到期", "The order has expired": "订单已到期",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暂时还不能吃单!请等{{timeMin}}分 {{timeSec}}秒", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暂时还不能吃单!请等{{timeMin}}分 {{timeSec}}秒",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "你通过{{method}}接收{{amount}}", "You receive via {{method}} {{amount}}": "你通过{{method}}接收{{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "你通过{{method}}发送{{amount}}", "You send via {{method}} {{amount}}": "你通过{{method}}发送{{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}", "Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "深色", "Dark": "深色",
"Disabled": "Disabled",
"Fiat": "法币", "Fiat": "法币",
"Light": "浅色", "Light": "浅色",
"Mainnet": "主网", "Mainnet": "主网",

View File

@ -464,8 +464,8 @@
"The order has expired": "訂單已到期", "The order has expired": "訂單已到期",
"The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.",
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暫時還不能吃單!請等{{timeMin}}分 {{timeSec}}秒", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暫時還不能吃單!請等{{timeMin}}分 {{timeSec}}秒",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You receive via {{method}} {{amount}}": "你通過{{method}}接收{{amount}}", "You receive via {{method}} {{amount}}": "你通過{{method}}接收{{amount}}",
"You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)",
"You send via {{method}} {{amount}}": "你通過{{method}}發送{{amount}}", "You send via {{method}} {{amount}}": "你通過{{method}}發送{{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}", "Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}",
"finished order": "finished order", "finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx", "#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "深色", "Dark": "深色",
"Disabled": "Disabled",
"Fiat": "法幣", "Fiat": "法幣",
"Light": "淺色", "Light": "淺色",
"Mainnet": "主網", "Mainnet": "主網",

View File

@ -71,6 +71,7 @@ const App = () => {
loadCookie('settings_mode'); loadCookie('settings_mode');
loadCookie('settings_light_qr'); loadCookie('settings_light_qr');
loadCookie('settings_network'); loadCookie('settings_network');
loadCookie('settings_use_proxy');
loadCookie('garage_slots').then(() => injectMessageResolve(responseId)); loadCookie('garage_slots').then(() => injectMessageResolve(responseId));
}; };

View File

@ -151,8 +151,8 @@ android {
applicationId "com.robosats" applicationId "com.robosats"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 2
versionName "0.6.0-alpha" versionName "0.6.2-alpha"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) { if (isNewArchitectureEnabled()) {

View File

@ -0,0 +1,12 @@
<p>RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.</p>
<p><br><b>Features:</b></p><ul>
<li>Privacy focused: your robot avatar is deterministically generated, no need for registration.</li>
<li>More than 10 languages available and over 60 fiat currencies</li>
<li>Safe: simply lock a lightning hodl invoice and show you are real and committed.</li>
<li>No data collection. Your communication with your peer is PGP encrypted, only you can read it.</li>
<li>Lightning fast: the average sovereign trade finishes in ~ 8 minutes. Faster than a single block confirmation!</li>
<li>Fully collateralized escrow: your peer is always committed and cannot run away with the funds.</li>
<li>Strong incentives system: attempts of cheating are penalized with the slashing of the Sats in the fidelity bond.</li>
<li>Guides and video tutorials available at https://learn.robosats.com/watch/en</li>
</ul>
<p>You can join other cool Robots and get community support at <a href="https://t.me/robosats">our Telegram group</a>.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1 @@
Simple and private app to exchange bitcoin for national currencies.

View File

@ -1,12 +1,12 @@
{ {
"name": "robosats", "name": "robosats",
"version": "0.6.0", "version": "0.6.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "robosats", "name": "robosats",
"version": "0.6.0", "version": "0.6.2",
"dependencies": { "dependencies": {
"@react-native-clipboard/clipboard": "^1.13.2", "@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/netinfo": "^11.3.0", "@react-native-community/netinfo": "^11.3.0",
@ -37,7 +37,7 @@
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1", "metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.2.5", "prettier": "^3.3.2",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"typescript": "^5.4.5" "typescript": "^5.4.5"
} }
@ -12714,9 +12714,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.2.5", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"

View File

@ -1,6 +1,6 @@
{ {
"name": "robosats", "name": "robosats",
"version": "0.6.0", "version": "0.6.2",
"private": true, "private": true,
"scripts": { "scripts": {
"android": "react-native run-android", "android": "react-native run-android",
@ -41,7 +41,7 @@
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1", "metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.2.5", "prettier": "^3.3.2",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"typescript": "^5.4.5" "typescript": "^5.4.5"
}, },

View File

@ -1,41 +1,38 @@
RoboSats v0.6.0 is now out! :rocket: RoboSats v0.6.2 is now out! :rocket:
# Changes # Changes
## New Features ## What's new
### Decentralization In v0.6.2 we only fix a regression introduced in v0.6.2 that makes the depth chart crash.
RoboSats v0.6.0 introduces a major upgrade, introducing the RoboSats Federation, a decentralized system of independent coordinators to host orders, enhancing the platform's robustness and user experience. This version is a significant step towards decentralization, allowing users to interact with any coordinator seamlessly.
It's crucial to choose trustworthy coordinators due to the potential risks of malicious activity. The federated client is available for testing at specific URLs, with a stable release planned. Key features include multiple coordinators competing for users, decentralized instances for increased robustness, and a focus on coordinators profiles and trust.
Learn more in https://learn.robosats.com/robosats/update/pre-release-robosats-decentralized/
### New avatar generator
Your Robot identity is now generated in your client app, when previously, the robot identity was created by the coordinator. This allows now for super-fast Robot avatar and nickname generation that works even if your connection to the coordinators is down. The new robot avatars, are in fact, more diverse and better looking, however, the same token will now yield a different avatar when compared to v0.5.4 (but the robot identity remains the same, also keeping the same nickname).
## Bug Fixes and Performance Improvements
The whole app architecture is new. There might be new bugs, solved bugs, worse performance and better performance: who knows!! :D
## Special thanks
Special thanks to @KoalaSat who has driven some of the largest development pushes needed to get The Federation Layer fully working.
# Android # Android
The Android app is currently not supported on this early phase of the Federated app. **[Click to download universal RoboSats APK for Android](https://github.com/RoboSats/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk)**
Smaller bundles for each CPU architecture available in the attachments.
### Verify the app using GPG:
1. [Download the ascii armored signature](https://github.com/Reckless-Satoshi/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk.asc)
2. Run this command on a directory that contains the apk file and and the ascii armored signature.
`gpg --verify robosats-v0.6.2.alpha-universal.apk.asc`
3. Verify the signer is actually Reckless-Satoshi (fingerprints match): [B4AB5F19113D4125DDF217739C4585B561315571](https://keys.openpgp.org/vks/v1/by-fingerprint/B4AB5F19113D4125DDF217739C4585B561315571)
Alternatively you can also verify with the release with the SHA256 checksum.
# Docker Images # Docker Images
[Coordinator Backend Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.0-alpha) [Coordinator Backend Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.2-alpha)
```bash ```bash
docker pull recksato/robosats:v0.6.0-alpha docker pull recksato/robosats:v0.6.2-alpha
``` ```
[Client App Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.0-alpha) [Client App Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.2-alpha)
```bash ```bash
docker pull recksato/robosats-client:v0.6.0-alpha docker pull recksato/robosats-client:v0.6.2-alpha
``` ```
See [nodeapp/docker-compose.yml](https://github.com/Reckless-Satoshi/robosats/blob/2cd9d748706a8dcc0f03006b483acc6000e0572a/nodeapp/docker-compose.yml) for an example docker-compose usage of the `robosats-client` image. See [nodeapp/docker-compose.yml](https://github.com/Reckless-Satoshi/robosats/blob/2cd9d748706a8dcc0f03006b483acc6000e0572a/nodeapp/docker-compose.yml) for an example docker-compose usage of the `robosats-client` image.

View File

@ -1,4 +1,4 @@
django==5.0.4 django==5.0.6
django-admin-relation-links==0.2.5 django-admin-relation-links==0.2.5
django-celery-beat==2.6.0 django-celery-beat==2.6.0
django-celery-results==2.5.1 django-celery-results==2.5.1
@ -16,11 +16,10 @@ Pillow==10.1.0
python-decouple==3.8 python-decouple==3.8
requests==2.31.0 requests==2.31.0
ring==0.10.1 ring==0.10.1
git+https://github.com/RoboSats/Robohash.git@master
gunicorn==22.0.0 gunicorn==22.0.0
psycopg2==2.9.9 psycopg2==2.9.9
SQLAlchemy==2.0.16 SQLAlchemy==2.0.16
django-import-export==3.3.8 django-import-export==4.0.7
requests[socks] requests[socks]
shapely==2.0.4 shapely==2.0.4
python-gnupg==0.5.2 python-gnupg==0.5.2

View File

@ -1,16 +1,13 @@
import hashlib import hashlib
from datetime import timedelta from datetime import timedelta
from pathlib import Path
from channels.db import database_sync_to_async from channels.db import database_sync_to_async
from channels.middleware import BaseMiddleware from channels.middleware import BaseMiddleware
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User, update_last_login from django.contrib.auth.models import AnonymousUser, User, update_last_login
from django.utils import timezone from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse from django.http import JsonResponse
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from robohash import Robohash
from api.nick_generator.nick_generator import NickGenerator from api.nick_generator.nick_generator import NickGenerator
from api.utils import base91_to_hex, hex_to_base91, is_valid_token, validate_pgp_keys from api.utils import base91_to_hex, hex_to_base91, is_valid_token, validate_pgp_keys
@ -19,9 +16,6 @@ NickGen = NickGenerator(
lang="English", use_adv=False, use_adj=True, use_noun=True, max_num=999 lang="English", use_adv=False, use_adj=True, use_noun=True, max_num=999
) )
avatar_path = Path(settings.AVATAR_ROOT)
avatar_path.mkdir(parents=True, exist_ok=True)
class DisableCSRFMiddleware(object): class DisableCSRFMiddleware(object):
def __init__(self, get_response): def __init__(self, get_response):
@ -164,21 +158,6 @@ class RobotTokenSHA256AuthenticationMiddleWare:
if not user.robot.encrypted_private_key: if not user.robot.encrypted_private_key:
user.robot.encrypted_private_key = encrypted_private_key user.robot.encrypted_private_key = encrypted_private_key
# Generate avatar. Does not replace if existing.
image_path = avatar_path.joinpath(nickname + ".webp")
if not image_path.exists():
rh = Robohash(hash)
rh.assemble(roboset="set1", bgset="any") # for backgrounds ON
with open(image_path, "wb") as f:
rh.img.save(f, format="WEBP", quality=80)
image_small_path = avatar_path.joinpath(nickname + ".small.webp")
with open(image_small_path, "wb") as f:
resized_img = rh.img.resize((80, 80))
resized_img.save(f, format="WEBP", quality=80)
user.robot.avatar = "static/assets/avatars/" + nickname + ".webp"
update_last_login(None, user) update_last_login(None, user)
user.save() user.save()

View File

@ -23,8 +23,6 @@ from .celery.conf import * # noqa
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_URL = "/static/"
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
@ -32,8 +30,10 @@ STATIC_URL = "/static/"
SECRET_KEY = config("SECRET_KEY") SECRET_KEY = config("SECRET_KEY")
DEBUG = False DEBUG = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "static/" STATIC_URL = "static/"
STATIC_ROOT = "/usr/src/static/"
# RoboSats version # RoboSats version
with open("version.json") as f: with open("version.json") as f:
@ -42,9 +42,6 @@ with open("version.json") as f:
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
if config("DEVELOPMENT", default=False): if config("DEVELOPMENT", default=False):
DEBUG = True DEBUG = True
STATIC_ROOT = "frontend/static/"
AVATAR_ROOT = STATIC_ROOT + "assets/avatars/"
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
config("HOST_NAME"), config("HOST_NAME"),
@ -228,10 +225,6 @@ USE_I18N = True
USE_TZ = True USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "static/"
ASGI_APPLICATION = "robosats.routing.application" ASGI_APPLICATION = "robosats.routing.application"
CHANNEL_LAYERS = { CHANNEL_LAYERS = {

View File

@ -10,9 +10,9 @@ curl --parallel -o lightning.proto https://raw.githubusercontent.com/lightningne
-o router.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto \ -o router.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto \
-o signer.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto \ -o signer.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto \
-o verrpc.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto \ -o verrpc.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto \
-o hold.proto https://raw.githubusercontent.com/daywalker90/lightning/cln-grpc-hold/proto/hold.proto \ -o hold.proto https://raw.githubusercontent.com/daywalker90/holdinvoice/master/proto/hold.proto \
-o primitives.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/primitives.proto \ -o primitives.proto https://raw.githubusercontent.com/ElementsProject/lightning/v24.05/cln-grpc/proto/primitives.proto \
-o node.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/node.proto -o node.proto https://raw.githubusercontent.com/ElementsProject/lightning/v24.05/cln-grpc/proto/node.proto
echo -n "Done\nBuilding api from GRPC specs..." echo -n "Done\nBuilding api from GRPC specs..."
python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto invoices.proto router.proto signer.proto verrpc.proto python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto invoices.proto router.proto signer.proto verrpc.proto

7
tests/README.md Normal file
View File

@ -0,0 +1,7 @@
# Run e2e tests
```
docker compose -f docker-tests.yml --env-file tests/compose.env up -d
docker exec coordinator coverage run manage.py test
docker exec coordinator coverage report
```

File diff suppressed because it is too large Load Diff

17
tests/bitcoind/entrypoint.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
# Start bitcoind in the background
bitcoind "$@" &
# Wait for bitcoind to be ready
while ! bitcoin-cli --regtest --rpcuser=test --rpcpassword=test getblockchaininfo 2>/dev/null | grep '"verificationprogress":'; do
echo "Waiting for bitcoind to be ready..."
sleep 1
done
# Run initialization commands
bitcoin-cli --regtest --rpcuser=test --rpcpassword=test createwallet default
bitcoin-cli --regtest --rpcuser=test --rpcpassword=test generatetoaddress 1 $(bitcoin-cli --regtest --rpcuser=test --rpcpassword=test getnewaddress)
# Bring bitcoind to the foreground
wait

View File

@ -2,6 +2,6 @@ ROBOSATS_ENVS_FILE=".env-sample"
BITCOIND_VERSION='24.0.1' BITCOIND_VERSION='24.0.1'
LND_VERSION='v0.17.0-beta' LND_VERSION='v0.17.0-beta'
CLN_VERSION='v23.08.1' CLN_VERSION='v24.05'
REDIS_VERSION='7.2.1' REDIS_VERSION='7.2.1'
POSTGRES_VERSION='14.2' POSTGRES_VERSION='14.2'

View File

@ -1,5 +1,5 @@
{ {
"major": 0, "major": 0,
"minor": 6, "minor": 6,
"patch": 0 "patch": 2
} }