mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Merge branch 'main' into android-notification-system
This commit is contained in:
commit
9a8a821b7b
@ -175,3 +175,6 @@ SLASHED_BOND_REWARD_SPLIT = 0.5
|
||||
|
||||
# Username for HTLCs escrows
|
||||
ESCROW_USERNAME = 'admin'
|
||||
|
||||
#Social
|
||||
NOSTR_NSEC = 'nsec1vxhs2zc4kqe0dhz4z2gfrdyjsrwf8pg3neeqx6w4nl8djfzdp0dqwd6rxh'
|
||||
|
139
.github/workflows/desktop-build.yml
vendored
Normal file
139
.github/workflows/desktop-build.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
name: "Build: Desktop"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
semver:
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths: [ "desktopApp", "frontend" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths: [ "desktopApp", "frontend" ]
|
||||
|
||||
jobs:
|
||||
build-desktop:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd desktopApp
|
||||
npm install
|
||||
|
||||
- name: Install zip utility
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y zip
|
||||
|
||||
- name: Build for macOS
|
||||
run: |
|
||||
cd desktopApp
|
||||
npm run package-mac
|
||||
|
||||
- name: Build for Windows
|
||||
run: |
|
||||
cd desktopApp
|
||||
npm run package-win
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
cd desktopApp
|
||||
npm run package-linux
|
||||
|
||||
- name: Create ZIP for macOS Build
|
||||
run: |
|
||||
cd desktopApp/release-builds/
|
||||
zip -r /desktopApp-mac.zip ./Robosats-darwin-x64/*
|
||||
|
||||
- name: Create ZIP for Windows Build
|
||||
run: |
|
||||
cd desktopApp/release-builds/
|
||||
zip -r /desktopApp-win.zip ./Robosats-win32-ia32/*
|
||||
|
||||
- name: Create ZIP for Linux Build
|
||||
run: |
|
||||
cd desktopApp/release-builds/
|
||||
zip -r /desktopApp-linux.zip ./Robosats-linux-x64/*
|
||||
|
||||
- name: 'Get Commit Hash'
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v3
|
||||
|
||||
- name: 'Upload mac-build Release Artifact (for Release)'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: inputs.semver != '' # only if this workflow is called from a push to tag (a Release)
|
||||
with:
|
||||
name: robosats-desktop-${{ inputs.semver }}-mac.zip
|
||||
path: desktopApp/release-builds/desktopApp-mac.zip
|
||||
|
||||
- name: 'Upload linux-build Release Artifact (for Release)'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: inputs.semver != '' # only if this workflow is called from a push to tag (a Release)
|
||||
with:
|
||||
name: robosats-desktop-${{ inputs.semver }}-linux.zip
|
||||
path: desktopApp/release-builds/desktopApp-linux.zip
|
||||
|
||||
- name: 'Upload win-build Release Artifact (for Release)'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: inputs.semver != '' # only if this workflow is called from a push to tag (a Release)
|
||||
with:
|
||||
name: robosats-desktop-${{ inputs.semver }}-win.zip
|
||||
path: desktopApp/release-builds/desktopApp-win.zip
|
||||
|
||||
|
||||
- name: Create Pre-release
|
||||
id: create_release
|
||||
if: inputs.semver == '' # only if this workflow is not called from a push to tag (a Release)
|
||||
uses: ncipollo/release-action@v1.13.0
|
||||
with:
|
||||
tag: desktop-${{ steps.commit.outputs.short }}
|
||||
name: robosats-desktop-${{ steps.commit.outputs.short }}
|
||||
prerelease: true
|
||||
|
||||
- name: Upload macOS Build Artifact
|
||||
id: upload-release-mac-zip-asset
|
||||
if: inputs.semver == '' # only if this workflow is not called from a push to tag (a Release)
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: /desktopApp-mac.zip
|
||||
asset_name: robosats-desktop-${{ steps.commit.outputs.short }}-mac.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Windows Build Artifact
|
||||
id: upload-release-win-zip-asset
|
||||
if: inputs.semver == '' # only if this workflow is not called from a push to tag (a Release)
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: /desktopApp-win.zip
|
||||
asset_name: robosats-desktop-${{ steps.commit.outputs.short }}-win.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Linux Build Artifact
|
||||
id: upload-release-linux-zip-asset
|
||||
if: inputs.semver == '' # only if this workflow is not called from a push to tag (a Release)
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: /desktopApp-linux.zip
|
||||
asset_name: robosats-desktop-${{ steps.commit.outputs.short }}-linux.zip
|
||||
asset_content_type: application/zip
|
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
@ -75,8 +75,15 @@ jobs:
|
||||
with:
|
||||
semver: ${{ needs.check-versions.outputs.semver }}
|
||||
|
||||
desktop-build:
|
||||
uses: RoboSats/robosats/.github/workflows/desktop-build.yml@main
|
||||
needs: [frontend-build, check-versions]
|
||||
secrets: inherit
|
||||
with:
|
||||
semver: ${{ needs.check-versions.outputs.semver }}
|
||||
|
||||
release:
|
||||
needs: [check-versions, integration-tests, coordinator-image, selfhosted-client-image, web-client-image, android-build]
|
||||
needs: [check-versions, integration-tests, coordinator-image, selfhosted-client-image, web-client-image, android-build, desktop-build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -172,3 +179,51 @@ jobs:
|
||||
asset_path: app-x86-release.apk
|
||||
asset_name: robosats-${{ needs.check-versions.outputs.semver }}-x86.apk
|
||||
asset_content_type: application/apk
|
||||
|
||||
- name: 'Download macOS Build Artifact'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-mac.zip
|
||||
path: .
|
||||
- name: 'Upload macOS Build Artifact'
|
||||
id: upload-release-mac-zip-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: desktopApp-mac.zip
|
||||
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-mac.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: 'Download linux Build Artifact'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-linux.zip
|
||||
path: .
|
||||
- name: 'Upload linux Build Artifact'
|
||||
id: upload-release-linux-zip-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: desktopApp-linux.zip
|
||||
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-linux.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: 'Download window Build Artifact'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-win.zip
|
||||
path: .
|
||||
- name: 'Upload macOS Build Artifact'
|
||||
id: upload-release-win-zip-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: desktopApp-win.zip
|
||||
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-win.zip
|
||||
asset_content_type: application/zip
|
@ -478,6 +478,7 @@ class LNDNode:
|
||||
payment_request=lnpayment.invoice,
|
||||
fee_limit_sat=fee_limit_sat,
|
||||
timeout_seconds=timeout_seconds,
|
||||
amp=True,
|
||||
)
|
||||
|
||||
routerstub = router_pb2_grpc.RouterStub(cls.channel)
|
||||
@ -536,6 +537,7 @@ class LNDNode:
|
||||
fee_limit_sat=fee_limit_sat,
|
||||
timeout_seconds=timeout_seconds,
|
||||
allow_self_payment=True,
|
||||
amp=True,
|
||||
)
|
||||
|
||||
order = lnpayment.order_paid_LN
|
||||
|
@ -8,7 +8,7 @@ from django.utils import timezone
|
||||
|
||||
from api.lightning.node import LNNode
|
||||
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order
|
||||
from api.tasks import send_devfund_donation, send_notification
|
||||
from api.tasks import send_devfund_donation, send_notification, nostr_send_order_event
|
||||
from api.utils import get_minning_fee, validate_onchain_address, location_country
|
||||
from chat.models import Message
|
||||
|
||||
@ -704,9 +704,9 @@ class Logics:
|
||||
|
||||
if context["invoice_amount"] < MIN_SWAP_AMOUNT:
|
||||
context["swap_allowed"] = False
|
||||
context[
|
||||
"swap_failure_reason"
|
||||
] = f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats"
|
||||
context["swap_failure_reason"] = (
|
||||
f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats"
|
||||
)
|
||||
order.log(
|
||||
f"Onchain payment option was not offered: amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats",
|
||||
level="WARN",
|
||||
@ -714,9 +714,9 @@ class Logics:
|
||||
return True, context
|
||||
elif context["invoice_amount"] > MAX_SWAP_AMOUNT:
|
||||
context["swap_allowed"] = False
|
||||
context[
|
||||
"swap_failure_reason"
|
||||
] = f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats"
|
||||
context["swap_failure_reason"] = (
|
||||
f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats"
|
||||
)
|
||||
order.log(
|
||||
f"Onchain payment option was not offered: amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats",
|
||||
level="WARN",
|
||||
@ -741,9 +741,9 @@ class Logics:
|
||||
)
|
||||
if not valid:
|
||||
context["swap_allowed"] = False
|
||||
context[
|
||||
"swap_failure_reason"
|
||||
] = "Not enough onchain liquidity available to offer a swap"
|
||||
context["swap_failure_reason"] = (
|
||||
"Not enough onchain liquidity available to offer a swap"
|
||||
)
|
||||
order.log(
|
||||
"Onchain payment option was not offered: onchain liquidity available to offer a swap",
|
||||
level="WARN",
|
||||
@ -1019,6 +1019,8 @@ class Logics:
|
||||
order.log("Order expired while waiting for maker bond")
|
||||
order.log("Maker bond was cancelled")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 2.a) When maker cancels after bond
|
||||
@ -1039,6 +1041,8 @@ class Logics:
|
||||
order.log("Order cancelled by maker while public or paused")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 2.b) When maker cancels after bond and before taker bond is locked
|
||||
@ -1058,6 +1062,8 @@ class Logics:
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
order.log("Taker bond was <b>cancelled</b>")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 3) When taker cancels before bond
|
||||
@ -1070,6 +1076,8 @@ class Logics:
|
||||
|
||||
order.log("Taker cancelled before locking the bond")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 4) When taker or maker cancel after bond (before escrow)
|
||||
@ -1099,6 +1107,8 @@ class Logics:
|
||||
order.log("Maker bond was <b>settled</b>")
|
||||
order.log("Taker bond was <b>unlocked</b>")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 4.b) When taker cancel after bond (before escrow)
|
||||
@ -1121,6 +1131,8 @@ class Logics:
|
||||
order.log("Taker bond was <b>settled</b>")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# 5) When trade collateral has been posted (after escrow)
|
||||
@ -1136,6 +1148,9 @@ class Logics:
|
||||
order.log(
|
||||
f"Taker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
|
||||
)
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# if the taker had asked, and now the maker does: cancel order, return everything
|
||||
@ -1144,6 +1159,9 @@ class Logics:
|
||||
order.log(
|
||||
f"Maker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
|
||||
)
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
return True, None
|
||||
|
||||
# Otherwise just make true the asked for cancel flags
|
||||
@ -1181,6 +1199,8 @@ class Logics:
|
||||
order.update_status(Order.Status.CCA)
|
||||
send_notification.delay(order_id=order.id, message="collaborative_cancelled")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
order.log("Order was collaboratively cancelled")
|
||||
order.log("Maker bond was <b>unlocked</b>")
|
||||
order.log("Taker bond was <b>unlocked</b>")
|
||||
@ -1208,6 +1228,8 @@ class Logics:
|
||||
|
||||
order.save() # update all fields
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
order.log(f"Order({order.id},{str(order)}) is public in the order book")
|
||||
return
|
||||
|
||||
@ -1350,6 +1372,9 @@ class Logics:
|
||||
except Exception:
|
||||
pass
|
||||
send_notification.delay(order_id=order.id, message="order_taken_confirmed")
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
|
||||
order.log(
|
||||
f"<b>Contract formalized.</b> Maker: Robot({order.maker.robot.id},{order.maker}). Taker: Robot({order.taker.robot.id},{order.taker}). API median price {order.currency.exchange_rate} {dict(Currency.currency_choices)[order.currency.currency]}/BTC. Premium is {order.premium}%. Contract size {order.last_satoshis} Sats"
|
||||
)
|
||||
@ -1741,11 +1766,15 @@ class Logics:
|
||||
order.log(
|
||||
f"Robot({user.robot.id},{user.username}) paused the public order"
|
||||
)
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
elif order.status == Order.Status.PAU:
|
||||
order.update_status(Order.Status.PUB)
|
||||
order.log(
|
||||
f"Robot({user.robot.id},{user.username}) made public the paused order"
|
||||
)
|
||||
|
||||
nostr_send_order_event.delay(order_id=order.id)
|
||||
else:
|
||||
order.log(
|
||||
f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused",
|
||||
|
18
api/migrations/0049_alter_currency_currency.py
Normal file
18
api/migrations/0049_alter_currency_currency.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.8 on 2024-08-15 18:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0048_alter_order_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='currency',
|
||||
name='currency',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'USD'), (2, 'EUR'), (3, 'JPY'), (4, 'GBP'), (5, 'AUD'), (6, 'CAD'), (7, 'CHF'), (8, 'CNY'), (9, 'HKD'), (10, 'NZD'), (11, 'SEK'), (12, 'KRW'), (13, 'SGD'), (14, 'NOK'), (15, 'MXN'), (16, 'BYN'), (17, 'RUB'), (18, 'ZAR'), (19, 'TRY'), (20, 'BRL'), (21, 'CLP'), (22, 'CZK'), (23, 'DKK'), (24, 'HRK'), (25, 'HUF'), (26, 'INR'), (27, 'ISK'), (28, 'PLN'), (29, 'RON'), (30, 'ARS'), (31, 'VES'), (32, 'COP'), (33, 'PEN'), (34, 'UYU'), (35, 'PYG'), (36, 'BOB'), (37, 'IDR'), (38, 'ANG'), (39, 'CRC'), (40, 'CUP'), (41, 'DOP'), (42, 'GHS'), (43, 'GTQ'), (44, 'ILS'), (45, 'JMD'), (46, 'KES'), (47, 'KZT'), (48, 'MYR'), (49, 'NAD'), (50, 'NGN'), (51, 'AZN'), (52, 'PAB'), (53, 'PHP'), (54, 'PKR'), (55, 'QAR'), (56, 'SAR'), (57, 'THB'), (58, 'TTD'), (59, 'VND'), (60, 'XOF'), (61, 'TWD'), (62, 'TZS'), (63, 'XAF'), (64, 'UAH'), (65, 'EGP'), (66, 'LKR'), (67, 'MAD'), (68, 'AED'), (69, 'TND'), (70, 'ETB'), (71, 'GEL'), (72, 'UGX'), (73, 'RSD'), (74, 'IRT'), (75, 'BDT'), (76, 'ALL'), (77, 'DZD'), (300, 'XAU'), (1000, 'BTC')], unique=True),
|
||||
),
|
||||
]
|
18
api/migrations/0050_alter_order_status.py
Normal file
18
api/migrations/0050_alter_order_status.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.8 on 2024-08-22 08:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0049_alter_currency_currency'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'Waiting for maker bond'), (1, 'Public'), (2, 'Paused'), (3, 'Waiting for taker bond'), (4, 'Cancelled'), (5, 'Expired'), (6, 'Waiting for trade collateral and buyer invoice'), (7, 'Waiting only for seller trade collateral'), (8, 'Waiting only for buyer invoice'), (9, 'Sending fiat - In chatroom'), (10, 'Fiat sent - In chatroom'), (11, 'In dispute'), (12, 'Collaboratively cancelled'), (13, 'Sending satoshis to buyer'), (14, 'Successful trade'), (15, 'Failed lightning network routing'), (16, 'Wait for dispute resolution'), (17, 'Maker lost dispute'), (18, 'Taker lost dispute')], default=0),
|
||||
),
|
||||
]
|
@ -46,7 +46,7 @@ class Order(models.Model):
|
||||
DIS = 11, "In dispute"
|
||||
CCA = 12, "Collaboratively cancelled"
|
||||
PAY = 13, "Sending satoshis to buyer"
|
||||
SUC = 14, "Sucessful trade"
|
||||
SUC = 14, "Successful trade"
|
||||
FAI = 15, "Failed lightning network routing"
|
||||
WFR = 16, "Wait for dispute resolution"
|
||||
MLD = 17, "Maker lost dispute"
|
||||
|
101
api/nostr.py
Normal file
101
api/nostr.py
Normal file
@ -0,0 +1,101 @@
|
||||
import pygeohash
|
||||
import hashlib
|
||||
import uuid
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, Kind, Tag
|
||||
from api.models import Order
|
||||
from decouple import config
|
||||
|
||||
|
||||
class Nostr:
|
||||
"""Simple nostr events manager to be used as a cache system for clients"""
|
||||
|
||||
async def send_order_event(self, order):
|
||||
"""Creates the event and sends it to the coordinator relay"""
|
||||
|
||||
if config("NOSTR_NSEC", cast=str, default="") == "":
|
||||
return
|
||||
|
||||
print("Sending nostr event")
|
||||
|
||||
# Initialize with coordinator Keys
|
||||
keys = Keys.parse(config("NOSTR_NSEC", cast=str))
|
||||
signer = NostrSigner.keys(keys)
|
||||
client = Client(signer)
|
||||
|
||||
# Add relays and connect
|
||||
await client.add_relays(["ws://localhost:7777"])
|
||||
await client.connect()
|
||||
|
||||
robot_name = await self.get_robot_name(order)
|
||||
currency = await self.get_robot_currency(order)
|
||||
|
||||
event = EventBuilder(
|
||||
Kind(38383), "", self.generate_tags(order, robot_name, currency)
|
||||
).to_event(keys)
|
||||
await client.send_event(event)
|
||||
print(f"Nostr event sent: {event.as_json()}")
|
||||
|
||||
@sync_to_async
|
||||
def get_robot_name(self, order):
|
||||
return order.maker.username
|
||||
|
||||
@sync_to_async
|
||||
def get_robot_currency(self, order):
|
||||
return str(order.currency)
|
||||
|
||||
def generate_tags(self, order, robot_name, currency):
|
||||
hashed_id = hashlib.md5(
|
||||
f"{config("COORDINATOR_ALIAS", cast=str)}{order.id}".encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
tags = [
|
||||
Tag.parse(["d", str(uuid.UUID(hashed_id))]),
|
||||
Tag.parse(["name", robot_name]),
|
||||
Tag.parse(["k", "sell" if order.type == Order.Types.SELL else "buy"]),
|
||||
Tag.parse(["f", currency]),
|
||||
Tag.parse(["s", self.get_status_tag(order)]),
|
||||
Tag.parse(["amt", "0"]),
|
||||
Tag.parse(
|
||||
["fa"]
|
||||
+ (
|
||||
[str(order.amount)]
|
||||
if not order.has_range
|
||||
else [str(order.min_amount), str(order.max_amount)]
|
||||
)
|
||||
),
|
||||
Tag.parse(["pm"] + order.payment_method.split(" ")),
|
||||
Tag.parse(["premium", str(order.premium)]),
|
||||
Tag.parse(
|
||||
[
|
||||
"source",
|
||||
f"http://{config("HOST_NAME")}/order/{config("COORDINATOR_ALIAS", cast=str).lower()}/{order.id}",
|
||||
]
|
||||
),
|
||||
Tag.parse(["expiration", str(int(order.expires_at.timestamp()))]),
|
||||
Tag.parse(["y", "robosats", config("COORDINATOR_ALIAS", cast=str).lower()]),
|
||||
Tag.parse(["n", str(config("NETWORK"))]),
|
||||
Tag.parse(["layer"] + self.get_layer_tag(order)),
|
||||
Tag.parse(["bond", str(order.bond_size)]),
|
||||
Tag.parse(["z", "order"]),
|
||||
]
|
||||
|
||||
if order.latitude and order.longitude:
|
||||
tags.extend(
|
||||
[Tag.parse(["g", pygeohash.encode(order.latitude, order.longitude)])]
|
||||
)
|
||||
|
||||
return tags
|
||||
|
||||
def get_status_tag(self, order):
|
||||
if order.status == Order.Status.PUB:
|
||||
return "pending"
|
||||
else:
|
||||
return "success"
|
||||
|
||||
def get_layer_tag(self, order):
|
||||
if order.type == Order.Types.SELL:
|
||||
return ["onchain", "lightning"]
|
||||
else:
|
||||
return ["lightning"]
|
@ -112,7 +112,7 @@ class OrderViewSchema:
|
||||
- `11` "In dispute"
|
||||
- `12` "Collaboratively cancelled"
|
||||
- `13` "Sending satoshis to buyer"
|
||||
- `14` "Sucessful trade"
|
||||
- `14` "Successful trade"
|
||||
- `15` "Failed lightning network routing"
|
||||
- `16` "Wait for dispute resolution"
|
||||
- `17` "Maker lost dispute"
|
||||
|
15
api/tasks.py
15
api/tasks.py
@ -1,3 +1,4 @@
|
||||
from asgiref.sync import async_to_sync
|
||||
from celery import shared_task
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
|
||||
@ -251,6 +252,20 @@ def cache_market():
|
||||
return
|
||||
|
||||
|
||||
@shared_task(name="", ignore_result=True, time_limit=120)
|
||||
def nostr_send_order_event(order_id=None):
|
||||
if order_id:
|
||||
from api.models import Order
|
||||
from api.nostr import Nostr
|
||||
|
||||
order = Order.objects.get(id=order_id)
|
||||
|
||||
nostr = Nostr()
|
||||
async_to_sync(nostr.send_order_event)(order)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@shared_task(name="send_notification", ignore_result=True, time_limit=120)
|
||||
def send_notification(order_id=None, chat_message_id=None, message=None):
|
||||
if order_id:
|
||||
|
41
desktopApp/Readme.md
Normal file
41
desktopApp/Readme.md
Normal file
@ -0,0 +1,41 @@
|
||||
# RoboSats Desktop App
|
||||
|
||||
RoboSats desktop app serves the RoboSats frontend app directly and redirects all API requests to RoboSats P2P market coordinator through your TOR proxy.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Step 1: Clone the Repository
|
||||
|
||||
First, clone the repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/RoboSats/robosats.git
|
||||
cd robosats
|
||||
```
|
||||
|
||||
|
||||
### Step 2: Install Dependencies
|
||||
```bash
|
||||
cd desktopApp
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
### Step 3: Run the App Locally
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
### Step 4: Package the App
|
||||
|
||||
To package the app for different platforms (Linux, Windows, macOS), use the corresponding npm commands:
|
||||
|
||||
```bash
|
||||
npm run package-linux
|
||||
npm run package-win
|
||||
npm run package-mac
|
||||
```
|
||||
|
||||
### Additional Information
|
||||
This desktop app ensures all API requests are redirected through a TOR proxy to maintain privacy and anonymity while accessing the RoboSats P2P market coordinator.
|
||||
|
BIN
desktopApp/assets/icon/Robosats.icns
Normal file
BIN
desktopApp/assets/icon/Robosats.icns
Normal file
Binary file not shown.
BIN
desktopApp/assets/icon/Robosats.ico
Normal file
BIN
desktopApp/assets/icon/Robosats.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
133
desktopApp/assets/icon/Robosats.svg
Normal file
133
desktopApp/assets/icon/Robosats.svg
Normal file
@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="128" y1="256" x2="128" y2="0">
|
||||
<stop offset="0.1269" style="stop-color:#CCCCCC"/>
|
||||
<stop offset="0.2947" style="stop-color:#E1E1E1"/>
|
||||
<stop offset="0.4889" style="stop-color:#FFFFFF"/>
|
||||
</linearGradient>
|
||||
<rect fill="url(#SVGID_1_)" width="256" height="256"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-2.2432" y1="60.3643" x2="167.041" y2="264.634" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M65.814,152.139c0.91,0.784,1.91,1.396,2.96,1.85c-1.711-1.462-3.403-2.891-5.073-4.277
|
||||
C64.274,150.59,64.974,151.414,65.814,152.139z"/>
|
||||
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-2.2031" y1="60.4131" x2="167.0789" y2="264.6803" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M65.814,152.139c0.91,0.784,1.91,1.396,2.96,1.85c-1.711-1.462-3.403-2.891-5.073-4.277
|
||||
C64.274,150.59,64.974,151.414,65.814,152.139z"/>
|
||||
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-1.457" y1="44.2812" x2="188.127" y2="273.0462" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M82.001,151.025c4.159-4.727,3.658-11.9-1.122-16.016c-4.78-4.117-12.026-3.62-16.188,1.109
|
||||
c-3.428,3.899-3.682,9.45-0.99,13.592c1.67,1.387,3.362,2.815,5.073,4.277C73.242,155.909,78.634,154.855,82.001,151.025z"/>
|
||||
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="6.127" y1="53.4326" x2="175.4021" y2="257.6916" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_5_)" d="M82.001,151.025c4.159-4.727,3.658-11.9-1.122-16.016c-4.78-4.117-12.026-3.62-16.188,1.109
|
||||
c-3.428,3.899-3.682,9.45-0.99,13.592c1.67,1.387,3.362,2.815,5.073,4.277C73.242,155.909,78.634,154.855,82.001,151.025z"/>
|
||||
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="6.166" y1="53.4805" x2="175.4397" y2="257.7377" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_6_)" d="M82.001,151.025c4.159-4.727,3.658-11.9-1.122-16.016c-4.78-4.117-12.026-3.62-16.188,1.109
|
||||
c-3.428,3.899-3.682,9.45-0.99,13.592c1.67,1.387,3.362,2.815,5.073,4.277C73.242,155.909,78.634,154.855,82.001,151.025z"/>
|
||||
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="6.5864" y1="37.6182" x2="196.1729" y2="266.3862" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_7_)" d="M131.52,178.628c-4.778-4.118-12.026-3.625-16.188,1.107
|
||||
c-4.159,4.729-3.654,11.902,1.124,16.021c4.779,4.114,12.028,3.62,16.185-1.113
|
||||
C136.801,189.911,136.299,182.742,131.52,178.628z"/>
|
||||
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="14.1919" y1="46.7949" x2="183.4888" y2="251.0801" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_8_)" d="M131.52,178.628c-4.778-4.118-12.026-3.625-16.188,1.107
|
||||
c-4.159,4.729-3.654,11.902,1.124,16.021c4.779,4.114,12.028,3.62,16.185-1.113
|
||||
C136.801,189.911,136.299,182.742,131.52,178.628z"/>
|
||||
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="14.2378" y1="46.8506" x2="183.5231" y2="251.1217" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_9_)" d="M131.52,178.628c-4.778-4.118-12.026-3.625-16.188,1.107
|
||||
c-4.159,4.729-3.654,11.902,1.124,16.021c4.779,4.114,12.028,3.62,16.185-1.113
|
||||
C136.801,189.911,136.299,182.742,131.52,178.628z"/>
|
||||
</g>
|
||||
|
||||
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="-56.7104" y1="46.0752" x2="181.1495" y2="333.0932" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_10_)" d="M47.215,171.653c-0.005,7.697-0.009,15.186-0.014,22.144c2.244-2.067,4.739-4.303,7.153-6.622
|
||||
c0.744-0.719,1.28-0.813,2.038-0.109c1.297,1.197,2.635,2.35,4.001,3.56c1.243-1.401,2.409-2.72,3.618-4.084
|
||||
C58.394,181.564,52.874,176.668,47.215,171.653z"/>
|
||||
|
||||
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="-71.7656" y1="17.4062" x2="178.9234" y2="319.9048" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_11_)" d="M96.606,215.496c-5.554-4.934-10.928-9.709-16.356-14.536c-1.225,1.39-2.362,2.679-3.551,4.024
|
||||
c1.533,1.378,2.997,2.693,4.539,4.078c-2.06,2.116-4.067,4.186-6.239,6.42C82.293,215.486,89.327,215.491,96.606,215.496z"/>
|
||||
|
||||
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="48.644" y1="2.749" x2="238.2449" y2="231.5344" gradientTransform="matrix(0.9999 -0.0101 0.0101 0.9999 1.9865 13.6532)">
|
||||
<stop offset="0.3295" style="stop-color:#1976D2"/>
|
||||
<stop offset="0.4214" style="stop-color:#2E69CC"/>
|
||||
<stop offset="0.6106" style="stop-color:#6548BE"/>
|
||||
<stop offset="0.7834" style="stop-color:#9C27B0"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_12_)" d="M151.123,140.642c4.551-2.31,8.836-5.034,12.748-8.313c7.891-6.625,13.992-14.499,16.895-24.56
|
||||
c2.469-8.565,3.027-17.312,2.158-26.142c-0.596-6.125-2.252-11.961-5.594-17.219c-7.176-11.29-17.574-18.036-30.375-21.218
|
||||
c-8.9-2.214-18.036-2.651-27.165-2.687c-23.748-0.088-47.498-0.055-71.249-0.067c-0.423-0.001-0.847,0.036-1.287,0.056
|
||||
c-0.015,24.535-0.031,48.95-0.046,73.32c15.731-11.838,31.863-14.195,48.42-2.57c2.176-2.02,4.324-4.011,6.562-6.088
|
||||
c-2.269-1.653-4.427-3.226-6.688-4.872c5.694-4.126,11.212-8.121,16.712-12.105c-1.47-3.392-0.892-6.063,1.599-7.667
|
||||
c2.145-1.383,5.17-0.997,6.868,0.874c1.745,1.923,1.889,4.86,0.337,6.912c-1.768,2.34-4.548,2.716-7.774,0.995
|
||||
c-2.781,3.42-5.572,6.854-8.424,10.36c2.357,1.672,4.611,3.269,6.938,4.919c-4.579,3.08-9.056,6.089-13.548,9.107
|
||||
c0.167,0.201,0.234,0.306,0.324,0.386c16.396,14.547,32.791,29.093,49.197,43.631c3.506,3.105,7.074,6.147,9.555,10.212
|
||||
c6.645,10.863,7.08,22.205,2.514,33.884c-2.002,5.131-5.035,9.634-9.098,13.737c19.465,0.012,38.66,0.024,58.096,0.036
|
||||
c-19.633-24.874-39.131-49.577-58.684-74.348C150.508,140.993,150.805,140.802,151.123,140.642z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
63
desktopApp/index.html
Normal file
63
desktopApp/index.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/png" href="./static/assets/images/favicon-96x96.png" />
|
||||
<link rel="icon" type="image/png" href="./static/assets/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="./static/assets/images/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="./static/assets/images/favicon-192x192.png" sizes="192x192">
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="A simple and private way to exchange bitcoin for national currencies. Robosats simplifies the peer-to-peer user experience and uses lightning hold invoices to minimize custody and trust requirements. No user registration required.">
|
||||
|
||||
<title>RoboSats - Simple and Private Bitcoin Exchange</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/fonts.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/loader.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/index.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/leaflet.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div>
|
||||
This site requires JavaScript. This message is only visible if you have it disabled. <br/><br/>
|
||||
If you are using TOR browser set the "Security Level" to "Standard". If you keep seeing this message clear cache and storage of TOR browser app and retry.<br/><br/>
|
||||
If the problem persists, ask for support in the RoboSats telegram group<a href="https://t.me/robosats"> (t.me/robosats)</a>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="main">
|
||||
<div id="app">
|
||||
<div class="loaderCenter">
|
||||
<div class="loaderSpinner"></div>
|
||||
<div class="content-slider">
|
||||
<div class="slider">
|
||||
<div class="mask">
|
||||
<ul>
|
||||
<li class="anim1">
|
||||
<div class="quote">Looking for robot parts ...</div>
|
||||
</li>
|
||||
<li class="anim2">
|
||||
<div class="quote">Adding layers to the onion ...</div>
|
||||
</li>
|
||||
<li class="anim3">
|
||||
<div class="quote">Winning at game theory ...</div>
|
||||
</li>
|
||||
<li class="anim4">
|
||||
<div class="quote">Moving Sats at light speed ...</div>
|
||||
</li>
|
||||
<li class="anim5">
|
||||
<div class="quote">Hiding in 2^256 bits of entropy...</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.RobosatsSettings = 'web-basic'
|
||||
window.RobosatsClient = 'desktop-app'
|
||||
</script>
|
||||
<script src="./static/frontend/main.js"></script>
|
||||
</body>
|
||||
</html>
|
94
desktopApp/index.js
Normal file
94
desktopApp/index.js
Normal file
@ -0,0 +1,94 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// Modules to control application life and create native browser window
|
||||
var electron_1 = require("electron");
|
||||
var child_process_1 = require("child_process");
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
var tor = null;
|
||||
// Function to determine the current OS and find the appropriate Tor binary
|
||||
function checkPlatformAndRunTor() {
|
||||
var platform = os.platform();
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
tor = (0, child_process_1.spawn)(path.join(__dirname, '/tor/tor-win/tor/tor.exe'));
|
||||
break;
|
||||
case 'darwin':
|
||||
tor = (0, child_process_1.spawn)(path.join(__dirname, '/tor/tor-mac/tor/tor'));
|
||||
break;
|
||||
case 'linux':
|
||||
tor = (0, child_process_1.spawn)(path.join(__dirname, '/tor/tor-linux/tor/tor'));
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported platform: ".concat(platform));
|
||||
}
|
||||
}
|
||||
// Function to start Tor process
|
||||
checkPlatformAndRunTor();
|
||||
// Listen for Tor process stdout data
|
||||
tor.stdout.on("data", function (data) {
|
||||
var message = data.toString();
|
||||
console.log("Data received: ".concat(message));
|
||||
});
|
||||
// Listen for Tor process stderr data
|
||||
tor.stderr.on("data", function (data) {
|
||||
console.error("Error received: ".concat(data.toString()));
|
||||
electron_1.app.exit(1); // Exit the app if there's an error in the Tor process
|
||||
});
|
||||
// Function to create the main application window
|
||||
function createWindow() {
|
||||
// Create the browser window with specific dimensions
|
||||
var mainWindow = new electron_1.BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
icon: path.join(__dirname, '/static/assets/images/favicon-32x32.png'),
|
||||
webPreferences: {
|
||||
nodeIntegration: false, // Disable Node.js integration in the renderer
|
||||
contextIsolation: true, // Enable context isolation for security
|
||||
},
|
||||
});
|
||||
// Load the index.html file from the app directory
|
||||
mainWindow.loadURL("file://".concat(path.resolve(__dirname, 'index.html#/robot')), {
|
||||
extraHeaders: "pragma: no-cache\n" // Prevent caching of the loaded file
|
||||
});
|
||||
// Handle failed load attempts by reloading the file
|
||||
mainWindow.webContents.on("did-fail-load", function () {
|
||||
console.log("Failed to load the page, retrying...");
|
||||
mainWindow.loadURL("file://".concat(__dirname, "/index.html#/robot"));
|
||||
});
|
||||
// Uncomment the following line to open the DevTools
|
||||
// mainWindow.webContents.openDevTools();
|
||||
}
|
||||
// This method is called when Electron has finished initialization
|
||||
electron_1.app.whenReady().then(function () {
|
||||
// Create the window after the app is ready
|
||||
createWindow();
|
||||
// Re-create a window if the app is activated and there are no other windows open (MacOS specific behavior)
|
||||
electron_1.app.on("activate", function () {
|
||||
if (electron_1.BrowserWindow.getAllWindows().length === 0)
|
||||
createWindow();
|
||||
});
|
||||
});
|
||||
// Setup the app session when Electron is ready
|
||||
electron_1.app.on("ready", function () {
|
||||
// Redirect requests to static files
|
||||
electron_1.session.defaultSession.webRequest.onBeforeRequest({ urls: ['file:///static/*'] }, function (details, callback) {
|
||||
var url = details.url;
|
||||
var modifiedUrl = url.slice(7);
|
||||
var staticFilePath = path.join(__dirname, modifiedUrl);
|
||||
callback({ redirectURL: "file://".concat(staticFilePath) });
|
||||
});
|
||||
// Set the proxy for the session to route through Tor
|
||||
electron_1.session.defaultSession.setProxy({
|
||||
proxyRules: "socks://localhost:9050",
|
||||
proxyBypassRules: "<local>",
|
||||
});
|
||||
});
|
||||
// Handle all windows closed event except on macOS
|
||||
electron_1.app.on("window-all-closed", function () {
|
||||
// Terminate the Tor process if it exists
|
||||
tor === null || tor === void 0 ? void 0 : tor.kill();
|
||||
if (process.platform !== "darwin")
|
||||
electron_1.app.quit();
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
1
desktopApp/index.js.map
Normal file
1
desktopApp/index.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;AAAA,uEAAuE;AACvE,qCAAsE;AACtE,+CAAsE;AACtE,2BAA6B;AAC7B,uBAAyB;AAEzB,IAAI,GAAG,GAA0C,IAAI,CAAC;AAEtD,2EAA2E;AAE3E,SAAS,sBAAsB;IAC7B,IAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAE/B,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO;YACV,GAAG,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,0BAA0B,CAAC,CAAC,CAAC;YAC9D,MAAM;QACR,KAAK,QAAQ;YACX,GAAG,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;YAC1D,MAAM;QACR,KAAK,OAAO;YACV,GAAG,GAAG,IAAA,qBAAK,EAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAAC;YAC5D,MAAM;QACR;YACE,MAAM,IAAI,KAAK,CAAC,gCAAyB,QAAQ,CAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,gCAAgC;AAChC,sBAAsB,EAAE,CAAA;AAGxB,qCAAqC;AACrC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAC,IAAY;IACjC,IAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,yBAAkB,OAAO,CAAE,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,qCAAqC;AACrC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAC,IAAY;IACjC,OAAO,CAAC,KAAK,CAAC,0BAAmB,IAAI,CAAC,QAAQ,EAAE,CAAE,CAAC,CAAC;IACpD,cAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,sDAAsD;AACrE,CAAC,CAAC,CAAC;AAEH,iDAAiD;AACjD,SAAS,YAAY;IACnB,qDAAqD;IACrD,IAAM,UAAU,GAAG,IAAI,wBAAa,CAAC;QACnC,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,GAAG;QACX,IAAI,EAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,yCAAyC,CAAC;QACpE,cAAc,EAAE;YACd,eAAe,EAAE,KAAK,EAAG,8CAA8C;YACvE,gBAAgB,EAAE,IAAI,EAAG,wCAAwC;SAClE;KACF,CAAC,CAAC;IAEH,kDAAkD;IAClD,UAAU,CAAC,OAAO,CAAC,iBAAU,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAE,EAAE;QAC3E,YAAY,EAAE,oBAAoB,CAAE,qCAAqC;KAC1E,CAAC,CAAC;IAEH,oDAAoD;IACpD,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE;QACzC,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,UAAU,CAAC,OAAO,CAAC,iBAAU,SAAS,uBAAoB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,oDAAoD;IACpD,yCAAyC;AAC3C,CAAC;AAED,kEAAkE;AAClE,cAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC;IACnB,2CAA2C;IAC3C,YAAY,EAAE,CAAC;IAEf,2GAA2G;IAC3G,cAAG,CAAC,EAAE,CAAC,UAAU,EAAE;QACjB,IAAI,wBAAa,CAAC,aAAa,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,YAAY,EAAE,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,+CAA+C;AAC/C,cAAG,CAAC,EAAE,CAAC,OAAO,EAAE;IACd,oCAAoC;IACpC,kBAAO,CAAC,cAAc,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,UAAC,OAAO,EAAE,QAAQ;QAClG,IAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACxB,IAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjC,IAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACzD,QAAQ,CAAC,EAAE,WAAW,EAAE,iBAAU,cAAc,CAAE,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,qDAAqD;IACrD,kBAAO,CAAC,cAAc,CAAC,QAAQ,CAAC;QAC9B,UAAU,EAAE,wBAAwB;QACpC,gBAAgB,EAAE,SAAS;KAC5B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,kDAAkD;AAClD,cAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE;IAC1B,yCAAyC;IACzC,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,IAAI,EAAE,CAAC;IACZ,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,cAAG,CAAC,IAAI,EAAE,CAAC;AAChD,CAAC,CAAC,CAAC"}
|
106
desktopApp/index.ts
Normal file
106
desktopApp/index.ts
Normal file
@ -0,0 +1,106 @@
|
||||
// Modules to control application life and create native browser window
|
||||
import { app, BrowserWindow, session, protocol, net } from 'electron';
|
||||
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as os from "os";
|
||||
|
||||
let tor: ChildProcessWithoutNullStreams | null = null;
|
||||
|
||||
// Function to determine the current OS and find the appropriate Tor binary
|
||||
|
||||
function checkPlatformAndRunTor(): void {
|
||||
const platform = os.platform();
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
tor = spawn(path.join(__dirname, '/tor/tor-win/tor/tor.exe'));
|
||||
break;
|
||||
case 'darwin':
|
||||
tor = spawn(path.join(__dirname, '/tor/tor-mac/tor/tor'));
|
||||
break;
|
||||
case 'linux':
|
||||
tor = spawn(path.join(__dirname, '/tor/tor-linux/tor/tor'));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start Tor process
|
||||
checkPlatformAndRunTor()
|
||||
|
||||
|
||||
// Listen for Tor process stdout data
|
||||
tor.stdout.on("data", (data: Buffer) => {
|
||||
const message = data.toString();
|
||||
console.log(`Data received: ${message}`);
|
||||
});
|
||||
|
||||
// Listen for Tor process stderr data
|
||||
tor.stderr.on("data", (data: Buffer) => {
|
||||
console.error(`Error received: ${data.toString()}`);
|
||||
app.exit(1); // Exit the app if there's an error in the Tor process
|
||||
});
|
||||
|
||||
// Function to create the main application window
|
||||
function createWindow(): void {
|
||||
// Create the browser window with specific dimensions
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
icon:path.join(__dirname, '/static/assets/images/favicon-32x32.png'),
|
||||
webPreferences: {
|
||||
nodeIntegration: false, // Disable Node.js integration in the renderer
|
||||
contextIsolation: true, // Enable context isolation for security
|
||||
},
|
||||
});
|
||||
|
||||
// Load the index.html file from the app directory
|
||||
mainWindow.loadURL(`file://${path.resolve(__dirname, 'index.html#/robot')}`, {
|
||||
extraHeaders: "pragma: no-cache\n" // Prevent caching of the loaded file
|
||||
});
|
||||
|
||||
// Handle failed load attempts by reloading the file
|
||||
mainWindow.webContents.on("did-fail-load", () => {
|
||||
console.log("Failed to load the page, retrying...");
|
||||
mainWindow.loadURL(`file://${__dirname}/index.html#/robot`);
|
||||
});
|
||||
|
||||
// Uncomment the following line to open the DevTools
|
||||
// mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// This method is called when Electron has finished initialization
|
||||
app.whenReady().then(() => {
|
||||
// Create the window after the app is ready
|
||||
createWindow();
|
||||
|
||||
// Re-create a window if the app is activated and there are no other windows open (MacOS specific behavior)
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
// Setup the app session when Electron is ready
|
||||
app.on("ready", () => {
|
||||
// Redirect requests to static files
|
||||
session.defaultSession.webRequest.onBeforeRequest({ urls: ['file:///static/*'] }, (details, callback) => {
|
||||
const url = details.url;
|
||||
const modifiedUrl = url.slice(7);
|
||||
const staticFilePath = path.join(__dirname, modifiedUrl);
|
||||
callback({ redirectURL: `file://${staticFilePath}` });
|
||||
});
|
||||
|
||||
// Set the proxy for the session to route through Tor
|
||||
session.defaultSession.setProxy({
|
||||
proxyRules: "socks://localhost:9050",
|
||||
proxyBypassRules: "<local>",
|
||||
});
|
||||
});
|
||||
|
||||
// Handle all windows closed event except on macOS
|
||||
app.on("window-all-closed", () => {
|
||||
// Terminate the Tor process if it exists
|
||||
tor?.kill();
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
4526
desktopApp/package-lock.json
generated
Normal file
4526
desktopApp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
desktopApp/package.json
Normal file
48
desktopApp/package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "desktop-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"compile": "./node_modules/.bin/tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"package-linux": "npx @electron/packager . Robosats --platform=linux --arch=x64 --icon=./assets/icon/Robosats.svg --overwrite --out=release-builds",
|
||||
"package-win": "npx @electron/packager . Robosats --platform=win32 --arch=ia32 --icon=./assets/icon/Robosats.ico --overwrite --out=release-builds",
|
||||
"package-mac": "npx @electron/packager . Robosats --platform=darwin --arch=x64 --icon=./assets/icon/Robosats.icns --overwrite --out=release-builds"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@electron/packager": "^18.3.2",
|
||||
"electron": "^30.0.3",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.electron.robosats",
|
||||
"productName": "RobosatsApp",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"NSIS"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
1
desktopApp/static
Symbolic link
1
desktopApp/static
Symbolic link
@ -0,0 +1 @@
|
||||
../frontend/static
|
28
desktopApp/tor/README.CONJURE.md
Normal file
28
desktopApp/tor/README.CONJURE.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Conjure
|
||||
|
||||
[Conjure](https://jhalderm.com/pub/papers/conjure-ccs19.pdf) is an anti-censorship tool in the refraction networking (a.k.a. decoy routing) lineage of circumvention systems. The key innovation of Conjure is to turn the unused IP address space of deploying ISPs into a large pool of **phantom** proxies that users can connect to. Due to the size of unused IPv6 address space and the potential for collateral damage against real websites hosted by the deploying ISPs, Conjure provides an effective solution to the problem of censors enumerating deployed bridges or proxies.
|
||||
|
||||
Conjure is currenty deployed on the University of Colorado network and a small to mid size ISP in Michigan.
|
||||
|
||||
# Conjure Pluggable Transport for Tor
|
||||
|
||||
This repository is an implementation of both the client and bridge side of a Tor pluggable transport that uses the deployed Conjure network to allow users to connect to the Tor network. The client side calls the [`gotapdance` library](https://github.com/refraction-networking/gotapdance) to communicate with deployed Conjure stations and route client traffic through the phantom proxies assigned by the station. The bridge side receives [haproxy](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) connections from the Conjure station that wrap the proxied client traffic.
|
||||
|
||||
# Deployment details
|
||||
|
||||
We currently have deployed a low capacity Conjure bridge named [Haunt](https://metrics.torproject.org/rs.html#details/A84C946BF4E14E63A3C92E140532A4594F2C24CD). To connect through this bridge, use the `torrc` file in the `client/` directory as follows:
|
||||
|
||||
```
|
||||
cd client/
|
||||
tor -f torrc
|
||||
```
|
||||
|
||||
# Warnings
|
||||
|
||||
This tool and the deployment is still under active development. We are still working on securing the connection between the deployed Conjure stations and the Conjure bridge. We are also working on improving the censorship resistance of the registration connection between the client and the station. Do not expect this to work out of the box in all areas.
|
||||
|
||||
The Conjure station sometimes suffers from a heavy load of users. When this happens, connections will fail. If you are testing this out, try waiting awhile and trying again later.
|
||||
|
||||
# Conjure development
|
||||
|
||||
Due to the complex nature of the Conjure deployment, it can be difficult to set up a local development environment. Check out [phantombox](https://gitlab.torproject.org/cohosh/phantombox) for an automated libvirt-based setup that works on Linux.
|
109
desktopApp/tor/README.SNOWFLAKE.md
Normal file
109
desktopApp/tor/README.SNOWFLAKE.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Snowflake
|
||||
|
||||
[![Build Status](https://travis-ci.org/keroserene/snowflake.svg?branch=master)](https://travis-ci.org/keroserene/snowflake)
|
||||
|
||||
Pluggable Transport using WebRTC, inspired by Flashproxy.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
|
||||
- [Structure of this Repository](#structure-of-this-repository)
|
||||
- [Usage](#usage)
|
||||
- [Using Snowflake with Tor](#using-snowflake-with-tor)
|
||||
- [Running a Snowflake Proxy](#running-a-snowflake-proxy)
|
||||
- [Using the Snowflake Library with Other Applications](#using-the-snowflake-library-with-other-applications)
|
||||
- [Test Environment](#test-environment)
|
||||
- [FAQ](#faq)
|
||||
- [More info and links](#more-info-and-links)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
### Structure of this Repository
|
||||
|
||||
- `broker/` contains code for the Snowflake broker
|
||||
- `doc/` contains Snowflake documentation and manpages
|
||||
- `client/` contains the Tor pluggable transport client and client library code
|
||||
- `common/` contains generic libraries used by multiple pieces of Snowflake
|
||||
- `proxy/` contains code for the Go standalone Snowflake proxy
|
||||
- `probetest/` contains code for a NAT probetesting service
|
||||
- `server/` contains the Tor pluggable transport server and server library code
|
||||
|
||||
### Usage
|
||||
|
||||
Snowflake is currently deployed as a pluggable transport for Tor.
|
||||
|
||||
#### Using Snowflake with Tor
|
||||
|
||||
To use the Snowflake client with Tor, you will need to add the appropriate `Bridge` and `ClientTransportPlugin` lines to your [torrc](https://2019.www.torproject.org/docs/tor-manual.html.en) file. See the [client README](client) for more information on building and running the Snowflake client.
|
||||
|
||||
#### Running a Snowflake Proxy
|
||||
|
||||
You can contribute to Snowflake by running a Snowflake proxy. We have the option to run a proxy in your browser or as a standalone Go program. See our [community documentation](https://community.torproject.org/relay/setup/snowflake/) for more details.
|
||||
|
||||
#### Using the Snowflake Library with Other Applications
|
||||
|
||||
Snowflake can be used as a Go API, and adheres to the [v2.1 pluggable transports specification](). For more information on using the Snowflake Go library, see the [Snowflake library documentation](doc/using-the-snowflake-library.md).
|
||||
|
||||
### Test Environment
|
||||
|
||||
There is a Docker-based test environment at https://github.com/cohosh/snowbox.
|
||||
|
||||
### FAQ
|
||||
|
||||
**Q: How does it work?**
|
||||
|
||||
In the Tor use-case:
|
||||
|
||||
1. Volunteers visit websites which host the "snowflake" proxy. (just
|
||||
like flashproxy)
|
||||
2. Tor clients automatically find available browser proxies via the Broker
|
||||
(the domain fronted signaling channel).
|
||||
3. Tor client and browser proxy establish a WebRTC peer connection.
|
||||
4. Proxy connects to some relay.
|
||||
5. Tor occurs.
|
||||
|
||||
More detailed information about how clients, snowflake proxies, and the Broker
|
||||
fit together on the way...
|
||||
|
||||
**Q: What are the benefits of this PT compared with other PTs?**
|
||||
|
||||
Snowflake combines the advantages of flashproxy and meek. Primarily:
|
||||
|
||||
- It has the convenience of Meek, but can support magnitudes more
|
||||
users with negligible CDN costs. (Domain fronting is only used for brief
|
||||
signalling / NAT-piercing to setup the P2P WebRTC DataChannels which handle
|
||||
the actual traffic.)
|
||||
|
||||
- Arbitrarily high numbers of volunteer proxies are possible like in
|
||||
flashproxy, but NATs are no longer a usability barrier - no need for
|
||||
manual port forwarding!
|
||||
|
||||
**Q: Why is this called Snowflake?**
|
||||
|
||||
It utilizes the "ICE" negotiation via WebRTC, and also involves a great
|
||||
abundance of ephemeral and short-lived (and special!) volunteer proxies...
|
||||
|
||||
### More info and links
|
||||
|
||||
We have more documentation in the [Snowflake wiki](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/wikis/home) and at https://snowflake.torproject.org/.
|
||||
|
||||
|
||||
##### -- Android AAR Reproducible Build Setup --
|
||||
|
||||
Using `gomobile` it is possible to build snowflake as shared libraries for all
|
||||
the architectures supported by Android. This is in the _.gitlab-ci.yml_, which
|
||||
runs in GitLab CI. It is also possible to run this setup in a Virtual Machine
|
||||
using [vagrant](https://www.vagrantup.com/). Just run `vagrant up` and it will
|
||||
create and provision the VM. `vagrant ssh` to get into the VM to use it as a
|
||||
development environment.
|
||||
|
||||
##### uTLS Settings
|
||||
|
||||
Snowflake communicate with broker that serves as signaling server with TLS based domain fronting connection, which may be identified by its usage of Go language TLS stack.
|
||||
|
||||
uTLS is a software library designed to initiate the TLS Client Hello fingerprint of browsers or other popular software's TLS stack to evade censorship based on TLS client hello fingerprint with `-utls-imitate` . You can use `-version` to see a list of supported values.
|
||||
|
||||
Depending on client and server configuration, it may not always work as expected as not all extensions are correctly implemented.
|
||||
|
||||
You can also remove SNI (Server Name Indication) from client hello to evade censorship with `-utls-nosni`, not all servers supports this.
|
263
desktopApp/tor/README.WEBTUNNEL.md
Normal file
263
desktopApp/tor/README.WEBTUNNEL.md
Normal file
@ -0,0 +1,263 @@
|
||||
# WebTunnel
|
||||
|
||||
Pluggable Transport based on HTTP Upgrade(HTTPT)
|
||||
|
||||
WebTunnel is pluggable transport that attempt to imitate web browsing activities based on [HTTPT](https://censorbib.nymity.ch/#Frolov2020b).
|
||||
|
||||
## Client Usage
|
||||
Connect to a WebTunnel server with a Tor configuration file like:
|
||||
```
|
||||
UseBridges 1
|
||||
DataDirectory datadir
|
||||
|
||||
ClientTransportPlugin webtunnel exec ./client
|
||||
|
||||
Bridge webtunnel 192.0.2.3:1 url=https://akbwadp9lc5fyyz0cj4d76z643pxgbfh6oyc-167-71-71-157.sslip.io/5m9yq0j4ghkz0fz7qmuw58cvbjon0ebnrsp0
|
||||
|
||||
SocksPort auto
|
||||
|
||||
Log info
|
||||
```
|
||||
## Server Setup
|
||||
|
||||
#### Install Tor
|
||||
On a Debian system, first install tor normally with
|
||||
```
|
||||
apt install apt-transport-https
|
||||
lsb_release -c
|
||||
nano /etc/apt/sources.list.d/tor.list
|
||||
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null
|
||||
apt update
|
||||
apt install tor deb.torproject.org-keyring
|
||||
```
|
||||
|
||||
### Disable default instance
|
||||
The default Tor configuration is not useful for this setup, so the next step will be disabling them.
|
||||
```
|
||||
systemctl stop tor@default.service
|
||||
systemctl mask tor@default.service
|
||||
```
|
||||
|
||||
### Get Environment Ready
|
||||
```
|
||||
#copy server file to server
|
||||
scp server root@$SERVER_ADDRESS:/var/lib/torwebtunnel/webtunnel
|
||||
```
|
||||
|
||||
then create server torrc at `/var/lib/torwebtunnel/torrc`
|
||||
```
|
||||
BridgeRelay 1
|
||||
|
||||
ORPort 10000
|
||||
|
||||
ServerTransportPlugin webtunnel exec /var/lib/torwebtunnel/webtunnel
|
||||
|
||||
ServerTransportListenAddr webtunnel 127.0.0.1:11000
|
||||
|
||||
ExtORPort auto
|
||||
|
||||
ContactInfo WebTunnel email: tor.relay.email@torproject.net ciissversion:2
|
||||
|
||||
Nickname WebTunnelTest
|
||||
|
||||
PublishServerDescriptor 1
|
||||
BridgeDistribution none
|
||||
|
||||
DataDirectory /var/lib/torwebtunnel/tor-data
|
||||
CacheDirectory /tmp/tor-tmp-torwebtunnel
|
||||
|
||||
SocksPort 0
|
||||
```
|
||||
|
||||
#### Configure service unit file
|
||||
Create a service unit file as follow
|
||||
```
|
||||
[Unit]
|
||||
Description=Tor Web Tunnel
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
DynamicUser=yes
|
||||
PrivateUsers=true
|
||||
PrivateMounts=true
|
||||
ProtectSystem=strict
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectClock=true
|
||||
NoNewPrivileges=true
|
||||
ProtectHome=tmpfs
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelLogs=true
|
||||
|
||||
StateDirectory=torwebtunnel
|
||||
|
||||
ExecStart=/usr/bin/tor -f /var/lib/torwebtunnel/torrc --RunAsDaemon 0
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Obtain Certificate
|
||||
WebTunnel Requires a valid TLS certificate, to obtain that
|
||||
```
|
||||
curl https://get.acme.sh | sh -s email=my@example.com
|
||||
~/.acme.sh/acme.sh --issue --standalone --domain $SERVER_ADDRESS
|
||||
```
|
||||
|
||||
#### Install & Configure Nginx
|
||||
To coexist with other content at a single port, it is necessary to install a reverse proxy like nginx:
|
||||
```
|
||||
apt install nginx
|
||||
```
|
||||
|
||||
And then configure HTTP Upgrade forwarding at /etc/nginx/nginx.conf.
|
||||
```
|
||||
--- a/before.conf
|
||||
+++ b/after.conf
|
||||
@@ -60,6 +60,13 @@ http {
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
+
|
||||
+ #WebSocket Support
|
||||
+ map $http_upgrade $connection_upgrade {
|
||||
+ default upgrade;
|
||||
+ '' close;
|
||||
+ }
|
||||
+
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Finally, add http forwarding setting to a new file at /etc/nginx/site-enabled .
|
||||
```
|
||||
server {
|
||||
listen [::]:443 ssl http2;
|
||||
listen 443 ssl http2;
|
||||
server_name $SERVER_ADDRESS;
|
||||
#ssl on;
|
||||
|
||||
# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.cer;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.key;
|
||||
|
||||
|
||||
ssl_session_timeout 15m;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
ssl_session_cache shared:MozSSL:50m;
|
||||
#ssl_ecdh_curve secp521r1,prime256v1,secp384r1;
|
||||
ssl_session_tickets off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
|
||||
location /$PATH {
|
||||
proxy_pass http://127.0.0.1:11000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
###Set WebSocket headers ####
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
### Set Proxy headers ####
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header Front-End-Https on;
|
||||
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Docker Setup
|
||||
|
||||
Webtunnel is a new pluggable transport available for bridge operators.
|
||||
|
||||
### Prerequisites
|
||||
An existing website using nginx balancer to handle traffic. (other load banlancer is currently untested)
|
||||
|
||||
Handle traffic directly, without CDN. (CDN passthrough is currently untested)
|
||||
|
||||
A container runtime like Docker.
|
||||
|
||||
### Configure nginx Forwarding
|
||||
If you haven't already, configure websocket forwarding support in nginx by configure HTTP Upgrade forwarding at /etc/nginx/nginx.conf:
|
||||
```
|
||||
--- a/before.conf
|
||||
+++ b/after.conf
|
||||
@@ -60,6 +60,13 @@ http {
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
+
|
||||
+ #WebSocket Support
|
||||
+ map $http_upgrade $connection_upgrade {
|
||||
+ default upgrade;
|
||||
+ '' close;
|
||||
+ }
|
||||
+
|
||||
}
|
||||
```
|
||||
And add a forwarded path under one the served domain, typically defined in files within `/etc/nginx/sites-enabled/`, replace $PATH with a random string(which you could generate with `echo $(cat /dev/urandom | tr -cd "qwertyuiopasdfghjklzxcvbnmMNBVCXZLKJHGFDSAQWERTUIOP0987654321"|head -c 24)`):
|
||||
```
|
||||
location /$PATH {
|
||||
proxy_pass http://127.0.0.1:11000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
###Set WebSocket headers ####
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
### Set Proxy headers ####
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header Front-End-Https on;
|
||||
|
||||
proxy_redirect off;
|
||||
}
|
||||
```
|
||||
|
||||
### Install Docker Runtime(if necessary)
|
||||
```
|
||||
apt install curl sudo
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh ./get-docker.sh
|
||||
```
|
||||
|
||||
### Run Dockerlized Webtunnel Server
|
||||
Replace `URL` with your domain and path, and `OPERATOR_EMAIL` with your email address, then run:
|
||||
```
|
||||
truncate --size 0 .env
|
||||
echo "URL=https://yourdomain/and/path" >> .env
|
||||
echo "OPERATOR_EMAIL=your@email.org" >> .env
|
||||
echo "BRIDGE_NICKNAME=WTBr$(cat /dev/urandom | tr -cd 'qwertyuiopasdfghjklzxcvbnmMNBVCXZLKJHGFDSAQWERTUIOP0987654321'|head -c 10)" >> .env
|
||||
echo "GENEDORPORT=4$(cat /dev/urandom | tr -cd '0987654321'|head -c 4)" >> .env
|
||||
```
|
||||
This will create an environment file for the configuration of webtunnel bridge.
|
||||
|
||||
After creating the configure file, download the webtunnel docker compose file, and instancize it.
|
||||
````shell
|
||||
curl https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/webtunnel/-/raw/main/release/container/docker-compose.yml?inline=false > docker-compose.yml
|
||||
docker compose up -d
|
||||
````
|
||||
It includes auto update by default, and will update webtunnel bridge server without any further action. Remove `watchtower` to disable this behavior.
|
||||
|
||||
### Get Bridgeline and Check it is Running
|
||||
You can obtain bridgeline and verify if it is working by running
|
||||
```shell
|
||||
docker compose exec webtunnel-bridge get-bridge-line.sh
|
||||
```
|
359517
desktopApp/tor/geoip
Normal file
359517
desktopApp/tor/geoip
Normal file
File diff suppressed because it is too large
Load Diff
155241
desktopApp/tor/geoip6
Normal file
155241
desktopApp/tor/geoip6
Normal file
File diff suppressed because it is too large
Load Diff
1
desktopApp/tor/tor-linux/data/geoip
Symbolic link
1
desktopApp/tor/tor-linux/data/geoip
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip
|
1
desktopApp/tor/tor-linux/data/geoip6
Symbolic link
1
desktopApp/tor/tor-linux/data/geoip6
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip6
|
BIN
desktopApp/tor/tor-linux/tor/libcrypto.so.3
Executable file
BIN
desktopApp/tor/tor-linux/tor/libcrypto.so.3
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/libevent-2.1.so.7
Executable file
BIN
desktopApp/tor/tor-linux/tor/libevent-2.1.so.7
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/libssl.so.3
Executable file
BIN
desktopApp/tor/tor-linux/tor/libssl.so.3
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/libstdc++.so.6
Executable file
BIN
desktopApp/tor/tor-linux/tor/libstdc++.so.6
Executable file
Binary file not shown.
@ -0,0 +1 @@
|
||||
../../../README.CONJURE.md
|
@ -0,0 +1 @@
|
||||
../../../README.SNOWFLAKE.md
|
@ -0,0 +1 @@
|
||||
../../../README.WEBTUNNEL.md
|
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/conjure-client
Executable file
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/conjure-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/lyrebird
Executable file
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/lyrebird
Executable file
Binary file not shown.
@ -0,0 +1,32 @@
|
||||
{
|
||||
"recommendedDefault" : "obfs4",
|
||||
"pluggableTransports" : {
|
||||
"lyrebird" : "ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec ${pt_path}lyrebird",
|
||||
"snowflake" : "ClientTransportPlugin snowflake exec ${pt_path}snowflake-client",
|
||||
"webtunnel" : "ClientTransportPlugin webtunnel exec ${pt_path}webtunnel-client",
|
||||
"conjure" : "ClientTransportPlugin conjure exec ${pt_path}conjure-client -registerURL https://registration.refraction.network/api"
|
||||
},
|
||||
"bridges" : {
|
||||
"meek-azure" : [
|
||||
"meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com"
|
||||
],
|
||||
"obfs4" : [
|
||||
"obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1",
|
||||
"obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0",
|
||||
"obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0",
|
||||
"obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0",
|
||||
"obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0",
|
||||
"obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0",
|
||||
"obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0",
|
||||
"obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0",
|
||||
"obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0"
|
||||
],
|
||||
"snowflake" : [
|
||||
"snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn",
|
||||
"snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/snowflake-client
Executable file
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/snowflake-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/webtunnel-client
Executable file
BIN
desktopApp/tor/tor-linux/tor/pluggable_transports/webtunnel-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-linux/tor/tor
Executable file
BIN
desktopApp/tor/tor-linux/tor/tor
Executable file
Binary file not shown.
1
desktopApp/tor/tor-mac/data/geoip
Symbolic link
1
desktopApp/tor/tor-mac/data/geoip
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip
|
1
desktopApp/tor/tor-mac/data/geoip6
Symbolic link
1
desktopApp/tor/tor-mac/data/geoip6
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip6
|
BIN
desktopApp/tor/tor-mac/tor/libevent-2.1.7.dylib
Executable file
BIN
desktopApp/tor/tor-mac/tor/libevent-2.1.7.dylib
Executable file
Binary file not shown.
@ -0,0 +1 @@
|
||||
../../../README.CONJURE.md
|
@ -0,0 +1 @@
|
||||
../../../README.SNOWFLAKE.md
|
@ -0,0 +1 @@
|
||||
../../../README.WEBTUNNEL.md
|
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/conjure-client
Executable file
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/conjure-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/lyrebird
Executable file
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/lyrebird
Executable file
Binary file not shown.
@ -0,0 +1,32 @@
|
||||
{
|
||||
"recommendedDefault" : "obfs4",
|
||||
"pluggableTransports" : {
|
||||
"lyrebird" : "ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec ${pt_path}lyrebird",
|
||||
"snowflake" : "ClientTransportPlugin snowflake exec ${pt_path}snowflake-client",
|
||||
"webtunnel" : "ClientTransportPlugin webtunnel exec ${pt_path}webtunnel-client",
|
||||
"conjure" : "ClientTransportPlugin conjure exec ${pt_path}conjure-client -registerURL https://registration.refraction.network/api"
|
||||
},
|
||||
"bridges" : {
|
||||
"meek-azure" : [
|
||||
"meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com"
|
||||
],
|
||||
"obfs4" : [
|
||||
"obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1",
|
||||
"obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0",
|
||||
"obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0",
|
||||
"obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0",
|
||||
"obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0",
|
||||
"obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0",
|
||||
"obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0",
|
||||
"obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0",
|
||||
"obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0"
|
||||
],
|
||||
"snowflake" : [
|
||||
"snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn",
|
||||
"snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/snowflake-client
Executable file
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/snowflake-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/webtunnel-client
Executable file
BIN
desktopApp/tor/tor-mac/tor/pluggable_transports/webtunnel-client
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-mac/tor/tor
Executable file
BIN
desktopApp/tor/tor-mac/tor/tor
Executable file
Binary file not shown.
1
desktopApp/tor/tor-win/data/geoip
Symbolic link
1
desktopApp/tor/tor-win/data/geoip
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip
|
1
desktopApp/tor/tor-win/data/geoip6
Symbolic link
1
desktopApp/tor/tor-win/data/geoip6
Symbolic link
@ -0,0 +1 @@
|
||||
../../geoip6
|
@ -0,0 +1 @@
|
||||
../../../README.CONJURE.md
|
@ -0,0 +1 @@
|
||||
../../../README.SNOWFLAKE.md
|
@ -0,0 +1 @@
|
||||
../../../README.WEBTUNNEL.md
|
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/conjure-client.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/conjure-client.exe
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/lyrebird.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/lyrebird.exe
Executable file
Binary file not shown.
@ -0,0 +1,32 @@
|
||||
{
|
||||
"recommendedDefault" : "obfs4",
|
||||
"pluggableTransports" : {
|
||||
"lyrebird" : "ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec ${pt_path}lyrebird.exe",
|
||||
"snowflake" : "ClientTransportPlugin snowflake exec ${pt_path}snowflake-client.exe",
|
||||
"webtunnel" : "ClientTransportPlugin webtunnel exec ${pt_path}webtunnel-client.exe",
|
||||
"conjure" : "ClientTransportPlugin conjure exec ${pt_path}conjure-client.exe -registerURL https://registration.refraction.network/api"
|
||||
},
|
||||
"bridges" : {
|
||||
"meek-azure" : [
|
||||
"meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com"
|
||||
],
|
||||
"obfs4" : [
|
||||
"obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1",
|
||||
"obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0",
|
||||
"obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0",
|
||||
"obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0",
|
||||
"obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0",
|
||||
"obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0",
|
||||
"obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0",
|
||||
"obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0",
|
||||
"obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0",
|
||||
"obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0"
|
||||
],
|
||||
"snowflake" : [
|
||||
"snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn",
|
||||
"snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/snowflake-client.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/snowflake-client.exe
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/webtunnel-client.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/pluggable_transports/webtunnel-client.exe
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-win/tor/tor-gencert.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/tor-gencert.exe
Executable file
Binary file not shown.
BIN
desktopApp/tor/tor-win/tor/tor.exe
Executable file
BIN
desktopApp/tor/tor-win/tor/tor.exe
Executable file
Binary file not shown.
14
desktopApp/tsconfig.json
Normal file
14
desktopApp/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": false,
|
||||
"outDir": "."
|
||||
},
|
||||
"exclude": [ "node_modules" ]
|
||||
}
|
@ -226,6 +226,16 @@ services:
|
||||
volumes:
|
||||
- ./node/db:/var/lib/postgresql/data
|
||||
|
||||
strfry:
|
||||
build: ./docker/strfry
|
||||
container_name: strfry-dev
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./docker/strfry/strfry.conf:/etc/strfry.conf:ro
|
||||
- ./docker/strfry/onion_urls.txt:/app/onion_urls.txt:ro
|
||||
- ./node/strfry/db:/app/strfry-db:rw
|
||||
network_mode: service:tor
|
||||
|
||||
# # Postgresql for CLN
|
||||
# postgres-cln:
|
||||
# image: postgres:14.2-alpine
|
||||
|
@ -27,6 +27,7 @@ services:
|
||||
- "9998:9998"
|
||||
- "5432:5432"
|
||||
- "6379:6379"
|
||||
- "7777:7777"
|
||||
volumes:
|
||||
- bitcoin:/bitcoin/.bitcoin/
|
||||
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
|
||||
@ -182,7 +183,7 @@ services:
|
||||
# celery-worker:
|
||||
# image: backend-image
|
||||
# pull_policy: never
|
||||
# container_name: celery-worker
|
||||
# container_name: test-celery-worker
|
||||
# restart: always
|
||||
# environment:
|
||||
# DEVELOPMENT: True
|
||||
|
41
docker/strfry/Dockerfile
Normal file
41
docker/strfry/Dockerfile
Normal file
@ -0,0 +1,41 @@
|
||||
FROM ubuntu:jammy
|
||||
ENV TZ=Europe/London
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends \
|
||||
git g++ make pkg-config libtool ca-certificates \
|
||||
libssl-dev zlib1g-dev liblmdb-dev libflatbuffers-dev \
|
||||
libsecp256k1-dev libzstd-dev
|
||||
|
||||
# setup app
|
||||
RUN git clone https://github.com/KoalaSat/strfry /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN git submodule update --init
|
||||
RUN make setup-golpe
|
||||
RUN make clean
|
||||
RUN make -j4
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends \
|
||||
liblmdb0 libflatbuffers1 libsecp256k1-0 libb2-1 libzstd1 torsocks cron\
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN echo "TorAddress 127.0.0.1" >> /etc/tor/torsocks.conf
|
||||
RUN echo "TorPort 9050" >> /etc/tor/torsocks.conf
|
||||
|
||||
# Setting up crontab
|
||||
COPY crontab /etc/cron.d/crontab
|
||||
RUN chmod 0644 /etc/cron.d/crontab
|
||||
RUN crontab /etc/cron.d/crontab
|
||||
|
||||
# Setting up entrypoints
|
||||
COPY sync.sh /etc/strfry/sync.sh
|
||||
COPY entrypoint.sh /etc/strfry/entrypoint.sh
|
||||
|
||||
RUN chmod +x /etc/strfry/entrypoint.sh
|
||||
RUN chmod +x /etc/strfry/sync.sh
|
||||
|
||||
#Setting up logs
|
||||
RUN touch /var/log/cron.log && chmod 0644 /var/log/cron.log
|
||||
|
||||
ENTRYPOINT ["/etc/strfry/entrypoint.sh"]
|
24
docker/strfry/crontab
Normal file
24
docker/strfry/crontab
Normal file
@ -0,0 +1,24 @@
|
||||
# Edit this file to introduce tasks to be run by cron.
|
||||
#
|
||||
# Each task to run has to be defined through a single line
|
||||
# indicating with different fields when the task will be run
|
||||
# and what command to run for the task
|
||||
#
|
||||
# To define the time you can provide concrete values for
|
||||
# minute (m), hour (h), day of month (dom), month (mon),
|
||||
# and day of week (dow) or use '*' in these fields (for 'any').
|
||||
#
|
||||
# Notice that tasks will be started based on the cron's system
|
||||
# daemon's notion of time and timezones.
|
||||
#
|
||||
# Output of the crontab jobs (including errors) is sent through
|
||||
# email to the user the crontab file belongs to (unless redirected).
|
||||
#
|
||||
# For example, you can run a backup of all your user accounts
|
||||
# at 5 a.m every week with:
|
||||
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
|
||||
#
|
||||
# For more information see the manual pages of crontab(5) and cron(8)
|
||||
#
|
||||
# m h dom mon dow command
|
||||
*/1 * * * * torsocks /etc/strfry/sync.sh >> /var/log/cron.log 2>&1
|
3
docker/strfry/entrypoint.sh
Executable file
3
docker/strfry/entrypoint.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
cron -f -l 8 & tail -f /var/log/cron.log & /app/strfry relay
|
4
docker/strfry/onion_urls.txt
Normal file
4
docker/strfry/onion_urls.txt
Normal file
@ -0,0 +1,4 @@
|
||||
ws://testraliar7xkhos2gipv2k65obykofb4jqzl5l4danfryacifi4t7qd.onion/nostr
|
||||
ws://jpp3w5tpxtyg6lifonisdszpriiapszzem4wod2zsdweyfenlsxeoxid.onion/nostr
|
||||
ws://ghbtv7lhoyhomyir4xvxaeyqgx4ylxksia343jaat3njqqlkqpdjqcyd.onion/nostr
|
||||
ws://wsjyhbashc4zrrex6vijpryujggbka5plry2o62dxqoz3pxinblnj4ad.onion/nostr
|
138
docker/strfry/strfry.conf
Normal file
138
docker/strfry/strfry.conf
Normal file
@ -0,0 +1,138 @@
|
||||
##
|
||||
## Default strfry config
|
||||
##
|
||||
|
||||
# Directory that contains the strfry LMDB database (restart required)
|
||||
db = "/app/strfry-db/"
|
||||
|
||||
dbParams {
|
||||
# Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)
|
||||
maxreaders = 256
|
||||
|
||||
# Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required)
|
||||
mapsize = 10995116277760
|
||||
|
||||
# Disables read-ahead when accessing the LMDB mapping. Reduces IO activity when DB size is larger than RAM. (restart required)
|
||||
noReadAhead = false
|
||||
}
|
||||
|
||||
events {
|
||||
# Maximum size of normalised JSON, in bytes
|
||||
maxEventSize = 65536
|
||||
|
||||
# Events newer than this will be rejected
|
||||
rejectEventsNewerThanSeconds = 900
|
||||
|
||||
# Events older than this will be rejected
|
||||
rejectEventsOlderThanSeconds = 94608000
|
||||
|
||||
# Ephemeral events older than this will be rejected
|
||||
rejectEphemeralEventsOlderThanSeconds = 60
|
||||
|
||||
# Ephemeral events will be deleted from the DB when older than this
|
||||
ephemeralEventsLifetimeSeconds = 300
|
||||
|
||||
# Maximum number of tags allowed
|
||||
maxNumTags = 2000
|
||||
|
||||
# Maximum size for tag values, in bytes
|
||||
maxTagValSize = 1024
|
||||
}
|
||||
|
||||
relay {
|
||||
# Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required)
|
||||
bind = "0.0.0.0"
|
||||
|
||||
# Port to open for the nostr websocket protocol (restart required)
|
||||
port = 7777
|
||||
|
||||
# Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)
|
||||
nofiles = 1000000
|
||||
|
||||
# HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)
|
||||
realIpHeader = ""
|
||||
|
||||
info {
|
||||
# NIP-11: Name of this server. Short/descriptive (< 30 characters)
|
||||
name = "Robosats"
|
||||
|
||||
# NIP-11: Detailed information about relay, free-form
|
||||
description = "Federation cache system."
|
||||
|
||||
# NIP-11: Administrative nostr pubkey, for contact purposes
|
||||
pubkey = ""
|
||||
|
||||
# NIP-11: Alternative administrative contact (email, website, etc)
|
||||
contact = ""
|
||||
}
|
||||
|
||||
# Maximum accepted incoming websocket frame size (should be larger than max event) (restart required)
|
||||
maxWebsocketPayloadSize = 131072
|
||||
|
||||
# Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required)
|
||||
autoPingSeconds = 55
|
||||
|
||||
# If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy)
|
||||
enableTcpKeepalive = false
|
||||
|
||||
# How much uninterrupted CPU time a REQ query should get during its DB scan
|
||||
queryTimesliceBudgetMicroseconds = 10000
|
||||
|
||||
# Maximum records that can be returned per filter
|
||||
maxFilterLimit = 500
|
||||
|
||||
# Maximum number of subscriptions (concurrent REQs) a connection can have open at any time
|
||||
maxSubsPerConnection = 3
|
||||
|
||||
writePolicy {
|
||||
# If non-empty, path to an executable script that implements the writePolicy plugin logic
|
||||
plugin = ""
|
||||
}
|
||||
|
||||
compression {
|
||||
# Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required)
|
||||
enabled = true
|
||||
|
||||
# Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required)
|
||||
slidingWindow = false
|
||||
}
|
||||
|
||||
logging {
|
||||
# Dump all incoming messages
|
||||
dumpInAll = false
|
||||
|
||||
# Dump all incoming EVENT messages
|
||||
dumpInEvents = false
|
||||
|
||||
# Dump all incoming REQ/CLOSE messages
|
||||
dumpInReqs = false
|
||||
|
||||
# Log performance metrics for initial REQ database scans
|
||||
dbScanPerf = false
|
||||
|
||||
# Log reason for invalid event rejection? Can be disabled to silence excessive logging
|
||||
invalidEvents = true
|
||||
}
|
||||
|
||||
numThreads {
|
||||
# Ingester threads: route incoming requests, validate events/sigs (restart required)
|
||||
ingester = 3
|
||||
|
||||
# reqWorker threads: Handle initial DB scan for events (restart required)
|
||||
reqWorker = 3
|
||||
|
||||
# reqMonitor threads: Handle filtering of new events (restart required)
|
||||
reqMonitor = 3
|
||||
|
||||
# negentropy threads: Handle negentropy protocol messages (restart required)
|
||||
negentropy = 2
|
||||
}
|
||||
|
||||
negentropy {
|
||||
# Support negentropy protocol messages
|
||||
enabled = true
|
||||
|
||||
# Maximum records that sync will process before returning an error
|
||||
maxSyncEvents = 1000000
|
||||
}
|
||||
}
|
7
docker/strfry/sync.sh
Executable file
7
docker/strfry/sync.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
filters='{"kinds":[38383]}'
|
||||
|
||||
while IFS= read -r line; do
|
||||
/app/strfry --config /etc/strfry.conf sync ${line} --filter "$filters" --dir both
|
||||
done < /app/onion_urls.txt
|
@ -365,7 +365,7 @@ paths:
|
||||
- `11` "In dispute"
|
||||
- `12` "Collaboratively cancelled"
|
||||
- `13` "Sending satoshis to buyer"
|
||||
- `14` "Sucessful trade"
|
||||
- `14` "Successful trade"
|
||||
- `15` "Failed lightning network routing"
|
||||
- `16` "Wait for dispute resolution"
|
||||
- `17` "Maker lost dispute"
|
||||
@ -1833,7 +1833,7 @@ components:
|
||||
* `11` - In dispute
|
||||
* `12` - Collaboratively cancelled
|
||||
* `13` - Sending satoshis to buyer
|
||||
* `14` - Sucessful trade
|
||||
* `14` - Successful trade
|
||||
* `15` - Failed lightning network routing
|
||||
* `16` - Wait for dispute resolution
|
||||
* `17` - Maker lost dispute
|
||||
|
@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "node --max-old-space-size=4096 ./node_modules/.bin/webpack --watch --progress --mode development",
|
||||
"test": "jest",
|
||||
"build": "webpack --mode production",
|
||||
"build": "webpack --config webpack.config.ts --mode production",
|
||||
"lint": "eslint src/**/*.{ts,tsx}",
|
||||
"lint:fix": "eslint --fix 'src/**/*.{ts,tsx}'",
|
||||
"format": "prettier --write '**/**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
|
||||
|
@ -24,7 +24,7 @@ const App = (): JSX.Element => {
|
||||
<GarageContextProvider>
|
||||
<FederationContextProvider>
|
||||
<CssBaseline />
|
||||
{window.NativeRobosats === undefined ? <HostAlert /> : <TorConnectionBadge />}
|
||||
{(window.NativeRobosats === undefined && window.RobosatsClient === undefined )? <HostAlert /> : <TorConnectionBadge />}
|
||||
<Main />
|
||||
</FederationContextProvider>
|
||||
</GarageContextProvider>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { MemoryRouter, BrowserRouter } from 'react-router-dom';
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import { MemoryRouter,HashRouter ,BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
||||
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
||||
|
||||
import { NavBar, MainDialogs } from './';
|
||||
@ -11,7 +11,16 @@ import { useTranslation } from 'react-i18next';
|
||||
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
||||
import Routes from './Routes';
|
||||
|
||||
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
||||
function getRouter() {
|
||||
if (window.NativeRobosats === undefined && window.RobosatsClient === undefined) {
|
||||
return BrowserRouter;
|
||||
} else if (window.RobosatsClient === 'desktop-app') {
|
||||
return HashRouter;
|
||||
} else {
|
||||
return MemoryRouter;
|
||||
}
|
||||
}
|
||||
const Router = getRouter();
|
||||
|
||||
const TestnetTypography = styled(Typography)({
|
||||
height: 0,
|
||||
@ -54,7 +63,87 @@ const Main: React.FC = () => {
|
||||
)}
|
||||
|
||||
<MainBox navbarHeight={navbarHeight}>
|
||||
<Routes />
|
||||
<Routes>
|
||||
{['/robot/:token?', '/', ''].map((path, index) => {
|
||||
return (
|
||||
<Route
|
||||
path={path}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'robot'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<RobotPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Route
|
||||
path={'/offers'}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'offers'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<BookPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/create'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'create'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<MakerPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/order/:shortAlias/:orderId'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'order'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<OrderPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/settings'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'settings'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<SettingsPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</MainBox>
|
||||
<NavBar />
|
||||
<MainDialogs />
|
||||
|
@ -90,6 +90,7 @@ const FlagWithProps = ({ code, width = '1.428em', height = '1.428em' }: Props):
|
||||
if (code === 'IRT') flag = <Flags.IR {...defaultProps} />;
|
||||
if (code === 'BDT') flag = <Flags.BD {...defaultProps} />;
|
||||
if (code === 'ALL') flag = <Flags.AL {...defaultProps} />;
|
||||
if (code === 'DZD') flag = <Flags.DZ {...defaultProps} />;
|
||||
if (code === 'ANY') flag = <EarthIcon {...defaultProps} />;
|
||||
if (code === 'XAU') flag = <GoldIcon {...defaultProps} />;
|
||||
if (code === 'BTC') flag = <SwapCallsIcon color='primary' />;
|
||||
|
@ -248,7 +248,7 @@ const Notifications = ({
|
||||
// 11: 'In dispute'
|
||||
// 12: 'Collaboratively cancelled'
|
||||
// 13: 'Sending satoshis to buyer'
|
||||
// 14: 'Sucessful trade'
|
||||
// 14: 'Successful trade'
|
||||
// 15: 'Failed lightning network routing'
|
||||
// 16: 'Wait for dispute resolution'
|
||||
// 17: 'Maker lost dispute'
|
||||
|
@ -174,8 +174,8 @@ const OrderDetails = ({
|
||||
|
||||
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
||||
const tradeFee = order.is_maker
|
||||
? coordinator.info?.maker_fee ?? 0
|
||||
: coordinator.info?.taker_fee ?? 0;
|
||||
? (coordinator.info?.maker_fee ?? 0)
|
||||
: (coordinator.info?.taker_fee ?? 0);
|
||||
const defaultRoutingBudget = 0.001;
|
||||
const btc_now = order.satoshis_now / 100000000;
|
||||
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
||||
|
@ -639,7 +639,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
||||
}
|
||||
break;
|
||||
|
||||
// 14: 'Sucessful trade'
|
||||
// 14: 'Successful trade'
|
||||
case 14:
|
||||
baseContract.title = 'Trade finished!';
|
||||
baseContract.titleColor = 'success';
|
||||
|
@ -40,6 +40,7 @@ export interface SlideDirection {
|
||||
export type TorStatus = 'ON' | 'STARTING' | 'STOPPING' | 'OFF';
|
||||
|
||||
export const isNativeRoboSats = !(window.NativeRobosats === undefined);
|
||||
export const isDesktopRoboSats = !(window.RobosatsClient === undefined);
|
||||
|
||||
const pageFromPath = window.location.pathname.split('/')[1];
|
||||
const isPagePathEmpty = pageFromPath === '';
|
||||
@ -77,15 +78,18 @@ const makeTheme = function (settings: Settings): Theme {
|
||||
const getHostUrl = (network = 'mainnet'): string => {
|
||||
let host = '';
|
||||
let protocol = '';
|
||||
if (window.NativeRobosats === undefined) {
|
||||
if(isDesktopRoboSats){
|
||||
host = defaultFederation.exp[network].onion;
|
||||
protocol = 'http:';
|
||||
}
|
||||
else if (window.NativeRobosats === undefined) {
|
||||
host = getHost();
|
||||
protocol = location.protocol;
|
||||
} else {
|
||||
host = defaultFederation.exp[network].Onion;
|
||||
host = defaultFederation.exp[network].onion;
|
||||
protocol = 'http:';
|
||||
}
|
||||
const hostUrl = `${protocol}//${host}`;
|
||||
|
||||
const hostUrl = `${host}`;
|
||||
return hostUrl;
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ const statusToDelay = [
|
||||
100000, // 'In dispute'
|
||||
999999, // 'Collaboratively cancelled'
|
||||
10000, // 'Sending satoshis to buyer'
|
||||
60000, // 'Sucessful trade'
|
||||
60000, // 'Successful trade'
|
||||
30000, // 'Failed lightning network routing'
|
||||
300000, // 'Wait for dispute resolution'
|
||||
300000, // 'Maker lost dispute'
|
||||
|
@ -17,8 +17,23 @@ type FederationHooks = 'onCoordinatorUpdate' | 'onFederationUpdate';
|
||||
|
||||
export class Federation {
|
||||
constructor(origin: Origin, settings: Settings, hostUrl: string) {
|
||||
this.coordinators = {};
|
||||
this.exchange = { ...defaultExchange };
|
||||
this.coordinators = Object.entries(defaultFederation).reduce(
|
||||
(acc: Record<string, Coordinator>, [key, value]: [string, any]) => {
|
||||
if (getHost() !== '127.0.0.1:8000' && key === 'local') {
|
||||
// Do not add `Local Dev` unless it is running on localhost
|
||||
return acc;
|
||||
} else {
|
||||
acc[key] = new Coordinator(value, origin, settings, hostUrl);
|
||||
|
||||
return acc;
|
||||
}
|
||||
},
|
||||
{},
|
||||
);
|
||||
this.exchange = {
|
||||
...defaultExchange,
|
||||
totalCoordinators: Object.keys(this.coordinators).length,
|
||||
};
|
||||
this.book = [];
|
||||
this.hooks = {
|
||||
onCoordinatorUpdate: [],
|
||||
|
@ -83,7 +83,7 @@ class Garage {
|
||||
// Slots
|
||||
getSlot: (token?: string) => Slot | null = (token) => {
|
||||
const currentToken = token ?? this.currentSlot;
|
||||
return currentToken ? this.slots[currentToken] ?? null : null;
|
||||
return currentToken ? (this.slots[currentToken] ?? null) : null;
|
||||
};
|
||||
|
||||
deleteSlot: (token?: string) => void = (token) => {
|
||||
|
1
frontend/src/services/Native/index.d.ts
vendored
1
frontend/src/services/Native/index.d.ts
vendored
@ -5,6 +5,7 @@ declare global {
|
||||
ReactNativeWebView?: ReactNativeWebView;
|
||||
NativeRobosats?: NativeRobosats;
|
||||
RobosatsSettings: 'web-basic' | 'web-pro' | 'selfhosted-basic' | 'selfhosted-pro';
|
||||
RobosatsClient: 'desktop-app' | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
70
frontend/src/services/System/SystemDesktopClient/index.ts
Normal file
70
frontend/src/services/System/SystemDesktopClient/index.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { type SystemClient } from '..';
|
||||
|
||||
class SystemDesktopClient implements SystemClient {
|
||||
public loading = false;
|
||||
|
||||
public copyToClipboard: (value: string) => void = (value) => {
|
||||
// navigator clipboard api needs a secure context (https)
|
||||
// this function attempts to copy also on http contexts
|
||||
// useful on the http i2p site and on torified browsers
|
||||
if (navigator.clipboard !== undefined && window.isSecureContext) {
|
||||
// navigator clipboard api method'
|
||||
void navigator.clipboard.writeText(value);
|
||||
} else {
|
||||
// text area method
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = value;
|
||||
// make the textarea out of viewport
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.visibility = 'hidden';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
// here the magic happens
|
||||
document.execCommand('copy');
|
||||
textArea.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// Cookies
|
||||
public getCookie: (key: string) => string = (key) => {
|
||||
let cookieValue = null;
|
||||
if (document?.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the key we want?
|
||||
if (cookie.substring(0, key.length + 1) === key + '=') {
|
||||
cookieValue = decodeURIComponent(cookie.substring(key.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookieValue ?? '';
|
||||
};
|
||||
|
||||
public setCookie: (key: string, value: string) => void = (key, value) => {
|
||||
document.cookie = `${key}=${value};path=/;SameSite=None;Secure`;
|
||||
};
|
||||
|
||||
public deleteCookie: (key: string) => void = (key) => {
|
||||
document.cookie = `${key}= ;path=/; expires = Thu, 01 Jan 1970 00:00:00 GMT`;
|
||||
};
|
||||
|
||||
// Local storage
|
||||
public getItem: (key: string) => string = (key) => {
|
||||
const value = window.sessionStorage.getItem(key);
|
||||
return value ?? '';
|
||||
};
|
||||
|
||||
public setItem: (key: string, value: string) => void = (key, value) => {
|
||||
window.sessionStorage.setItem(key, value);
|
||||
};
|
||||
|
||||
public deleteItem: (key: string) => void = (key) => {
|
||||
window.sessionStorage.removeItem(key);
|
||||
};
|
||||
}
|
||||
|
||||
export default SystemDesktopClient;
|
@ -1,5 +1,6 @@
|
||||
import SystemNativeClient from './SystemNativeClient';
|
||||
import SystemWebClient from './SystemWebClient';
|
||||
import SystemDesktopClient from './SystemDesktopClient';
|
||||
|
||||
export interface SystemClient {
|
||||
loading: boolean;
|
||||
@ -12,9 +13,18 @@ export interface SystemClient {
|
||||
deleteItem: (key: string) => void;
|
||||
}
|
||||
|
||||
export const systemClient: SystemClient =
|
||||
function getSystemClient(): SystemClient {
|
||||
if (window.navigator.userAgent.includes('robosats')) {
|
||||
// If userAgent has "RoboSats", we assume the app is running inside of the
|
||||
// react-native-web view of the RoboSats Android app.
|
||||
window.navigator.userAgent.includes('robosats')
|
||||
? new SystemNativeClient()
|
||||
: new SystemWebClient();
|
||||
return new SystemNativeClient();
|
||||
} else if (window.navigator.userAgent.includes('Electron')) {
|
||||
// If userAgent has "Electron", we assume the app is running inside of an Electron app.
|
||||
return new SystemDesktopClient();
|
||||
} else {
|
||||
// Otherwise, we assume the app is running in a web browser.
|
||||
return new SystemWebClient();
|
||||
}
|
||||
}
|
||||
|
||||
export const systemClient: SystemClient = getSystemClient();
|
||||
|
@ -75,6 +75,7 @@
|
||||
"74": "IRT",
|
||||
"75": "BDT",
|
||||
"76": "ALL",
|
||||
"77": "DZD",
|
||||
"300": "XAU",
|
||||
"1000": "BTC"
|
||||
}
|
||||
|
@ -5,15 +5,15 @@
|
||||
"Chart": "Gràfic",
|
||||
"Create": "Crear",
|
||||
"List": "Taula",
|
||||
"Map": "Map",
|
||||
"Map": "Mapa",
|
||||
"#3": "Phrases in basic/MakerPage/index.tsx",
|
||||
"Existing orders match yours!": "Existeixen ordres que coincideixen!",
|
||||
"#4": "Phrases in basic/NavBar/MoreTooltip.tsx",
|
||||
"Community and public support": "Suport comunitari i públic",
|
||||
"Exchange summary": "Exchange summary",
|
||||
"Exchange summary": "Resum de l'intercanvi",
|
||||
"Learn RoboSats": "Aprèn RoboSats",
|
||||
"RoboSats information": "Informació de RoboSats",
|
||||
"client for nerds": "client for nerds",
|
||||
"client for nerds": "client per nerds",
|
||||
"#5": "Phrases in basic/NavBar/NavBar.tsx",
|
||||
"More": "Més",
|
||||
"Offers": "Ofertes",
|
||||
@ -44,7 +44,7 @@
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Introdueix el teu token per reconstruir el teu robot i accedir a les seves operacions.",
|
||||
"Paste token here": "Enganxa el token aquí",
|
||||
"Recover": "Recuperar",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Robot recovery": "Recuperació de Robots",
|
||||
"#9": "Phrases in basic/RobotPage/RobotProfile.tsx",
|
||||
"Active order #{{orderID}}": "Ordre activa #{{orderID}}",
|
||||
"Add Robot": "Afegir Robot",
|
||||
@ -53,8 +53,8 @@
|
||||
"Delete Garage": "Esborrar Garatge",
|
||||
"Last order #{{orderID}}": "Última ordre #{{orderID}}",
|
||||
"Logout": "Sortir",
|
||||
"Looking for orders!": "Looking for orders!",
|
||||
"No existing orders found": "No existing orders found",
|
||||
"Looking for orders!": "Buscant ordres!",
|
||||
"No existing orders found": "No s'han trobat ordres",
|
||||
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "La reutilització de la identitat de trading degrada la teva privadesa davant d'altres usuaris, coordinadors i observadors.",
|
||||
"Robot Garage": "Garatge de Robots",
|
||||
"Store your token safely": "Guarda el teu token de manera segura",
|
||||
@ -91,7 +91,7 @@
|
||||
"DESTINATION": "DESTÍ",
|
||||
"I want to": "Vull",
|
||||
"METHOD": "MÈTODE",
|
||||
"Select Host": "Select Host",
|
||||
"Select Host": "Selecciona Amfitrió",
|
||||
"Select Payment Currency": "Selecciona moneda de pagament",
|
||||
"Select Payment Method": "Tria mètode de pagament",
|
||||
"Sell": "Vendre",
|
||||
@ -99,7 +99,7 @@
|
||||
"Swap In": "Swap In",
|
||||
"Swap Out": "Swap Out",
|
||||
"and use": "i fer servir",
|
||||
"hosted by": "hosted by",
|
||||
"hosted by": "allotjat per",
|
||||
"pay with": "pagar amb",
|
||||
"#16": "Phrases in components/BookTable/index.tsx",
|
||||
"Add filter": "Afegir filtre",
|
||||
@ -167,23 +167,23 @@
|
||||
"yes": "si",
|
||||
"#17": "Phrases in components/Charts/DepthChart/index.tsx",
|
||||
"#18": "Phrases in components/Charts/MapChart/index.tsx",
|
||||
"Accept": "Accept",
|
||||
"By doing so, you will be fetching map tiles from a third-party provider. Depending on your setup, private information might be leaked to servers outside the RoboSats federation.": "By doing so, you will be fetching map tiles from a third-party provider. Depending on your setup, private information might be leaked to servers outside the RoboSats federation.",
|
||||
"Accept": "Accepta",
|
||||
"By doing so, you will be fetching map tiles from a third-party provider. Depending on your setup, private information might be leaked to servers outside the RoboSats federation.": "En fer-ho, s'obtindran fitxes de mapes d'un proveïdor de tercers. Depenent de la configuració, la informació privada es pot filtrar a servidors fora de la federació RoboSats",
|
||||
"Close": "Tancar",
|
||||
"Download high resolution map?": "Download high resolution map?",
|
||||
"Download high resolution map?": "Descarregar el mapa d'alta resolució?",
|
||||
"Show tiles": "Show tiles",
|
||||
"#19": "Phrases in components/Charts/helpers/OrderTooltip/index.tsx",
|
||||
"#20": "Phrases in components/Dialogs/About.tsx",
|
||||
"(GitHub).": "(GitHub).",
|
||||
"(Telegram)": "(Telegram)",
|
||||
". RoboSats developers will never contact you. The developers or the coordinators will definitely never ask for your robot token.": ". RoboSats developers will never contact you. The developers or the coordinators will definitely never ask for your robot token.",
|
||||
". RoboSats developers will never contact you. The developers or the coordinators will definitely never ask for your robot token.": ". RoboSats mai et contactarà. Ni els desenvolupadors ni els coordinadors de RoboSats mai et preguntaran pel token del teu Robot.",
|
||||
"All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.": "Tots sempre que siguin ràpids. Pots escriure a sota el teu mètode de pagament preferit(s). Hauràs de trobar un company que accepti aquest mètode. El pas per intercanviar el fiat té un temps d'expiració de 24 hores abans no s'obri una disputa automàticament. Et recomanem mètodes d'enviament de fiat.",
|
||||
"Are there trade limits?": "Hi ha límit d'intercanvis?",
|
||||
"At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, the RoboSats coordinator will help resolving the dispute.": "At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, the RoboSats coordinator will help resolving the dispute.",
|
||||
"At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, the RoboSats coordinator will help resolving the dispute.": "En cap moment AnonymousAlice01 ni BafflingBob02 han de confiar els fons de bitcoin a l'altra part. En cas de conflicte, el personal de RoboSats ajudarà a resoldre la disputa.",
|
||||
"Be aware your fiat payment provider might charge extra fees. In any case, the buyer bears the costs of sending fiat. That includes banking charges, transfer fees and foreign exchange spreads. The seller must receive exactly the amount stated in the order details.": "Tingues en compte que la teva pasarel·la de pagaments fiat pot cobrar-te comissions extra. En qualsevol cas, aquests costos sempre són coberts pel comprador. Això inclou. comissions de transferència, costos bancaris i forquilla d'intercavis de divises. El venedor ha de rebre la quantitat exacte que es detalla a l'ordre.",
|
||||
"Disclaimer": "Avís",
|
||||
"During a typical order, your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.": "During a typical order, your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.",
|
||||
"Each RoboSats coordinator will set a maximum trade size to minimize the hassle of lightning routing failures. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously using the Robot garage. Remember to back up your robot tokens!": "Each RoboSats coordinator will set a maximum trade size to minimize the hassle of lightning routing failures. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously using the Robot garage. Remember to back up your robot tokens!",
|
||||
"During a typical order, your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.": "Durant un intercanvi típic, el teu company d'intercanvi és l'únic que pot potencialment esbrinar quelcom sobre tu. Mantingues una conversa curta i concisa. Evita donar dades que no siguin estrictament necessàries pel pagament del fiat.",
|
||||
"Each RoboSats coordinator will set a maximum trade size to minimize the hassle of lightning routing failures. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously using the Robot garage. Remember to back up your robot tokens!": "Cada coordinador de RoboSats establirà una mida d’intercanvi màxima per minimitzar problemes d’enrutament Lightning. No hi ha límit d'intercanvis per dia. Un robot només pot tenir una ordre a la vegada. No obstant això, es poden utilitzar múltiples robots simultàniament utilitzant el garatge Robot. Recorda fer una còpia de seguretat dels tokens dels teus robots!",
|
||||
"How does it work?": "Com funciona?",
|
||||
"How it works": "Com funciona",
|
||||
"How to use": "Com utilizar",
|
||||
@ -193,26 +193,26 @@
|
||||
"It is a BTC/FIAT peer-to-peer exchange over lightning.": "És un exchange P2P que facilita intercanvis bitcoin/fiat sobre Lightning.",
|
||||
"It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.": "Simplifica l'emparellament i minimitza la necessitat de confiança. RoboSats es centra en la privacitat i la velocitat.",
|
||||
"Project source code": "Codi font del projecte",
|
||||
"RoboSats is a decentralized exchange with multiple, fully redundant, trade coordinators. The coordinator provides the infrastructure for your trade: mantains the intermediary lightning node, does book keeping, and relays your encrypted chat messages. The coordinator is also the judge in case your order enters a dispute. The coordinator is a trusted role, make sure you trust your coordinator by exploring its profile, webpage, social media and the comments from other users online.": "RoboSats is a decentralized exchange with multiple, fully redundant, trade coordinators. The coordinator provides the infrastructure for your trade: mantains the intermediary lightning node, does book keeping, and relays your encrypted chat messages. The coordinator is also the judge in case your order enters a dispute. The coordinator is a trusted role, make sure you trust your coordinator by exploring its profile, webpage, social media and the comments from other users online.",
|
||||
"RoboSats is a decentralized exchange with multiple, fully redundant, trade coordinators. The coordinator provides the infrastructure for your trade: mantains the intermediary lightning node, does book keeping, and relays your encrypted chat messages. The coordinator is also the judge in case your order enters a dispute. The coordinator is a trusted role, make sure you trust your coordinator by exploring its profile, webpage, social media and the comments from other users online.": "RoboSats és un intercanvi descentralitzat amb múltiples coordinadors comercials totalment redundants. El coordinador proporciona la infraestructura pel teu intercanvi: manté el node intermedi de LN, manté la reserva i transmet els teus missatges de xat xifrats. El coordinador és també el jutge en cas que la teva comanda entri a una disputa. El coordinador és un rol de confiança, assegura't que confies en el teu coordinador explorant el seu perfil, pàgina web, xarxes socials i els comentaris d'altres usuaris en línia.",
|
||||
"RoboSats is an open source project ": "RoboSats és un projecte de codi obert ",
|
||||
"The RoboSats client, which you run on your local machine or browser, does not collect or share your IP address, location, name, or personal data. The client encrypts your private messages, which can only be decrypted by your trade partner.": "The RoboSats client, which you run on your local machine or browser, does not collect or share your IP address, location, name, or personal data. The client encrypts your private messages, which can only be decrypted by your trade partner.",
|
||||
"The coordinator you choose will maintain a database of pseudonymous robots and orders for the application to function correctly. You can further enhance your privacy by using proxy nodes or coinjoining.": "The coordinator you choose will maintain a database of pseudonymous robots and orders for the application to function correctly. You can further enhance your privacy by using proxy nodes or coinjoining.",
|
||||
"The RoboSats client, which you run on your local machine or browser, does not collect or share your IP address, location, name, or personal data. The client encrypts your private messages, which can only be decrypted by your trade partner.": "El client RoboSats, que executes al teu ordinador o navegador local, no recull ni comparteix la teva adreça IP, ubicació, nom o dades personals. El client encripta els teus missatges privats, que només poden ser desencriptats pel teu company d’intercanvi.",
|
||||
"The coordinator you choose will maintain a database of pseudonymous robots and orders for the application to function correctly. You can further enhance your privacy by using proxy nodes or coinjoining.": "El coordinador que triïs mantindrà una base de dades de robots i ordres pseudònima perquè l'aplicació funcioni correctament. Pots millorar encara més la teva privacitat utilitzant nodes proxy o coinjoining.",
|
||||
"The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.": "El venedor té els mateixos riscos de devolució que amb qualsevol altre servei P2P. PayPal o targetes de crèdit no són recomanades.",
|
||||
"The trade fee is collected by the robosats coordinator as a compensation for their service. You can see the fees of each coordinator by checking out their profile. The trade fee is split to be covered by both: the order maker and the order taker. Typically, the maker fee will be significantly smaller than the taker fee. In case an onchain address is used to received the Sats a variable swap fee applies. The onchain payout fee can also be seen in the profile of the coordinator.": "The trade fee is collected by the robosats coordinator as a compensation for their service. You can see the fees of each coordinator by checking out their profile. The trade fee is split to be covered by both: the order maker and the order taker. Typically, the maker fee will be significantly smaller than the taker fee. In case an onchain address is used to received the Sats a variable swap fee applies. The onchain payout fee can also be seen in the profile of the coordinator.",
|
||||
"The trade fee is collected by the robosats coordinator as a compensation for their service. You can see the fees of each coordinator by checking out their profile. The trade fee is split to be covered by both: the order maker and the order taker. Typically, the maker fee will be significantly smaller than the taker fee. In case an onchain address is used to received the Sats a variable swap fee applies. The onchain payout fee can also be seen in the profile of the coordinator.": "Les comissions les cobra el coordinador de Robosats com a compensació pel seu servei. Pots consultar les tarifes de cada coordinador a través del seu perfil. Les comissions es divideixen per ser cobertes pels dos: el creador d’ordres i el prenedor d’ordres. Normalment, el cost pel creador significativament més petit que el cost pel prenedor. En cas que s'utilitzi una adreça onchain per rebre els Sats s'aplicarà una comissió variable d'intercanvi. Les taxes de pagament onchain també es poden veure al perfil del coordinador.",
|
||||
"This is an experimental application, things could go wrong. Trade small amounts!": "Aquesta és una aplicació experimental, quelcom pot no sortir bé. Intercanvia petites quantitats!",
|
||||
"This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ": "Aquesta aplicació de Lightning està en desenvolupament continu i s'entrega tal qual: intercanvia amb la màxima precaució. No hi ha suport privat. El suport s'ofereix només a canals públics.",
|
||||
"What are the fees?": "Quines són les comissions?",
|
||||
"What are the risks?": "Quins són els riscos?",
|
||||
"What happens if my coordinator goes offline forever?": "What happens if my coordinator goes offline forever?",
|
||||
"What happens if my coordinator goes offline forever?": "Què passa si el meu coordinador es desconnecta per sempre?",
|
||||
"What is RoboSats?": "Què és RoboSats?",
|
||||
"What is a coordinator?": "What is a coordinator?",
|
||||
"What is a coordinator?": "Què és un coordinador?",
|
||||
"What is the trust model?": "Quin és el model de confiança?",
|
||||
"What payment methods are accepted?": "Quins mètodes de pagament s'accepten?",
|
||||
"You can also check the full guide in ": "També pots revisar la guia sencera a ",
|
||||
"You can build more trust on the RoboSats and coordinator infrastructure by inspecting the source code.": "You can build more trust on the RoboSats and coordinator infrastructure by inspecting the source code.",
|
||||
"You can build more trust on the RoboSats and coordinator infrastructure by inspecting the source code.": "Pots generar més confiança a la infraestructura de RoboSats i coordinadors inspeccionant el codi font.",
|
||||
"You can find a step-by-step description of the trade pipeline in ": "Pots trobar una descripció pas a pas dels intercanvis a ",
|
||||
"Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if the coordinator goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if the coordinator disappears. This window is usually about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels or directly to your trade coordinator using one of the contact methods listed on their profile.": "Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if the coordinator goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if the coordinator disappears. This window is usually about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels or directly to your trade coordinator using one of the contact methods listed on their profile.",
|
||||
"Your trade partner will not know the destination of the Lightning payment. The permanence of the data collected by the coordinators depend on their privacy and data policies. If a dispute arises, a coordinator may request additional information. The specifics of this process can vary from coordinator to coordinator.": "Your trade partner will not know the destination of the Lightning payment. The permanence of the data collected by the coordinators depend on their privacy and data policies. If a dispute arises, a coordinator may request additional information. The specifics of this process can vary from coordinator to coordinator.",
|
||||
"Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if the coordinator goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if the coordinator disappears. This window is usually about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels or directly to your trade coordinator using one of the contact methods listed on their profile.": "Els teus Sats et seran retornats. Qualsevol factura no assentada serà automàticament retornada encara que el coordinador desaparegui. Això és cert tant per fiances com pels col·laterals. De totes formes, des de que el venedor confirma haver rebut el fiat i el comprador rep els Sats, hi ha un temps d'aprox. 1 segon en que los fons podrien perdre's si el coordinador desaparegués. Assegura't de tenir suficient liquiditat entrant per evitar errors d'enrutament. Si tens algun problema, busca als canals públics de RoboSats o directament al teu coordinador fent servir algun dels mètodes llistats al seu perfil.",
|
||||
"Your trade partner will not know the destination of the Lightning payment. The permanence of the data collected by the coordinators depend on their privacy and data policies. If a dispute arises, a coordinator may request additional information. The specifics of this process can vary from coordinator to coordinator.": "El teu soci d’intercanvi no coneixerà el destí del pagament LN. La permanència de les dades recollides pels coordinadors depèn de les seves polítiques de privacitat i dades. En cas de controvèrsia, un coordinador pot sol·licitar informació addicional. Els detalls d'aquest procés poden variar de coordinador a coordinador.",
|
||||
"#21": "Phrases in components/Dialogs/AuditPGP.tsx",
|
||||
"Go back": "Tornar",
|
||||
"Keys": "Claus",
|
||||
@ -233,84 +233,84 @@
|
||||
"... somewhere on Earth!": "... en algun indret de la Terra!",
|
||||
"Client info": "Client info",
|
||||
"Made with": "Fet amb",
|
||||
"RoboSats client version": "RoboSats client version",
|
||||
"RoboSats client version": "Versió de client RoboSats",
|
||||
"and": "i",
|
||||
"#23": "Phrases in components/Dialogs/Community.tsx",
|
||||
"Community": "Comunitat",
|
||||
"Follow RoboSats in Nostr": "Follow RoboSats in Nostr",
|
||||
"Follow RoboSats in X": "Follow RoboSats in X",
|
||||
"Follow RoboSats in Nostr": "Segueix Robosats a Nostr",
|
||||
"Follow RoboSats in X": "Segueix Robosats a X",
|
||||
"Github Issues - The Robotic Satoshis Open Source Project": "Problemes de Github - The Robotic Satoshis Open Source Project",
|
||||
"Join RoboSats English speaking community!": "Uneix-te a la nostra comunitat de RoboSats en anglès!",
|
||||
"Join RoboSats SimpleX group": "Join RoboSats SimpleX group",
|
||||
"Join RoboSats SimpleX group": "Uneix-te al grup de RoboSats a SimpleX",
|
||||
"Join RoboSats Spanish speaking community!": "Uneix-te a la nostra comunitat de RoboSats en espanyol!",
|
||||
"Nostr Official Account": "Nostr Official Account",
|
||||
"Nostr Official Account": "Compte oficial a Nostr",
|
||||
"RoboSats in Reddit": "RoboSats a Reddit",
|
||||
"RoboSats main public support": "RoboSats main public support",
|
||||
"Support is only offered via SimpleX. Join our community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!": "Support is only offered via SimpleX. Join our community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!",
|
||||
"RoboSats main public support": "uport públic principal de RoboSats",
|
||||
"Support is only offered via SimpleX. Join our community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!": "l suport només s'ofereix a través de SimpleX. Uneix-te a la nostra comunitat si tens preguntes o vols trobar altres robots genials. Utilitzeu els nostres Problemes de Github si trobeu un error o voleu veure noves característiques!",
|
||||
"Tell us about a new feature or a bug": "Proposa funcionalitats o notifica errors",
|
||||
"We are abandoning Telegram! Our old TG groups": "We are abandoning Telegram! Our old TG groups",
|
||||
"X Official Account": "X Official Account",
|
||||
"#24": "Phrases in components/Dialogs/Coordinator.tsx",
|
||||
"...Opening on Nostr gateway. Pubkey copied!": "...Opening on Nostr gateway. Pubkey copied!",
|
||||
"We are abandoning Telegram! Our old TG groups": "Estem deixant Telegram! Els nostres grups antics de TG",
|
||||
"X Official Account": "Compte oficial a X",
|
||||
"#23": "Phrases in components/Dialogs/Coordinator.tsx",
|
||||
"...Opening on Nostr gateway. Pubkey copied!": "...Obrint la passarel·la Nostr. Clau pública copiada!",
|
||||
"24h contracted volume": "Volum contractat en 24h",
|
||||
"24h non-KYC bitcoin premium": "24h non-KYC bitcoin premium",
|
||||
"24h non-KYC bitcoin premium": "Prima de bitcoin sense KYC en 24h",
|
||||
"Book liquidity": "Liquiditat de les reserves",
|
||||
"CLN version": "CLN version",
|
||||
"CLN version": "Versió CLN",
|
||||
"Client": "Client",
|
||||
"Coordinator": "Coordinador",
|
||||
"Coordinator Notice": "Coordinator Notice",
|
||||
"Coordinator commit hash": "Coordinator commit hash",
|
||||
"Coordinator description": "Coordinator description",
|
||||
"Coordinator hosted web app": "Coordinator hosted web app",
|
||||
"Coordinator offline": "Coordinator offline",
|
||||
"Current onchain payout fee": "Cost actual de rebre onchain",
|
||||
"Current onchain payout status": "Current onchain payout status",
|
||||
"Development fund supporter: donates {{percent}}% to make RoboSats better.": "Development fund supporter: donates {{percent}}% to make RoboSats better.",
|
||||
"Does not have large trade limits.": "Does not have large trade limits.",
|
||||
"Download PGP Pubkey. Fingerprint: ": "Download PGP Pubkey. Fingerprint: ",
|
||||
"Established": "Established",
|
||||
"Founder: coordinating trades since the testnet federation.": "Founder: coordinating trades since the testnet federation.",
|
||||
"Good OpSec: the coordinator follows best practices to protect his and your privacy.": "Good OpSec: the coordinator follows best practices to protect his and your privacy.",
|
||||
"Coordinator Notice": "Avís del coordinador",
|
||||
"Coordinator commit hash": "Hash de confirmació del coordinador",
|
||||
"Coordinator description": "Descripció del coordinador",
|
||||
"Coordinator hosted web app": "Web app del coordinador",
|
||||
"Coordinator offline": "Coordinador offline",
|
||||
"Current onchain payout fee": "Cost actual del pagament onchain",
|
||||
"Current onchain payout status": "Estat actual del pagament onchain",
|
||||
"Development fund supporter: donates {{percent}}% to make RoboSats better.": "Contribuïdor al fons de desenvolupament: dona el {{percent}}% per millorar RoboSats.",
|
||||
"Does not have large trade limits.": "No té grans límits d'intercanvi.",
|
||||
"Download PGP Pubkey. Fingerprint: ": "Descarrega la clau PGP. Emprempta: ",
|
||||
"Established": "Establert",
|
||||
"Founder: coordinating trades since the testnet federation.": "Fundador: coordinant els intercanvis des de la federació testnet.",
|
||||
"Good OpSec: the coordinator follows best practices to protect his and your privacy.": "Bon OpSec: el coordinador segueix les millors pràctiques per protegir la seva privacitat i la teva.",
|
||||
"LN Node": "Node LN",
|
||||
"LND version": "Versió LND",
|
||||
"Large limits: the coordinator has large trade limits.": "Large limits: the coordinator has large trade limits.",
|
||||
"Large limits: the coordinator has large trade limits.": "Grans límits: el coordinador té grans límits de transacció",
|
||||
"Lifetime contracted volume": "Volum contractat total",
|
||||
"Loved by robots: receives positive comments by robots over the internet.": "Loved by robots: receives positive comments by robots over the internet.",
|
||||
"Loved by robots: receives positive comments by robots over the internet.": "Adorat pels robots: rep comentaris positius per part dels robots a través d'Internet.",
|
||||
"Maker fee": "Comissió del creador",
|
||||
"Matrix channel copied! {{matrix}}": "Matrix channel copied! {{matrix}}",
|
||||
"Maximum onchain swap size": "Maximum onchain swap size",
|
||||
"Maximum order size": "Maximum order size",
|
||||
"Not a federation founder": "Not a federation founder",
|
||||
"Onchain payouts disabled": "Onchain payouts disabled",
|
||||
"Policies": "Policies",
|
||||
"Matrix channel copied! {{matrix}}": "Canal de Matrix copiat! {{matrix}}",
|
||||
"Maximum onchain swap size": "Mida màxima del swap onchain",
|
||||
"Maximum order size": "Mida màxima de l'ordre",
|
||||
"Not a federation founder": "No és un fundador de la federació",
|
||||
"Onchain payouts disabled": "Pagaments onchain deshabilitats",
|
||||
"Policies": "Polítiques",
|
||||
"Public buy orders": "Ordres de compra públiques",
|
||||
"Public sell orders": "Ordres de venta públiques",
|
||||
"Reddit": "Reddit",
|
||||
"RoboSats version": "Versió de RoboSats",
|
||||
"Send Email": "Send Email",
|
||||
"Send Email": "Enviar Email",
|
||||
"Simplex": "Simplex",
|
||||
"Stats for Nerds": "Stats for Nerds",
|
||||
"Summary": "Summary",
|
||||
"Stats for Nerds": "Estadístiques per Nerds",
|
||||
"Summary": "Resum",
|
||||
"Taker fee": "Comissió del prenedor",
|
||||
"Telegram": "Telegram",
|
||||
"The coordinator does not seem to receive exceptional love from robots over the internet": "The coordinator does not seem to receive exceptional love from robots over the internet",
|
||||
"The privacy practices of this coordinator could improve": "The privacy practices of this coordinator could improve",
|
||||
"The coordinator does not seem to receive exceptional love from robots over the internet": "El coordinador no sembla rebre un amor excepcional dels robots a través d'Internet",
|
||||
"The privacy practices of this coordinator could improve": "Les pràctiques de privacitat d'aquest coordinador podrien millorar",
|
||||
"Today active robots": "Robots actius avui",
|
||||
"Website": "Website",
|
||||
"Website": "Web",
|
||||
"X": "X",
|
||||
"Zaps voluntarily for development": "Zaps voluntarily for development",
|
||||
"Zaps voluntarily for development": "Zaps voluntàriament per al desenvolupament",
|
||||
"#25": "Phrases in components/Dialogs/EnableTelegram.tsx",
|
||||
"Browser": "Browser",
|
||||
"Browser": "Navegador",
|
||||
"Enable": "Activar",
|
||||
"Enable TG Notifications": "Activar Notificacions TG",
|
||||
"You will be taken to a conversation with RoboSats telegram bot. Simply open the chat and press Start. Note that by enabling telegram notifications you might lower your level of anonymity.": "Seràs dut a un xat amb el bot de Telegram de Robosats. Simplement prem Començar. Tingues en compte que si actives les notificaciones de Telegram reduiràs el teu anonimat.",
|
||||
"#26": "Phrases in components/Dialogs/Exchange.tsx",
|
||||
"Enabled RoboSats coordinators": "Enabled RoboSats coordinators",
|
||||
"Exchange Summary": "Exchange Summary",
|
||||
"Online RoboSats coordinators": "Online RoboSats coordinators",
|
||||
"Enabled RoboSats coordinators": "Habilita els coordinadors de RoboSats",
|
||||
"Exchange Summary": "Resum de l'Exchange",
|
||||
"Online RoboSats coordinators": "Coordinadors RoboSats online",
|
||||
"#27": "Phrases in components/Dialogs/F2fMap.tsx",
|
||||
"Choose a location": "Choose a location",
|
||||
"Save": "Save",
|
||||
"Choose a location": "Tria una ubicació",
|
||||
"Save": "Guardar",
|
||||
"#28": "Phrases in components/Dialogs/Learn.tsx",
|
||||
"Back": "Tornar",
|
||||
"You are about to visit Learn RoboSats. It hosts tutorials and documentation to help you learn how to use RoboSats and understand how it works.": "Visitaràs la pàgina Learn RoboSats. Ha estat construïda per la comunitat i conté tutorials i documentació que t'ajudarà a aprendre como s'utilitza RoboSats i a entendre com funciona.",
|
||||
@ -319,8 +319,8 @@
|
||||
"Generate a robot avatar first. Then create your own order.": "Primer genera un avatar de robot. A continuació, crea la teva pròpia oferta.",
|
||||
"You do not have a robot avatar": "No tens un avatar robot",
|
||||
"#30": "Phrases in components/Dialogs/Profile.tsx",
|
||||
"Coordinators that know your robot:": "Coordinators that know your robot:",
|
||||
"Looking for your robot!": "Looking for your robot!",
|
||||
"Coordinators that know your robot:": "Coordinadors que coneixen el teu robot:",
|
||||
"Looking for your robot!": "Buscant el teu robot!",
|
||||
"Your Robot": "El teu Robot",
|
||||
"#31": "Phrases in components/Dialogs/StoreToken.tsx",
|
||||
"Back it up!": "Guarda-ho!",
|
||||
@ -337,14 +337,14 @@
|
||||
"The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.": "El coordinador de RoboSats és a la versió {{coordinatorVersion}}, però la app del teu client és {{clientVersion}}. Aquesta discrepància de versió pot provocar una mala experiència d'usuari.",
|
||||
"Update your RoboSats client": "Actualitza el teu client RoboSats",
|
||||
"#33": "Phrases in components/Dialogs/Warning.tsx",
|
||||
"Coordinators of p2p trades are the source of trust, provide the infrastructure, pricing and will mediate in case of dispute. Make sure you research and trust \"{{coordinator_name}}\" before locking your bond. A malicious p2p coordinator can find ways to steal from you.": "Coordinators of p2p trades are the source of trust, provide the infrastructure, pricing and will mediate in case of dispute. Make sure you research and trust \"{{coordinator_name}}\" before locking your bond. A malicious p2p coordinator can find ways to steal from you.",
|
||||
"I understand": "I understand",
|
||||
"Warning": "Warning",
|
||||
"Coordinators of p2p trades are the source of trust, provide the infrastructure, pricing and will mediate in case of dispute. Make sure you research and trust \"{{coordinator_name}}\" before locking your bond. A malicious p2p coordinator can find ways to steal from you.": "Els coordinadors de les operacions p2p són la font de confiança, proporcionen la infraestructura, la tarifació i intervindran en cas de disputa. Assegureu-vos que investigueu i confieu en \"{{coordinator.name}}\" abans de bloquejar la vostra fiança. Un coordinador p2p maliciós pot trobar maneres de robar-te.",
|
||||
"I understand": "Ho entenc",
|
||||
"Warning": "Avís",
|
||||
"#34": "Phrases in components/FederationTable/index.tsx",
|
||||
"Coordinators per page:": "Coordinators per page:",
|
||||
"Enabled": "Enabled",
|
||||
"No coordinators found.": "No coordinators found.",
|
||||
"Up": "Up",
|
||||
"Coordinators per page:": "Coordinadors per pàgina:",
|
||||
"Enabled": "Habilitat",
|
||||
"No coordinators found.": "No s'han trobat coordinadors.",
|
||||
"Up": "Pujar",
|
||||
"#35": "Phrases in components/HostAlert/SelfhostedAlert.tsx",
|
||||
"RoboSats client is served from your own node granting you the strongest security and privacy.": "El client RoboSats és servit pel teu propi node, gaudeixes de la major seguretat i privacitat.",
|
||||
"You are self-hosting RoboSats": "Estàs hostejant RoboSats",
|
||||
@ -359,7 +359,7 @@
|
||||
" at market price": " a preu de mercat",
|
||||
" of {{satoshis}} Satoshis": " de {{satoshis}} Sats",
|
||||
"Add New": "Afegir nou",
|
||||
"Add geolocation for a face to face trade": "Add geolocation for a face to face trade",
|
||||
"Add geolocation for a face to face trade": "Afegeix geolocalització per a un intercanvi cara a cara",
|
||||
"Amount Range": "Interval de la quantitat",
|
||||
"Amount of BTC to swap for LN Sats": "Quantitat de BTC a canviar per LN Sats",
|
||||
"Amount of fiat to exchange for bitcoin": "Quantitat de fiat a canviar per bitcoin",
|
||||
@ -374,7 +374,7 @@
|
||||
"Escrow/invoice step length": "Longitud passos Dipòsit/Factura",
|
||||
"Exact": "Exacte",
|
||||
"Exact Amount": "Quantitat Exacte",
|
||||
"Face to Face Location": "Face to Face Location",
|
||||
"Face to Face Location": "Ubicació del cara cara",
|
||||
"Fiat Payment Method(s)": "Mètode(s) de Pagament Fiat",
|
||||
"Fidelity Bond Size": "Grandària de la fiança",
|
||||
"In or Out of Lightning?": "Introduir o Treure de Lightning?",
|
||||
@ -404,16 +404,16 @@
|
||||
"Swap of ": "Swap of ",
|
||||
"Swap out of LN ": "Swap out of LN ",
|
||||
"Swap?": "Intercanviar?",
|
||||
"To protect your privacy, the exact location you pin will be slightly randomized.": "To protect your privacy, the exact location you pin will be slightly randomized.",
|
||||
"You are already maker of an active order": "You are already maker of an active order",
|
||||
"To protect your privacy, the exact location you pin will be slightly randomized.": "Per protegir la teva privacitat, la ubicació exacta que fixis serà lleugerament aleatòria.",
|
||||
"You are already maker of an active order": "Ja ets el creador d'un ordre actiu",
|
||||
"You can add new methods": "Pots afegir nous mètodes",
|
||||
"You must fill the form correctly": "Has d'omplir el formulari correctament",
|
||||
"You receive approx {{swapSats}} LN Sats (fees might vary)": "Reps aprox. {{swapSats}} LN Sats (les taxes poden variar)",
|
||||
"You send approx {{swapSats}} LN Sats (fees might vary)": "Envies aprox. {{swapSats}} LN Sats (les taxes poden variar)",
|
||||
"Your order fixed exchange rate": "La tasa de canvi fixa de la teva ordre",
|
||||
"#39": "Phrases in components/MakerForm/SelectCoordinator.tsx",
|
||||
"Order Host": "Order Host",
|
||||
"The provider the lightning and communication infrastructure. The host will be in charge of providing support and solving disputes. The trade fees are set by the host. Make sure to only select order hosts that you trust!": "The provider the lightning and communication infrastructure. The host will be in charge of providing support and solving disputes. The trade fees are set by the host. Make sure to only select order hosts that you trust!",
|
||||
"Order Host": "Amfitrió de l'ordre",
|
||||
"The provider the lightning and communication infrastructure. The host will be in charge of providing support and solving disputes. The trade fees are set by the host. Make sure to only select order hosts that you trust!": "El proveïdor de la infraestructura LN i comunicacions. L'amfitrió serà l'encarregat de donar suport i resoldre disputes. LEs comissions de les transaccions són fixades per l'amfitrió. Assegureu-vos de seleccionar només els amfitrions en què confieu!",
|
||||
"#40": "Phrases in components/Notifications/index.tsx",
|
||||
"Lightning routing failed": "L'enrutament Lightning ha fallat",
|
||||
"New chat message": "Nou missatge al xat",
|
||||
@ -456,7 +456,7 @@
|
||||
"Amount of Satoshis": "Quantitat de Sats",
|
||||
"Deposit timer": "Per a dipositar",
|
||||
"Expires in": "Expira en",
|
||||
"F2F location": "F2F location",
|
||||
"F2F location": "Ubicació F2F",
|
||||
"Order Details": "Detalls",
|
||||
"Order ID": "ID de l'ordre",
|
||||
"Order host": "Order host",
|
||||
@ -468,18 +468,18 @@
|
||||
"Price and Premium": "Preu i prima",
|
||||
"Swap destination": "Destí del swap",
|
||||
"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.": "La ubicació fixada és aproximada. La ubicació exacta del lloc de trobada s'ha d'intercanviar al xat xifrat.",
|
||||
"You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s",
|
||||
"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}}%",
|
||||
"#43": "Phrases in components/RobotInfo/index.tsx",
|
||||
"#42": "Phrases in components/RobotInfo/index.tsx",
|
||||
"Active order!": "Active order!",
|
||||
"Claim": "Retirar",
|
||||
"Claim Sats!": "Claim Sats!",
|
||||
"Enable Telegram Notifications": "Notificar en Telegram",
|
||||
"Claim Sats!": "Reclamar Sats!",
|
||||
"Enable Telegram Notifications": "Habilita notificacions a Telegram",
|
||||
"Generate with Webln": "Generar amb Webln",
|
||||
"Inactive order": "Ordre inactiva",
|
||||
"Invoice for {{amountSats}} Sats": "Factura per {{amountSats}} Sats",
|
||||
@ -487,13 +487,13 @@
|
||||
"One active order #{{orderID}}": "Anar a ordre activa #{{orderID}}",
|
||||
"Submit": "Enviar",
|
||||
"Telegram enabled": "Telegram activat",
|
||||
"There it goes!": "There it goes!",
|
||||
"There it goes!": "Aquí va!",
|
||||
"Use stealth invoices": "Factures ofuscades",
|
||||
"You do not have previous orders": "No tens ordres prèvies",
|
||||
"Your compensations": "Your compensations",
|
||||
"Your compensations": "Les teves compensacions",
|
||||
"Your current order": "La teva ordre actual",
|
||||
"Your last order #{{orderID}}": "La teva última ordre #{{orderID}}",
|
||||
"finished order": "finished order",
|
||||
"finished order": "Ordre finalitzada",
|
||||
"#44": "Phrases in components/SettingsForm/index.tsx",
|
||||
"Build-in": "Build-in",
|
||||
"Dark": "Fosc",
|
||||
@ -574,7 +574,7 @@
|
||||
"You can close now your WebLN wallet popup.": "Ara pots tancar el popup de la teva wallet WebLN.",
|
||||
"#57": "Phrases in components/TradeBox/EncryptedChat/ChatBottom/index.tsx",
|
||||
"Audit PGP": "Auditar",
|
||||
"Export": "Exporta",
|
||||
"Export": "Exportar",
|
||||
"Save full log as a JSON file (messages and credentials)": "Guardar el log complet com JSON (credencials i missatges)",
|
||||
"Verify your privacy": "Verifica la teva privacitat",
|
||||
"#58": "Phrases in components/TradeBox/EncryptedChat/ChatHeader/index.tsx",
|
||||
@ -593,7 +593,7 @@
|
||||
"#61": "Phrases in components/TradeBox/EncryptedChat/MessageCard/index.tsx",
|
||||
"#62": "Phrases in components/TradeBox/Forms/Dispute.tsx",
|
||||
"Attach chat logs": "Adjuntar registres de xat",
|
||||
"Attaching chat logs helps the dispute resolution process and adds transparency. However, it might compromise your privacy.": "Adjuntar registres de xat ajuda el procés de resolució de disputes i afegeix transparència. Tanmateix, pot comprometre la vostra privadesa.",
|
||||
"Attaching chat logs helps the dispute resolution process and adds transparency. However, it might compromise your privacy.": "Adjuntar registres de xat ajuda el procés de resolució de disputes i afegeix transparència. Tanmateix, pot comprometre la vostra privacitat.",
|
||||
"Submit dispute statement": "Presentar declaració",
|
||||
"#63": "Phrases in components/TradeBox/Forms/LightningPayout.tsx",
|
||||
"Advanced options": "Opcions avançades",
|
||||
@ -622,14 +622,14 @@
|
||||
"To open a dispute you need to wait": "Per obrir una disputa cal esperar",
|
||||
"Wait for the seller to confirm he has received the payment.": "Espera a que el vendedor confirmi que ha rebut el pagament.",
|
||||
"#66": "Phrases in components/TradeBox/Prompts/Dispute.tsx",
|
||||
"Please, submit your statement. Be clear and specific about what happened and provide the necessary evidence. You MUST provide a contact method: burner email, SimpleX incognito link or telegram (make sure to create a searchable username) to follow up with the dispute solver (your trade host/coordinator). Disputes are solved at the discretion of real robots (aka humans), so be as helpful as possible to ensure a fair outcome.": "Please, submit your statement. Be clear and specific about what happened and provide the necessary evidence. You MUST provide a contact method: burner email, SimpleX incognito link or telegram (make sure to create a searchable username) to follow up with the dispute solver (your trade host/coordinator). Disputes are solved at the discretion of real robots (aka humans), so be as helpful as possible to ensure a fair outcome.",
|
||||
"Please, submit your statement. Be clear and specific about what happened and provide the necessary evidence. You MUST provide a contact method: burner email, SimpleX incognito link or telegram (make sure to create a searchable username) to follow up with the dispute solver (your trade host/coordinator). Disputes are solved at the discretion of real robots (aka humans), so be as helpful as possible to ensure a fair outcome.": "Si us plau, envia la teva declaració. Sigues clars i concís sobre el que va ocórrer i aportar les proves necessàries. Has de proporcionar un mètode de contacte: correu electrònic d'un sòl ús, enllaç d'incògnit de SimpleX o el telegram ID (assegureu-vos de crear un nom d'usuari que es pugui cercar) per fer el seguiment amb el solucionador de disputes (el vostre amfitrió/coordinador). Els litigis es resolen amb discreció pels robots reals (també coneguts com a humans), així que sigues el més col·laborador possible per assegurar un resultat just.",
|
||||
"#67": "Phrases in components/TradeBox/Prompts/DisputeLoser.tsx",
|
||||
"Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case by contacting your coordinator. If you think your coordinator was unfair, please fill a claim via email to robosats@protonmail.com": "Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case by contacting your coordinator. If you think your coordinator was unfair, please fill a claim via email to robosats@protonmail.com",
|
||||
"Unfortunately you have lost the dispute. If you think this is a mistake you can ask to re-open the case by contacting your coordinator. If you think your coordinator was unfair, please fill a claim via email to robosats@protonmail.com": "Desgraciadament, has perdut la disputa. Si creus que això és un error, pots sol·licitar reobrir el cas contactant amb el teu coordinador. Si creus que el teu coordinador és injust, si us plau, omple una reclamació via correu electrònic a robosats@protonmail.com",
|
||||
"#68": "Phrases in components/TradeBox/Prompts/DisputeWaitPeer.tsx",
|
||||
"Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself using that information if you contact your trade coordinator.": "Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself using that information if you contact your trade coordinator.",
|
||||
"We are waiting for your trade counterpart statement. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods.": "We are waiting for your trade counterpart statement. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods.",
|
||||
"Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself using that information if you contact your trade coordinator.": "Si us plau, guarda la informació necessària per identificar la teva ordre i els teus pagaments: ID de l'ordre; resums de pagament dels bons o fiança (consulteu a la cartera LN); quantitat exacta de satoshis; i sobrenom de robot. Hauràs d'identificar-te utilitzant aquesta informació si contactes amb el teu coordinador comercial.",
|
||||
"We are waiting for your trade counterpart statement. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods.": "Estem esperant la teva declaració de contrapartida comercial. Si tens dubtes sobre l'estat de la disputa o vols afegir més informació, contacta amb el teu coordinador (l'amfitrió) a través d'un dels seus mètodes de contacte.",
|
||||
"#69": "Phrases in components/TradeBox/Prompts/DisputeWaitResolution.tsx",
|
||||
"Both statements have been received, wait for the staff to resolve the dispute. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods. If you did not provide a contact method, or are unsure whether you wrote it right, write your coordinator immediately.": "Both statements have been received, wait for the staff to resolve the dispute. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods. If you did not provide a contact method, or are unsure whether you wrote it right, write your coordinator immediately.",
|
||||
"Both statements have been received, wait for the staff to resolve the dispute. If you are hesitant about the state of the dispute or want to add more information, contact your order trade coordinator (the host) via one of their contact methods. If you did not provide a contact method, or are unsure whether you wrote it right, write your coordinator immediately.": "S'han rebut totes dues declaracions, a l'espera que el personal resolgui el conflicte. Si tens dubtes sobre l'estat de la disputa o vols afegir més informació, contacta amb el teu coordinador (l'amfitrió) a través d'un dels seus mètodes de contacte. Si no has proporcionat un mètode de contacte o no estàs segur de si l'has escrit correctament, escriviu el vostre coordinador immediatament.",
|
||||
"Please, save the information needed to identify your order and your payments: order ID; payment hashes of the bonds or escrow (check on your lightning wallet); exact amount of satoshis; and robot nickname. You will have to identify yourself as the user involved in this trade via email (or other contact methods).": "Si us plau, guarda l'informació necessària per identificar la teva ordre i pagaments: ID de l'ordre; claus del pagament de la fiança o el col·lateral (comprova la teva cartera Lightning); quantitat exacta de Sats; i nom del Robot. Tindràs que identificar-te com l'usuari involucrat en aquest intercanvi per email (o altre mètode de contacte).",
|
||||
"#70": "Phrases in components/TradeBox/Prompts/DisputeWinner.tsx",
|
||||
"You can claim the dispute resolution amount (escrow and fidelity bond) from your profile rewards. If there is anything the staff can help with, do not hesitate to contact to robosats@protonmail.com (or via your provided burner contact method).": "Pots retirar la quantitat de la resolució de la disputa (fiança i col·lateral) des de les recompenses del teu perfil. Si creus que l'equip pot fer alguna cosa més, no dubtis a contactar amb robosats@protonmail.com (o a través del mètode de contacte d'usar i llençar que vas especificar).",
|
||||
@ -673,7 +673,7 @@
|
||||
"Start Again": "Començar de nou",
|
||||
"Thank you for using Robosats!": "Gràcies per fer servir RoboSats!",
|
||||
"Thank you! RoboSats loves you too": "Gràcies! RoboSats també t'estima",
|
||||
"What do you think your order host \"{{coordinator}}\"?": "What do you think your order host \"{{coordinator}}\"?",
|
||||
"What do you think your order host \"{{coordinator}}\"?": "Què en penses del teu amfitrió \"{{coordinator}}\"?",
|
||||
"Your TXID": "El teu TXID",
|
||||
"#81": "Phrases in components/TradeBox/Prompts/TakerFound.tsx",
|
||||
"Please wait for the taker to lock a bond. If the taker does not lock a bond in time, the order will be made public again.": "Si us plau, espera a que el prenedor bloquegi la seva fiança. Si no ho fa a temps, l'ordre serà pública de nou.",
|
||||
@ -684,7 +684,7 @@
|
||||
"#83": "Phrases in pro/ToolBar/index.tsx",
|
||||
"Customize viewports": "Personalitza l'àrea de visió",
|
||||
"Freeze viewports": "Congela l'àrea de visió",
|
||||
"unsafe_alert": "To protect your data and privacy use<1>Tor Browser</1> and visit a federation hosted <3>Onion</3> site. Or host your own <5>Client.</5>",
|
||||
"unsafe_alert": "Per protegir les vostres dades i la vostra privadesa utilitzeu <1>Tor Browser</1> i visiteu una federació allotjada a <3>Onion</3>. O hostatgeu el vostre propi <5>Client.</5>",
|
||||
"let_us_know_hot_to_improve": "Diga'ns com podria millorar la plataforma (<1>Telegram</1> / <3>Github</3>)",
|
||||
"open_dispute": "Per obrir una disputa has d'esperar <1><1/>",
|
||||
"Waiting for maker bond": "Esperant la fiança del creador",
|
||||
@ -700,7 +700,7 @@
|
||||
"In dispute": "En disputa",
|
||||
"Collaboratively cancelled": "Cancel·lat col·laborativament",
|
||||
"Sending satoshis to buyer": "Enviant satoshis al comprador",
|
||||
"Sucessful trade": "Intercanvi exitós",
|
||||
"Successful trade": "Intercanvi exitós",
|
||||
"Failed lightning network routing": "L'enrrutament lightning network ha fallat",
|
||||
"Wait for dispute resolution": "Esperant la resolució de la disputa",
|
||||
"Maker lost dispute": "El creador ha perdut la disputa",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "In dispute",
|
||||
"Collaboratively cancelled": "Collaboratively cancelled",
|
||||
"Sending satoshis to buyer": "Sending satoshis to buyer",
|
||||
"Sucessful trade": "Successful trade",
|
||||
"Successful trade": "Successful trade",
|
||||
"Failed lightning network routing": "Failed lightning network routing",
|
||||
"Wait for dispute resolution": "Wait for dispute resolution",
|
||||
"Maker lost dispute": "Maker lost dispute",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "In dispute",
|
||||
"Collaboratively cancelled": "Collaboratively cancelled",
|
||||
"Sending satoshis to buyer": "Sending satoshis to buyer",
|
||||
"Sucessful trade": "Successful trade",
|
||||
"Successful trade": "Successful trade",
|
||||
"Failed lightning network routing": "Failed lightning network routing",
|
||||
"Wait for dispute resolution": "Wait for dispute resolution",
|
||||
"Maker lost dispute": "Maker lost dispute",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "In dispute",
|
||||
"Collaboratively cancelled": "Collaboratively cancelled",
|
||||
"Sending satoshis to buyer": "Sending satoshis to buyer",
|
||||
"Sucessful trade": "Successful trade",
|
||||
"Successful trade": "Successful trade",
|
||||
"Failed lightning network routing": "Failed lightning network routing",
|
||||
"Wait for dispute resolution": "Wait for dispute resolution",
|
||||
"Maker lost dispute": "Maker lost dispute",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "En disputa",
|
||||
"Collaboratively cancelled": "Cancelada colaborativamente",
|
||||
"Sending satoshis to buyer": "Enviando Sats al comprador",
|
||||
"Sucessful trade": "Intercambio exitoso",
|
||||
"Successful trade": "Intercambio exitoso",
|
||||
"Failed lightning network routing": "Enrutamiento fallido en la red Lightning",
|
||||
"Wait for dispute resolution": "Espera a la resolución de la disputa",
|
||||
"Maker lost dispute": "El creador ha perdido la disputa",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "In dispute",
|
||||
"Collaboratively cancelled": "Collaboratively cancelled",
|
||||
"Sending satoshis to buyer": "Sending satoshis to buyer",
|
||||
"Sucessful trade": "Successful trade",
|
||||
"Successful trade": "Successful trade",
|
||||
"Failed lightning network routing": "Failed lightning network routing",
|
||||
"Wait for dispute resolution": "Wait for dispute resolution",
|
||||
"Maker lost dispute": "Maker lost dispute",
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "En litige",
|
||||
"Collaboratively cancelled": "Annulation commune",
|
||||
"Sending satoshis to buyer": "Envoi des satoshis à l'acheteur",
|
||||
"Sucessful trade": "Transaction réussie",
|
||||
"Successful trade": "Transaction réussie",
|
||||
"Failed lightning network routing": "Échec du routage du réseau lightning",
|
||||
"Wait for dispute resolution": "Attendre la résolution du litige",
|
||||
"Maker lost dispute": "Litige perdu par l'auteur",
|
||||
|
@ -32,7 +32,7 @@ phrases = OrderedDict(
|
||||
("In dispute", "In dispute"),
|
||||
("Collaboratively cancelled", "Collaboratively cancelled"),
|
||||
("Sending satoshis to buyer", "Sending satoshis to buyer"),
|
||||
("Sucessful trade", "Successful trade"),
|
||||
("Successful trade", "Successful trade"),
|
||||
("Failed lightning network routing", "Failed lightning network routing"),
|
||||
("Wait for dispute resolution", "Wait for dispute resolution"),
|
||||
("Maker lost dispute", "Maker lost dispute"),
|
||||
|
@ -700,7 +700,7 @@
|
||||
"In dispute": "In contestazione",
|
||||
"Collaboratively cancelled": "Annullato collaborativamente",
|
||||
"Sending satoshis to buyer": "Invio satoshi all'acquirente",
|
||||
"Sucessful trade": "Scambio completato con successo",
|
||||
"Successful trade": "Scambio completato con successo",
|
||||
"Failed lightning network routing": "Routing Lightning Network fallito",
|
||||
"Wait for dispute resolution": "In attesa della risoluzione della contestazione",
|
||||
"Maker lost dispute": "Il maker ha perso la contestazione",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user