Merge pull request #1362 from RoboSats/use-nostr-as-cache-system

Use nostr as cache system
This commit is contained in:
KoalaSat
2024-09-06 08:18:57 +00:00
committed by GitHub
19 changed files with 435 additions and 11 deletions

View File

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

View File

@ -8,7 +8,7 @@ from django.utils import timezone
from api.lightning.node import LNNode from api.lightning.node import LNNode
from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order 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 api.utils import get_minning_fee, validate_onchain_address, location_country
from chat.models import Message from chat.models import Message
@ -704,9 +704,9 @@ class Logics:
if context["invoice_amount"] < MIN_SWAP_AMOUNT: if context["invoice_amount"] < MIN_SWAP_AMOUNT:
context["swap_allowed"] = False context["swap_allowed"] = False
context[ context["swap_failure_reason"] = (
"swap_failure_reason" f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats"
] = f"Order amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats" )
order.log( order.log(
f"Onchain payment option was not offered: amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats", f"Onchain payment option was not offered: amount is smaller than the minimum swap available of {MIN_SWAP_AMOUNT} Sats",
level="WARN", level="WARN",
@ -714,9 +714,9 @@ class Logics:
return True, context return True, context
elif context["invoice_amount"] > MAX_SWAP_AMOUNT: elif context["invoice_amount"] > MAX_SWAP_AMOUNT:
context["swap_allowed"] = False context["swap_allowed"] = False
context[ context["swap_failure_reason"] = (
"swap_failure_reason" f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats"
] = f"Order amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats" )
order.log( order.log(
f"Onchain payment option was not offered: amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats", f"Onchain payment option was not offered: amount is bigger than the maximum swap available of {MAX_SWAP_AMOUNT} Sats",
level="WARN", level="WARN",
@ -741,9 +741,9 @@ class Logics:
) )
if not valid: if not valid:
context["swap_allowed"] = False context["swap_allowed"] = False
context[ context["swap_failure_reason"] = (
"swap_failure_reason" "Not enough onchain liquidity available to offer a swap"
] = "Not enough onchain liquidity available to offer a swap" )
order.log( order.log(
"Onchain payment option was not offered: onchain liquidity available to offer a swap", "Onchain payment option was not offered: onchain liquidity available to offer a swap",
level="WARN", level="WARN",
@ -1019,6 +1019,8 @@ class Logics:
order.log("Order expired while waiting for maker bond") order.log("Order expired while waiting for maker bond")
order.log("Maker bond was cancelled") order.log("Maker bond was cancelled")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 2.a) When maker cancels after bond # 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("Order cancelled by maker while public or paused")
order.log("Maker bond was <b>unlocked</b>") order.log("Maker bond was <b>unlocked</b>")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 2.b) When maker cancels after bond and before taker bond is locked # 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("Maker bond was <b>unlocked</b>")
order.log("Taker bond was <b>cancelled</b>") order.log("Taker bond was <b>cancelled</b>")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 3) When taker cancels before bond # 3) When taker cancels before bond
@ -1070,6 +1076,8 @@ class Logics:
order.log("Taker cancelled before locking the bond") order.log("Taker cancelled before locking the bond")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 4) When taker or maker cancel after bond (before escrow) # 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("Maker bond was <b>settled</b>")
order.log("Taker bond was <b>unlocked</b>") order.log("Taker bond was <b>unlocked</b>")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 4.b) When taker cancel after bond (before escrow) # 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("Taker bond was <b>settled</b>")
order.log("Maker bond was <b>unlocked</b>") order.log("Maker bond was <b>unlocked</b>")
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# 5) When trade collateral has been posted (after escrow) # 5) When trade collateral has been posted (after escrow)
@ -1136,6 +1148,9 @@ class Logics:
order.log( order.log(
f"Taker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation" f"Taker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
) )
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# if the taker had asked, and now the maker does: cancel order, return everything # if the taker had asked, and now the maker does: cancel order, return everything
@ -1144,6 +1159,9 @@ class Logics:
order.log( order.log(
f"Maker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation" f"Maker Robot({user.robot.id},{user.username}) accepted the collaborative cancellation"
) )
nostr_send_order_event.delay(order_id=order.id)
return True, None return True, None
# Otherwise just make true the asked for cancel flags # Otherwise just make true the asked for cancel flags
@ -1181,6 +1199,8 @@ class Logics:
order.update_status(Order.Status.CCA) order.update_status(Order.Status.CCA)
send_notification.delay(order_id=order.id, message="collaborative_cancelled") 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("Order was collaboratively cancelled")
order.log("Maker bond was <b>unlocked</b>") order.log("Maker bond was <b>unlocked</b>")
order.log("Taker bond was <b>unlocked</b>") order.log("Taker bond was <b>unlocked</b>")
@ -1208,6 +1228,8 @@ class Logics:
order.save() # update all fields 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") order.log(f"Order({order.id},{str(order)}) is public in the order book")
return return
@ -1350,6 +1372,9 @@ class Logics:
except Exception: except Exception:
pass pass
send_notification.delay(order_id=order.id, message="order_taken_confirmed") send_notification.delay(order_id=order.id, message="order_taken_confirmed")
nostr_send_order_event.delay(order_id=order.id)
order.log( 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" f"<b>Contract formalized.</b> Maker: Robot({order.maker.robot.id},{order.maker}). Taker: Robot({order.taker.robot.id},{order.taker}). API median price {order.currency.exchange_rate} {dict(Currency.currency_choices)[order.currency.currency]}/BTC. Premium is {order.premium}%. Contract size {order.last_satoshis} Sats"
) )
@ -1741,11 +1766,15 @@ class Logics:
order.log( order.log(
f"Robot({user.robot.id},{user.username}) paused the public order" 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: elif order.status == Order.Status.PAU:
order.update_status(Order.Status.PUB) order.update_status(Order.Status.PUB)
order.log( order.log(
f"Robot({user.robot.id},{user.username}) made public the paused order" f"Robot({user.robot.id},{user.username}) made public the paused order"
) )
nostr_send_order_event.delay(order_id=order.id)
else: else:
order.log( order.log(
f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused", f"Robot({user.robot.id},{user.username}) tried to pause/unpause an order that was not public or paused",

98
api/nostr.py Normal file
View File

@ -0,0 +1,98 @@
import pygeohash
import hashlib
import uuid
from asgiref.sync import sync_to_async
from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, Kind, Tag
from api.models import Order
from decouple import config
class Nostr:
"""Simple nostr events manager to be used as a cache system for clients"""
async def send_order_event(self, order):
"""Creates the event and sends it to the coordinator relay"""
if config("NOSTR_NSEC", cast=str, default="") == "":
return
print("Sending nostr event")
# Initialize with coordinator Keys
keys = Keys.parse(config("NOSTR_NSEC", cast=str))
signer = NostrSigner.keys(keys)
client = Client(signer)
# Add relays and connect
await client.add_relays(["ws://localhost:7777"])
await client.connect()
robot_name = await self.get_robot_name(order)
currency = await self.get_robot_currency(order)
event = EventBuilder(
Kind(38383), "", self.generate_tags(order, robot_name, currency)
).to_event(keys)
await client.send_event(event)
print(f"Nostr event sent: {event.as_json()}")
@sync_to_async
def get_robot_name(self, order):
return order.maker.username
@sync_to_async
def get_robot_currency(self, order):
return str(order.currency)
def generate_tags(self, order, robot_name, currency):
hashed_id = hashlib.md5(
f"{config("COORDINATOR_ALIAS", cast=str)}{order.id}".encode("utf-8")
).hexdigest()
tags = [
Tag.parse(["d", str(uuid.UUID(hashed_id))]),
Tag.parse(["name", robot_name]),
Tag.parse(["k", "sell" if order.type == Order.Types.SELL else "buy"]),
Tag.parse(["f", currency]),
Tag.parse(["s", self.get_status_tag(order)]),
Tag.parse(["amt", "0"]),
Tag.parse(
["fa"] + [str(order.amount)]
if not order.has_range
else [str(order.min_amount), str(order.max_amount)]
),
Tag.parse(["pm"] + order.payment_method.split(" ")),
Tag.parse(["premium", str(order.premium)]),
Tag.parse(
[
"source",
f"http://{config("HOST_NAME")}/order/{config("COORDINATOR_ALIAS", cast=str).lower()}/{order.id}",
]
),
Tag.parse(["expiration", str(int(order.expires_at.timestamp()))]),
Tag.parse(["y", "robosats", config("COORDINATOR_ALIAS", cast=str).lower()]),
Tag.parse(["n", str(config("NETWORK"))]),
Tag.parse(["layer"] + self.get_layer_tag(order)),
Tag.parse(["bond", str(order.bond_size)]),
Tag.parse(["z", "order"]),
]
if order.latitude and order.longitude:
tags.extend(
[Tag.parse(["g", pygeohash.encode(order.latitude, order.longitude)])]
)
return tags
def get_status_tag(self, order):
if order.status == Order.Status.PUB:
return "pending"
else:
return "success"
def get_layer_tag(self, order):
if order.type == Order.Types.SELL:
return ["onchain", "lightning"]
else:
return ["lightning"]

View File

@ -1,3 +1,4 @@
from asgiref.sync import async_to_sync
from celery import shared_task from celery import shared_task
from celery.exceptions import SoftTimeLimitExceeded from celery.exceptions import SoftTimeLimitExceeded
@ -251,6 +252,20 @@ def cache_market():
return 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) @shared_task(name="send_notification", ignore_result=True, time_limit=120)
def send_notification(order_id=None, chat_message_id=None, message=None): def send_notification(order_id=None, chat_message_id=None, message=None):
if order_id: if order_id:

View File

@ -226,6 +226,16 @@ services:
volumes: volumes:
- ./node/db:/var/lib/postgresql/data - ./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 # # Postgresql for CLN
# postgres-cln: # postgres-cln:
# image: postgres:14.2-alpine # image: postgres:14.2-alpine

View File

@ -27,6 +27,7 @@ services:
- "9998:9998" - "9998:9998"
- "5432:5432" - "5432:5432"
- "6379:6379" - "6379:6379"
- "7777:7777"
volumes: volumes:
- bitcoin:/bitcoin/.bitcoin/ - bitcoin:/bitcoin/.bitcoin/
- ./tests/bitcoind/entrypoint.sh:/entrypoint.sh - ./tests/bitcoind/entrypoint.sh:/entrypoint.sh
@ -182,7 +183,7 @@ services:
# celery-worker: # celery-worker:
# image: backend-image # image: backend-image
# pull_policy: never # pull_policy: never
# container_name: celery-worker # container_name: test-celery-worker
# restart: always # restart: always
# environment: # environment:
# DEVELOPMENT: True # 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

@ -37,6 +37,14 @@ location /mainnet/exp/ws/ {
proxy_set_header Host $host; proxy_set_header Host $host;
} }
location /mainnet/exp/nostr/ {
proxy_pass http://mainnet_exp/nostr/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# Experimental Coordinator Testnet Locations # Experimental Coordinator Testnet Locations
location /test/exp/static/assets/avatars/ { location /test/exp/static/assets/avatars/ {
proxy_pass http://testnet_exp/static/assets/avatars/; proxy_pass http://testnet_exp/static/assets/avatars/;

View File

@ -37,6 +37,14 @@ location /mainnet/lake/ws/ {
proxy_set_header Host $host; proxy_set_header Host $host;
} }
location /mainnet/lake/nostr/ {
proxy_pass http://mainnet_lake/nostr/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# TheBigLake Coordinator Testnet Locations # TheBigLake Coordinator Testnet Locations
location /test/lake/static/assets/avatars/ { location /test/lake/static/assets/avatars/ {
proxy_pass http://testnet_lake/static/assets/avatars/; proxy_pass http://testnet_lake/static/assets/avatars/;

View File

@ -37,6 +37,14 @@ location /mainnet/satstralia/ws/ {
proxy_set_header Host $host; proxy_set_header Host $host;
} }
location /mainnet/satstralia/nostr/ {
proxy_pass http://mainnet_satstralia/nostr/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# Satstralia Coordinator Testnet Locations # Satstralia Coordinator Testnet Locations
location /test/satstralia/static/assets/avatars/ { location /test/satstralia/static/assets/avatars/ {
proxy_pass http://testnet_satstralia/static/assets/avatars/; proxy_pass http://testnet_satstralia/static/assets/avatars/;

View File

@ -37,6 +37,14 @@ location /mainnet/temple/ws/ {
proxy_set_header Host $host; proxy_set_header Host $host;
} }
location /mainnet/temple/nostr/ {
proxy_pass http://mainnet_temple/nostr/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# Temple of Sats Coordinator Testnet Locations # Temple of Sats Coordinator Testnet Locations
location /test/temple/static/assets/avatars/ { location /test/temple/static/assets/avatars/ {
proxy_pass http://testnet_temple/static/assets/avatars/; proxy_pass http://testnet_temple/static/assets/avatars/;

View File

@ -37,6 +37,14 @@ location /mainnet/veneto/ws/ {
proxy_set_header Host $host; proxy_set_header Host $host;
} }
location /mainnet/veneto/nostr/ {
proxy_pass http://mainnet_veneto/nostr/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# BitcoinVeneto Coordinator Testnet Locations # BitcoinVeneto Coordinator Testnet Locations
location /test/veneto/static/assets/avatars/ { location /test/veneto/static/assets/avatars/ {
proxy_pass http://testnet_veneto/static/assets/avatars/; proxy_pass http://testnet_veneto/static/assets/avatars/;

View File

@ -64,6 +64,14 @@ http {
autoindex on; autoindex on;
} }
location /nostr {
proxy_pass http://127.0.0.1:7777;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location = /favicon.ico { location = /favicon.ico {
alias /usr/src/robosats/static/assets/images/favicon-96x96.png; alias /usr/src/robosats/static/assets/images/favicon-96x96.png;
} }

View File

@ -28,3 +28,6 @@ drf-spectacular==0.27.2
drf-spectacular-sidecar==2024.7.1 drf-spectacular-sidecar==2024.7.1
django-cors-headers==4.4.0 django-cors-headers==4.4.0
base91==1.0.1 base91==1.0.1
nostr-sdk==0.32.2
pygeohash==1.2.0
asgiref == 3.8.1