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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -90,9 +90,9 @@ class CLNNode:
@classmethod
def decode_payreq(cls, invoice):
"""Decodes a lightning payment request (invoice)"""
request = hold_pb2.DecodeBolt11Request(bolt11=invoice)
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
response = holdstub.DecodeBolt11(request)
nodestub = node_pb2_grpc.NodeStub(cls.node_channel)
request = node_pb2.DecodeRequest(string=invoice)
response = nodestub.Decode(request)
return response
@classmethod
@ -236,7 +236,7 @@ class CLNNode:
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
response = holdstub.HoldInvoiceCancel(request)
return response.state == hold_pb2.HoldInvoiceCancelResponse.Holdstate.CANCELED
return response.state == hold_pb2.Holdstate.CANCELED
@classmethod
def settle_hold_invoice(cls, preimage):
@ -247,7 +247,7 @@ class CLNNode:
holdstub = hold_pb2_grpc.HoldStub(cls.hold_channel)
response = holdstub.HoldInvoiceSettle(request)
return response.state == hold_pb2.HoldInvoiceSettleResponse.Holdstate.SETTLED
return response.state == hold_pb2.Holdstate.SETTLED
@classmethod
def gen_hold_invoice(
@ -272,7 +272,7 @@ class CLNNode:
request = hold_pb2.HoldInvoiceRequest(
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}",
expiry=invoice_expiry,
cltv=cltv_expiry_blocks,
@ -286,7 +286,7 @@ class CLNNode:
hold_payment["preimage"] = preimage.hex()
hold_payment["payment_hash"] = response.payment_hash.hex()
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(
datetime.fromtimestamp(response.expires_at)
@ -309,13 +309,13 @@ class CLNNode:
# 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
# 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
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.SETTLED:
if response.state == hold_pb2.Holdstate.SETTLED:
pass
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.CANCELED:
if response.state == hold_pb2.Holdstate.CANCELED:
pass
if response.state == hold_pb2.HoldInvoiceLookupResponse.Holdstate.ACCEPTED:
if response.state == hold_pb2.Holdstate.ACCEPTED:
lnpayment.expiry_height = response.htlc_expiry
lnpayment.status = LNPayment.Status.LOCKED
lnpayment.save(update_fields=["expiry_height", "status"])
@ -359,7 +359,7 @@ class CLNNode:
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).
# 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):
print(str(e))
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
# 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
if routing_budget_ppm == 0:
@ -438,8 +438,10 @@ class CLNNode:
route_cost = 0
# ...add up the cost of every hinted hop...
for hop_hint in hinted_route.hops:
route_cost += hop_hint.feebase.msat / 1_000
route_cost += hop_hint.feeprop * num_satoshis / 1_000_000
route_cost += hop_hint.fee_base_msat.msat / 1_000
route_cost += (
hop_hint.fee_proportional_millionths * num_satoshis / 1_000_000
)
# ...and store the cost of the route to the array
routes_cost.append(route_cost)
@ -466,7 +468,7 @@ class CLNNode:
return payout
payout["created_at"] = timezone.make_aware(
datetime.fromtimestamp(payreq_decoded.timestamp)
datetime.fromtimestamp(payreq_decoded.created_at)
)
payout["expires_at"] = payout["created_at"] + timedelta(
seconds=payreq_decoded.expiry
@ -869,4 +871,4 @@ class CLNNode:
else:
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
from decimal import Decimal
from django.core.validators import MinValueValidator
from django.db import models
from django.utils import timezone
@ -18,7 +19,7 @@ class Currency(models.Model):
decimal_places=4,
default=None,
null=True,
validators=[MinValueValidator(0)],
validators=[MinValueValidator(Decimal(0))],
)
timestamp = models.DateTimeField(default=timezone.now)

View File

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

View File

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

View File

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

View File

@ -494,7 +494,8 @@ class OrderPublicSerializer(serializers.ModelSerializer):
maker_nick = serializers.CharField(required=False)
maker_hash_id = serializers.CharField(required=False)
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(
help_text="Price in order's fiat currency", required=False

View File

@ -1,5 +1,4 @@
from datetime import datetime, timedelta
from pathlib import Path
from decouple import config
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"))
RETRY_TIME = int(config("RETRY_TIME"))
avatar_path = Path(settings.AVATAR_ROOT)
avatar_path.mkdir(parents=True, exist_ok=True)
class MakerView(CreateAPIView):
serializer_class = MakeOrderSerializer

View File

@ -3,6 +3,8 @@
# 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 -generate 101
@ -27,6 +29,8 @@ services:
- "6379:6379"
volumes:
- bitcoin:/bitcoin/.bitcoin/
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
entrypoint: ["/entrypoint.sh"]
command:
--txindex=1
--printtoconsole
@ -77,16 +81,16 @@ services:
network_mode: service:bitcoind
coordinator-CLN:
image: elementsproject/lightningd:${CLN_VERSION:-v23.08.1}
image: elementsproject/lightningd:${CLN_VERSION:-v24.05}
restart: always
container_name: coordinator-CLN
environment:
LIGHTNINGD_NETWORK: 'regtest'
volumes:
- 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
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:
- bitcoind
network_mode: service:bitcoind

View File

@ -1,7 +1,7 @@
FROM debian:bullseye-slim as builder
ARG DEBIAN_FRONTEND=noninteractive
ARG LIGHTNINGD_VERSION=v23.08
ARG LIGHTNINGD_VERSION=v24.05
RUN apt-get update -qq && \
apt-get install -qq -y --no-install-recommends \
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
WORKDIR /opt/lightningd
RUN git clone --recursive --branch cln-grpc-hold https://github.com/daywalker90/lightning.git /tmp/cln-grpc-hold
RUN cd /tmp/cln-grpc-hold \
RUN git clone https://github.com/daywalker90/holdinvoice.git /tmp/holdinvoice
RUN cd /tmp/holdinvoice \
&& 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 entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

View File

@ -5,6 +5,6 @@ addr=statictor:127.0.0.1:9051
grpc-port=9999
grpc-hold-port=9998
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
# 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" &
fg %-
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
cp /tmp/cln-grpc-hold /root/.lightning/plugins/cln-grpc-hold
cp /tmp/holdinvoice /root/.lightning/plugins/holdinvoice
if [ ! -f /root/.lightning/config ]; then
cp /tmp/config /root/.lightning/config
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> |
|:---|:---|:--:|:--:|:--:|:--:|:--:|:--:|
|[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}}|
|[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}}|
|[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}}|
|[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}}|
|[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}}|
|[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}}|
@ -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/)
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)
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)
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)
Works as expected. Some payments and locks may fail depending on the Lightning node the channel is created to. Channels to ASINQ work fine.
### Electrum (Mobile & Desktop)
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)
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)
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)
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
### 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.
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
info:
title: RoboSats REST API
version: 0.6.0
version: 0.6.2
x-logo:
url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
backgroundColor: '#FFFFFF'
@ -1638,7 +1638,7 @@ components:
type: string
maker_status:
type: string
description: Status of the nick - "Active" or "Inactive"
description: Status of the nick - "Active", "Seen Recently" or "Inactive"
price:
type: number
format: double

View File

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

View File

@ -1712,7 +1712,7 @@ components:
type: string
maker_status:
type: string
description: Status of the nick - "Active" or "Inactive"
description: Status of the nick - "Active", "Seen Recently" or "Inactive"
price:
type: number
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",
"version": "0.6.0",
"version": "0.6.2",
"description": "",
"main": "index.js",
"scripts": {
@ -41,7 +41,7 @@
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.6.1",
"prettier": "^3.2.5",
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"webpack": "^5.89.0",
@ -57,10 +57,10 @@
"@mui/lab": "^5.0.0-alpha.136",
"@mui/material": "^5.15.14",
"@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",
"@nivo/core": "^0.85.1",
"@nivo/line": "^0.85.1",
"@nivo/core": "^0.86.0",
"@nivo/line": "^0.86.0",
"base-ex": "^0.8.1",
"country-flag-icons": "^1.5.11",
"date-fns": "^2.30.0",

View File

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

View File

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

View File

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

View File

@ -178,7 +178,7 @@ const OrderDetails = ({
: coordinator.info?.taker_fee ?? 0;
const defaultRoutingBudget = 0.001;
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 (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 { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import {
@ -28,17 +28,19 @@ import {
QrCode,
} from '@mui/icons-material';
import { systemClient } from '../../services/System';
import { TorIcon } from '../Icons';
import SwapCalls from '@mui/icons-material/SwapCalls';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
interface SettingsFormProps {
dense?: boolean;
}
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
const { fav, setFav, origin, hostUrl, settings, setSettings } =
useContext<UseAppStoreType>(AppContext);
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const theme = useTheme();
const { t } = useTranslation();
const fontSizes = [
@ -237,6 +239,29 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
</ToggleButton>
</ToggleButtonGroup>
</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>
</Grid>
</Grid>

View File

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

View File

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

View File

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

View File

@ -111,12 +111,14 @@ export const FederationContextProvider = ({
}, []);
useEffect(() => {
// On bitcoin network change we reset book, limits and federation info and fetch everything again
if (window.NativeRobosats === undefined || torStatus === 'ON') {
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
void federation.updateUrl(origin, settings, hostUrl);
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 => {
let newDelay = defaultDelay;
@ -178,15 +180,6 @@ export const FederationContextProvider = ({
if (page === 'offers') void federation.updateBook();
}, [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
useEffect(() => {
const slot = garage.getSlot();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ export interface Auth {
}
export interface ApiClient {
useProxy: boolean;
post: (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>;

View File

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

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "Envies via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "La teva última ordre #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Fosc",
"Disabled": "Disabled",
"Fiat": "Fiat",
"Light": "Clar",
"Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{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}}",
"finished order": "finished order",
"#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": "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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}",
"finished order": "finished order",
"#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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
"finished order": "finished order",
"#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": "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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Tu última orden #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Oscuro",
"Disabled": "Disabled",
"Fiat": "Fiat",
"Light": "Claro",
"Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}",
"finished order": "finished order",
"#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": "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.",
"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 {{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 {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Sombre",
"Disabled": "Disabled",
"Fiat": "Fiat",
"Light": "Light",
"Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "Invii {{amount}} via {{method}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Scuro",
"Disabled": "Disabled",
"Fiat": "Fiat",
"Light": "Chiaro",
"Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"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 receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Satsを受け取ります",
"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 {{method}} {{amount}}": "{{method}}で{{amount}}を送信します",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "前回のオーダー #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "ダーク",
"Disabled": "Disabled",
"Fiat": "フィアット",
"Light": "ライト",
"Mainnet": "メインネット",

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Your last order #{{orderID}}",
"finished order": "finished order",
"#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": "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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Sua última ordem #{{orderID}}",
"finished order": "ordem finalizada",
"#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 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 receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{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 {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Темный",
"Disabled": "Disabled",
"Fiat": "Фиат",
"Light": "Светлый",
"Mainnet": "Основная сеть",

View File

@ -464,8 +464,8 @@
"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.",
"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 {{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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Din senaste order #{{orderID}}",
"finished order": "finished order",
"#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": "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.",
"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 {{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 {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "Giza",
"Disabled": "Disabled",
"Fiat": "Fiat",
"Light": "Nuru",
"Mainnet": "Mainnet",

View File

@ -464,8 +464,8 @@
"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 receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"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 {{method}} {{amount}}": "You send via {{method}} {{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}",
"finished order": "finished order",
"#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 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 receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"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 {{method}} {{amount}}": "你通过{{method}}发送{{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "深色",
"Disabled": "Disabled",
"Fiat": "法币",
"Light": "浅色",
"Mainnet": "主网",

View File

@ -464,8 +464,8 @@
"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 receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)",
"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 {{method}} {{amount}}": "你通過{{method}}發送{{amount}}",
"{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%",
@ -489,7 +489,9 @@
"Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}",
"finished order": "finished order",
"#43": "Phrases in components/SettingsForm/index.tsx",
"Build-in": "Build-in",
"Dark": "深色",
"Disabled": "Disabled",
"Fiat": "法幣",
"Light": "淺色",
"Mainnet": "主網",

View File

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

View File

@ -151,8 +151,8 @@ android {
applicationId "com.robosats"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "0.6.0-alpha"
versionCode 2
versionName "0.6.2-alpha"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
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",
"version": "0.6.0",
"version": "0.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "robosats",
"version": "0.6.0",
"version": "0.6.2",
"dependencies": {
"@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/netinfo": "^11.3.0",
@ -37,7 +37,7 @@
"eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.2.5",
"prettier": "^3.3.2",
"react-test-renderer": "18.2.0",
"typescript": "^5.4.5"
}
@ -12714,9 +12714,9 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"

View File

@ -1,6 +1,6 @@
{
"name": "robosats",
"version": "0.6.0",
"version": "0.6.2",
"private": true,
"scripts": {
"android": "react-native run-android",
@ -41,7 +41,7 @@
"eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.2.5",
"prettier": "^3.3.2",
"react-test-renderer": "18.2.0",
"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
## New Features
### Decentralization
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.
## What's new
In v0.6.2 we only fix a regression introduced in v0.6.2 that makes the depth chart crash.
# 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
[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
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
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.

View File

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

View File

@ -1,16 +1,13 @@
import hashlib
from datetime import timedelta
from pathlib import Path
from channels.db import database_sync_to_async
from channels.middleware import BaseMiddleware
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User, update_last_login
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from rest_framework.authtoken.models import Token
from robohash import Robohash
from api.nick_generator.nick_generator import NickGenerator
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
)
avatar_path = Path(settings.AVATAR_ROOT)
avatar_path.mkdir(parents=True, exist_ok=True)
class DisableCSRFMiddleware(object):
def __init__(self, get_response):
@ -164,21 +158,6 @@ class RobotTokenSHA256AuthenticationMiddleWare:
if not user.robot.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)
user.save()

View File

@ -23,8 +23,6 @@ from .celery.conf import * # noqa
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_URL = "/static/"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
@ -32,8 +30,10 @@ STATIC_URL = "/static/"
SECRET_KEY = config("SECRET_KEY")
DEBUG = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "static/"
STATIC_ROOT = "/usr/src/static/"
# RoboSats version
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!
if config("DEVELOPMENT", default=False):
DEBUG = True
STATIC_ROOT = "frontend/static/"
AVATAR_ROOT = STATIC_ROOT + "assets/avatars/"
ALLOWED_HOSTS = [
config("HOST_NAME"),
@ -228,10 +225,6 @@ USE_I18N = 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"
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 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 hold.proto https://raw.githubusercontent.com/daywalker90/lightning/cln-grpc-hold/proto/hold.proto \
-o primitives.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/primitives.proto \
-o node.proto https://raw.githubusercontent.com/ElementsProject/lightning/v23.08/cln-grpc/proto/node.proto
-o hold.proto https://raw.githubusercontent.com/daywalker90/holdinvoice/master/proto/hold.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/v24.05/cln-grpc/proto/node.proto
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

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'
LND_VERSION='v0.17.0-beta'
CLN_VERSION='v23.08.1'
CLN_VERSION='v24.05'
REDIS_VERSION='7.2.1'
POSTGRES_VERSION='14.2'

View File

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