release-v-0-1-0

v 0 1 0
This commit is contained in:
shroominic
2025-08-05 21:53:46 -03:00
committed by GitHub
9 changed files with 38 additions and 39 deletions
+2 -2
View File
@@ -24,13 +24,13 @@ MODEL_BASED_PRICING = "true"
# EXCHANGE_FEE = "1.005" # 0.5 % currency exchange fee
# password used to log into admin interface
ADMIN_PASSWORD="CHANGE-THIS"
# ADMIN_PASSWORD=""
# Public Endpoint
HTTP_URL="https://your.domain.com"
# Tor Endpoint (copy from docker logs)
ONION_URL=".onion"
# ONION_URL=".onion"
RELAYS="wss://relay.routstr.com,wss://relay.nostr.band"
CASHU_MINTS="https://mint.minibits.cash/Bitcoin,https://mint.cubabitcoin.org"
+1
View File
@@ -9,6 +9,7 @@ wallet.sqlite3
.*wallet.sqlite3
*models.json
.cashu
.dockerignore
compose.override.yml
+24 -10
View File
@@ -32,22 +32,32 @@ sequenceDiagram
- **Cashu Wallet Integration** Accept Lightning payments and redeem eCash tokens before forwarding requests
- **API Key Management** Hashed keys stored in SQLite with balance tracking and optional expiry/refund address
- **Model-Based Pricing** Convert USD prices in `models.json` to sats using live BTC/USD rates
- **Admin Dashboard** Simple HTML interface at `/admin` to view balances and API keys
- **Admin Dashboard** Simple HTML interface at `/admin/` to view balances and API keys
- **Discovery** Fetch available providers from Nostr relays
- **Docker Support** Provided `Dockerfile` and `compose.yml` for running with an optional Tor hidden service
## Getting Started
### Requirements
### Running the proxy using Docker
```bash
docker run -d \
--name routstr-proxy \
-p 8000:8000 \
-e UPSTREAM_BASE_URL=https://api.openai.com/v1 \
-e UPSTREAM_API_KEY=your-openai-api-key \
ghcr.io/routstr/proxy:latest
```
### Development Requirements
- Python 3.11+
- [uv](https://github.com/astral-sh/uv) package manager (used in development)
- A Cashu wallet secret (`NSEC`) and Lightning address for receiving payments
### Installation
```bash
uv sync --dev # install dependencies
uv sync # install dependencies
```
Create a `.env` file based on `.env.example` and fill in the required values:
@@ -78,15 +88,18 @@ The most common settings are shown below. See `.env.example` for the full list.
- `UPSTREAM_BASE_URL` URL of the OpenAI-compatible service
- `UPSTREAM_API_KEY` API key for the upstream service (optional)
- `RECEIVE_LN_ADDRESS` Lightning address that receives payouts
- `MINIMUM_PAYOUT` Minimum sats before forwarding earnings
- `MODEL_BASED_PRICING` Set to `true` to use pricing from `models.json`
- `REFUND_PROCESSING_INTERVAL` Seconds between automatic refunds
- `ADMIN_PASSWORD` Password for the `/admin` dashboard
- `ADMIN_PASSWORD` Password for the `/admin/` dashboard
- `CASHU_MINTS` Comma-separated list of Cashu mint URLs
- `NAME` Name of the proxy
- `DESCRIPTION` Description of the proxy
- `NPUB` Nostr public key of the proxy
- `HTTP_URL` Public-facing URL of the proxy
- `ONION_URL` Tor hidden service URL of the proxy
## Withdrawing Balance
Go to `https://<your.routstr.proxy>/admin/` (NOTE: be sure to add the '/' at the end), enter the `ADMIN_PASSWORD` you set above and withdraw your balance as a Cashu token.
Go to `https://<your.routstr.proxy>/admin/` (NOTE: be sure to add the '/' at the end), enter the `ADMIN_PASSWORD` you set above and withdraw your balance as a Cashu token.
## Example Client
@@ -147,7 +160,8 @@ The proxy should implement either a dedicated endpoint to communicate minimum eC
To use this feature, you'll need a client that handles both OpenAI API calls and eCash header management. The following clients provide seamless integration:
- **[cashu-402-client](https://github.com/9qeklajc/ecash-402-client)** rust client with automatic wallet management
- **[routstr-chat](https://github.com/routstr/routstr-chat)** chat app for the routstr network
- **[otrta-client](https://github.com/routstr/otrta-client)** rust web app for the routstr network
clients automatically:
+1
View File
@@ -276,6 +276,7 @@ def setup_logging() -> None:
"propagate": False,
},
"watchfiles.main": {"level": "WARNING", "handlers": [], "propagate": False},
"aiosqlite": {"level": "ERROR", "handlers": [], "propagate": False},
},
"root": {
"level": log_level,
+3 -7
View File
@@ -10,7 +10,7 @@ from ..balance import balance_router, deprecated_wallet_router
from ..discovery import providers_router
from ..payment.models import MODELS, models_router, update_sats_pricing
from ..proxy import proxy_router
from ..wallet import check_for_refunds, periodic_payout
from ..wallet import periodic_payout
from .admin import admin_router
from .db import init_db
from .logging import get_logger, setup_logging
@@ -19,7 +19,7 @@ from .logging import get_logger, setup_logging
setup_logging()
logger = get_logger(__name__)
__version__ = "0.0.1"
__version__ = "0.1.0"
@asynccontextmanager
@@ -30,7 +30,6 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
await init_db()
pricing_task = asyncio.create_task(update_sats_pricing())
refund_task = asyncio.create_task(check_for_refunds())
payout_task = asyncio.create_task(periodic_payout())
yield
@@ -44,14 +43,11 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
finally:
logger.info("Application shutdown initiated")
refund_task.cancel()
pricing_task.cancel()
payout_task.cancel()
try:
await asyncio.gather(
pricing_task, refund_task, payout_task, return_exceptions=True
)
await asyncio.gather(pricing_task, payout_task, return_exceptions=True)
logger.info("Background tasks stopped successfully")
except Exception as e:
logger.error(
+4 -9
View File
@@ -10,17 +10,12 @@ from .models import MODELS
logger = get_logger(__name__)
UPSTREAM_BASE_URL = os.environ["UPSTREAM_BASE_URL"]
UPSTREAM_BASE_URL = os.environ.get("UPSTREAM_BASE_URL", "")
UPSTREAM_API_KEY = os.environ.get("UPSTREAM_API_KEY", "")
logger.info(
"Payment helpers initialized",
extra={
"upstream_base_url": UPSTREAM_BASE_URL,
"has_upstream_api_key": bool(UPSTREAM_API_KEY),
"model_based_pricing": MODEL_BASED_PRICING,
},
)
if not UPSTREAM_BASE_URL:
raise ValueError("Please set the UPSTREAM_BASE_URL environment variable")
def get_cost_per_request(model: str | None = None) -> int:
-5
View File
@@ -18,7 +18,6 @@ async def kraken_btc_usd(client: httpx.AsyncClient) -> float | None:
"""Fetch BTC/USD price from Kraken API."""
api = "https://api.kraken.com/0/public/Ticker?pair=XBTUSD"
try:
logger.debug("Fetching BTC price from Kraken")
response = await client.get(api)
price_data = response.json()
price = float(price_data["result"]["XXBTZUSD"]["c"][0])
@@ -40,7 +39,6 @@ async def coinbase_btc_usd(client: httpx.AsyncClient) -> float | None:
"""Fetch BTC/USD price from Coinbase API."""
api = "https://api.coinbase.com/v2/prices/BTC-USD/spot"
try:
logger.debug("Fetching BTC price from Coinbase")
response = await client.get(api)
price_data = response.json()
price = float(price_data["data"]["amount"])
@@ -62,7 +60,6 @@ async def binance_btc_usdt(client: httpx.AsyncClient) -> float | None:
"""Fetch BTC/USDT price from Binance API."""
api = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
try:
logger.debug("Fetching BTC price from Binance")
response = await client.get(api)
price_data = response.json()
price = float(price_data["price"])
@@ -82,7 +79,6 @@ async def binance_btc_usdt(client: httpx.AsyncClient) -> float | None:
async def btc_usd_ask_price() -> float:
"""Get the highest BTC/USD price from multiple exchanges with fee adjustment."""
logger.debug("Starting BTC price aggregation from multiple exchanges")
async with httpx.AsyncClient(timeout=30.0) as client:
try:
@@ -113,7 +109,6 @@ async def btc_usd_ask_price() -> float:
async def sats_usd_ask_price() -> float:
"""Get the USD price per satoshi."""
logger.debug("Calculating satoshi price from BTC price")
try:
btc_price = await btc_usd_ask_price()
+2 -5
View File
@@ -11,7 +11,8 @@ logger = get_logger(__name__)
CurrencyUnit = Literal["sat", "msat"]
TRUSTED_MINTS = os.environ["CASHU_MINTS"].split(",")
CASHU_MINTS = os.environ.get("CASHU_MINTS", "https://mint.minibits.cash/Bitcoin")
TRUSTED_MINTS = CASHU_MINTS.split(",")
PRIMARY_MINT_URL = TRUSTED_MINTS[0]
@@ -122,10 +123,6 @@ async def send_to_lnurl(amount: int, unit: CurrencyUnit, lnurl: str) -> dict[str
raise NotImplementedError
async def check_for_refunds() -> None:
logger.warning("check_for_refunds, temporary not implemented")
async def periodic_payout() -> None:
logger.warning("periodic_payout, temporary not implemented")
Generated
+1 -1
View File
@@ -1323,7 +1323,7 @@ wheels = [
[[package]]
name = "routstr"
version = "0.0.1"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "aiosqlite" },