Merge pull request #1534 from RoboSats/ui-update-merge

UI update merge
This commit is contained in:
KoalaSat 2024-10-18 23:20:38 +00:00 committed by GitHub
commit adc34fdfa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
224 changed files with 535785 additions and 5257 deletions

View File

@ -175,3 +175,6 @@ SLASHED_BOND_REWARD_SPLIT = 0.5
# Username for HTLCs escrows
ESCROW_USERNAME = 'admin'
#Social
NOSTR_NSEC = 'nsec1vxhs2zc4kqe0dhz4z2gfrdyjsrwf8pg3neeqx6w4nl8djfzdp0dqwd6rxh'

View File

@ -21,31 +21,15 @@ jobs:
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-main-js
path: frontend/static/frontend/
name: django-main-static
path: frontend
- name: 'Download Basic main.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-main-js
path: frontend/static/frontend/
- name: 'Download pro.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-pro-js
path: frontend/static/frontend/
- name: 'Download pro.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-pro-js
path: frontend/static/frontend/
name: django-main-static
path: frontend
- name: 'Log in to Docker Hub'
uses: docker/login-action@v3

128
.github/workflows/desktop-build.yml vendored Normal file
View File

@ -0,0 +1,128 @@
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: 'Download Basic main.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: desktop-main-static
path: desktopApp
- name: 'Download Basic main.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: desktop-main-static
path: desktopApp
- name: Install dependencies
run: |
cd desktopApp
npm install
- 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: 'Get Commit Hash'
id: commit
uses: pr-mpt/actions-commit-hash@v3
- name: Print semver
run: echo The semver is ${{ github.event.inputs.semver }}
- name: Upload macOS Build Artifact
if: inputs.semver != ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-darwin-x64
name: robosats-desktop-${{ inputs.semver }}-mac-darwin-x64.zip
- name: Upload Windows Build Artifact
if: inputs.semver != ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-win32-ia32
name: robosats-desktop-${{ inputs.semver }}-win32-ia32.zip
- name: Upload Linux Build Artifact
if: inputs.semver != ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-linux-x64
name: robosats-desktop-${{ inputs.semver }}-linux-x64.zip
- name: Upload macOS Build Artifact
id: upload-release-mac-zip-asset
if: inputs.semver == ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-darwin-x64
name: robosats-desktop-${{ steps.commit.outputs.short }}-mac-darwin-x64.zip
- name: Upload Windows Build Artifact
id: upload-release-win-zip-asset
if: inputs.semver == ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-win32-ia32
name: robosats-desktop-${{ steps.commit.outputs.short }}-win32-ia32.zip
- name: Upload Linux Build Artifact
id: upload-release-linux-zip-asset
if: inputs.semver == ''
uses: actions/upload-artifact@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path: desktopApp/release-builds/Robosats-linux-x64
name: robosats-desktop-${{ steps.commit.outputs.short }}-linux-x64.zip

View File

@ -50,34 +50,34 @@ jobs:
export NODE_OPTIONS="--max-old-space-size=4096"
cd frontend
npm run build
- name: 'Archive Web Basic Build Results'
- name: 'Archive Django Static Build Results'
uses: actions/upload-artifact@v4
with:
name: web-main-js
name: django-main-static
path: |
frontend/static/frontend/*main.js
frontend/static/frontend/*.wasm
- name: 'Archive Web Basic Selfhosted Build Results'
frontend/static
frontend/templates/frontend/*.html
- name: 'Archive Node App Static Build Results'
uses: actions/upload-artifact@v4
with:
name: web-basic-selfhosted-js
name: nodeapp-main-static
path: |
frontend/static/frontend/*basic.selfhosted.js
frontend/static/frontend/*.wasm
- name: 'Archive Web PRO Build Results'
nodeapp/static
nodeapp/*.html
- name: 'Archive Desktop App Static Build Results'
uses: actions/upload-artifact@v4
with:
name: web-pro-js
name: desktop-main-static
path: |
frontend/static/frontend/*pro.js
frontend/static/frontend/*.wasm
- name: 'Archive Web PRO SelhostedBuild Results'
desktopApp/static
desktopApp/*.html
- name: 'Archive Django Static Build Results'
uses: actions/upload-artifact@v4
with:
name: web-pro-selfhosted-js
name: web-main-static
path: |
frontend/static/frontend/*pro.selfhosted.js
frontend/static/frontend/*.wasm
web/static
web/*.html
- name: 'Archive Mobile Build Results'
uses: actions/upload-artifact@v4
with:

View File

@ -17,18 +17,27 @@ concurrency:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
max-parallel: 2
matrix:
python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm']
lnd-version: ['v0.17.4-beta']
cln-version: ['v24.05']
python-tag: ['3.12.3-slim-bookworm']
lnd-version: ['v0.18.2-beta']
cln-version: ['v24.08']
ln-vendor: ['LND', 'CLN']
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Download static files Artifact'
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: django-main-static
path: frontend
- name: Patch Dockerfile and .env-sample
run: |
sed -i "1s/FROM python:.*/FROM python:${{ matrix.python-tag }}/" Dockerfile
@ -60,16 +69,16 @@ jobs:
- name: Wait for coordinator (django server)
run: |
while [ "$(docker inspect --format "{{.State.Health.Status}}" coordinator)" != "healthy" ]; do
while [ "$(docker inspect --format "{{.State.Health.Status}}" test-coordinator)" != "healthy" ]; do
echo "Waiting for coordinator to be healthy..."
sleep 5
done
- name: 'Run tests with coverage'
run: |
docker exec coordinator coverage run manage.py test
docker exec coordinator coverage report
docker exec coordinator coverage html
docker exec test-coordinator coverage run manage.py test
docker exec test-coordinator coverage report
docker exec test-coordinator coverage html
env:
LNVENDOR: ${{ matrix.ln-vendor }}
DEVELOPMENT: True

View File

@ -74,9 +74,16 @@ jobs:
secrets: inherit
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
@ -171,4 +178,55 @@ jobs:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: app-x86-release.apk
asset_name: robosats-${{ needs.check-versions.outputs.semver }}-x86.apk
asset_content_type: application/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-darwin-x64.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: .
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-mac-darwin-x64.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-x64.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: robosats-desktop-${{ needs.check-versions.outputs.semver }}-linux-x64.zip
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-linux-x64.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 }}-win32-ia32.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: robosats-desktop-${{ needs.check-versions.outputs.semver }}-win32-ia32.zip
asset_name: robosats-desktop-${{ needs.check-versions.outputs.semver }}-win32-ia32.zip
asset_content_type: application/zip

View File

@ -21,40 +21,21 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: 'Copy Static' # Needed since Github actions does not support symlinks
run: cp -r frontend/static nodeapp/static
- name: 'Download basic.selfhosted.js Artifact'
- name: 'Download Basic main.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-basic-selfhosted-js
path: nodeapp/static/frontend/
name: nodeapp-main-static
path: nodeapp
- name: 'Download main.js Artifact for a release'
- name: 'Download Basic main.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-basic-selfhosted-js
path: nodeapp/static/frontend/
- name: 'Download pro.selfhosted.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-pro-selfhosted-js
path: nodeapp/static/frontend/
- name: 'Download pro.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-pro-selfhosted-js
path: nodeapp/static/frontend/
name: nodeapp-main-static
path: nodeapp
- name: 'Log in to Docker Hub'
uses: docker/login-action@v3

View File

@ -21,40 +21,21 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: 'Copy Static' # Needed since Github actions does not support symlinks
run: cp -r frontend/static web/static
- name: 'Download main.js Artifact'
- name: 'Download Basic main.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-main-js
path: web/static/frontend/
name: web-main-static
path: web
- name: 'Download main.js Artifact for a release'
- name: 'Download Basic main.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-main-js
path: web/static/frontend/
- name: 'Download pro.js Artifact'
if: inputs.semver == '' # Only if workflow fired from frontend-build.yml
uses: dawidd6/action-download-artifact@v6
with:
workflow: frontend-build.yml
workflow_conclusion: success
name: web-pro-js
path: web/static/frontend/
- name: 'Download pro.js Artifact for a release'
if: inputs.semver != '' # Only if fired as job in release.yml
uses: actions/download-artifact@v4
with:
name: web-pro-js
path: web/static/frontend/
name: web-main-static
path: web
- name: 'Log in to Docker Hub'
uses: docker/login-action@v3

17
.gitignore vendored
View File

@ -634,6 +634,7 @@ frontend/static/assets/avatars*
api/lightning/*_grpc.py
api/lightning/*_pb2.py
api/lightning/pymp*
api/lightning/pip*
frontend/static/locales/collected_phrases.json
frontend/static/admin*
frontend/static/rest_framework*
@ -641,15 +642,21 @@ frontend/static/import_export*
frontend/static/drf_spectacular_sidecar/
frontend/src/components/PaymentMethods/Icons/code*
frontend/src/components/PaymentMethods/Icons/webp*
frontend/static/frontend/**
frontend/static/frontend
docs/.jekyll-cache*
docs/_site*
node
desktopApp/release-builds
# mobile frontend statics
mobile/html/Web.bundle/js*
mobile/html/Web.bundle/css*
mobile/html/Web.bundle/assets*
# frontend statics
frontend/templates/frontend/*.html
mobile/html/Web.bundle
desktopApp/static
desktopApp/*.html
web/static
web/*.html
nodeapp/static
nodeapp/*.html
# Protocol Buffers
api/lightning/*.proto

View File

@ -7,7 +7,7 @@ This contributing guide is based on the [Bisq contributing guide](https://github
## Communication Channels
Most communication about development takes place on our [Matrix Development group](https://matrix.to/#/#robosats:matrix.org).
Most communication about development takes place on our [SimpleX Development Group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FKEkNLMlgM8vrrU3xjBt5emS7EsP0c4s1%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEABehx7Tgefl_vvOGOe2SThJCGACKRgSU2wiUdIJ5bQHw%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22gFi-9hvL3XgXXTgnlZPyJw%3D%3D%22%7D).
Discussion about code changes happens in GitHub issues and pull requests.

View File

@ -449,7 +449,7 @@ class CLNNode:
# If the cheapest possible private route is more expensive than what RoboSats is willing to pay
if min(routes_cost) >= max_routing_fee_sats:
payout["context"] = {
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget."
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget. This can be adjusted with Advanced Options enabled."
}
return payout

View File

@ -424,7 +424,7 @@ class LNDNode:
# If the cheapest possible private route is more expensive than what RoboSats is willing to pay
if min(routes_cost) >= max_routing_fee_sats:
payout["context"] = {
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget."
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget. This can be adjusted with Advanced Options enabled."
}
return payout

View File

@ -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
@ -1255,9 +1277,9 @@ class Logics:
bond_satoshis = int(order.last_satoshis * order.bond_size / 100)
if user.robot.wants_stealth:
description = f"Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
description = f"{config("NODE_ALIAS")} - Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
else:
description = f"RoboSats - Publishing '{str(order)}' - Maker bond - This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
description = f"{config("NODE_ALIAS")} - Publishing '{str(order)}' - Maker bond - This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
# Gen hold Invoice
try:
@ -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"
)
@ -1377,10 +1402,10 @@ class Logics:
bond_satoshis = int(order.last_satoshis * order.bond_size / 100)
pos_text = "Buying" if cls.is_buyer(order, user) else "Selling"
if user.robot.wants_stealth:
description = f"Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
description = f"{config("NODE_ALIAS")} - Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
else:
description = (
f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
f"{config("NODE_ALIAS")} - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
+ " - Taker bond - This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
)
@ -1476,9 +1501,9 @@ class Logics:
order.log(f"Escrow invoice amount is calculated as {escrow_satoshis} Sats")
if user.robot.wants_stealth:
description = f"Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
description = f"{config("NODE_ALIAS")} - Payment reference: {order.reference}. This payment WILL FREEZE IN YOUR WALLET, check on RoboSats if the lock was successful. It will be unlocked (fail) unless you cheat or cancel unilaterally."
else:
description = f"RoboSats - Escrow amount for '{str(order)}' - It WILL FREEZE IN YOUR WALLET. It will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
description = f"{config("NODE_ALIAS")} - Escrow amount for '{str(order)}' - It WILL FREEZE IN YOUR WALLET. It will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
# Gen hold Invoice
try:
@ -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",

View File

@ -1,7 +1,10 @@
import hashlib
import time
from .utils import human_format
# UNUSED
# import time
# UNUSED
# from .utils import human_format
class NickGenerator:
@ -94,7 +97,7 @@ class NickGenerator:
# if self.verbose:
# print(f"Adverb: {adv}, id {adv_id}.")
else:
adv_id, adv, remainder = 0, "", nick_id
adv, remainder = "", nick_id
# Compute adjective id
if self.use_adj:

114
api/nostr.py Normal file
View File

@ -0,0 +1,114 @@
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)
robot_hash_id = await self.get_robot_hash_id(order)
currency = await self.get_robot_currency(order)
event = EventBuilder(
Kind(38383),
"",
self.generate_tags(order, robot_name, robot_hash_id, 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_hash_id(self, order):
return order.maker.robot.hash_id
@sync_to_async
def get_robot_currency(self, order):
return str(order.currency)
def generate_tags(self, order, robot_name, robot_hash_id, 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, robot_hash_id]),
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())),
str(order.escrow_duration),
]
),
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"]

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View 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

94
desktopApp/index.js Normal file
View 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
View 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
View 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#/garage')}`, {
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#/garage`);
});
// 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();
});

2834
desktopApp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
desktopApp/package.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "desktop-app",
"version": "0.7.1",
"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.21.0"
},
"build": {
"appId": "com.electron.robosats",
"productName": "RobosatsApp",
"directories": {
"output": "dist"
},
"win": {
"target": [
"NSIS"
]
},
"mac": {
"target": [
"dmg"
]
},
"linux": {
"target": [
"AppImage",
"deb"
]
}
}
}

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

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

View 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

File diff suppressed because it is too large Load Diff

155241
desktopApp/tor/geoip6 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
../../geoip

View File

@ -0,0 +1 @@
../../geoip6

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
../../../README.CONJURE.md

View File

@ -0,0 +1 @@
../../../README.SNOWFLAKE.md

View File

@ -0,0 +1 @@
../../../README.WEBTUNNEL.md

Binary file not shown.

View File

@ -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/tor Executable file

Binary file not shown.

View File

@ -0,0 +1 @@
../../geoip

View File

@ -0,0 +1 @@
../../geoip6

Binary file not shown.

View File

@ -0,0 +1 @@
../../../README.CONJURE.md

View File

@ -0,0 +1 @@
../../../README.SNOWFLAKE.md

View File

@ -0,0 +1 @@
../../../README.WEBTUNNEL.md

Binary file not shown.

View File

@ -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/tor Executable file

Binary file not shown.

View File

@ -0,0 +1 @@
../../geoip

View File

@ -0,0 +1 @@
../../geoip6

View File

@ -0,0 +1 @@
../../../README.CONJURE.md

View File

@ -0,0 +1 @@
../../../README.SNOWFLAKE.md

View File

@ -0,0 +1 @@
../../../README.WEBTUNNEL.md

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

14
desktopApp/tsconfig.json Normal file
View 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" ]
}

View File

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

View File

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

@ -0,0 +1,3 @@
#!/bin/sh
cron -f -l 8 & tail -f /var/log/cron.log & /app/strfry relay

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

View File

@ -13,7 +13,7 @@ gem "webrick"
gem "breakpoint"
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
gem "github-pages", "~> 231", group: :jekyll_plugins
gem "github-pages", "~> 232", group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem "jekyll-feed", "~> 0.17"

View File

@ -7,8 +7,8 @@ GEM
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
breakpoint (2.7.1)
sass (~> 3.3)
sassy-maps (< 1.0.0)
@ -18,8 +18,9 @@ GEM
coffee-script-source (1.12.2)
colorator (1.1.0)
commonmarker (0.23.10)
concurrent-ruby (1.2.3)
dnsruby (1.70.0)
concurrent-ruby (1.3.4)
csv (3.3.0)
dnsruby (1.72.2)
simpleidn (~> 0.2.1)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
@ -28,19 +29,21 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.9.1)
faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2)
faraday-net_http (3.1.0)
faraday (2.11.0)
faraday-net_http (>= 2.0, < 3.4)
logger
faraday-net_http (3.3.0)
net-http
ffi (1.16.3)
ffi (1.17.0-x86_64-linux-gnu)
ffi (1.17.0-x86_64-linux-musl)
forwardable-extended (2.6.0)
gemoji (4.1.0)
github-pages (231)
github-pages (232)
github-pages-health-check (= 1.18.2)
jekyll (= 3.9.5)
jekyll (= 3.10.0)
jekyll-avatar (= 0.8.0)
jekyll-coffeescript (= 1.2.2)
jekyll-commonmark-ghpages (= 0.4.0)
jekyll-commonmark-ghpages (= 0.5.1)
jekyll-default-layout (= 0.1.5)
jekyll-feed (= 0.17.0)
jekyll-gist (= 1.5.0)
@ -77,9 +80,10 @@ GEM
liquid (= 4.0.4)
mercenary (~> 0.3)
minima (= 2.5.1)
nokogiri (>= 1.13.6, < 2.0)
nokogiri (>= 1.16.2, < 2.0)
rouge (= 3.30.0)
terminal-table (~> 1.4)
webrick (~> 1.8)
github-pages-health-check (1.18.2)
addressable (~> 2.3)
dnsruby (~> 1.60)
@ -90,11 +94,12 @@ GEM
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
i18n (1.14.4)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
jekyll (3.9.5)
jekyll (3.10.0)
addressable (~> 2.4)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0)
@ -105,6 +110,7 @@ GEM
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
webrick (>= 1.0)
jekyll-avatar (0.8.0)
jekyll (>= 3.0, < 5.0)
jekyll-coffeescript (1.2.2)
@ -112,9 +118,9 @@ GEM
coffee-script-source (~> 1.12)
jekyll-commonmark (1.4.0)
commonmarker (~> 0.22)
jekyll-commonmark-ghpages (0.4.0)
commonmarker (~> 0.23.7)
jekyll (~> 3.9.0)
jekyll-commonmark-ghpages (0.5.1)
commonmarker (>= 0.23.7, < 1.1.0)
jekyll (>= 3.9, < 4.0)
jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 5.0)
jekyll-default-layout (0.1.5)
@ -208,6 +214,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.0)
mercenary (0.3.6)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
@ -220,22 +227,23 @@ GEM
jekyll-include-cache (~> 0.1)
jekyll-paginate (~> 1.1)
jekyll-sitemap (~> 1.3)
minitest (5.22.2)
minitest (5.25.1)
net-http (0.4.1)
uri
nokogiri (1.16.2-x86_64-linux)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.0.5)
racc (1.7.3)
public_suffix (5.1.1)
racc (1.8.1)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.2.6)
rexml (3.3.6)
strscan
rouge (3.30.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
@ -249,8 +257,8 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
simpleidn (0.2.1)
unf (~> 0.1.4)
simpleidn (0.2.3)
strscan (3.1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
@ -258,13 +266,10 @@ GEM
ethon (>= 0.9.0)
tzinfo (1.2.10)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.9.1)
unicode-display_width (1.8.0)
uri (0.13.0)
webrick (1.8.1)
zeitwerk (2.6.13)
uri (0.13.1)
webrick (1.8.2)
zeitwerk (2.6.17)
PLATFORMS
x86_64-linux
@ -272,7 +277,7 @@ PLATFORMS
DEPENDENCIES
breakpoint
github-pages (~> 231)
github-pages (~> 232)
http_parser.rb (~> 0.6.0)
jekyll-feed (~> 0.17)
jekyll-include-cache

View File

@ -18,11 +18,11 @@ This contributing guide is based on the [Bisq contributing guide](https://github
*!!! Beware of scammers impersonating RoboSats admins. Admins will NEVER privately message/call you.*
- **Simplex:** [RoboSats Main Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). Got questions or a problem? Find community-driven support in the public SimpleX group chat. If you're wanting to hang out with other cool robots and learn more about RoboSats, then those discussions happen in SimpleX, Nostr, and Matrix group chats.
- **Simplex:**
- [RoboSats Main Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). Got questions or a problem? Find community-driven support in the public SimpleX group chat. If you're wanting to hang out with other cool robots and learn more about RoboSats, then those discussions happen in SimpleX, Nostr, and Matrix group chats.
- [RoboSats Development Group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FKEkNLMlgM8vrrU3xjBt5emS7EsP0c4s1%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEABehx7Tgefl_vvOGOe2SThJCGACKRgSU2wiUdIJ5bQHw%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22gFi-9hvL3XgXXTgnlZPyJw%3D%3D%22%7D). Main developer communication group chat where open and technical discussions about development takes place. Discussion about code changes happens in GitHub issues and pull requests (PRs).
- **Nostr:** [RoboSats General Group](https://snort.social/e/note1tfwvglg8xz8420pfgav0dc9mqekv02nkpck2axefklrema7lk6wszmwxdy). Hang out with other cool robots and do not hesitate to ask questions about RoboSats! Also, the [RoboSats Nostr account](https://snort.social/p/npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) (npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) provides important project updates, tips and tricks of using RoboSats, and other privacy-centric commentary. Questions and engagement are welcome. Keep in mind: problems requiring RoboSats staff support should be directed to the main SimpleX group chat instead, where responses are quicker and staff can further investigate your problem.
- **Matrix:** [RoboSats Development Group](https://matrix.to/#/#robosats:matrix.org). Main developer communication group chat where open and technical discussions about development takes place. Discussion about code changes happens in GitHub issues and pull requests (PRs).
- **Nostr:** [RoboSats General 0xGroup](nostr:naddr1qpqrjv34vgckzcfjxp3kgvtzxcuxgepevycrzvesv5en2wps8pjrvd3hxuexvefs8qexxe3nvcun2v3ex3jxgdfhx56kxdm9vyck2ep48yq3wamnwvaz7tm8wfhh2urn9cc8scmgv96zucm0d5pqqqcyqqqfskq06gxuf). Hang out with other cool robots and do not hesitate to ask questions about RoboSats! Also, the [RoboSats Nostr account](https://snort.social/p/npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) (npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) provides important project updates, tips and tricks of using RoboSats, and other privacy-centric commentary. Questions and engagement are welcome. Keep in mind: problems requiring RoboSats staff support should be directed to the main SimpleX group chat instead, where responses are quicker and staff can further investigate your problem.
- **Twitter:** [RoboSats](https://twitter.com/RoboSats). Announces project updates, shares tips and tricks of using RoboSats, and discusses other privacy-related topics. Feel free to ask questions and to share your experience using RoboSats! The psuedonymous "Robotoshi" character, a privacy-loving and sometimes sassy robot, runs the RoboSats Twitter and tries his best to answer questions! Keep in mind that the aforementioned public Simplex group chat may be more effective in seeking technical help and users seeking dispute help will be directed to the SimpleX chat.

View File

@ -18,12 +18,12 @@ Esta guía de contribución se basa en la [Guía de contribución de Bisq](https
*!!! Cuidado con los estafadores que se hacen pasar por administradores de RoboSats. Los administradores NUNCA te llamarán o enviarán mensajes privados.*
- **Simplex:** [Grupo principal RoboSats](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). ¿Tienes preguntas o un problema? Encuentra apoyo comunitario en el chat público del grupo SimpleX. Si quieres pasar el rato con otros robots geniales y aprender más sobre RoboSats, entonces esas discusiones ocurren en los chats de grupo SimpleX, Nostr y Matrix.
- **Simplex:**
- [Grupo principal RoboSats](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). ¿Tienes preguntas o un problema? Encuentra apoyo comunitario en el chat público del grupo SimpleX. Si quieres pasar el rato con otros robots geniales y aprender más sobre RoboSats, entonces esas discusiones ocurren en los chats de grupo SimpleX, Nostr y Matrix.
- [Grupo de Desarrollo RoboSats](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FKEkNLMlgM8vrrU3xjBt5emS7EsP0c4s1%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEABehx7Tgefl_vvOGOe2SThJCGACKRgSU2wiUdIJ5bQHw%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22gFi-9hvL3XgXXTgnlZPyJw%3D%3D%22%7D). Principal grupo de chat de comunicación de desarrolladores donde tienen lugar discusiones abiertas y técnicas sobre el desarrollo. La discusión sobre los cambios en el código se realiza en GitHub issues y pull requests (PRs).
- **Nostr:** [Grupo General RoboSats](https://snort.social/e/note1tfwvglg8xz8420pfgav0dc9mqekv02nkpck2axefklrema7lk6wszmwxdy). Pasa el rato con otros robots geniales y no dudes en hacer preguntas sobre RoboSats. Además, la cuenta [Nostr RoboSats](https://snort.social/p/npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) (npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) proporciona actualizaciones importantes del proyecto, consejos y trucos sobre el uso de RoboSats y otros comentarios centrados en la privacidad. Las preguntas y la participación son bienvenidas. Recuerda: los problemas que requieran la ayuda del personal de RoboSats deben dirigirse al chat principal del grupo SimpleX, donde las respuestas son más rápidas y el personal puede investigar más a fondo su problema.
- **Matrix:** [Grupo de Desarrollo RoboSats](https://matrix.to/#/#robosats:matrix.org). Principal grupo de chat de comunicación de desarrolladores donde tienen lugar discusiones abiertas y técnicas sobre el desarrollo. La discusión sobre los cambios en el código se realiza en GitHub issues y pull requests (PRs).
- **Twitter:** [RoboSats](https://twitter.com/RoboSats). Anuncia actualizaciones del proyecto, comparte consejos y trucos sobre el uso de RoboSats y debate otros temas relacionados con la privacidad. No dudes en hacer preguntas y compartir tu experiencia con RoboSats. El personaje "Robotoshi", un robot amante de la privacidad y a veces descarado, dirige el Twitter de RoboSats e intenta responder a todas las preguntas. Ten en cuenta que el chat público del grupo Simplex antes mencionado puede ser más eficaz para buscar ayuda técnica y que los usuarios que busquen ayuda en disputas serán dirigidos al chat SimpleX.
## Flujo de trabajo de los colaboradores

View File

@ -18,12 +18,12 @@ This contributing guide is based on the [Bisq contributing guide](https://github
*!!! Beware of scammers impersonating RoboSats admins. Admins will NEVER privately message/call you.*
- **Simplex:** [RoboSats Main Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). Got questions or a problem? Find community-driven support in the public SimpleX group chat. If you're wanting to hang out with other cool robots and learn more about RoboSats, then those discussions happen in SimpleX, Nostr, and Matrix group chats.
- **Simplex:**
- [RoboSats Main Group](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FyEX_vdhWew_FkovCQC3mRYRWZB1j_cBq%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnrf9Jw3Ajdp4EQw71kqA64VgsIIzw8YNn68WjF09jFY%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22hWnMVPnJl-KT3-virDk0JA%3D%3D%22%7D). Got questions or a problem? Find community-driven support in the public SimpleX group chat. If you're wanting to hang out with other cool robots and learn more about RoboSats, then those discussions happen in SimpleX, Nostr, and Matrix group chats.
- [RoboSats Development Group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FKEkNLMlgM8vrrU3xjBt5emS7EsP0c4s1%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEABehx7Tgefl_vvOGOe2SThJCGACKRgSU2wiUdIJ5bQHw%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22gFi-9hvL3XgXXTgnlZPyJw%3D%3D%22%7D). Main developer communication group chat where open and technical discussions about development takes place. Discussion about code changes happens in GitHub issues and pull requests (PRs).
- **Nostr:** [RoboSats General Group](https://snort.social/e/note1tfwvglg8xz8420pfgav0dc9mqekv02nkpck2axefklrema7lk6wszmwxdy). Hang out with other cool robots and do not hesitate to ask questions about RoboSats! Also, the [RoboSats Nostr account](https://snort.social/p/npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) (npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82) provides important project updates, tips and tricks of using RoboSats, and other privacy-centric commentary. Questions and engagement are welcome. Keep in mind: problems requiring RoboSats staff support should be directed to the main SimpleX group chat instead, where responses are quicker and staff can further investigate your problem.
- **Matrix:** [RoboSats Development Group](https://matrix.to/#/#robosats:matrix.org). Main developer communication group chat where open and technical discussions about development takes place. Discussion about code changes happens in GitHub issues and pull requests (PRs).
- **Twitter:** [RoboSats](https://twitter.com/RoboSats). Announces project updates, shares tips and tricks of using RoboSats, and discusses other privacy-related topics. Feel free to ask questions and to share your experience using RoboSats! The psuedonymous "Robotoshi" character, a privacy-loving and sometimes sassy robot, runs the RoboSats Twitter and tries his best to answer questions! Keep in mind that the aforementioned public Simplex group chat may be more effective in seeking technical help and users seeking dispute help will be directed to the SimpleX chat.
## Contributor Workflow

View File

@ -56,7 +56,7 @@ Simply open a new issue in GitHub and select the form "Coordinator Registration"
Coordinator registrations are **always OPEN** . All fields can be updated or completed later through pull requests. Don't worry about getting everything perfect initially. It's okay if your data policy, privacy policy, node ids, and endpoints aren't fully defined or are subject to change.
### How to run a RoboSats Coordinator?
The easiest way to run a RoboSats coordinator is by using our Docker image releases (see Github Release). That's it, there are no more guides at the moment so it requires a fair bit of technical knowledge as of now. The details of how to wire everything will vary depending on your existing infrastructure. You will find help on our [Matrix Development group](https://matrix.to/#/#robosats:matrix.org).
The easiest way to run a RoboSats coordinator is by using our Docker image releases (see Github Release). That's it, there are no more guides at the moment so it requires a fair bit of technical knowledge as of now. The details of how to wire everything will vary depending on your existing infrastructure. You will find help on our [SimpleX Development Group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FKEkNLMlgM8vrrU3xjBt5emS7EsP0c4s1%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEABehx7Tgefl_vvOGOe2SThJCGACKRgSU2wiUdIJ5bQHw%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22gFi-9hvL3XgXXTgnlZPyJw%3D%3D%22%7D).
We are working to release three flavors of pre-configured orchestrations with decent defaults: 1) a well-tested docker-compose orchestration and 2) a [StartOS](https://github.com/Start9Labs/start-os) RoboSats Coordinator App, and lastly 3) an elegant, but little tested, Kubernetes orchestration. The infrastructure-as-code will be open source as well. You can find the repository holding the docker-compose and kubernetes orchestrations here [RoboSats-Deploy](https://github.com/RoboSats/robosats-deploy). The Kubernetes orchestration as wel as the StartOS app are still work in progress.

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
{
"name": "frontend",
"version": "0.6.3",
"version": "0.7.1",
"description": "",
"main": "index.js",
"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"
@ -22,6 +22,7 @@
"@babel/preset-typescript": "^7.22.5",
"@babel/runtime": "^7.22.6",
"@types/jest": "^29.5.3",
"@types/latlon-geohash": "^2.0.3",
"@types/leaflet": "^1.9.7",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
@ -29,19 +30,20 @@
"@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.1",
"eslint": "^8.45.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^36.1.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"filemanager-webpack-plugin": "^8.0.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.6.1",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"typescript": "^5.5.4",
"webpack": "^5.89.0",
@ -70,8 +72,10 @@
"i18next-http-backend": "^2.5.0",
"install": "^0.13.0",
"js-sha256": "^0.11.0",
"latlon-geohash": "^2.0.0",
"leaflet": "^1.9.4",
"light-bolt11-decoder": "^3.1.1",
"nostr-tools": "^2.7.2",
"npm": "^10.8.1",
"openpgp": "^5.11.0",
"react": "^18.2.0",

View File

@ -1,7 +1,7 @@
import React, { StrictMode, Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import Main from './basic/Main';
import { CssBaseline } from '@mui/material';
import { CssBaseline, Grid } from '@mui/material';
import HostAlert from './components/HostAlert';
import TorConnectionBadge from './components/TorConnection';
@ -13,21 +13,30 @@ import ErrorBoundary from './components/ErrorBoundary';
import { AppContextProvider } from './contexts/AppContext';
import { GarageContextProvider } from './contexts/GarageContext';
import { FederationContextProvider } from './contexts/FederationContext';
import NotificationSwitchBadge from './components/NotificationSwitch';
const App = (): JSX.Element => {
const [client, _view] = window.RobosatsSettings.split('-');
return (
<StrictMode>
<ErrorBoundary>
<Suspense fallback='loading'>
<I18nextProvider i18n={i18n}>
<AppContextProvider>
<GarageContextProvider>
<FederationContextProvider>
<FederationContextProvider>
<GarageContextProvider>
<CssBaseline />
{window.NativeRobosats === undefined ? <HostAlert /> : <TorConnectionBadge />}
{client === 'mobile' ? (
<div style={{ display: 'inline-flex', position: 'fixed', top: '0.5em' }}>
<TorConnectionBadge />
<NotificationSwitchBadge />
</div>
) : (
<HostAlert />
)}
<Main />
</FederationContextProvider>
</GarageContextProvider>
</GarageContextProvider>
</FederationContextProvider>
</AppContextProvider>
</I18nextProvider>
</Suspense>

View File

@ -11,12 +11,10 @@ import BookTable from '../../components/BookTable';
import { BarChart, FormatListBulleted, Map } from '@mui/icons-material';
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
import MapChart from '../../components/Charts/MapChart';
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
const BookPage = (): JSX.Element => {
const { windowSize } = useContext<UseAppStoreType>(AppContext);
const { setDelay, setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation();
const navigate = useNavigate();
@ -31,8 +29,6 @@ const BookPage = (): JSX.Element => {
const onOrderClicked = function (id: number, shortAlias: string): void {
if (garage.getSlot()?.hashId) {
setDelay(10000);
setCurrentOrderId({ id, shortAlias });
navigate(`/order/${shortAlias}/${id}`);
} else {
setOpenNoRobot(true);
@ -89,7 +85,7 @@ const BookPage = (): JSX.Element => {
setOpenNoRobot(false);
}}
onClickGenerateRobot={() => {
navigate('/robot');
navigate('/garage');
}}
/>
{openMaker ? (
@ -101,11 +97,8 @@ const BookPage = (): JSX.Element => {
>
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
<MakerForm
onOrderCreated={(id) => {
navigate(`/order/${id}`);
}}
onClickGenerateRobot={() => {
navigate('/robot');
navigate('/garage');
}}
/>
</Box>

View File

@ -1,27 +1,26 @@
import React, { useContext } from 'react';
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
import { Box, Slide, styled } from '@mui/material';
import { MemoryRouter, BrowserRouter, HashRouter } from 'react-router-dom';
import { Box, styled } from '@mui/material';
import { type UseAppStoreType, AppContext } from '../contexts/AppContext';
import {
TopNavBar,
NavBar,
RobotPage,
MakerPage,
BookPage,
OrderPage,
SettingsPage,
MainDialogs,
} from './';
import { TopNavBar, NavBar, MainDialogs } from './';
import Notifications from '../components/Notifications';
import { useTranslation } from 'react-i18next';
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
import Routes from './Routes';
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
const getRouter = (): any => {
const [client, _view] = window.RobosatsSettings.split('-');
if (client === 'web') {
return BrowserRouter;
} else if (client === 'desktop') {
return HashRouter;
} else {
return MemoryRouter;
}
};
const Router = getRouter();
const Main: React.FC = () => {
const { t } = useTranslation();
const { settings, page, slideDirection, setOpen, windowSize } =
useContext<UseAppStoreType>(AppContext);
const { page, setOpen, windowSize } = useContext<UseAppStoreType>(AppContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
return (
@ -36,81 +35,7 @@ const Main: React.FC = () => {
windowWidth={windowSize?.width}
/>
<MainContent>
<Routes>
{['/robot/:token?', '/', ''].map((path, index) => (
<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>
<Routes />
</MainContent>
<NavBar />
<MainDialogs />
@ -132,5 +57,3 @@ const MainContent = styled(Box)(({ theme }) => ({
}));
export default Main;

View File

@ -23,6 +23,7 @@ export interface OpenDialogs {
client: boolean;
update: boolean;
profile: boolean;
recovery: boolean;
}
const MainDialogs = (): JSX.Element => {

View File

@ -1,4 +1,4 @@
import React, { useContext, useMemo, useState } from 'react';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Grid, Paper, Collapse, Typography } from '@mui/material';
@ -13,9 +13,8 @@ import { FederationContext, type UseFederationStoreType } from '../../contexts/F
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
const MakerPage = (): JSX.Element => {
const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
const { federation, setDelay, setCurrentOrderId } =
useContext<UseFederationStoreType>(FederationContext);
const { fav, windowSize, navbarHeight, page } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage, maker } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation();
const navigate = useNavigate();
@ -26,7 +25,7 @@ const MakerPage = (): JSX.Element => {
const matches = useMemo(() => {
return filterOrders({
orders: federation.book,
federation,
baseFilter: {
currency: fav.currency === 0 ? 1 : fav.currency,
type: fav.type,
@ -54,8 +53,6 @@ const MakerPage = (): JSX.Element => {
const onOrderClicked = function (id: number, shortAlias: string): void {
if (garage.getSlot()?.hashId) {
setDelay(10000);
setCurrentOrderId({ id, shortAlias });
navigate(`/order/${shortAlias}/${id}`);
} else {
setOpenNoRobot(true);
@ -70,7 +67,7 @@ const MakerPage = (): JSX.Element => {
setOpenNoRobot(false);
}}
onClickGenerateRobot={() => {
navigate('/robot');
navigate('/garage');
}}
/>
<Grid item>
@ -105,10 +102,6 @@ const MakerPage = (): JSX.Element => {
}}
>
<MakerForm
onOrderCreated={(shortAlias, id) => {
setCurrentOrderId({ id, shortAlias });
navigate(`/order/${shortAlias}/${id}`);
}}
disableRequest={matches.length > 0 && !showMatches}
collapseAll={showMatches}
onSubmit={() => {
@ -119,7 +112,7 @@ const MakerPage = (): JSX.Element => {
}}
submitButtonLabel={matches.length > 0 && !showMatches ? 'Submit' : 'Create order'}
onClickGenerateRobot={() => {
navigate('/robot');
navigate('/garage');
}}
/>
</Paper>

View File

@ -17,7 +17,6 @@ import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { type Page, isPage } from '.';
const NavBar = (): JSX.Element => {
@ -27,7 +26,6 @@ const NavBar = (): JSX.Element => {
const { page, setPage, setSlideDirection, open, setOpen } =
useContext<UseAppStoreType>(AppContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
const navigate = useNavigate();
const location = useLocation();
@ -44,8 +42,8 @@ const NavBar = (): JSX.Element => {
useEffect(() => {
const pathPage: Page | string = location.pathname.split('/')[1];
if (pathPage === 'index.html') {
navigate('/robot');
setPage('robot');
navigate('/garage');
setPage('garage');
}
if (isPage(pathPage)) {
setPage(pathPage);
@ -65,15 +63,12 @@ const NavBar = (): JSX.Element => {
if (newPage !== 'none') {
const slot = garage.getSlot();
handleSlideDirection(page, newPage);
console.log(page);
setPage(newPage);
const shortAlias = String(slot?.activeShortAlias);
const activeOrderId = slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId;
const lastOrderId = slot?.getRobot(slot?.lastShortAlias ?? '')?.lastOrderId;
const param =
newPage === 'order' ? `${shortAlias}/${String(activeOrderId ?? lastOrderId)}` : '';
if (newPage === 'order') {
setCurrentOrderId({ id: activeOrderId ?? lastOrderId, shortAlias });
}
const shortAlias = slot?.activeOrder?.shortAlias;
const orderId = slot?.activeOrder?.id;
const param = newPage === 'order' ? `${String(shortAlias)}/${String(orderId)}` : '';
setTimeout(() => {
navigate(`/${newPage}/${param}`);
}, theme.transitions.duration.leavingScreen * 3);
@ -88,8 +83,8 @@ const NavBar = (): JSX.Element => {
<StyledPaper elevation={isMobile ? 0 : 3} $isMobile={isMobile}>
<StyledBottomNavigation value={value} onChange={changePage} showLabels>
<StyledBottomNavigationAction
label={t('Robot')}
value='robot'
label={t('Garage')}
value=''
icon={<SmartToyOutlinedIcon />}
/>
<StyledBottomNavigationAction
@ -106,7 +101,7 @@ const NavBar = (): JSX.Element => {
label={t('Orders')}
value='order'
icon={<AssignmentOutlinedIcon />}
disabled={!garage.getSlot()?.getRobot()?.activeOrderId}
disabled={!garage.getSlot()?.activeOrder}
/>
<StyledBottomNavigationAction
label={t('Settings')}
@ -164,4 +159,4 @@ const StyledBottomNavigationAction = styled(BottomNavigationAction)(({ theme })
},
}));
export default NavBar;
export default NavBar;

View File

@ -1,8 +1,8 @@
import NavBar from './NavBar';
export type Page = 'robot' | 'order' | 'create' | 'offers' | 'settings' | 'none';
export type Page = 'garage' | 'order' | 'create' | 'offers' | 'settings' | 'none';
export default NavBar;
export function isPage(page: string): page is Page {
return ['robot', 'order', 'create', 'offers', 'settings', 'none'].includes(page);
return ['garage', 'order', 'create', 'offers', 'settings', 'none'].includes(page);
}

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Tab, Tabs, Paper, CircularProgress, Grid, Typography, Box } from '@mui/material';
import { useNavigate, useParams } from 'react-router-dom';
@ -6,60 +6,59 @@ import { useNavigate, useParams } from 'react-router-dom';
import TradeBox from '../../components/TradeBox';
import OrderDetails from '../../components/OrderDetails';
import { AppContext, closeAll, type UseAppStoreType } from '../../contexts/AppContext';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { WarningDialog } from '../../components/Dialogs';
import { Order, type Slot } from '../../models';
import { type UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
const OrderPage = (): JSX.Element => {
const {
windowSize,
open,
setOpen,
acknowledgedWarning,
setAcknowledgedWarning,
settings,
navbarHeight,
hostUrl,
origin,
} = useContext<UseAppStoreType>(AppContext);
const { federation, currentOrder, currentOrderId, setCurrentOrderId } =
useContext<UseFederationStoreType>(FederationContext);
const { badOrder } = useContext<UseGarageStoreType>(GarageContext);
const { windowSize, setOpen, acknowledgedWarning, setAcknowledgedWarning, navbarHeight } =
useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation();
const navigate = useNavigate();
const params = useParams();
const paramsRef = useRef(params);
const doublePageWidth: number = 50;
const maxHeight: number = (windowSize?.height - navbarHeight) * 0.85 - 3;
const [tab, setTab] = useState<'order' | 'contract'>('contract');
const [baseUrl, setBaseUrl] = useState<string>(hostUrl);
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
useEffect(() => {
paramsRef.current = params;
const shortAlias = params.shortAlias;
const coordinator = federation.getCoordinator(shortAlias ?? '');
if (coordinator) {
const endpoint = coordinator?.getEndpoint(
settings.network,
origin,
settings.selfhostedClient,
hostUrl,
);
if (endpoint) setBaseUrl(`${endpoint?.url}${endpoint?.basePath}`);
const orderId = Number(params.orderId);
if (
orderId &&
currentOrderId.id !== orderId &&
currentOrderId.shortAlias !== shortAlias &&
shortAlias
)
setCurrentOrderId({ id: orderId, shortAlias });
if (!acknowledgedWarning) setOpen({ ...closeAll, warning: true });
const orderId = Number(params.orderId);
const slot = garage.getSlot();
if (slot?.token) {
let order = new Order({ id: orderId, shortAlias });
if (slot.activeOrder?.id === orderId && slot.activeOrder?.shortAlias === shortAlias) {
order = slot.activeOrder;
} else if (slot.lastOrder?.id === orderId && slot.lastOrder?.shortAlias === shortAlias) {
order = slot.lastOrder;
}
void order.fecth(federation, slot).then((updatedOrder) => {
updateSlotFromOrder(updatedOrder, slot);
});
}
}, [params, currentOrderId]);
return () => {
setCurrentOrder(null);
};
}, [params.orderId]);
const updateSlotFromOrder = (updatedOrder: Order, slot: Slot): void => {
if (
Number(paramsRef.current.orderId) === updatedOrder.id &&
paramsRef.current.shortAlias === updatedOrder.shortAlias
) {
setCurrentOrder(updatedOrder);
slot.updateSlotFromOrder(updatedOrder);
}
};
const onClickCoordinator = function (): void {
if (currentOrder?.shortAlias != null) {
@ -70,7 +69,7 @@ const OrderPage = (): JSX.Element => {
};
const startAgain = (): void => {
navigate('/robot');
navigate('/garage');
};
const orderDetailsSpace = currentOrder ? (
@ -79,7 +78,7 @@ const OrderPage = (): JSX.Element => {
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator}
onClickGenerateRobot={() => {
navigate('/robot');
navigate('/garage');
}}
/>
) : (
@ -87,7 +86,7 @@ const OrderPage = (): JSX.Element => {
);
const tradeBoxSpace = currentOrder ? (
<TradeBox baseUrl={baseUrl} onStartAgain={startAgain} />
<TradeBox onStartAgain={startAgain} currentOrder={currentOrder} />
) : (
<></>
);
@ -95,20 +94,19 @@ const OrderPage = (): JSX.Element => {
return (
<Box>
<WarningDialog
open={open.warning}
open={!acknowledgedWarning && currentOrder?.status === 0}
onClose={() => {
setOpen(closeAll);
setAcknowledgedWarning(true);
}}
longAlias={federation.getCoordinator(params.shortAlias ?? '')?.longAlias}
/>
{currentOrder === null && badOrder === undefined && <CircularProgress />}
{badOrder !== undefined ? (
{!currentOrder?.maker_hash_id && <CircularProgress />}
{currentOrder?.bad_request && currentOrder.status !== 5 ? (
<Typography align='center' variant='subtitle2' color='secondary'>
{t(badOrder)}
{t(currentOrder.bad_request)}
</Typography>
) : null}
{currentOrder !== null && badOrder === undefined ? (
{currentOrder?.maker_hash_id && (!currentOrder.bad_request || currentOrder.status === 5) ? (
currentOrder.is_participant ? (
windowSize.width > doublePageWidth ? (
// DOUBLE PAPER VIEW

View File

@ -36,22 +36,16 @@ import TokenInput from './TokenInput';
import { genBase62Token } from '../../utils';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
interface OnboardingProps {
setView: (view: string) => void;
inputToken: string;
setInputToken: (token: string) => void;
badToken: boolean;
getGenerateRobot: (token: string) => void;
setInputToken: (state: string) => void;
baseUrl: string;
}
const Onboarding = ({
setView,
inputToken,
setInputToken,
badToken,
getGenerateRobot,
}: OnboardingProps): JSX.Element => {
const Onboarding = ({ setView, inputToken, setInputToken }: OnboardingProps): JSX.Element => {
const { t } = useTranslation();
const navigate = useNavigate();
const theme = useTheme();
@ -59,6 +53,7 @@ const Onboarding = ({
const { setPage } = useContext<UseAppStoreType>(AppContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const [step, setStep] = useState<'1' | '2' | '3'>('1');
const [generatedToken, setGeneratedToken] = useState<boolean>(false);
@ -141,7 +136,6 @@ const Onboarding = ({
autoFocusTarget='copyButton'
inputToken={inputToken}
setInputToken={setInputToken}
badToken={badToken}
onPressEnter={() => null}
sx={{ flexGrow: 1 }}
/>
@ -158,7 +152,7 @@ const Onboarding = ({
<StyledButton
onClick={() => {
setStep('2');
getGenerateRobot(inputToken);
garage.createRobot(federation, inputToken);
}}
variant='contained'
size='large'
@ -405,4 +399,3 @@ function StyledStepIcon(props: StepIconProps) {
}
export default Onboarding;

View File

@ -1,66 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Grid, Typography } from '@mui/material';
import TokenInput from './TokenInput';
import Key from '@mui/icons-material/Key';
interface RecoveryProps {
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
inputToken: string;
badToken: string;
setInputToken: (state: string) => void;
getRecoverRobot: (token: string) => void;
}
const Recovery = ({
inputToken,
badToken,
setView,
setInputToken,
getRecoverRobot,
}: RecoveryProps): JSX.Element => {
const { t } = useTranslation();
const onClickRecover = (): void => {
getRecoverRobot(inputToken);
setView('profile');
};
return (
<Grid container direction='column' alignItems='center' spacing={1} padding={2}>
<Grid item>
<Typography variant='h5' align='center'>
{t('Robot recovery')}
</Typography>
</Grid>
<Grid item>
<Typography align='center'>
{t('Enter your robot token to re-build your robot and gain access to its trades.')}
</Typography>
</Grid>
<Grid item>
<TokenInput
showCopy={false}
inputToken={inputToken}
setInputToken={setInputToken}
label={t('Paste token here')}
onPressEnter={onClickRecover}
badToken={badToken}
/>
</Grid>
<Grid item>
<Button
variant='contained'
size='large'
disabled={Boolean(badToken)}
onClick={onClickRecover}
>
<Key /> <div style={{ width: '0.5em' }} />
{t('Recover')}
</Button>
</Grid>
</Grid>
);
};
export default Recovery;

View File

@ -8,17 +8,19 @@ import {
MenuItem,
Box,
TextField,
SelectChangeEvent,
useTheme,
useMediaQuery,
styled,
type SelectChangeEvent,
} from '@mui/material';
import { Bolt, Add, DeleteSweep, Logout, Download, FileCopy } from '@mui/icons-material';
import { Key, Bolt, Add, DeleteSweep, Logout, FileCopy, Download } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar';
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
import { genBase62Token } from '../../utils';
import { LoadingButton } from '@mui/lab';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
import { Slot } from '../../models';
const BUTTON_COLORS = {
primary: '#2196f3',
@ -37,22 +39,14 @@ const COLORS = {
interface RobotProfileProps {
inputToken: string;
getGenerateRobot: (token: string) => void;
setInputToken: (token: string) => void;
logoutRobot: () => void;
setView: (view: string) => void;
}
const RobotProfile = ({
inputToken,
getGenerateRobot,
setInputToken,
logoutRobot,
setView,
}: RobotProfileProps): JSX.Element => {
const { windowSize } = useContext<UseAppStoreType>(AppContext);
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
const RobotProfile = ({ inputToken, setInputToken, setView }: RobotProfileProps): JSX.Element => {
const { windowSize, client, setOpen } = useContext<UseAppStoreType>(AppContext);
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { t } = useTranslation();
const theme = useTheme();
@ -66,10 +60,12 @@ const RobotProfile = ({
if (slot?.hashId) {
setLoading(false);
}
}, [orderUpdatedAt, robotUpdatedAt, loading]);
}, [slotUpdatedAt, loading]);
const handleAddRobot = (): void => {
getGenerateRobot(genBase62Token(36));
const token = genBase62Token(36);
garage.createRobot(federation, token);
setInputToken(token);
setLoading(true);
};
@ -84,14 +80,10 @@ const RobotProfile = ({
const slot = garage.getSlot();
const robot = slot?.getRobot();
const loadingCoordinators = Object.values(slot?.robots ?? {}).filter(
(robot) => robot.loading,
).length;
return (
<ProfileContainer $isMobile={isMobile}>
<InfoSection colors={COLORS} $isMobile={isMobile}>
<NicknameTypography variant={isMobile ? "h6" : "h5"} align="center" $isMobile={isMobile}>
<NicknameTypography variant={isMobile ? 'h6' : 'h5'} align='center' $isMobile={isMobile}>
<BoltIcon $isMobile={isMobile} />
{slot?.nickname}
<BoltIcon $isMobile={isMobile} />
@ -102,21 +94,24 @@ const RobotProfile = ({
placeholderType='generating'
style={{ width: isMobile ? '80px' : '120px', height: isMobile ? '80px' : '120px' }}
/>
<StatusTypography variant={isMobile ? "body2" : "body1"} align="center" $isMobile={isMobile}>
{loadingCoordinators > 0 && !robot?.activeOrderId ? t('Looking for orders!') : t('Ready to Trade')}
<StatusTypography
variant={isMobile ? 'body2' : 'body1'}
align='center'
$isMobile={isMobile}
>
{federation.loading && !slot?.activeOrder?.id
? t('Looking for orders!')
: t('Ready to Trade')}
</StatusTypography>
{loadingCoordinators > 0 && !robot?.activeOrderId && <StyledLinearProgress $isMobile={isMobile} />}
{federation.loading && !slot?.activeOrder?.id && (
<StyledLinearProgress $isMobile={isMobile} />
)}
<TokenBox $isMobile={isMobile}>
<CustomIconButton onClick={() => {
logoutRobot();
setView('welcome');
}}>
<StyledLogoutIcon $isMobile={isMobile} />
</CustomIconButton>
<StyledTextField
W{' '}
<StyledTextField
fullWidth
value={inputToken}
variant="standard"
variant='standard'
$isMobile={isMobile}
InputProps={{
readOnly: true,
@ -130,10 +125,10 @@ const RobotProfile = ({
/>
</TokenBox>
</InfoSection>
<RightSection $isMobile={isMobile}>
<TitleSection>
<TitleTypography variant={isMobile ? "subtitle1" : "h6"} align="center">
<TitleTypography variant={isMobile ? 'subtitle1' : 'h6'} align='center'>
{t('Robot Garage')}
</TitleTypography>
</TitleSection>
@ -144,7 +139,7 @@ const RobotProfile = ({
>
{loading ? (
<MenuItem key={'loading'} value={'loading'}>
<Typography variant={isMobile ? "body2" : "body1"}>{t('Building...')}</Typography>
<Typography variant={isMobile ? 'body2' : 'body1'}>{t('Building...')}</Typography>
</MenuItem>
) : (
Object.values(garage.slots).map((slot: Slot, index: number) => (
@ -157,7 +152,7 @@ const RobotProfile = ({
placeholderType='loading'
small={true}
/>
<Typography variant={isMobile ? "body2" : "body1"}>{slot?.nickname}</Typography>
<Typography variant={isMobile ? 'body2' : 'body1'}>{slot?.nickname}</Typography>
</MenuItemContent>
</StyledMenuItem>
))
@ -185,17 +180,16 @@ const RobotProfile = ({
</StyledButton>
)}
<StyledButton
$buttonColor="transparent"
$buttonColor='transparent'
$hoverColor={BUTTON_COLORS.deleteHover}
$textColor="red"
$textColor='red'
$isMobile={isMobile}
onClick={() => {
garage.delete();
logoutRobot();
setView('welcome');
garage.deleteSlot();
if (Object.keys(garage.slots).length < 1) setView('welcome');
}}
>
<StyledDeleteSweepIcon $isMobile={isMobile} /> {t('DELETE GARAGE')}
<StyledDeleteSweepIcon $isMobile={isMobile} /> {t('DELETE ROBOT')}
</StyledButton>
</ButtonContainer>
</RightSection>
@ -216,18 +210,20 @@ const ProfileContainer = styled(Box)<{ $isMobile: boolean }>(({ theme, $isMobile
boxShadow: $isMobile ? '4px 4px 0px #000000' : '8px 8px 0px #000000',
}));
const InfoSection = styled(Box)<{ colors: typeof COLORS; $isMobile: boolean }>(({ theme, colors, $isMobile }) => ({
flexGrow: 1,
flexBasis: $isMobile ? 'auto' : 0,
backgroundColor: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: $isMobile ? theme.spacing(2) : theme.spacing(4),
borderBottom: $isMobile ? '1px solid #000' : 'none',
borderRight: $isMobile ? 'none' : '2px solid #000',
}));
const InfoSection = styled(Box)<{ colors: typeof COLORS; $isMobile: boolean }>(
({ theme, colors, $isMobile }) => ({
flexGrow: 1,
flexBasis: $isMobile ? 'auto' : 0,
backgroundColor: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: $isMobile ? theme.spacing(2) : theme.spacing(4),
borderBottom: $isMobile ? '1px solid #000' : 'none',
borderRight: $isMobile ? 'none' : '2px solid #000',
}),
);
const NicknameTypography = styled(Typography)<{ $isMobile: boolean }>(({ $isMobile }) => ({
display: 'flex',
@ -266,11 +262,11 @@ const TokenBox = styled(Box)<{ $isMobile: boolean }>(({ $isMobile }) => ({
}));
const StyledTextField = styled(TextField)<{ $isMobile: boolean }>(({ $isMobile }) => ({
'& .MuiInputBase-root': {
'& .MuiInputBase-root': {
height: $isMobile ? '36px' : '48px',
padding: $isMobile ? '2px 4px' : '4px 8px',
fontSize: $isMobile ? '0.8rem' : '1rem',
}
},
}));
const RightSection = styled(Box)<{ $isMobile: boolean }>(({ $isMobile }) => ({
@ -346,7 +342,10 @@ const StyledButton = styled('button')<{
color: $buttonColor === 'transparent' ? '#fff' : $textColor,
},
'&:active': {
backgroundColor: $buttonColor === BUTTON_COLORS.primary ? BUTTON_COLORS.activePrimary : BUTTON_COLORS.activeSecondary,
backgroundColor:
$buttonColor === BUTTON_COLORS.primary
? BUTTON_COLORS.activePrimary
: BUTTON_COLORS.activeSecondary,
},
'&:focus': {
outline: 'none',
@ -394,4 +393,4 @@ const StyledDeleteSweepIcon = styled(DeleteSweep)<{ $isMobile: boolean }>(({ $is
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
export default RobotProfile;
export default RobotProfile;

View File

@ -4,6 +4,7 @@ import { IconButton, LinearProgress, TextField, Tooltip } from '@mui/material';
import { ContentCopy } from '@mui/icons-material';
import { systemClient } from '../../services/System';
import { type UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
import { validateTokenEntropy } from '../../utils';
interface TokenInputProps {
editable?: boolean;
@ -12,7 +13,7 @@ interface TokenInputProps {
inputToken: string;
autoFocusTarget?: 'textfield' | 'copyButton' | 'none';
onPressEnter: () => void;
badToken?: string;
setValidToken?: (valid: boolean) => void;
setInputToken: (state: string) => void;
showCopy?: boolean;
label?: string;
@ -26,18 +27,33 @@ const TokenInput = ({
onPressEnter,
autoFocusTarget = 'textfield',
inputToken,
badToken = '',
loading = false,
setInputToken,
setValidToken = () => {},
}: TokenInputProps): JSX.Element => {
const { t } = useTranslation();
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const [showCopied, setShowCopied] = useState<boolean>(false);
const [badToken, setBadToken] = useState<string>('');
useEffect(() => {}, [inputToken]);
useEffect(() => {
setShowCopied(false);
if (inputToken.length < 20) {
setBadToken(t('The token is too short'));
} else if (!validateTokenEntropy(inputToken).hasEnoughEntropy) {
setBadToken(t('Not enough entropy, make it more complex'));
} else {
setBadToken('');
}
}, [inputToken]);
useEffect(() => {
setShowCopied(false);
setValidToken(badToken === '');
}, [badToken]);
useEffect(() => {
setShowCopied(false);
}, [showCopied]);

View File

@ -1,14 +1,17 @@
import React from 'react';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Button, Typography, styled, useTheme } from '@mui/material';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import { RoboSatsTextIcon } from '../../components/Icons';
import { genBase62Token } from '../../utils';
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
import { UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
import { useNavigate } from 'react-router-dom';
import { UseAppStoreType, AppContext } from '../../contexts/AppContext';
interface WelcomeProps {
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
getGenerateRobot: (token: string) => void;
width: number;
setInputToken: (state: string) => void;
}
const BUTTON_COLORS = {
@ -17,9 +20,14 @@ const BUTTON_COLORS = {
text: '#ffffff',
};
const Welcome = ({ setView, getGenerateRobot, width }: WelcomeProps): JSX.Element => {
const Welcome = ({ setView, setInputToken }: WelcomeProps): JSX.Element => {
const { setPage } = useContext<UseAppStoreType>(AppContext);
const { t } = useTranslation();
const navigate = useNavigate();
const theme = useTheme();
const { setOpen } = useContext<UseAppStoreType>(AppContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const COLORS = {
background: theme.palette.background.paper,
@ -70,7 +78,11 @@ const Welcome = ({ setView, getGenerateRobot, width }: WelcomeProps): JSX.Elemen
$shadowColor={COLORS.shadow}
$borderRadius={{ xs: '0', md: '0' }}
endIcon={<ArrowForwardIcon />}
onClick={() => setView('recovery')}
onClick={() => {
setOpen((open) => {
return { ...open, recovery: true };
});
}}
>
<ButtonContent>
<Typography variant='body2'>Recover an existing</Typography>
@ -89,12 +101,15 @@ const Welcome = ({ setView, getGenerateRobot, width }: WelcomeProps): JSX.Elemen
$borderRadius={{ xs: '0 0 8px 8px', md: '0 0 8px 0' }}
sx={{ justifyContent: 'flex-start' }}
onClick={() => {
setView('profile');
getGenerateRobot(genBase62Token(36));
const token = genBase62Token(36);
garage.createRobot(federation, token);
setInputToken(token);
navigate('/create');
setPage('create');
}}
>
<Typography variant='body1' sx={{ fontWeight: 'bold' }}>
Fast Generate Robot
Fast Generate Order
</Typography>
</StyledButton>
</ButtonsSection>
@ -186,4 +201,4 @@ const ButtonContent = styled(Box)({
flexDirection: 'column',
});
export default Welcome;
export default Welcome;

View File

@ -15,77 +15,38 @@ import { useParams } from 'react-router-dom';
import Onboarding from './Onboarding';
import Welcome from './Welcome';
import RobotProfile from './RobotProfile';
import Recovery from './Recovery';
import { TorIcon } from '../../components/Icons';
import { genKey } from '../../pgp';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { validateTokenEntropy } from '../../utils';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import RecoveryDialog from '../../components/Dialogs/Recovery';
const RobotPage = (): JSX.Element => {
const { torStatus, windowSize, settings, page } = useContext<UseAppStoreType>(AppContext);
const { torStatus, settings, page, client } = useContext<UseAppStoreType>(AppContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
const { t } = useTranslation();
const params = useParams();
const urlToken = settings.selfhostedClient ? params.token : null;
const width = Math.min(windowSize.width * 0.8, 28);
const maxHeight = windowSize.height * 0.85 - 3;
const theme = useTheme();
const [badToken, setBadToken] = useState<string>('');
const [inputToken, setInputToken] = useState<string>('');
const [view, setView] = useState<'welcome' | 'onboarding' | 'recovery' | 'profile'>(
const [view, setView] = useState<'welcome' | 'onboarding' | 'profile'>(
garage.currentSlot !== null ? 'profile' : 'welcome',
);
useEffect(() => {
const token = urlToken ?? garage.currentSlot;
if (token !== undefined && token !== null && page === 'robot') {
if (token !== undefined && token !== null && page === 'garage') {
setInputToken(token);
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
getGenerateRobot(token);
if (client !== 'mobile' || torStatus === 'ON' || !settings.useProxy) {
setView('profile');
}
}
}, [torStatus, page]);
useEffect(() => {
if (inputToken.length < 20) {
setBadToken(t('The token is too short'));
} else if (!validateTokenEntropy(inputToken).hasEnoughEntropy) {
setBadToken(t('Not enough entropy, make it more complex'));
} else {
setBadToken('');
}
}, [inputToken]);
const getGenerateRobot = (token: string): void => {
setInputToken(token);
genKey(token)
.then((key) => {
garage.createRobot(token, sortedCoordinators, {
token,
pubKey: key.publicKeyArmored,
encPrivKey: key.encryptedPrivateKeyArmored,
});
void federation.fetchRobot(garage, token);
garage.setCurrentSlot(token);
})
.catch((error) => {
console.error('Error:', error);
});
};
const logoutRobot = (): void => {
setInputToken('');
garage.deleteSlot();
};
if (settings.useProxy && !(window.NativeRobosats === undefined) && !(torStatus === 'ON')) {
if (settings.useProxy && client === 'mobile' && !(torStatus === 'ON')) {
return (
<StyledConnectingBox>
<RecoveryDialog setInputToken={setInputToken} setView={setView} />
<Grid container direction='column' alignItems='center' spacing={1} padding={2}>
<Grid item>
<Typography align='center' variant='h6'>
@ -128,38 +89,19 @@ const RobotPage = (): JSX.Element => {
} else {
return (
<StyledMainBox>
{view === 'welcome' && (
<Welcome setView={setView} getGenerateRobot={getGenerateRobot} width={1200} />
)}
<RecoveryDialog setInputToken={setInputToken} setView={setView} />
{view === 'welcome' && <Welcome setView={setView} width={1200} />}
{view === 'onboarding' && (
<Onboarding
setView={setView}
badToken={badToken}
inputToken={inputToken}
setInputToken={setInputToken}
getGenerateRobot={getGenerateRobot}
/>
<Onboarding setView={setView} inputToken={inputToken} setInputToken={setInputToken} />
)}
{view === 'profile' && (
<RobotProfile
setView={setView}
logoutRobot={logoutRobot}
width={1200}
inputToken={inputToken}
setInputToken={setInputToken}
getGenerateRobot={getGenerateRobot}
/>
)}
{view === 'recovery' && (
<Recovery
setView={setView}
badToken={badToken}
inputToken={inputToken}
setInputToken={setInputToken}
getRecoverRobot={getGenerateRobot}
/>
)}
</StyledMainBox>
@ -171,13 +113,13 @@ const RobotPage = (): JSX.Element => {
const StyledConnectingBox = styled(Box)({
width: '100vw',
height: 'auto',
backgroundColor: 'transparent',
backgroundColor: 'transparent',
});
const StyledTorIconBox = styled(Box)({
position: 'fixed',
top: '4.6em',
backgroundColor: 'transparent',
backgroundColor: 'transparent',
});
const StyledMainBox = styled(Box)({
@ -189,7 +131,7 @@ const StyledMainBox = styled(Box)({
padding: '2em',
backgroundColor: 'transparent',
border: 'none',
boxShadow: 'none',
boxShadow: 'none',
});
export default RobotPage;

Some files were not shown because too many files have changed in this diff Show More